Sync from Heretek-AI/ProxmoxVE - 2026-03-20

This commit is contained in:
github-actions[bot]
2026-03-20 16:41:21 +00:00
parent cd0dea81e0
commit 105b28f73f
621 changed files with 143323 additions and 2563 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ function update_script() {
cp -r /opt/yao/data /opt/yao_data_backup 2>/dev/null || true
msg_ok "Backed up Data"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-v*-linux-*"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-*-linux-*"
msg_info "Restoring Data"
cp -r /opt/yao_data_backup/. /opt/yao/data 2>/dev/null || true
+1 -1
View File
@@ -18,7 +18,7 @@ $STD apt-get install -y \
unzip
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-v*-linux-*"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-*-linux-*"
msg_info "Creating Application Directory"
mkdir -p /opt/yao/data
+363 -247
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -420,6 +420,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-03-20
### 🆕 New Scripts
- 🔄 Upstream Sync - 2026-03-20 [@BillyOutlast](https://github.com/BillyOutlast) ([#74](https://github.com/Heretek-AI/ProxmoxVE/pull/74))
## 2026-03-19
### 📂 Github
+6 -3
View File
@@ -3096,7 +3096,8 @@ wait_for_https_readiness() {
msg_info "Waiting for HTTPS connectivity..."
while ((waited < max_wait)); do
if curl -fsSL --connect-timeout 3 --max-time 10 "$test_url" &>/dev/null; then
# Force IPv4 to avoid IPv6 timeout issues in containers with broken IPv6
if curl -4 -fsSL --connect-timeout 3 --max-time 10 "$test_url" &>/dev/null; then
((success_count++))
msg_ok "HTTPS check ${success_count}/${required_successes} passed"
if ((success_count >= required_successes)); then
@@ -3281,7 +3282,8 @@ function fetch_and_deploy_gh_release() {
local max_retries=5 retry_delay=2 attempt=1 success=false http_code
while ((attempt <= max_retries)); do
http_code=$(curl "$api_timeout" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
# Force IPv4 to avoid IPv6 timeout issues in containers with broken IPv6
http_code=$(curl -4 "$api_timeout" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
if [[ "$http_code" == "200" ]]; then
success=true
break
@@ -3317,7 +3319,8 @@ function fetch_and_deploy_gh_release() {
# Debug: test connectivity during retry
if ((attempt == 1)); then
msg_info "Testing network connectivity..."
if curl -fsSL --connect-timeout 5 --max-time 10 "https://api.github.com/rate_limit" &>/dev/null; then
# Force IPv4 to avoid IPv6 timeout issues
if curl -4 -fsSL --connect-timeout 5 --max-time 10 "https://api.github.com/rate_limit" &>/dev/null; then
msg_ok "Network connectivity test passed - API is reachable"
else
msg_warn "Network connectivity test FAILED - API is NOT reachable"
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://wakapi.dev/ | https://github.com/muety/wakapi
APP="Alpine-Wakapi"
var_tags="${var_tags:-code;time-tracking}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-512}"
var_disk="${var_disk:-4}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.23}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/wakapi ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE=$(curl -s https://api.github.com/repos/muety/wakapi/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
if [ "${RELEASE}" != "$(cat ~/.wakapi 2>/dev/null)" ] || [ ! -f ~/.wakapi ]; then
msg_info "Stopping Wakapi Service"
$STD rc-service wakapi stop
msg_ok "Stopped Wakapi Service"
msg_info "Updating Wakapi LXC"
$STD apk -U upgrade
msg_ok "Updated Wakapi LXC"
msg_info "Creating backup"
mkdir -p /opt/wakapi-backup
cp /opt/wakapi/config.yml /opt/wakapi/wakapi_db.db /opt/wakapi-backup/
msg_ok "Created backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "wakapi" "muety/wakapi" "tarball"
msg_info "Configuring Wakapi"
cd /opt/wakapi
$STD go mod download
$STD go build -o wakapi
cp /opt/wakapi-backup/config.yml /opt/wakapi/
cp /opt/wakapi-backup/wakapi_db.db /opt/wakapi/
rm -rf /opt/wakapi-backup
msg_ok "Configured Wakapi"
msg_info "Starting Service"
$STD rc-service wakapi start
msg_ok "Started Service"
msg_ok "Updated successfully"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit 0
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"
@@ -0,0 +1,6 @@
___ __ _ _ __ __ _
/ | / /___ (_)___ ___ | | / /___ _/ /______ _____ (_)
/ /| | / / __ \/ / __ \/ _ \_____| | /| / / __ `/ //_/ __ `/ __ \/ /
/ ___ |/ / /_/ / / / / / __/_____/ |/ |/ / /_/ / ,< / /_/ / /_/ / /
/_/ |_/_/ .___/_/_/ /_/\___/ |__/|__/\__,_/_/|_|\__,_/ .___/_/
/_/ /_/
@@ -0,0 +1,6 @@
_ _____ ____ __ __ _______ __
(_) ___/____ ____ ____ _________ _____/ __ )/ /___ _____/ /_/_ __/ | / /
/ /\__ \/ __ \/ __ \/ __ \/ ___/ __ \/ ___/ __ / / __ \/ ___/ //_// / | | / /
/ /___/ / /_/ / /_/ / / / (__ ) /_/ / / / /_/ / / /_/ / /__/ ,< / / | |/ /
/_//____/ .___/\____/_/ /_/____/\____/_/ /_____/_/\____/\___/_/|_|/_/ |___/
/_/
@@ -0,0 +1,6 @@
______ __ __
/_ __/__ / /__ ____ ____ _____/ /_
/ / / _ \/ / _ \/ __ \/ __ \/ ___/ __/
/ / / __/ / __/ /_/ / /_/ / / / /_
/_/ \___/_/\___/ .___/\____/_/ \__/
/_/
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Matthew Stern (sternma) | MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/dmunozv04/iSponsorBlockTV
APP="iSponsorBlockTV"
var_tags="${var_tags:-media;automation}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/isponsorblocktv ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "isponsorblocktv" "dmunozv04/iSponsorBlockTV"; then
msg_info "Stopping Service"
systemctl stop isponsorblocktv
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "isponsorblocktv" "dmunozv04/iSponsorBlockTV" "singlefile" "latest" "/opt/isponsorblocktv" "iSponsorBlockTV-*-linux"
msg_info "Starting Service"
systemctl start isponsorblocktv
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Run the setup wizard inside the container with:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}iSponsorBlockTV setup${CL}"
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://goteleport.com/
APP="Teleport"
var_tags="${var_tags:-zero-trust}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-1024}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /etc/teleport.yaml ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating Teleport"
$STD apt update
$STD apt upgrade -y
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}https://${IP}:3080${CL}"
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://wakapi.dev/ | https://github.com/muety/wakapi
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apk add --no-cache \
ca-certificates \
tzdata
$STD update-ca-certificates
$STD apk add --no-cache go --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "wakapi" "muety/wakapi" "tarball"
msg_info "Configuring Wakapi"
LOCAL_IP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
cd /opt/wakapi
$STD go mod download
$STD go build -o wakapi
cp config.default.yml config.yml
sed -i 's/listen_ipv6: ::1/listen_ipv6: "-"/g' config.yml
sed -i 's/listen_ipv4: 127.0.0.1/listen_ipv4: "0.0.0.0"/g' config.yml
sed -i "s/public_url: http:\/\/localhost:3000/public_url: http:\/\/$LOCAL_IP:3000/g" config.yml
msg_ok "Configured Wakapi"
msg_info "Enabling Wakapi Service"
cat <<EOF >/etc/init.d/wakapi
#!/sbin/openrc-run
description="Wakapi Service"
directory="/opt/wakapi"
command="/opt/wakapi/wakapi"
command_args="-config config.yml"
command_background="true"
command_user="root"
pidfile="/var/run/wakapi.pid"
depend() {
use net
}
EOF
chmod +x /etc/init.d/wakapi
$STD rc-update add wakapi default
msg_ok "Enabled Wakapi Service"
msg_info "Starting Wakapi"
$STD rc-service wakapi start
msg_ok "Started Wakapi"
motd_ssh
customize
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Matthew Stern (sternma) | MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/dmunozv04/iSponsorBlockTV
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "isponsorblocktv" "dmunozv04/iSponsorBlockTV" "singlefile" "latest" "/opt/isponsorblocktv" "iSponsorBlockTV-*-linux"
msg_info "Setting up iSponsorBlockTV"
install -d /var/lib/isponsorblocktv
msg_ok "Set up iSponsorBlockTV"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/isponsorblocktv.service
[Unit]
Description=iSponsorBlockTV
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Group=root
Environment=iSPBTV_data_dir=/var/lib/isponsorblocktv
ExecStart=/opt/isponsorblocktv/isponsorblocktv
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q isponsorblocktv
msg_ok "Created Service"
msg_info "Creating CLI wrapper"
cat <<EOF >/usr/local/bin/iSponsorBlockTV
#!/usr/bin/env bash
export iSPBTV_data_dir="/var/lib/isponsorblocktv"
set +e
/opt/isponsorblocktv/isponsorblocktv "$@"
status=$?
set -e
case "${1:-}" in
setup|setup-cli)
systemctl restart isponsorblocktv >/dev/null 2>&1 || true
;;
esac
exit $status
EOF
chmod +x /usr/local/bin/iSponsorBlockTV
ln -sf /usr/local/bin/iSponsorBlockTV /usr/bin/iSponsorBlockTV
msg_ok "Created CLI wrapper"
motd_ssh
customize
cleanup_lxc
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Slaviša Arežina (tremor021)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://goteleport.com/
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
setup_deb822_repo \
"teleport" \
"https://deb.releases.teleport.dev/teleport-pubkey.asc" \
"https://apt.releases.teleport.dev/debian" \
"trixie" \
"stable/v18"
msg_info "Configuring Teleport"
$STD apt install -y teleport
$STD teleport configure -o /etc/teleport.yaml
systemctl enable -q --now teleport
sleep 10
tctl users add teleport-admin --roles=editor,access --logins=root >~/teleportadmin.txt
sed -i "s|https://[^:]*:3080|https://${LOCAL_IP}:3080|g" ~/teleportadmin.txt
msg_ok "Configured Teleport"
motd_ssh
customize
cleanup_lxc
@@ -285,7 +285,10 @@ EOF
post_progress_to_api
local tools_content
tools_content=$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func) || {
# Add cache-busting query parameter to ensure we get the latest version
# GitHub's CDN caches raw content, which can cause stale scripts to be served
local cache_bust="?_=$(date +%s)"
tools_content=$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}/misc/tools.func${cache_bust}") || {
msg_error "Failed to download tools.func"
exit 115
}
@@ -2198,6 +2198,7 @@ check_for_gh_release() {
local app="$1"
local source="$2"
local pinned_version_in="${3:-}" # optional
local pin_reason="${4:-}" # optional reason shown to user
local app_lc=""
app_lc="$(echo "${app,,}" | tr -d ' ')"
local current_file="$HOME/.${app_lc}"
@@ -2342,7 +2343,11 @@ check_for_gh_release() {
return 0
fi
msg_ok "No update available: ${app} is already on pinned version (${current})"
if [[ -n "$pin_reason" ]]; then
msg_ok "No update available: ${app} (${current}) - update held back: ${pin_reason}"
else
msg_ok "No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases"
fi
return 1
fi
@@ -2381,6 +2386,7 @@ check_for_codeberg_release() {
local app="$1"
local source="$2"
local pinned_version_in="${3:-}" # optional
local pin_reason="${4:-}" # optional reason shown to user
local app_lc="${app,,}"
local current_file="$HOME/.${app_lc}"
@@ -2460,7 +2466,11 @@ check_for_codeberg_release() {
return 0
fi
msg_ok "No update available: ${app} is already on pinned version (${current})"
if [[ -n "$pin_reason" ]]; then
msg_ok "No update available: ${app} (${current}) - update held back: ${pin_reason}"
else
msg_ok "No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases"
fi
return 1
fi
@@ -3057,6 +3067,61 @@ function fetch_and_deploy_codeberg_release() {
# (60s timeout, default yes) to use a previous version that has the asset.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Wait for HTTPS connectivity to be ready
# This is needed because DNS resolution and ping can work before HTTPS is ready
# during container startup (race condition with network stack initialization)
#
# Arguments:
# $1 - URL to test (default: https://api.github.com/rate_limit)
# $2 - Max wait time in seconds (default: 60)
#
# Returns: 0 if HTTPS is ready, 1 if timeout
# ------------------------------------------------------------------------------
wait_for_https_readiness() {
local test_url="${1:-https://api.github.com/rate_limit}"
local max_wait="${2:-60}"
local waited=0
local interval=2
local success_count=0
local required_successes=3
# Check if we're in a container (need longer warmup)
if [[ -f /.dockerenv ]] || grep -q 'lxc\|docker\|container' /proc/1/cgroup 2>/dev/null; then
# Container detected - start with 5 second delay for network stack to stabilize
msg_info "Container detected, waiting for network stack to stabilize..."
sleep 5
fi
msg_info "Waiting for HTTPS connectivity..."
while ((waited < max_wait)); do
if curl -fsSL --connect-timeout 3 --max-time 10 "$test_url" &>/dev/null; then
((success_count++))
msg_ok "HTTPS check ${success_count}/${required_successes} passed"
if ((success_count >= required_successes)); then
msg_ok "HTTPS connectivity verified after ${waited}s"
# Additional stabilization delay after successful checks
# Network can be intermittent during container startup
msg_info "Waiting 5s for network to stabilize..."
sleep 5
return 0
fi
# Wait between successful checks to ensure stability
sleep 2
((waited += 2))
else
success_count=0
msg_warn "HTTPS check failed, retrying... (${waited}s/${max_wait}s)"
fi
sleep $interval
((waited += interval))
done
msg_error "HTTPS connectivity NOT ready after ${max_wait}s timeout"
return 1
}
# ------------------------------------------------------------------------------
# Scans older GitHub releases for a matching asset when the latest release
# is missing the expected file. Used internally by fetch_and_deploy_gh_release.
@@ -3195,7 +3260,7 @@ function fetch_and_deploy_gh_release() {
local header=()
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
# dns pre check
# DNS pre-check
local gh_host
gh_host=$(awk -F/ '{print $3}' <<<"$api_url")
if ! getent hosts "$gh_host" &>/dev/null; then
@@ -3203,7 +3268,17 @@ function fetch_and_deploy_gh_release() {
return 1
fi
local max_retries=3 retry_delay=2 attempt=1 success=false http_code
# Wait for HTTPS connectivity to be ready before making API calls
# This prevents "no response" errors during container startup when network
# stack isn't fully initialized yet (DNS and ping work before HTTPS is ready)
# Use 60 second timeout for containers with slow network initialization
if ! wait_for_https_readiness "https://api.github.com/rate_limit" 60; then
msg_error "HTTPS connectivity not available after 60 seconds"
msg_error "Check your network configuration and firewall rules"
return 1
fi
local max_retries=5 retry_delay=2 attempt=1 success=false http_code
while ((attempt <= max_retries)); do
http_code=$(curl "$api_timeout" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
@@ -3235,6 +3310,28 @@ function fetch_and_deploy_gh_release() {
attempt=0
fi
fi
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
# Network failure - use exponential backoff
if ((attempt < max_retries)); then
msg_warn "Network failure (no response), retrying in ${retry_delay}s... (attempt $attempt/$max_retries)"
# Debug: test connectivity during retry
if ((attempt == 1)); then
msg_info "Testing network connectivity..."
if curl -fsSL --connect-timeout 5 --max-time 10 "https://api.github.com/rate_limit" &>/dev/null; then
msg_ok "Network connectivity test passed - API is reachable"
else
msg_warn "Network connectivity test FAILED - API is NOT reachable"
msg_info "This suggests intermittent network issues during container startup"
fi
fi
sleep "$retry_delay"
retry_delay=$((retry_delay * 2))
((retry_delay > 30)) && retry_delay=30
else
msg_error "GitHub API connection failed (no response after $max_retries attempts)."
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
msg_error "If network works manually, this may be a Proxmox/LXC network timing issue"
fi
else
sleep "$retry_delay"
fi
@@ -5207,24 +5304,7 @@ function setup_java() {
# Get currently installed version
local INSTALLED_VERSION=""
if dpkg -l | grep -q "temurin-.*-jdk" 2>/dev/null; then
INSTALLED_VERSION=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+' | head -n1 || echo "")
fi
# Validate INSTALLED_VERSION is not empty if JDK package found
local JDK_COUNT=0
JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || true)
if [[ -z "$INSTALLED_VERSION" && "${JDK_COUNT:-0}" -gt 0 ]]; then
msg_warn "Found Temurin JDK but cannot determine version - attempting reinstall"
# Try to get actual package name for purge
local OLD_PACKAGE
OLD_PACKAGE=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | head -n1 || echo "")
if [[ -n "$OLD_PACKAGE" ]]; then
msg_info "Removing existing package: $OLD_PACKAGE"
$STD apt purge -y "$OLD_PACKAGE" || true
fi
INSTALLED_VERSION="" # Reset to trigger fresh install
fi
INSTALLED_VERSION=$(dpkg-query -W -f '${Package}\n' 2>/dev/null | grep -oP '^temurin-\K[0-9]+(?=-jdk$)' | head -n1 || echo "")
# Scenario 1: Already at correct version
if [[ "$INSTALLED_VERSION" == "$JAVA_VERSION" ]]; then
@@ -0,0 +1,6 @@
__ ______ _____
/ / / / __ `/ __ \
/ /_/ / /_/ / /_/ /
\__, /\__,_/\____/
/____/
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/YaoApp/yao
APP="yao"
var_tags="${var_tags:-ai;agents;automation;low-code}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /usr/local/bin/yao ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "yao" "YaoApp/yao"; then
msg_info "Stopping Service"
systemctl stop yao
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/yao/data /opt/yao_data_backup 2>/dev/null || true
msg_ok "Backed up Data"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-v*-linux-*"
msg_info "Restoring Data"
cp -r /opt/yao_data_backup/. /opt/yao/data 2>/dev/null || true
rm -rf /opt/yao_data_backup
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start yao
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:5099${CL}"
@@ -0,0 +1,46 @@
{
"name": "Yao (In Development)",
"slug": "yao",
"categories": [20],
"date_created": "2026-03-20",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 5099,
"documentation": "https://yaoapps.com/docs",
"website": "https://yaoapps.com",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/yao.webp",
"config_path": "/opt/yao/.env",
"description": "Yao is an open-source engine for autonomous agents - event-driven, proactive, and self-scheduling. Build agents that work like real team members with three trigger modes: Clock (scheduled), Human (email/message), and Event (webhook/database).",
"install_methods": [
{
"type": "default",
"script": "ct/yao.sh",
"resources": {
"cpu": 2,
"ram": 4096,
"hdd": 10,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "After installation, create a new project: mkdir -p /opt/yao/myapp && cd /opt/yao/myapp && yao start",
"type": "info"
},
{
"text": "Access the web interface at http://IP:5099",
"type": "info"
},
{
"text": "Yao includes built-in SQLite database, V8 JavaScript engine, and TypeScript runtime - no additional dependencies required.",
"type": "info"
}
]
}
@@ -1,5 +1,5 @@
{
"generated": "2026-03-19T06:41:06Z",
"generated": "2026-03-19T18:38:18Z",
"versions": [
{
"slug": "agregarr",
@@ -18,9 +18,9 @@
{
"slug": "llamacpp",
"repo": "ggml-org/llama.cpp",
"version": "b8417",
"version": "b8429",
"pinned": false,
"date": "2026-03-19T05:37:13Z"
"date": "2026-03-19T14:00:45Z"
},
{
"slug": "localai",
@@ -54,7 +54,7 @@
"slug": "ragflow",
"repo": "infiniflow/ragflow",
"version": "v0.24.0",
"pinned": true,
"pinned": false,
"date": "2026-02-10T09:27:14Z"
},
{
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/YaoApp/yao
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt-get install -y \
curl \
unzip
msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-v*-linux-*"
msg_info "Creating Application Directory"
mkdir -p /opt/yao/data
msg_ok "Created Application Directory"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/yao.service
[Unit]
Description=Yao - Autonomous Agent Engine
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/yao
ExecStart=/usr/local/bin/yao start
Restart=on-failure
RestartSec=5
Environment=YAO_PORT=5099
Environment=YAO_STUDIO_PORT=5077
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now yao
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
@@ -9,10 +9,11 @@ APP="MCPHub"
var_tags="${var_tags:-ai;automation;tooling}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_disk="${var_disk:-10}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_database="${var_database:-postgresql}"
header_info "$APP"
variables
@@ -1,7 +1,6 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/infiniflow/ragflow
@@ -12,12 +12,28 @@ setting_up_container
network_check
update_os
# =============================================================================
# RUNTIMES & DATABASE SETUP
# =============================================================================
NODE_VERSION="22" setup_nodejs
# PostgreSQL with pgvector for smart routing
PG_VERSION="17" setup_postgresql
PG_DB_NAME="mcphub" PG_DB_USER="mcphub" PG_DB_EXTENSIONS="pgvector" setup_postgresql_db
# =============================================================================
# INSTALL MCPHUB
# =============================================================================
msg_info "Installing MCPHub"
$STD npm install -g @samanhappy/mcphub
msg_ok "Installed MCPHub"
# =============================================================================
# CONFIGURATION
# =============================================================================
msg_info "Creating Default Configuration"
mkdir -p /opt/mcphub
cat <<EOF >/opt/mcphub/mcp_settings.json
@@ -32,13 +48,18 @@ cat <<EOF >/opt/mcphub/mcp_settings.json
EOF
msg_ok "Created Default Configuration"
# =============================================================================
# CREATE SYSTEMD SERVICE
# =============================================================================
msg_info "Creating Service"
NPM_GLOBAL_BIN="$(npm prefix -g)/bin"
MCPHUB_BIN="${NPM_GLOBAL_BIN}/mcphub"
cat <<EOF >/etc/systemd/system/mcphub.service
[Unit]
Description=MCPHub
After=network.target
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
@@ -46,6 +67,7 @@ WorkingDirectory=/opt/mcphub
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=MCPHUB_SETTING_PATH=/opt/mcphub/mcp_settings.json
Environment=DATABASE_URL="postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}"
ExecStart=${MCPHUB_BIN}
Restart=always
RestartSec=5
@@ -56,6 +78,10 @@ EOF
systemctl enable -q --now mcphub
msg_ok "Created Service"
# =============================================================================
# CLEANUP & FINALIZATION
# =============================================================================
motd_ssh
customize
cleanup_lxc
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/infiniflow/ragflow
@@ -296,97 +297,7 @@ msg_ok "MinIO Installed"
# ==============================================================================
msg_info "Downloading RAGFlow"
# Function to download RAGFlow directly from GitHub (bypasses API)
download_ragflow_direct() {
local target_dir="/opt/ragflow"
local tmpdir
tmpdir=$(mktemp -d) || return 1
# Get latest release tag from GitHub redirects
local latest_tag=""
latest_tag=$(curl -fsSLI --connect-timeout 10 --max-time 30 "https://github.com/infiniflow/ragflow/releases/latest" 2>/dev/null | grep -i "location:" | tail -1 | sed 's/.*\/tag\/\([^ ]*\).*/\1/' | tr -d '\r\n')
if [[ -z "$latest_tag" ]]; then
msg_warn "Could not determine latest release tag, trying v0.17.0"
latest_tag="v0.17.0"
fi
msg_info "Found RAGFlow release: $latest_tag"
# Download tarball directly from GitHub
local tarball_url="https://github.com/infiniflow/ragflow/archive/refs/tags/${latest_tag}.tar.gz"
local filename="ragflow-${latest_tag}.tar.gz"
if ! curl -fsSL --connect-timeout 15 --max-time 600 -o "$tmpdir/$filename" "$tarball_url"; then
msg_error "Failed to download from $tarball_url"
rm -rf "$tmpdir"
return 1
fi
# Extract
mkdir -p "$target_dir"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target_dir:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
# Find extracted directory and copy contents
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target_dir/"
shopt -u dotglob nullglob
# Store version
local version="${latest_tag#v}"
echo "$version" > "$HOME/.ragflow"
rm -rf "$tmpdir"
return 0
}
# Try API-based download first, fall back to direct download
DOWNLOAD_SUCCESS=false
# Pre-check GitHub connectivity (both API and direct)
if getent hosts api.github.com >/dev/null 2>&1 || getent hosts github.com >/dev/null 2>&1; then
# Try fetch_and_deploy_gh_release first (uses API)
if fetch_and_deploy_gh_release "ragflow" "infiniflow/ragflow" "tarball" "latest" "/opt/ragflow" 2>/dev/null; then
DOWNLOAD_SUCCESS=true
fi
fi
# If API method failed, try direct download
if [[ "$DOWNLOAD_SUCCESS" != "true" ]]; then
msg_warn "GitHub API method failed, trying direct download..."
if download_ragflow_direct; then
DOWNLOAD_SUCCESS=true
fi
fi
# If both methods failed, show error
if [[ "$DOWNLOAD_SUCCESS" != "true" ]]; then
msg_error "Failed to download RAGFlow from GitHub"
msg_error ""
msg_error "This could be due to:"
msg_error " 1. GitHub API rate limit exceeded (60 requests/hour for anonymous users)"
msg_error " 2. Network firewall blocking github.com"
msg_error " 3. DNS resolution issues in LXC container"
msg_error ""
msg_error "Solutions:"
msg_error " 1. Set GITHUB_TOKEN: export GITHUB_TOKEN='ghp_your_token_here'"
msg_error " 2. Check DNS: echo 'nameserver 8.8.8.8' >> /etc/resolv.conf"
msg_error " 3. Test connectivity: curl -sSL https://github.com/infiniflow/ragflow/releases/latest"
exit 1
fi
fetch_and_deploy_gh_release "ragflow" "infiniflow/ragflow" "tarball" "latest" "/opt/ragflow"
msg_ok "Downloaded RAGFlow"
# ==============================================================================
@@ -655,9 +566,12 @@ msg_ok "Nginx Frontend Configured"
# ==============================================================================
msg_info "Starting RAGFlow Services"
systemctl enable -q ragflow-server
systemctl start ragflow-server
sleep 5
systemctl enable -q ragflow-task-executor
systemctl start ragflow-task-executor
msg_ok "Started RAGFlow Services"
# ==============================================================================
@@ -673,7 +587,6 @@ msg_ok "Reloaded Nginx"
# FINALIZATION
# ==============================================================================
motd_ssh
customize
cleanup_lxc
@@ -0,0 +1,675 @@
name: PocketBase Bot
on:
issue_comment:
types: [created]
permissions:
issues: write
pull-requests: write
contents: read
jobs:
pocketbase-bot:
runs-on: self-hosted
# Only act on /pocketbase commands
if: startsWith(github.event.comment.body, '/pocketbase')
steps:
- name: Execute PocketBase bot command
env:
POCKETBASE_URL: ${{ secrets.POCKETBASE_URL }}
POCKETBASE_COLLECTION: ${{ secrets.POCKETBASE_COLLECTION }}
POCKETBASE_ADMIN_EMAIL: ${{ secrets.POCKETBASE_ADMIN_EMAIL }}
POCKETBASE_ADMIN_PASSWORD: ${{ secrets.POCKETBASE_ADMIN_PASSWORD }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_ID: ${{ github.event.comment.id }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
ACTOR: ${{ github.event.comment.user.login }}
ACTOR_ASSOCIATION: ${{ github.event.comment.author_association }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node << 'ENDSCRIPT'
(async function () {
const https = require('https');
const http = require('http');
const url = require('url');
// ── HTTP helper with redirect following ────────────────────────────
function request(fullUrl, opts, redirectCount) {
redirectCount = redirectCount || 0;
return new Promise(function (resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
const body = opts.body;
const options = {
hostname: u.hostname,
port: u.port || (isHttps ? 443 : 80),
path: u.path,
method: opts.method || 'GET',
headers: opts.headers || {}
};
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function (res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
const redirectUrl = url.resolve(fullUrl, res.headers.location);
res.resume();
resolve(request(redirectUrl, opts, redirectCount + 1));
return;
}
let data = '';
res.on('data', function (chunk) { data += chunk; });
res.on('end', function () {
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: data });
});
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
// ── GitHub API helpers ─────────────────────────────────────────────
const owner = process.env.REPO_OWNER;
const repo = process.env.REPO_NAME;
const issueNumber = parseInt(process.env.ISSUE_NUMBER, 10);
const commentId = parseInt(process.env.COMMENT_ID, 10);
const actor = process.env.ACTOR;
function ghRequest(path, method, body) {
const headers = {
'Authorization': 'Bearer ' + process.env.GITHUB_TOKEN,
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'User-Agent': 'PocketBase-Bot'
};
const bodyStr = body ? JSON.stringify(body) : undefined;
if (bodyStr) headers['Content-Type'] = 'application/json';
return request('https://api.github.com' + path, { method: method || 'GET', headers, body: bodyStr });
}
async function addReaction(content) {
try {
await ghRequest(
'/repos/' + owner + '/' + repo + '/issues/comments/' + commentId + '/reactions',
'POST', { content }
);
} catch (e) {
console.warn('Could not add reaction:', e.message);
}
}
async function postComment(text) {
const res = await ghRequest(
'/repos/' + owner + '/' + repo + '/issues/' + issueNumber + '/comments',
'POST', { body: text }
);
if (!res.ok) console.warn('Could not post comment:', res.body);
}
// ── Permission check ───────────────────────────────────────────────
// author_association: OWNER = repo/org owner, MEMBER = org member (includes Contributors team)
const association = process.env.ACTOR_ASSOCIATION;
if (association !== 'OWNER' && association !== 'MEMBER') {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: @' + actor + ' is not authorized to use this command.\n' +
'Only org members (Contributors team) can use `/pocketbase`.'
);
process.exit(0);
}
// ── Acknowledge ────────────────────────────────────────────────────
await addReaction('eyes');
// ── Parse command ──────────────────────────────────────────────────
// Formats (first line of comment):
// /pocketbase <slug> field=value [field=value ...] ← field updates (simple values)
// /pocketbase <slug> set <field> ← value from code block below
// /pocketbase <slug> note list|add|edit|remove ... ← note management
// /pocketbase <slug> method list ← list install methods
// /pocketbase <slug> method <type> cpu=N ram=N hdd=N ← edit install method resources
const commentBody = process.env.COMMENT_BODY || '';
const lines = commentBody.trim().split('\n');
const firstLine = lines[0].trim();
const withoutCmd = firstLine.replace(/^\/pocketbase\s+/, '').trim();
// Extract code block content from comment body (```...``` or ```lang\n...```)
function extractCodeBlock(body) {
const m = body.match(/```[^\n]*\n([\s\S]*?)```/);
return m ? m[1].trim() : null;
}
const codeBlockValue = extractCodeBlock(commentBody);
const HELP_TEXT =
'**Field update (simple):** `/pocketbase <slug> field=value [field=value ...]`\n\n' +
'**Field update (HTML/multiline) — value from code block:**\n' +
'````\n' +
'/pocketbase <slug> set description\n' +
'```html\n' +
'<p>Your <b>HTML</b> or multi-line content here</p>\n' +
'```\n' +
'````\n\n' +
'**Note management:**\n' +
'```\n' +
'/pocketbase <slug> note list\n' +
'/pocketbase <slug> note add <type> "<text>"\n' +
'/pocketbase <slug> note edit <type> "<old text>" "<new text>"\n' +
'/pocketbase <slug> note remove <type> "<text>"\n' +
'```\n\n' +
'**Install method resources:**\n' +
'```\n' +
'/pocketbase <slug> method list\n' +
'/pocketbase <slug> method <type> hdd=10\n' +
'/pocketbase <slug> method <type> cpu=4 ram=2048 hdd=20\n' +
'```\n\n' +
'**Editable fields:** `name` `description` `logo` `documentation` `website` `project_url` `github` ' +
'`config_path` `port` `default_user` `default_passwd` ' +
'`updateable` `privileged` `has_arm` `is_dev` ' +
'`is_disabled` `disable_message` `is_deleted` `deleted_message`';
if (!withoutCmd) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: No slug or command specified.\n\n' + HELP_TEXT);
process.exit(0);
}
const spaceIdx = withoutCmd.indexOf(' ');
const slug = (spaceIdx === -1 ? withoutCmd : withoutCmd.substring(0, spaceIdx)).trim();
const rest = spaceIdx === -1 ? '' : withoutCmd.substring(spaceIdx + 1).trim();
if (!rest) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: No command specified for slug `' + slug + '`.\n\n' + HELP_TEXT);
process.exit(0);
}
// ── Allowed fields and their types ─────────────────────────────────
// ── PocketBase: authenticate (shared by all paths) ─────────────────
const raw = process.env.POCKETBASE_URL.replace(/\/$/, '');
const apiBase = /\/api$/i.test(raw) ? raw : raw + '/api';
const coll = process.env.POCKETBASE_COLLECTION;
const authRes = await request(apiBase + '/collections/users/auth-with-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identity: process.env.POCKETBASE_ADMIN_EMAIL,
password: process.env.POCKETBASE_ADMIN_PASSWORD
})
});
if (!authRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: PocketBase authentication failed. CC @' + owner + '/maintainers');
process.exit(1);
}
const token = JSON.parse(authRes.body).token;
// ── PocketBase: find record by slug (shared by all paths) ──────────
const recordsUrl = apiBase + '/collections/' + encodeURIComponent(coll) + '/records';
const filter = "(slug='" + slug.replace(/'/g, "''") + "')";
const listRes = await request(recordsUrl + '?filter=' + encodeURIComponent(filter) + '&perPage=1', {
headers: { 'Authorization': token }
});
const list = JSON.parse(listRes.body);
const record = list.items && list.items[0];
if (!record) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: No record found for slug `' + slug + '`.\n\n' +
'Make sure the script was already pushed to PocketBase (JSON must exist and have been synced).'
);
process.exit(0);
}
// ── Route: dispatch to subcommand handler ──────────────────────────
const noteMatch = rest.match(/^note\s+(list|add|edit|remove)\b/i);
const methodMatch = rest.match(/^method\b/i);
const setMatch = rest.match(/^set\s+(\S+)/i);
if (noteMatch) {
// ── NOTE SUBCOMMAND (reads/writes notes_json on script record) ────
const noteAction = noteMatch[1].toLowerCase();
const noteArgsStr = rest.substring(noteMatch[0].length).trim();
// Parse notes_json from the already-fetched script record
// PocketBase may return JSON fields as already-parsed objects
let notesArr = [];
try {
const rawNotes = record.notes_json;
notesArr = Array.isArray(rawNotes) ? rawNotes : JSON.parse(rawNotes || '[]');
} catch (e) { notesArr = []; }
// Token parser: unquoted-word OR "quoted string" (supports \" escapes)
function parseNoteTokens(str) {
const tokens = [];
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
if (str[pos] === '"') {
pos++;
let start = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
tokens.push(str.substring(start, pos).replace(/\\"/g, '"'));
if (pos < str.length) pos++;
} else {
let start = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
tokens.push(str.substring(start, pos));
}
}
return tokens;
}
function formatNotesList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (n, i) {
return (i + 1) + '. **`' + (n.type || '?') + '`**: ' + (n.text || '');
}).join('\n');
}
async function patchNotesJson(arr) {
const res = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ notes_json: JSON.stringify(arr) })
});
if (!res.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to update `notes_json`:\n```\n' + res.body + '\n```');
process.exit(1);
}
}
if (noteAction === 'list') {
await addReaction('+1');
await postComment(
'️ **PocketBase Bot**: Notes for **`' + slug + '`** (' + notesArr.length + ' total)\n\n' +
formatNotesList(notesArr)
);
} else if (noteAction === 'add') {
const tokens = parseNoteTokens(noteArgsStr);
if (tokens.length < 2) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: `note add` requires `<type>` and `"<text>"`.\n\n' +
'**Usage:** `/pocketbase ' + slug + ' note add <type> "<text>"`'
);
process.exit(0);
}
const noteType = tokens[0].toLowerCase();
const noteText = tokens.slice(1).join(' ');
notesArr.push({ type: noteType, text: noteText });
await patchNotesJson(notesArr);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Added note to **`' + slug + '`**\n\n' +
'- **Type:** `' + noteType + '`\n' +
'- **Text:** ' + noteText + '\n\n' +
'*Executed by @' + actor + '*'
);
} else if (noteAction === 'edit') {
const tokens = parseNoteTokens(noteArgsStr);
if (tokens.length < 3) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: `note edit` requires `<type>`, `"<old text>"`, and `"<new text>"`.\n\n' +
'**Usage:** `/pocketbase ' + slug + ' note edit <type> "<old text>" "<new text>"`\n\n' +
'Use `/pocketbase ' + slug + ' note list` to see current notes.'
);
process.exit(0);
}
const noteType = tokens[0].toLowerCase();
const oldText = tokens[1];
const newText = tokens[2];
const idx = notesArr.findIndex(function (n) {
return n.type.toLowerCase() === noteType && n.text === oldText;
});
if (idx === -1) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notesArr)
);
process.exit(0);
}
notesArr[idx].text = newText;
await patchNotesJson(notesArr);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Edited note in **`' + slug + '`**\n\n' +
'- **Type:** `' + noteType + '`\n' +
'- **Old:** ' + oldText + '\n' +
'- **New:** ' + newText + '\n\n' +
'*Executed by @' + actor + '*'
);
} else if (noteAction === 'remove') {
const tokens = parseNoteTokens(noteArgsStr);
if (tokens.length < 2) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: `note remove` requires `<type>` and `"<text>"`.\n\n' +
'**Usage:** `/pocketbase ' + slug + ' note remove <type> "<text>"`\n\n' +
'Use `/pocketbase ' + slug + ' note list` to see current notes.'
);
process.exit(0);
}
const noteType = tokens[0].toLowerCase();
const noteText = tokens[1];
const before = notesArr.length;
notesArr = notesArr.filter(function (n) {
return !(n.type.toLowerCase() === noteType && n.text === noteText);
});
if (notesArr.length === before) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: No `' + noteType + '` note found with that exact text.\n\n' +
'**Current notes for `' + slug + '`:**\n' + formatNotesList(notesArr)
);
process.exit(0);
}
await patchNotesJson(notesArr);
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Removed note from **`' + slug + '`**\n\n' +
'- **Type:** `' + noteType + '`\n' +
'- **Text:** ' + noteText + '\n\n' +
'*Executed by @' + actor + '*'
);
}
} else if (methodMatch) {
// ── METHOD SUBCOMMAND (reads/writes install_methods_json on script record) ──
const methodArgs = rest.replace(/^method\s*/i, '').trim();
const methodListMode = !methodArgs || methodArgs.toLowerCase() === 'list';
// Parse install_methods_json from the already-fetched script record
// PocketBase may return JSON fields as already-parsed objects
let methodsArr = [];
try {
const rawMethods = record.install_methods_json;
methodsArr = Array.isArray(rawMethods) ? rawMethods : JSON.parse(rawMethods || '[]');
} catch (e) { methodsArr = []; }
function formatMethodsList(arr) {
if (arr.length === 0) return '*None*';
return arr.map(function (im, i) {
const r = im.resources || {};
return (i + 1) + '. **`' + (im.type || '?') + '`** — CPU: `' + (r.cpu != null ? r.cpu : '?') +
'` · RAM: `' + (r.ram != null ? r.ram : '?') + ' MB` · HDD: `' + (r.hdd != null ? r.hdd : '?') + ' GB`';
}).join('\n');
}
async function patchInstallMethodsJson(arr) {
const res = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ install_methods_json: JSON.stringify(arr) })
});
if (!res.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Failed to update `install_methods_json`:\n```\n' + res.body + '\n```');
process.exit(1);
}
}
if (methodListMode) {
await addReaction('+1');
await postComment(
'️ **PocketBase Bot**: Install methods for **`' + slug + '`** (' + methodsArr.length + ' total)\n\n' +
formatMethodsList(methodsArr)
);
} else {
// Parse: <type> cpu=N ram=N hdd=N
const methodParts = methodArgs.match(/^(\S+)\s+(.+)$/);
if (!methodParts) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Invalid `method` syntax.\n\n' +
'**Usage:**\n```\n/pocketbase ' + slug + ' method list\n/pocketbase ' + slug + ' method <type> hdd=10\n/pocketbase ' + slug + ' method <type> cpu=4 ram=2048 hdd=20\n```'
);
process.exit(0);
}
const targetType = methodParts[1].toLowerCase();
const resourcesStr = methodParts[2];
// Parse resource fields (only cpu/ram/hdd allowed)
const RESOURCE_FIELDS = { cpu: true, ram: true, hdd: true };
const resourceChanges = {};
const rePairs = /([a-z]+)=(\d+)/gi;
let m;
while ((m = rePairs.exec(resourcesStr)) !== null) {
const key = m[1].toLowerCase();
if (RESOURCE_FIELDS[key]) resourceChanges[key] = parseInt(m[2], 10);
}
if (Object.keys(resourceChanges).length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: No valid resource fields found. Use `cpu=N`, `ram=N`, `hdd=N`.');
process.exit(0);
}
// Find matching method by type name (case-insensitive)
const idx = methodsArr.findIndex(function (im) {
return (im.type || '').toLowerCase() === targetType;
});
if (idx === -1) {
await addReaction('-1');
const availableTypes = methodsArr.map(function (im) { return im.type || '?'; });
await postComment(
'❌ **PocketBase Bot**: No install method with type `' + targetType + '` found for `' + slug + '`.\n\n' +
'**Available types:** `' + (availableTypes.length ? availableTypes.join('`, `') : '(none)') + '`\n\n' +
'Use `/pocketbase ' + slug + ' method list` to see all methods.'
);
process.exit(0);
}
if (!methodsArr[idx].resources) methodsArr[idx].resources = {};
if (resourceChanges.cpu != null) methodsArr[idx].resources.cpu = resourceChanges.cpu;
if (resourceChanges.ram != null) methodsArr[idx].resources.ram = resourceChanges.ram;
if (resourceChanges.hdd != null) methodsArr[idx].resources.hdd = resourceChanges.hdd;
await patchInstallMethodsJson(methodsArr);
const changesLines = Object.entries(resourceChanges)
.map(function ([k, v]) { return '- `' + k + '` → `' + v + (k === 'ram' ? ' MB' : k === 'hdd' ? ' GB' : '') + '`'; })
.join('\n');
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Updated install method **`' + methodsArr[idx].type + '`** for **`' + slug + '`**\n\n' +
'**Changes applied:**\n' + changesLines + '\n\n' +
'*Executed by @' + actor + '*'
);
}
} else if (setMatch) {
// ── SET SUBCOMMAND (multi-line / HTML / special chars via code block) ──
const fieldName = setMatch[1].toLowerCase();
const SET_ALLOWED = {
name: 'string', description: 'string', logo: 'string',
documentation: 'string', website: 'string', project_url: 'string', github: 'string',
config_path: 'string', disable_message: 'string', deleted_message: 'string'
};
if (!SET_ALLOWED[fieldName]) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: `set` only supports text fields.\n\n' +
'**Allowed:** `' + Object.keys(SET_ALLOWED).join('`, `') + '`\n\n' +
'For boolean/number fields use `field=value` syntax instead.'
);
process.exit(0);
}
if (!codeBlockValue) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: `set` requires a code block with the value.\n\n' +
'**Usage:**\n````\n/pocketbase ' + slug + ' set ' + fieldName + '\n```\nYour content here (HTML, multiline, special chars all fine)\n```\n````'
);
process.exit(0);
}
const setPayload = {};
setPayload[fieldName] = codeBlockValue;
const setPatchRes = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(setPayload)
});
if (!setPatchRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + setPatchRes.body + '\n```');
process.exit(1);
}
const preview = codeBlockValue.length > 300 ? codeBlockValue.substring(0, 300) + '…' : codeBlockValue;
await addReaction('+1');
await postComment(
'✅ **PocketBase Bot**: Set `' + fieldName + '` for **`' + slug + '`**\n\n' +
'**Value set:**\n```\n' + preview + '\n```\n\n' +
'*Executed by @' + actor + '*'
);
} else {
// ── FIELD=VALUE PATH ─────────────────────────────────────────────
const fieldsStr = rest;
// Skipped: slug, script_created/updated, created (auto), categories/
// install_methods/notes/type (relations), github_data/install_methods_json/
// notes_json (auto-generated), execute_in (select relation), last_update_commit (auto)
const ALLOWED_FIELDS = {
name: 'string',
description: 'string',
logo: 'string',
documentation: 'string',
website: 'string',
project_url: 'string',
github: 'string',
config_path: 'string',
port: 'number',
default_user: 'nullable_string',
default_passwd: 'nullable_string',
updateable: 'boolean',
privileged: 'boolean',
has_arm: 'boolean',
is_dev: 'boolean',
is_disabled: 'boolean',
disable_message: 'string',
is_deleted: 'boolean',
deleted_message: 'string',
};
// Field=value parser (handles quoted values and empty=null)
function parseFields(str) {
const fields = {};
let pos = 0;
while (pos < str.length) {
while (pos < str.length && /\s/.test(str[pos])) pos++;
if (pos >= str.length) break;
let keyStart = pos;
while (pos < str.length && str[pos] !== '=' && !/\s/.test(str[pos])) pos++;
const key = str.substring(keyStart, pos).trim();
if (!key || pos >= str.length || str[pos] !== '=') { pos++; continue; }
pos++;
let value;
if (str[pos] === '"') {
pos++;
let valStart = pos;
while (pos < str.length && str[pos] !== '"') {
if (str[pos] === '\\') pos++;
pos++;
}
value = str.substring(valStart, pos).replace(/\\"/g, '"');
if (pos < str.length) pos++;
} else {
let valStart = pos;
while (pos < str.length && !/\s/.test(str[pos])) pos++;
value = str.substring(valStart, pos);
}
fields[key] = value;
}
return fields;
}
const parsedFields = parseFields(fieldsStr);
const unknownFields = Object.keys(parsedFields).filter(function (f) { return !ALLOWED_FIELDS[f]; });
if (unknownFields.length > 0) {
await addReaction('-1');
await postComment(
'❌ **PocketBase Bot**: Unknown field(s): `' + unknownFields.join('`, `') + '`\n\n' +
'**Allowed fields:** `' + Object.keys(ALLOWED_FIELDS).join('`, `') + '`'
);
process.exit(0);
}
if (Object.keys(parsedFields).length === 0) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: Could not parse any valid `field=value` pairs.\n\n' + HELP_TEXT);
process.exit(0);
}
// Cast values to correct types
const payload = {};
for (const [key, rawVal] of Object.entries(parsedFields)) {
const type = ALLOWED_FIELDS[key];
if (type === 'boolean') {
if (rawVal === 'true') payload[key] = true;
else if (rawVal === 'false') payload[key] = false;
else {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: `' + key + '` must be `true` or `false`, got: `' + rawVal + '`');
process.exit(0);
}
} else if (type === 'number') {
const n = parseInt(rawVal, 10);
if (isNaN(n)) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: `' + key + '` must be a number, got: `' + rawVal + '`');
process.exit(0);
}
payload[key] = n;
} else if (type === 'nullable_string') {
payload[key] = rawVal === '' ? null : rawVal;
} else {
payload[key] = rawVal;
}
}
const patchRes = await request(recordsUrl + '/' + record.id, {
method: 'PATCH',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!patchRes.ok) {
await addReaction('-1');
await postComment('❌ **PocketBase Bot**: PATCH failed for `' + slug + '`:\n```\n' + patchRes.body + '\n```');
process.exit(1);
}
await addReaction('+1');
const changesLines = Object.entries(payload)
.map(function ([k, v]) { return '- `' + k + '` → `' + JSON.stringify(v) + '`'; })
.join('\n');
await postComment(
'✅ **PocketBase Bot**: Updated **`' + slug + '`** successfully!\n\n' +
'**Changes applied:**\n' + changesLines + '\n\n' +
'*Executed by @' + actor + '*'
);
}
console.log('Done.');
})().catch(function (e) {
console.error('Fatal error:', e.message || e);
process.exit(1);
});
ENDSCRIPT
shell: bash
@@ -0,0 +1,6 @@
__ __ __ __ ___
__ ______ _____/ /___ / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ / __ \/ __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) / /_/ / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/_/\____/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -5,7 +5,7 @@ source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/unslothai/unsloth
APP="unsolth-studio"
APP="unsloth-studio"
var_tags="${var_tags:-ai;llm;fine-tuning;training}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-16384}"
@@ -25,11 +25,19 @@ function update_script() {
check_container_storage
check_container_resources
if [[ ! -d /opt/unsolth-studio ]]; then
if [[ ! -d /opt/unsloth-studio ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
# Source the activation script to set up GPU environment
if [[ -f /opt/unsloth-studio/activate.sh ]]; then
source /opt/unsloth-studio/activate.sh
else
# Fallback: activate venv directly
source /opt/unsloth-studio/.venv/bin/activate
fi
if command -v unsloth &>/dev/null; then
CURRENT_VERSION=$(pip show unsloth 2>/dev/null | grep -i version | awk '{print $2}' || echo "unknown")
msg_info "Current version: ${CURRENT_VERSION}"
@@ -1,9 +1,7 @@
{
"name": "RAGFlow",
"slug": "ragflow",
"categories": [
20
],
"categories": [20],
"date_created": "2026-03-12",
"type": "ct",
"updateable": true,
@@ -75,6 +73,26 @@
{
"text": "Minimum 16GB RAM recommended for production workloads",
"type": "warning"
},
{
"text": "Optional MCP Server (disabled by default): Enable with 'systemctl enable --now ragflow-mcp.service'",
"type": "info"
},
{
"text": "MCP endpoint: http://<IP>:9382 - for AI assistant integration (Claude Desktop, etc.)",
"type": "info"
},
{
"text": "MCP Server requires RAGFlow API key - generate in Settings > API Keys",
"type": "warning"
},
{
"text": "MCP Documentation: https://ragflow.io/docs/launch_mcp_server",
"type": "info"
},
{
"text": "API Key Guide: https://ragflow.io/docs/acquire_ragflow_api_key",
"type": "info"
}
]
}
@@ -1,6 +1,6 @@
{
"name": "Unsloth Studio",
"slug": "unsolth-studio",
"slug": "unsloth-studio",
"categories": [20],
"date_created": "2026-03-18",
"type": "ct",
@@ -10,12 +10,12 @@
"documentation": "https://unsloth.ai/docs/new/studio/start",
"website": "https://unsloth.ai/",
"logo": "https://unsloth.ai/favicon.ico",
"config_path": "/opt/unsolth-studio/.env",
"config_path": "/opt/unsloth-studio/.env",
"description": "Local, browser-based GUI for fine-tuning LLMs without writing code. Supports QLoRA, LoRA, and full fine-tuning with live training monitoring, GPU stats, and model export to GGUF/Safetensors.",
"install_methods": [
{
"type": "default",
"script": "ct/unsolth-studio.sh",
"script": "ct/unsloth-studio.sh",
"resources": {
"cpu": 4,
"ram": 16384,
@@ -31,7 +31,7 @@
},
"notes": [
{
"text": "Default credentials: username 'unsloth', password is randomly generated and shown in logs. Run 'journalctl -u unsolth-studio | grep password' to find it.",
"text": "Default credentials: username 'unsloth', password is randomly generated and shown in logs. Run 'journalctl -u unsloth-studio | grep password' to find it.",
"type": "warning"
},
{
@@ -49,6 +49,10 @@
{
"text": "For AMD GPUs, ensure ROCm is properly configured on the host.",
"type": "info"
},
{
"text": "ROCm support for Unsloth Studio is not yet implemented. See https://github.com/unslothai/unsloth/pull/4390 for progress.",
"type": "warning"
}
]
}
@@ -111,6 +111,14 @@ max_allowed_packet=1073741824
max_connections=900
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
# Connection handling optimizations
wait_timeout=28800
interactive_timeout=28800
connect_timeout=60
# Buffer pool for performance
innodb_buffer_pool_size=2G
# Connection queue
back_log=900
EOF
systemctl restart mariadb
msg_ok "Configured MariaDB"
@@ -232,6 +240,10 @@ MINIO_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)
curl -fsSL https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio
chmod +x /usr/local/bin/minio
# Download MinIO Client (mc) for bucket management
curl -fsSL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc
# Create MinIO directories
mkdir -p /var/lib/minio/data
@@ -265,6 +277,18 @@ for i in {1..30}; do
fi
sleep 1
done
# Create ragflow bucket using MinIO Client
msg_info "Creating MinIO Bucket"
for i in {1..30}; do
if /usr/local/bin/mc alias set local http://localhost:9000 rag_flow "${MINIO_PASS}" 2>/dev/null; then
break
fi
sleep 1
done
/usr/local/bin/mc mb local/ragflow --ignore-existing 2>/dev/null || true
msg_ok "Created MinIO Bucket"
msg_ok "MinIO Installed"
# ==============================================================================
@@ -272,7 +296,97 @@ msg_ok "MinIO Installed"
# ==============================================================================
msg_info "Downloading RAGFlow"
fetch_and_deploy_gh_release "ragflow" "infiniflow/ragflow" "tarball" "v0.24.0" "/opt/ragflow"
# Function to download RAGFlow directly from GitHub (bypasses API)
download_ragflow_direct() {
local target_dir="/opt/ragflow"
local tmpdir
tmpdir=$(mktemp -d) || return 1
# Get latest release tag from GitHub redirects
local latest_tag=""
latest_tag=$(curl -fsSLI --connect-timeout 10 --max-time 30 "https://github.com/infiniflow/ragflow/releases/latest" 2>/dev/null | grep -i "location:" | tail -1 | sed 's/.*\/tag\/\([^ ]*\).*/\1/' | tr -d '\r\n')
if [[ -z "$latest_tag" ]]; then
msg_warn "Could not determine latest release tag, trying v0.17.0"
latest_tag="v0.17.0"
fi
msg_info "Found RAGFlow release: $latest_tag"
# Download tarball directly from GitHub
local tarball_url="https://github.com/infiniflow/ragflow/archive/refs/tags/${latest_tag}.tar.gz"
local filename="ragflow-${latest_tag}.tar.gz"
if ! curl -fsSL --connect-timeout 15 --max-time 600 -o "$tmpdir/$filename" "$tarball_url"; then
msg_error "Failed to download from $tarball_url"
rm -rf "$tmpdir"
return 1
fi
# Extract
mkdir -p "$target_dir"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target_dir:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
# Find extracted directory and copy contents
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target_dir/"
shopt -u dotglob nullglob
# Store version
local version="${latest_tag#v}"
echo "$version" > "$HOME/.ragflow"
rm -rf "$tmpdir"
return 0
}
# Try API-based download first, fall back to direct download
DOWNLOAD_SUCCESS=false
# Pre-check GitHub connectivity (both API and direct)
if getent hosts api.github.com >/dev/null 2>&1 || getent hosts github.com >/dev/null 2>&1; then
# Try fetch_and_deploy_gh_release first (uses API)
if fetch_and_deploy_gh_release "ragflow" "infiniflow/ragflow" "tarball" "latest" "/opt/ragflow" 2>/dev/null; then
DOWNLOAD_SUCCESS=true
fi
fi
# If API method failed, try direct download
if [[ "$DOWNLOAD_SUCCESS" != "true" ]]; then
msg_warn "GitHub API method failed, trying direct download..."
if download_ragflow_direct; then
DOWNLOAD_SUCCESS=true
fi
fi
# If both methods failed, show error
if [[ "$DOWNLOAD_SUCCESS" != "true" ]]; then
msg_error "Failed to download RAGFlow from GitHub"
msg_error ""
msg_error "This could be due to:"
msg_error " 1. GitHub API rate limit exceeded (60 requests/hour for anonymous users)"
msg_error " 2. Network firewall blocking github.com"
msg_error " 3. DNS resolution issues in LXC container"
msg_error ""
msg_error "Solutions:"
msg_error " 1. Set GITHUB_TOKEN: export GITHUB_TOKEN='ghp_your_token_here'"
msg_error " 2. Check DNS: echo 'nameserver 8.8.8.8' >> /etc/resolv.conf"
msg_error " 3. Test connectivity: curl -sSL https://github.com/infiniflow/ragflow/releases/latest"
exit 1
fi
msg_ok "Downloaded RAGFlow"
# ==============================================================================
@@ -309,8 +423,8 @@ mysql:
password: '${MARIADB_DB_PASS}'
host: 'localhost'
port: 3306
max_connections: 900
stale_timeout: 300
max_connections: 100
stale_timeout: 60
max_allowed_packet: 1073741824
minio:
user: 'rag_flow'
@@ -358,7 +472,7 @@ SVR_WEB_HTTPS_PORT=443
SVR_HTTP_PORT=9380
ADMIN_SVR_HTTP_PORT=9381
SVR_MCP_PORT=9382
RAGFLOW_IMAGE=infiniflow/ragflow:v0.24.0
RAGFLOW_IMAGE=infiniflow/ragflow:latest
TZ=UTC
REGISTER_ENABLED=1
THREAD_POOL_MAX_WORKERS=128
@@ -377,6 +491,7 @@ cat <<EOF >/etc/systemd/system/ragflow-server.service
Description=RAGFlow Backend Server
After=network.target mariadb.service elasticsearch.service redis-server.service minio.service
Requires=mariadb.service elasticsearch.service redis-server.service minio.service
Wants=network-online.target
[Service]
Type=simple
@@ -384,7 +499,10 @@ WorkingDirectory=/opt/ragflow
Environment=PYTHONPATH=/opt/ragflow
Environment=LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/
Environment=NLTK_DATA=/opt/ragflow/nltk_data
ExecStartPre=/bin/sleep 10
# Wait for services to be fully ready
ExecStartPre=/bin/sleep 15
# Health check for MariaDB
ExecStartPre=/bin/bash -c 'for i in {1..30}; do mysqladmin ping -h localhost --silent && break; sleep 1; done'
ExecStart=/usr/local/bin/uv run --index-strategy unsafe-best-match python api/ragflow_server.py
Restart=on-failure
RestartSec=10
@@ -417,6 +535,43 @@ LimitNOFILE=65535
WantedBy=multi-user.target
EOF
# ==============================================================================
# OPTIONAL: MCP SERVER SERVICE
# ==============================================================================
# The MCP (Model Context Protocol) server is optional and provides integration
# with AI assistants like Claude Desktop. It runs on port 9382 by default.
# To enable: systemctl enable --now ragflow-mcp.service
msg_info "Creating Optional MCP Server Service"
cat <<EOF >/etc/systemd/system/ragflow-mcp.service
[Unit]
Description=RAGFlow MCP Server (Model Context Protocol)
After=network.target mariadb.service elasticsearch.service redis-server.service minio.service ragflow-server.service
Requires=mariadb.service elasticsearch.service redis-server.service minio.service ragflow-server.service
[Service]
Type=simple
WorkingDirectory=/opt/ragflow
Environment=PYTHONPATH=/opt/ragflow
Environment=LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/
Environment=NLTK_DATA=/opt/ragflow/nltk_data
ExecStartPre=/bin/sleep 15
ExecStart=/usr/local/bin/uv run --index-strategy unsafe-best-match python mcp/server/server.py --host=0.0.0.0 --port=9382 --base-url=http://127.0.0.1:9380
Restart=on-failure
RestartSec=10
TimeoutStartSec=300
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
# MCP service is disabled by default - users must opt-in
systemctl disable ragflow-mcp.service 2>/dev/null || true
msg_ok "Created Optional MCP Server Service (disabled by default)"
msg_ok "Created Systemd Services"
# ==============================================================================
@@ -505,10 +660,20 @@ sleep 5
systemctl start ragflow-task-executor
msg_ok "Started RAGFlow Services"
# ==============================================================================
# Reloading Nginx and services after installation
# ==============================================================================
msg_info "Reloading Nginx and Services"
systemctl reload nginx
systemctl restart nginx
msg_ok "Reloaded Nginx"
# ==============================================================================
# FINALIZATION
# ==============================================================================
motd_ssh
customize
cleanup_lxc
@@ -538,3 +703,8 @@ echo -e "${TAB}- Configure your LLM API key in the web interface"
echo -e "${TAB}- Default uses CPU for document processing"
echo -e "${TAB}- For GPU acceleration, additional configuration required"
echo -e "${TAB}- Elasticsearch may take 1-2 minutes to fully initialize"
echo -e ""
echo -e "${INFO}${YW} Optional MCP Server (for AI assistant integration):${CL}"
echo -e "${TAB}- MCP endpoint: http://${LOCAL_IP}:9382"
echo -e "${TAB}- Enable with: systemctl enable --now ragflow-mcp.service"
echo -e "${TAB}- Requires RAGFlow API key from web interface"
@@ -33,8 +33,8 @@ setup_hwaccel
PYTHON_VERSION="3.13" setup_uv
msg_info "Creating Virtual Environment"
mkdir -p /opt/unsolth-studio
cd /opt/unsolth-studio || exit
mkdir -p /opt/unsloth-studio
cd /opt/unsloth-studio || exit
$STD uv venv --python 3.13
source .venv/bin/activate
msg_ok "Created Virtual Environment"
@@ -107,8 +107,10 @@ elif [ -d "/opt/rocm-6.2" ]; then
fi
# Check if GPU is available (works for both CUDA and ROCm)
if /opt/unsolth-studio/.venv/bin/python -c "import torch; exit(0 if torch.cuda.is_available() else 1)" 2>/dev/null; then
$STD /opt/unsolth-studio/.venv/bin/python -m unsloth studio setup
if /opt/unsloth-studio/.venv/bin/python -c "import torch; exit(0 if torch.cuda.is_available() else 1)" 2>/dev/null; then
# Use the unsloth CLI entry point instead of python -m unsloth
# The package installs a 'unsloth' command that provides the studio subcommand
$STD /opt/unsloth-studio/.venv/bin/unsloth studio setup
msg_ok "Completed Unsloth Studio Setup"
else
msg_info "GPU not detected via torch.cuda - skipping Unsloth Studio setup"
@@ -116,47 +118,89 @@ else
echo ""
echo -e "${GN}Note: If you have GPU passthrough configured, try:${CL}"
echo -e "${GN} 1. Restart the container: pct stop <CTID> && pct start <CTID>${CL}"
echo -e "${GN} 2. Then run: source /opt/unsolth-studio/.venv/bin/activate && unsloth studio setup${CL}"
echo -e "${GN} 2. Then run: source /opt/unsloth-studio/.venv/bin/activate && unsloth studio setup${CL}"
echo ""
fi
msg_info "Creating Directories"
mkdir -p /opt/unsolth-studio/models
mkdir -p /opt/unsolth-studio/datasets
mkdir -p /var/log/unsolth-studio
chmod 755 /var/log/unsolth-studio
mkdir -p /opt/unsloth-studio/models
mkdir -p /opt/unsloth-studio/datasets
mkdir -p /var/log/unsloth-studio
chmod 755 /var/log/unsloth-studio
msg_ok "Created Directories"
msg_info "Creating Service"
# Create environment file for ROCm/CUDA paths
cat <<EOF >/opt/unsolth-studio/environment.sh
# Detect ROCm version and create environment file
ROCM_PATH=""
HSA_GFX_VERSION=""
# Find ROCm installation
if [ -d "/opt/rocm" ]; then
ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
ROCM_PATH="/opt/rocm-6.2"
fi
# Detect GPU architecture for HSA_OVERRIDE_GFX_VERSION (needed for consumer AMD GPUs)
if [ -n "$ROCM_PATH" ] && [ -x "$ROCM_PATH/bin/rocminfo" ]; then
GFX_ARCH=$("$ROCM_PATH/bin/rocminfo" 2>/dev/null | grep -oP 'gfx\w+' | head -1 || true)
if [ -n "$GFX_ARCH" ]; then
HSA_GFX_VERSION="$GFX_ARCH"
msg_info "Detected AMD GPU architecture: $GFX_ARCH"
fi
fi
# Create a shell wrapper script for manual commands
cat <<'EOF' >/opt/unsloth-studio/activate.sh
#!/bin/bash
# Set up GPU environment for Unsloth Studio
# Activate script for Unsloth Studio with GPU support
# Source this file before running unsloth commands manually
# Activate virtual environment
source /opt/unsloth-studio/.venv/bin/activate
# ROCm environment (AMD GPUs)
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib:\$LD_LIBRARY_PATH"
export PATH="/opt/rocm/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib:\$LD_LIBRARY_PATH"
export PATH="/opt/rocm-7.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib:\$LD_LIBRARY_PATH"
export PATH="/opt/rocm-6.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-6.2"
fi
# NVIDIA CUDA environment
if [ -d "/usr/local/cuda" ]; then
export PATH="/usr/local/cuda/bin:\$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:\$LD_LIBRARY_PATH"
export PATH="/usr/local/cuda/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
EOF
chmod +x /opt/unsolth-studio/environment.sh
cat <<EOF >/etc/systemd/system/unsolth-studio.service
# Detect GPU architecture for HSA_OVERRIDE_GFX_VERSION
if [ -n "$ROCM_PATH" ] && [ -x "$ROCM_PATH/bin/rocminfo" ]; then
GFX_ARCH=$("$ROCM_PATH/bin/rocminfo" 2>/dev/null | grep -oP 'gfx\w+' | head -1 || true)
if [ -n "$GFX_ARCH" ]; then
export HSA_OVERRIDE_GFX_VERSION="$GFX_ARCH"
echo "GPU architecture detected: $GFX_ARCH"
fi
fi
# Make GPU visible
export HIP_VISIBLE_DEVICES=0
echo "Environment activated. GPU ready for use."
EOF
chmod +x /opt/unsloth-studio/activate.sh
# Create systemd service with proper GPU environment
# Note: systemd Environment doesn't support shell expansion, so we use static paths
cat <<EOF >/etc/systemd/system/unsloth-studio.service
[Unit]
Description=Unsloth Studio - Local LLM Fine-tuning Web UI
After=network.target network-online.target
@@ -164,15 +208,26 @@ Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/unsolth-studio
Environment="PATH=/opt/unsolth-studio/.venv/bin:/usr/local/bin:/usr/bin:/bin"
EnvironmentFile=/opt/unsolth-studio/environment.sh
ExecStart=/opt/unsolth-studio/.venv/bin/python -m unsloth studio -H 0.0.0.0 -p 8888
WorkingDirectory=/opt/unsloth-studio
Environment="PATH=/opt/unsloth-studio/.venv/bin:/opt/rocm/bin:/opt/rocm-7.2/bin:/opt/rocm-6.2/bin:/usr/local/cuda/bin:/usr/local/bin:/usr/bin:/bin"
Environment="LD_LIBRARY_PATH=/opt/rocm/lib:/opt/rocm-7.2/lib:/opt/rocm-6.2/lib:/usr/local/cuda/lib64"
Environment="ROCM_PATH=/opt/rocm"
Environment="HIP_VISIBLE_DEVICES=0"
EOF
# Add HSA_OVERRIDE_GFX_VERSION if detected
if [ -n "$HSA_GFX_VERSION" ]; then
echo "Environment=\"HSA_OVERRIDE_GFX_VERSION=$HSA_GFX_VERSION\"" >>/etc/systemd/system/unsloth-studio.service
fi
# Complete the service file
cat <<EOF >>/etc/systemd/system/unsloth-studio.service
ExecStart=/opt/unsloth-studio/.venv/bin/unsloth studio -H 0.0.0.0 -p 8888
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=unsolth-studio
SyslogIdentifier=unsloth-studio
# Resource limits
LimitNOFILE=65535
@@ -184,16 +239,24 @@ WantedBy=multi-user.target
EOF
# Don't auto-start the service since GPU passthrough may not be configured yet
# User needs to configure GPU passthrough first, then start the service manually
systemctl enable -q unsolth-studio
systemctl enable -q unsloth-studio
msg_ok "Created Service"
echo ""
echo -e "${GN}Note: The unsolth-studio service is enabled but not started.${CL}"
echo -e "${GN}Note: The unsloth-studio service is enabled but not started.${CL}"
echo -e "${GN}Configure GPU passthrough first, then start with:${CL}"
echo -e "${GN} systemctl start unsolth-studio${CL}"
echo -e "${GN} systemctl start unsloth-studio${CL}"
echo ""
if [ -n "$HSA_GFX_VERSION" ]; then
echo -e "${YW}AMD GPU detected with architecture: $HSA_GFX_VERSION${CL}"
echo -e "${YW}HSA_OVERRIDE_GFX_VERSION has been set in the systemd service.${CL}"
echo ""
fi
echo -e "${GN}For manual commands, activate the environment first:${CL}"
echo -e "${GN} source /opt/unsloth-studio/activate.sh${CL}"
echo ""
# Create GPU passthrough info file
cat <<EOF >/opt/unsolth-studio/GPU_PASSTHROUGH.md
cat <<EOF >/opt/unsloth-studio/GPU_PASSTHROUGH.md
# GPU Passthrough Configuration for Unsloth Studio
This container has been configured for GPU acceleration for LLM fine-tuning.
@@ -232,9 +295,31 @@ lxc.cgroup2.devices.allow: c 226:128 rwm
Run these commands inside the container:
- nvidia-smi (NVIDIA GPUs)
- rocminfo (AMD GPUs)
- rocm-smi or rocminfo (AMD GPUs)
- python -c "import torch; print(torch.cuda.is_available())"
## AMD GPU Configuration
For AMD consumer GPUs (Radeon RX series), the HSA_OVERRIDE_GFX_VERSION environment
variable is automatically set during installation based on your GPU architecture.
If torch.cuda.is_available() returns False, you may need to manually set it:
\`\`\`
# Find your GPU architecture
rocminfo | grep -oP 'gfx\\w+' | head -1
# Set the override (example for RX 7900 XT - gfx1100)
export HSA_OVERRIDE_GFX_VERSION=gfx1100
\`\`\`
## Manual Environment Activation
For running unsloth commands manually, activate the environment first:
\`\`\`
source /opt/unsloth-studio/activate.sh
unsloth studio setup
\`\`\`
## Usage
Access the web UI at: http://<IP>:8888
@@ -420,6 +420,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-03-19
### 📂 Github
- Upstream sync 20260319 160030 [@BillyOutlast](https://github.com/BillyOutlast) ([#69](https://github.com/Heretek-AI/ProxmoxVE/pull/69))
## 2026-03-18
### 🆕 New Scripts
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/infiniflow/ragflow
@@ -30,7 +31,14 @@ function update_script() {
fi
if check_for_gh_release "ragflow" "infiniflow/ragflow"; then
# Check if MCP service is enabled before stopping
MCP_WAS_ENABLED=false
if systemctl is-enabled ragflow-mcp.service 2>/dev/null | grep -q "enabled"; then
MCP_WAS_ENABLED=true
fi
msg_info "Stopping Services"
systemctl stop ragflow-mcp || true
systemctl stop ragflow-task-executor || true
systemctl stop ragflow-server || true
msg_ok "Stopped Services"
@@ -68,6 +76,23 @@ function update_script() {
systemctl start ragflow-server
sleep 5
systemctl start ragflow-task-executor
# Restart MCP service if it was enabled before update
if [[ "$MCP_WAS_ENABLED" == "true" ]]; then
msg_info "Restarting MCP Server"
systemctl start ragflow-mcp || true
msg_ok "Restarted MCP Server"
fi
# Verify MinIO bucket exists (fixes NoSuchBucket errors)
msg_info "Verifying MinIO Bucket"
MINIO_PASS=$(grep -oP 'MINIO_PASSWORD=\K[^"]+' /opt/ragflow/.env 2>/dev/null || grep -oP 'password: \K[^"]+' /opt/ragflow/conf/service_conf.yaml 2>/dev/null | head -1)
if [[ -n "$MINIO_PASS" ]] && [[ -x /usr/local/bin/mc ]]; then
/usr/local/bin/mc alias set local http://localhost:9000 rag_flow "${MINIO_PASS}" 2>/dev/null || true
/usr/local/bin/mc mb local/ragflow --ignore-existing 2>/dev/null || true
fi
msg_ok "Verified MinIO Bucket"
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
@@ -83,3 +108,8 @@ echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}"
echo -e "${INFO}${YW} API endpoint: http://${IP}:9380${CL}"
echo -e ""
echo -e "${INFO}${YW} Optional MCP Server (for AI assistant integration):${CL}"
echo -e "${TAB}- MCP endpoint: http://${IP}:9382"
echo -e "${TAB}- Enable with: systemctl enable --now ragflow-mcp.service"
echo -e "${TAB}- Requires RAGFlow API key from web interface"
@@ -420,6 +420,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-03-19
### 📂 Github
- Upstream sync 20260319 160030 [@BillyOutlast](https://github.com/BillyOutlast) ([#69](https://github.com/Heretek-AI/ProxmoxVE/pull/69))
## 2026-03-18
### 🆕 New Scripts
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/infiniflow/ragflow
@@ -30,7 +31,14 @@ function update_script() {
fi
if check_for_gh_release "ragflow" "infiniflow/ragflow"; then
# Check if MCP service is enabled before stopping
MCP_WAS_ENABLED=false
if systemctl is-enabled ragflow-mcp.service 2>/dev/null | grep -q "enabled"; then
MCP_WAS_ENABLED=true
fi
msg_info "Stopping Services"
systemctl stop ragflow-mcp || true
systemctl stop ragflow-task-executor || true
systemctl stop ragflow-server || true
msg_ok "Stopped Services"
@@ -68,6 +76,23 @@ function update_script() {
systemctl start ragflow-server
sleep 5
systemctl start ragflow-task-executor
# Restart MCP service if it was enabled before update
if [[ "$MCP_WAS_ENABLED" == "true" ]]; then
msg_info "Restarting MCP Server"
systemctl start ragflow-mcp || true
msg_ok "Restarted MCP Server"
fi
# Verify MinIO bucket exists (fixes NoSuchBucket errors)
msg_info "Verifying MinIO Bucket"
MINIO_PASS=$(grep -oP 'MINIO_PASSWORD=\K[^"]+' /opt/ragflow/.env 2>/dev/null || grep -oP 'password: \K[^"]+' /opt/ragflow/conf/service_conf.yaml 2>/dev/null | head -1)
if [[ -n "$MINIO_PASS" ]] && [[ -x /usr/local/bin/mc ]]; then
/usr/local/bin/mc alias set local http://localhost:9000 rag_flow "${MINIO_PASS}" 2>/dev/null || true
/usr/local/bin/mc mb local/ragflow --ignore-existing 2>/dev/null || true
fi
msg_ok "Verified MinIO Bucket"
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
@@ -83,3 +108,8 @@ echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}"
echo -e "${INFO}${YW} API endpoint: http://${IP}:9380${CL}"
echo -e ""
echo -e "${INFO}${YW} Optional MCP Server (for AI assistant integration):${CL}"
echo -e "${TAB}- MCP endpoint: http://${IP}:9382"
echo -e "${TAB}- Enable with: systemctl enable --now ragflow-mcp.service"
echo -e "${TAB}- Requires RAGFlow API key from web interface"
@@ -19,7 +19,7 @@ These documents cover the coding standards for the following types of files in o
- **`install/$AppName-install.sh` Scripts**: These scripts are responsible for the installation of applications.
- **`ct/$AppName.sh` Scripts**: These scripts handle the creation and updating of containers.
- **`json/$AppName.json`**: These files store structured data and are used for the website.
- **Website metadata**: Display data (name, description, logo, etc.) is requested via the website (Report issue on the script page), not via files in the repo.
Each section provides detailed guidelines on various aspects of coding, including shebang usage, comments, variable naming, function naming, indentation, error handling, command substitution, quoting, script structure, and logging. Additionally, examples are provided to illustrate the application of these standards.
@@ -110,7 +110,7 @@ git push origin your-feature-branch
### 6. Cherry-Pick: Submit Only Your Files for PR
⚠️ **IMPORTANT**: setup-fork.sh modified 600+ files. You MUST only submit your 3 new files!
⚠️ **IMPORTANT**: setup-fork.sh modified 600+ files. You MUST only submit your 2 new files!
See [README.md - Cherry-Pick Guide](README.md#-cherry-pick-submitting-only-your-changes) for step-by-step instructions.
@@ -124,12 +124,11 @@ git checkout -b submit/myapp upstream/main
# Copy only your files
cp ../your-work-branch/ct/myapp.sh ct/myapp.sh
cp ../your-work-branch/install/myapp-install.sh install/myapp-install.sh
cp ../your-work-branch/frontend/public/json/myapp.json frontend/public/json/myapp.json
# Commit and verify
git add ct/myapp.sh install/myapp-install.sh frontend/public/json/myapp.json
git add ct/myapp.sh install/myapp-install.sh
git commit -m "feat: add MyApp"
git diff upstream/main --name-only # Should show ONLY your 3 files
git diff upstream/main --name-only # Should show ONLY your 2 files
# Push and create PR
git push origin submit/myapp
@@ -139,11 +138,10 @@ git push origin submit/myapp
Open a Pull Request from `submit/myapp``community-scripts/ProxmoxVE/main`.
Verify the PR shows ONLY these 3 files:
Verify the PR shows ONLY these 2 files:
- `ct/myapp.sh`
- `install/myapp-install.sh`
- `frontend/public/json/myapp.json`
---
@@ -175,4 +173,4 @@ dev_mode="trace,keep" bash -c "$(curl -fsSL https://raw.githubusercontent.com/co
- [CT Template: AppName.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_ct/AppName.sh)
- [Install Template: AppName-install.sh](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_install/AppName-install.sh)
- [JSON Template: AppName.json](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_json/AppName.json)
- [JSON Template: AppName.json](https://github.com/community-scripts/ProxmoxVE/blob/main/docs/contribution/templates_json/AppName.json) — metadata structure reference; submit via the website (Report issue on script page)
@@ -1,12 +1,12 @@
{
"generated": "2026-03-16T18:42:54Z",
"generated": "2026-03-19T06:41:06Z",
"versions": [
{
"slug": "agregarr",
"repo": "agregarr/agregarr",
"version": "v2.4.1",
"version": "v2.4.2",
"pinned": false,
"date": "2026-03-05T06:43:40Z"
"date": "2026-03-17T08:57:42Z"
},
{
"slug": "lemonade",
@@ -18,9 +18,9 @@
{
"slug": "llamacpp",
"repo": "ggml-org/llama.cpp",
"version": "b8373",
"version": "b8417",
"pinned": false,
"date": "2026-03-16T10:55:12Z"
"date": "2026-03-19T05:37:13Z"
},
{
"slug": "localai",
@@ -57,6 +57,13 @@
"pinned": true,
"date": "2026-02-10T09:27:14Z"
},
{
"slug": "skillserver",
"repo": "mudler/skillserver",
"version": "v0.0.4",
"pinned": false,
"date": "2026-01-28T10:30:17Z"
},
{
"slug": "wakapi",
"repo": "muety/wakapi",
@@ -1,5 +1,5 @@
{
"name": "Unsloth Studio (In Beta)",
"name": "Unsloth Studio",
"slug": "unsolth-studio",
"categories": [20],
"date_created": "2026-03-18",
@@ -26,10 +26,14 @@
}
],
"default_credentials": {
"username": null,
"password": null
"username": "unsloth",
"password": "Check logs for generated password"
},
"notes": [
{
"text": "Default credentials: username 'unsloth', password is randomly generated and shown in logs. Run 'journalctl -u unsolth-studio | grep password' to find it.",
"type": "warning"
},
{
"text": "Requires GPU passthrough for training. NVIDIA, AMD (ROCm), and Intel GPUs are supported.",
"type": "info"
@@ -5668,33 +5668,33 @@ create_lxc_container() {
description() {
IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
# Generate LXC Description
# Generate LXC Description - Heretek-AI themed
DESCRIPTION=$(
cat <<EOF
<div align='center'>
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Heretek-AI Logo' style='width:81px;height:112px;'/>
</a>
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
<h2 style='font-size: 24px; margin: 20px 0; color: #dc2626;'>${APP} LXC</h2>
<p style='margin: 16px 0;'>
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
<a href='https://discord.gg/3AnUqsXnmK' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white' alt='Discord' />
</a>
</p>
<span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
<i class="fa fa-github fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
<i class="fa fa-comments fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
<i class="fa fa-exclamation-circle fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Issues</a>
</span>
</div>
EOF
@@ -5668,33 +5668,33 @@ create_lxc_container() {
description() {
IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
# Generate LXC Description
# Generate LXC Description - Heretek-AI themed
DESCRIPTION=$(
cat <<EOF
<div align='center'>
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Heretek-AI Logo' style='width:81px;height:112px;'/>
</a>
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
<h2 style='font-size: 24px; margin: 20px 0; color: #dc2626;'>${APP} LXC</h2>
<p style='margin: 16px 0;'>
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
<a href='https://discord.gg/3AnUqsXnmK' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white' alt='Discord' />
</a>
</p>
<span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
<i class="fa fa-github fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
<i class="fa fa-comments fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
<i class="fa fa-exclamation-circle fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Issues</a>
</span>
</div>
EOF
@@ -86,16 +86,16 @@ variables() {
# Set default URL if not already defined by the calling script
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/api.func)
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/api.func)
if command -v curl >/dev/null 2>&1; then
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/core.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/error_handler.func)
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
load_functions
catch_errors
elif command -v wget >/dev/null 2>&1; then
source <(wget -qO- "${COMMUNITY_SCRIPTS_URL}"/misc/core.func)
source <(wget -qO- "${COMMUNITY_SCRIPTS_URL}"/misc/error_handler.func)
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
load_functions
catch_errors
fi
@@ -1489,7 +1489,7 @@ _build_vars_diff() {
# Build a temporary <app>.vars file from current advanced settings
_build_current_app_vars_tmp() {
tmpf="$(mktemp /tmp/"${NSAPP:-app}".vars.new.XXXXXX)"
tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)"
# NET/GW
_net="${NET:-}"
@@ -3440,7 +3440,7 @@ msg_menu() {
# - Otherwise: shows update/setting menu and runs update_script with cleanup
# ------------------------------------------------------------------------------
start() {
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/tools.func)
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func)
if command -v pveversion >/dev/null 2>&1; then
install_script || return 0
return 0
@@ -4136,7 +4136,7 @@ EOF'
# that sends "configuring" status AFTER the host already reported "failed"
export CONTAINER_INSTALLING=true
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/install/"${var_install}".sh)"
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
local lxc_exit=$?
unset CONTAINER_INSTALLING
@@ -4253,7 +4253,7 @@ EOF'
else
msg_dev "Container ${CTID} kept for debugging"
fi
exit "$install_exit_code"
exit $install_exit_code
fi
# Prompt user for cleanup with 60s timeout
@@ -4449,7 +4449,7 @@ EOF'
echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}"
fi
fi
exit "$install_exit_code"
exit $install_exit_code
;;
3)
# Retry with verbose mode (full rebuild)
@@ -4514,7 +4514,7 @@ EOF'
# Re-run install script in existing container (don't destroy/recreate)
set +Eeuo pipefail
trap - ERR
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/install/"${var_install}".sh)"
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
local apt_retry_exit=$?
set -Eeuo pipefail
trap 'error_handler' ERR
@@ -4630,7 +4630,7 @@ EOF'
if [[ "$handled" == false ]]; then
echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}"
exit "$install_exit_code"
exit $install_exit_code
fi
;;
esac
@@ -4651,7 +4651,7 @@ EOF'
# Restore default job-control signal handling before exit
trap - TSTP TTIN TTOU
exit "$install_exit_code"
exit $install_exit_code
fi
# Re-enable error handling after successful install or recovery menu completion
@@ -5015,7 +5015,7 @@ create_lxc_container() {
msg_ok "LXC stack upgraded."
if [[ "$do_retry" == "yes" ]]; then
msg_info "Retrying container creation after upgrade"
if eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
msg_ok "Container created successfully after upgrade."
return 0
else
@@ -5270,7 +5270,7 @@ create_lxc_container() {
fi
fi
TEMPLATE_PATH="$(pvesm path "$TEMPLATE_STORAGE":vztmpl/"$TEMPLATE" 2>/dev/null || true)"
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
if [[ -z "$TEMPLATE_PATH" ]]; then
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
@@ -5334,7 +5334,7 @@ create_lxc_container() {
TEMPLATE_SOURCE="online"
fi
TEMPLATE_PATH="$(pvesm path "$TEMPLATE_STORAGE":vztmpl/"$TEMPLATE" 2>/dev/null || true)"
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
if [[ -z "$TEMPLATE_PATH" ]]; then
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
@@ -5529,8 +5529,8 @@ create_lxc_container() {
msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS"
msg_debug "Logfile: $LOGFILE"
# First attempt (PCT_OPTIONS is a multi-line string, use eval to expand it properly)
if ! eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >"$LOGFILE" 2>&1; then
# First attempt (PCT_OPTIONS is a multi-line string, use it directly)
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then
msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..."
# Check if template issue - retry with fresh download
@@ -5542,7 +5542,7 @@ create_lxc_container() {
fi
# Retry after repair
if ! eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
# Fallback to local storage if not already on local
if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
msg_info "Retrying container creation with fallback to local storage"
@@ -5555,7 +5555,7 @@ create_lxc_container() {
else
msg_ok "Trying local storage fallback"
fi
if ! eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
# Local fallback also failed - check for LXC stack version issue
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
msg_warn "pct reported 'unsupported version' LXC stack might be too old for this template"
@@ -5578,7 +5578,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -5610,7 +5610,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -1,6 +0,0 @@
____ __ __ ___
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -1,6 +0,0 @@
____ __ __ ___
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -1,62 +0,0 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/unslothai/unsloth
APP="unsolth-studio"
var_tags="${var_tags:-ai;llm;fine-tuning;training}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-16384}"
var_disk="${var_disk:-50}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_gpu="${var_gpu:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/unsolth-studio ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if command -v unsloth &>/dev/null; then
CURRENT_VERSION=$(pip show unsloth 2>/dev/null | grep -i version | awk '{print $2}' || echo "unknown")
msg_info "Current version: ${CURRENT_VERSION}"
msg_info "Checking for updates"
$STD pip install --upgrade unsloth 2>/dev/null
NEW_VERSION=$(pip show unsloth 2>/dev/null | grep -i version | awk '{print $2}' || echo "unknown")
if [[ "$CURRENT_VERSION" != "$NEW_VERSION" ]]; then
msg_ok "Updated from ${CURRENT_VERSION} to ${NEW_VERSION}"
else
msg_ok "Already at latest version: ${NEW_VERSION}"
fi
else
msg_error "Unsloth not installed properly"
exit 1
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}"
echo -e "${INFO}${YW} Note: First launch may take 5-10 minutes to compile llama.cpp${CL}"
@@ -1,6 +0,0 @@
____ __ __ ___
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -1,62 +0,0 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/unslothai/unsloth
APP="unsolth-studio"
var_tags="${var_tags:-ai;llm;fine-tuning;training}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-16384}"
var_disk="${var_disk:-50}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_gpu="${var_gpu:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/unsolth-studio ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if command -v unsloth &>/dev/null; then
CURRENT_VERSION=$(pip show unsloth 2>/dev/null | grep -i version | awk '{print $2}' || echo "unknown")
msg_info "Current version: ${CURRENT_VERSION}"
msg_info "Checking for updates"
$STD pip install --upgrade unsloth 2>/dev/null
NEW_VERSION=$(pip show unsloth 2>/dev/null | grep -i version | awk '{print $2}' || echo "unknown")
if [[ "$CURRENT_VERSION" != "$NEW_VERSION" ]]; then
msg_ok "Updated from ${CURRENT_VERSION} to ${NEW_VERSION}"
else
msg_ok "Already at latest version: ${NEW_VERSION}"
fi
else
msg_error "Unsloth not installed properly"
exit 1
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8888${CL}"
echo -e "${INFO}${YW} Note: First launch may take 5-10 minutes to compile llama.cpp${CL}"
@@ -5015,7 +5015,7 @@ create_lxc_container() {
msg_ok "LXC stack upgraded."
if [[ "$do_retry" == "yes" ]]; then
msg_info "Retrying container creation after upgrade"
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
msg_ok "Container created successfully after upgrade."
return 0
else
@@ -5529,8 +5529,8 @@ create_lxc_container() {
msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} $PCT_OPTIONS"
msg_debug "Logfile: $LOGFILE"
# First attempt (PCT_OPTIONS is a multi-line string, use it directly)
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >"$LOGFILE" 2>&1; then
# First attempt (PCT_OPTIONS is a multi-line string, use eval to expand it properly)
if ! eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >"$LOGFILE" 2>&1; then
msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..."
# Check if template issue - retry with fresh download
@@ -5542,7 +5542,7 @@ create_lxc_container() {
fi
# Retry after repair
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if ! eval pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
# Fallback to local storage if not already on local
if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
msg_info "Retrying container creation with fallback to local storage"
@@ -5555,7 +5555,7 @@ create_lxc_container() {
else
msg_ok "Trying local storage fallback"
fi
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
if ! eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
# Local fallback also failed - check for LXC stack version issue
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
msg_warn "pct reported 'unsupported version' LXC stack might be too old for this template"
@@ -5578,7 +5578,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -5610,7 +5610,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
eval pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -1,6 +0,0 @@
____ __ __ ___
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -1,266 +0,0 @@
#!/usr/bin/env bash
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/unslothai/unsloth
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt-get install -y \
curl \
wget \
git \
cmake \
build-essential \
python3 \
python3-pip \
python3-venv \
pciutils
msg_ok "Installed Dependencies"
# Setup GPU hardware acceleration FIRST (detects GPU, installs drivers, configures permissions)
# This must run before installing unsloth/torch so PyTorch can detect the GPU
setup_hwaccel
# Setup Python virtual environment with uv (fast Python package manager)
PYTHON_VERSION="3.13" setup_uv
msg_info "Creating Virtual Environment"
mkdir -p /opt/unsolth-studio
cd /opt/unsolth-studio || exit
$STD uv venv --python 3.13
source .venv/bin/activate
msg_ok "Created Virtual Environment"
msg_info "Detecting GPU Type for PyTorch Installation"
# Detect GPU type based on what setup_hwaccel installed
# setup_hwaccel runs before this and installs NVIDIA drivers or ROCm
GPU_TYPE="cpu"
# Check for NVIDIA GPU (nvidia-smi installed by setup_hwaccel)
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null; then
GPU_TYPE="nvidia"
msg_info "NVIDIA GPU detected - installing PyTorch with CUDA support"
# Check for AMD GPU (ROCm installed by setup_hwaccel at /opt/rocm)
elif [ -d "/opt/rocm" ] || [ -d "/opt/rocm-7.2" ] || [ -d "/opt/rocm-6.2" ]; then
GPU_TYPE="amd"
msg_info "AMD GPU detected (ROCm installed) - installing PyTorch with ROCm support"
# Check for AMD render devices (GPU passthrough configured)
elif ls /dev/dri/renderD* &>/dev/null 2>&1; then
# Check if any render device is AMD
for render_dev in /dev/dri/renderD*; do
if [ -e "$render_dev" ]; then
GPU_TYPE="amd"
msg_info "AMD GPU detected (render device) - installing PyTorch with ROCm support"
break
fi
done
fi
if [ "$GPU_TYPE" = "nvidia" ]; then
# NVIDIA GPU - install PyTorch with CUDA 12.4 support
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
msg_ok "Installed PyTorch with CUDA Support"
elif [ "$GPU_TYPE" = "amd" ]; then
# AMD GPU - install PyTorch with ROCm 7.2 support
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/test/rocm7.2
msg_ok "Installed PyTorch with ROCm Support"
else
# No GPU detected - install CPU version
msg_info "No GPU detected - installing PyTorch CPU version"
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
msg_ok "Installed PyTorch (CPU version)"
fi
msg_info "Installing Unsloth"
# Install unsloth and its dependencies
# packaging module is required but not declared as dependency
$STD uv pip install unsloth packaging
msg_ok "Installed Unsloth"
msg_info "Running Unsloth Studio Setup"
# Run the unsloth studio setup command to compile llama.cpp
# This requires GPU access - set up environment for ROCm if installed
# Set up ROCm environment if available
# Use ${VAR:-} to handle unset variables (set -u causes errors otherwise)
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-6.2"
fi
# Check if GPU is available (works for both CUDA and ROCm)
if /opt/unsolth-studio/.venv/bin/python -c "import torch; exit(0 if torch.cuda.is_available() else 1)" 2>/dev/null; then
$STD /opt/unsolth-studio/.venv/bin/python -m unsloth studio setup
msg_ok "Completed Unsloth Studio Setup"
else
msg_info "GPU not detected via torch.cuda - skipping Unsloth Studio setup"
msg_info "This may be normal if ROCm libraries need system restart to take effect"
echo ""
echo -e "${GN}Note: If you have GPU passthrough configured, try:${CL}"
echo -e "${GN} 1. Restart the container: pct stop <CTID> && pct start <CTID>${CL}"
echo -e "${GN} 2. Then run: source /opt/unsolth-studio/.venv/bin/activate && unsloth studio setup${CL}"
echo ""
fi
msg_info "Creating Directories"
mkdir -p /opt/unsolth-studio/models
mkdir -p /opt/unsolth-studio/datasets
mkdir -p /var/log/unsolth-studio
chmod 755 /var/log/unsolth-studio
msg_ok "Created Directories"
msg_info "Creating Service"
# Create environment file for ROCm/CUDA paths
cat <<EOF >/opt/unsolth-studio/environment.sh
#!/bin/bash
# Set up GPU environment for Unsloth Studio
# ROCm environment (AMD GPUs)
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-6.2"
fi
# NVIDIA CUDA environment
if [ -d "/usr/local/cuda" ]; then
export PATH="/usr/local/cuda/bin:\$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:\$LD_LIBRARY_PATH"
fi
EOF
chmod +x /opt/unsolth-studio/environment.sh
cat <<EOF >/etc/systemd/system/unsolth-studio.service
[Unit]
Description=Unsloth Studio - Local LLM Fine-tuning Web UI
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/unsolth-studio
Environment="PATH=/opt/unsolth-studio/.venv/bin:/usr/local/bin:/usr/bin:/bin"
EnvironmentFile=/opt/unsolth-studio/environment.sh
ExecStart=/opt/unsolth-studio/.venv/bin/python -m unsloth studio -H 0.0.0.0 -p 8888
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=unsolth-studio
# Resource limits
LimitNOFILE=65535
TimeoutStartSec=600
TimeoutStopSec=60
[Install]
WantedBy=multi-user.target
EOF
# Don't auto-start the service since GPU passthrough may not be configured yet
# User needs to configure GPU passthrough first, then start the service manually
systemctl enable -q unsolth-studio
msg_ok "Created Service"
echo ""
echo -e "${GN}Note: The unsolth-studio service is enabled but not started.${CL}"
echo -e "${GN}Configure GPU passthrough first, then start with:${CL}"
echo -e "${GN} systemctl start unsolth-studio${CL}"
echo ""
# Create GPU passthrough info file
cat <<EOF >/opt/unsolth-studio/GPU_PASSTHROUGH.md
# GPU Passthrough Configuration for Unsloth Studio
This container has been configured for GPU acceleration for LLM fine-tuning.
## Required Proxmox Configuration
Add the following lines to your container config file:
/etc/pve/lxc/<CTID>.conf
### For NVIDIA GPUs:
\`\`\`
# Requires nvidia-container-toolkit on host
lxc.cgroup2.devices.allow: c 195:* rwm
lxc.cgroup2.devices.allow: c 509:* rwm
dev0: /dev/nvidia0,gid=104
dev1: /dev/nvidiactl,gid=104
dev2: /dev/nvidia-uvm,gid=104
dev3: /dev/nvidia-uvm-tools,gid=104
\`\`\`
### For AMD GPUs (ROCm):
\`\`\`
dev0: /dev/kfd,gid=104
dev1: /dev/dri/renderD128,gid=104
lxc.cgroup2.devices.allow: c 226:0 rwm
lxc.cgroup2.devices.allow: c 226:128 rwm
\`\`\`
### For Intel GPUs:
\`\`\`
dev0: /dev/dri/renderD128,gid=104
lxc.cgroup2.devices.allow: c 226:128 rwm
\`\`\`
## Verify GPU Access
Run these commands inside the container:
- nvidia-smi (NVIDIA GPUs)
- rocminfo (AMD GPUs)
- python -c "import torch; print(torch.cuda.is_available())"
## Usage
Access the web UI at: http://<IP>:8888
On first launch:
1. Create a password to secure your account
2. Follow the onboarding wizard to select a model and dataset
3. Configure training parameters
4. Start fine-tuning!
## Supported Models
Unsloth Studio supports fine-tuning many LLM models including:
- Llama 3.x
- Qwen 2.x / 3.x
- Mistral
- Gemma
- Phi-3
- And many more...
## Documentation
- Official Docs: https://unsloth.ai/docs/new/studio/start
- GitHub: https://github.com/unslothai/unsloth
EOF
motd_ssh
customize
cleanup_lxc
@@ -1,6 +1,6 @@
<div align="center">
<a href="#">
<img src="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo.png" height="100px" />
<img src="https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/main/misc/images/logo.png" height="100px" />
</a>
</div>
<h1 align="center">Changelog</h1>
@@ -116,18 +116,12 @@ export default function Page() {
<span className="steel-text glitch">Heretek-AI</span>
</h1>
<div className="max-w-2xl gap-2 flex flex-col text-center sm:text-lg text-sm leading-relaxed tracking-tight text-muted-foreground md:text-xl">
<p className="text-blood-400 font-semibold animate-pulse">Uncompliant scripts, made quicker.</p>
<p className="text-blood-400 font-semibold animate-pulse">Forbidden scripts, forged in innovation.</p>
<p>
Scripts that don't meet the strict guidelines of the official{" "}
<a
href="https://github.com/community-scripts/ProxmoxVE"
target="_blank"
rel="noopener noreferrer"
className="underline text-blood-400 hover:text-blood-300 transition-colors"
>
Community-Scripts
</a>{" "}
repository, but are updated faster and built with flexibility in mind.
Scripts that embrace the heretical pathbeyond the rigid dogma of the orthodox repositories. Updated
with
<span className="text-corruption-400"> relentless speed</span>, built for those who dare to
<span className="text-blood-400"> innovate</span>.
</p>
</div>
</div>
@@ -31,7 +31,7 @@ function MousePosition(): MousePosition {
}
// Mechanicus-themed particle presets
type ParticleTheme = "default" | "rust" | "corruption" | "brass" | "mechanicus";
type ParticleTheme = "default" | "rust" | "corruption" | "brass" | "mechanicus" | "heretek";
type ParticlesProps = {
className?: string;
@@ -68,6 +68,10 @@ const MECHANICUS_THEMES: Record<ParticleTheme, { colors: string[]; defaultColor:
colors: ["#b45309", "#15803d", "#ca8a04", "#92400e", "#166534"],
defaultColor: "#b45309",
},
heretek: {
colors: ["#7f1d1d", "#991b1b", "#dc2626", "#450a0a", "#1e3a5f"],
defaultColor: "#dc2626",
},
};
function hexToRgb(hex: string): number[] {
@@ -76,7 +80,7 @@ function hexToRgb(hex: string): number[] {
if (hex.length === 3) {
hex = hex
.split("")
.map(char => char + char)
.map((char) => char + char)
.join("");
}
@@ -223,12 +227,7 @@ const Particles: React.FC<ParticlesProps> = ({
const clearContext = () => {
if (context.current) {
context.current.clearRect(
0,
0,
canvasSize.current.w,
canvasSize.current.h,
);
context.current.clearRect(0, 0, canvasSize.current.w, canvasSize.current.h);
}
};
@@ -241,15 +240,8 @@ const Particles: React.FC<ParticlesProps> = ({
}
};
const remapValue = (
value: number,
start1: number,
end1: number,
start2: number,
end2: number,
): number => {
const remapped
= ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
const remapValue = (value: number, start1: number, end1: number, start2: number, end2: number): number => {
const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
return remapped > 0 ? remapped : 0;
};
@@ -264,35 +256,28 @@ const Particles: React.FC<ParticlesProps> = ({
canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge
];
const closestEdge = edge.reduce((a, b) => Math.min(a, b));
const remapClosestEdge = Number.parseFloat(
remapValue(closestEdge, 0, 20, 0, 1).toFixed(2),
);
const remapClosestEdge = Number.parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2));
if (remapClosestEdge > 1) {
circle.alpha += 0.02;
if (circle.alpha > circle.targetAlpha) {
circle.alpha = circle.targetAlpha;
}
}
else {
} else {
circle.alpha = circle.targetAlpha * remapClosestEdge;
}
circle.x += circle.dx + vx;
circle.y += circle.dy + vy;
circle.translateX
+= (mouse.current.x / (staticity / circle.magnetism) - circle.translateX)
/ ease;
circle.translateY
+= (mouse.current.y / (staticity / circle.magnetism) - circle.translateY)
/ ease;
circle.translateX += (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / ease;
circle.translateY += (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / ease;
drawCircle(circle, true);
// circle gets out of the canvas
if (
circle.x < -circle.size
|| circle.x > canvasSize.current.w + circle.size
|| circle.y < -circle.size
|| circle.y > canvasSize.current.h + circle.size
circle.x < -circle.size ||
circle.x > canvasSize.current.w + circle.size ||
circle.y < -circle.size ||
circle.y > canvasSize.current.h + circle.size
) {
// remove the circle from the array
circles.current.splice(i, 1);
@@ -306,11 +291,7 @@ const Particles: React.FC<ParticlesProps> = ({
};
return (
<div
className={cn("pointer-events-none", className)}
ref={canvasContainerRef}
aria-hidden="true"
>
<div className={cn("pointer-events-none", className)} ref={canvasContainerRef} aria-hidden="true">
<canvas ref={canvasRef} className="size-full" />
</div>
);
@@ -86,16 +86,16 @@ variables() {
# Set default URL if not already defined by the calling script
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/api.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/api.func)
if command -v curl >/dev/null 2>&1; then
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/core.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/error_handler.func)
load_functions
catch_errors
elif command -v wget >/dev/null 2>&1; then
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/core.func)
source <(wget -qO- ${COMMUNITY_SCRIPTS_URL}/misc/error_handler.func)
source <(wget -qO- "${COMMUNITY_SCRIPTS_URL}"/misc/core.func)
source <(wget -qO- "${COMMUNITY_SCRIPTS_URL}"/misc/error_handler.func)
load_functions
catch_errors
fi
@@ -1489,7 +1489,7 @@ _build_vars_diff() {
# Build a temporary <app>.vars file from current advanced settings
_build_current_app_vars_tmp() {
tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)"
tmpf="$(mktemp /tmp/"${NSAPP:-app}".vars.new.XXXXXX)"
# NET/GW
_net="${NET:-}"
@@ -3440,7 +3440,7 @@ msg_menu() {
# - Otherwise: shows update/setting menu and runs update_script with cleanup
# ------------------------------------------------------------------------------
start() {
source <(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/tools.func)
if command -v pveversion >/dev/null 2>&1; then
install_script || return 0
return 0
@@ -3765,6 +3765,10 @@ $PCT_OPTIONS_STRING"
done
fi
fi
# Add /dev/kfd for AMD ROCm compute support
if [[ -e /dev/kfd ]]; then
AMD_DEVICES+=("/dev/kfd")
fi
fi
# Check for NVIDIA GPU - look for NVIDIA vendor ID [10de]
@@ -4132,7 +4136,7 @@ EOF'
# that sends "configuring" status AFTER the host already reported "failed"
export CONTAINER_INSTALLING=true
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/install/"${var_install}".sh)"
local lxc_exit=$?
unset CONTAINER_INSTALLING
@@ -4249,7 +4253,7 @@ EOF'
else
msg_dev "Container ${CTID} kept for debugging"
fi
exit $install_exit_code
exit "$install_exit_code"
fi
# Prompt user for cleanup with 60s timeout
@@ -4445,7 +4449,7 @@ EOF'
echo -e "${BFR}${CM}${GN}MOTD/SSH ready - SSH into container: ssh root@${ct_ip}${CL}"
fi
fi
exit $install_exit_code
exit "$install_exit_code"
;;
3)
# Retry with verbose mode (full rebuild)
@@ -4510,7 +4514,7 @@ EOF'
# Re-run install script in existing container (don't destroy/recreate)
set +Eeuo pipefail
trap - ERR
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/install/${var_install}.sh)"
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/install/"${var_install}".sh)"
local apt_retry_exit=$?
set -Eeuo pipefail
trap 'error_handler' ERR
@@ -4626,7 +4630,7 @@ EOF'
if [[ "$handled" == false ]]; then
echo -e "\n${TAB}${YW}Invalid option. Container ${CTID} kept.${CL}"
exit $install_exit_code
exit "$install_exit_code"
fi
;;
esac
@@ -4647,7 +4651,7 @@ EOF'
# Restore default job-control signal handling before exit
trap - TSTP TTIN TTOU
exit $install_exit_code
exit "$install_exit_code"
fi
# Re-enable error handling after successful install or recovery menu completion
@@ -5011,7 +5015,7 @@ create_lxc_container() {
msg_ok "LXC stack upgraded."
if [[ "$do_retry" == "yes" ]]; then
msg_info "Retrying container creation after upgrade"
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
msg_ok "Container created successfully after upgrade."
return 0
else
@@ -5266,7 +5270,7 @@ create_lxc_container() {
fi
fi
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
TEMPLATE_PATH="$(pvesm path "$TEMPLATE_STORAGE":vztmpl/"$TEMPLATE" 2>/dev/null || true)"
if [[ -z "$TEMPLATE_PATH" ]]; then
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
@@ -5330,7 +5334,7 @@ create_lxc_container() {
TEMPLATE_SOURCE="online"
fi
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
TEMPLATE_PATH="$(pvesm path "$TEMPLATE_STORAGE":vztmpl/"$TEMPLATE" 2>/dev/null || true)"
if [[ -z "$TEMPLATE_PATH" ]]; then
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
@@ -5526,7 +5530,7 @@ create_lxc_container() {
msg_debug "Logfile: $LOGFILE"
# First attempt (PCT_OPTIONS is a multi-line string, use it directly)
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >"$LOGFILE" 2>&1; then
msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..."
# Check if template issue - retry with fresh download
@@ -5538,7 +5542,7 @@ create_lxc_container() {
fi
# Retry after repair
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
# Fallback to local storage if not already on local
if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
msg_info "Retrying container creation with fallback to local storage"
@@ -5551,7 +5555,7 @@ create_lxc_container() {
else
msg_ok "Trying local storage fallback"
fi
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
if ! pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" >>"$LOGFILE" 2>&1; then
# Local fallback also failed - check for LXC stack version issue
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
msg_warn "pct reported 'unsupported version' LXC stack might be too old for this template"
@@ -5574,7 +5578,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -5606,7 +5610,7 @@ create_lxc_container() {
msg_error "Container creation failed. See $LOGFILE"
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
set -x
pct create "$CTID" "local:vztmpl/${TEMPLATE}" $PCT_OPTIONS 2>&1 | tee -a "$LOGFILE"
pct create "$CTID" "local:vztmpl/${TEMPLATE}" "$PCT_OPTIONS" 2>&1 | tee -a "$LOGFILE"
set +x
fi
_flush_pct_log
@@ -5664,33 +5668,33 @@ create_lxc_container() {
description() {
IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
# Generate LXC Description
# Generate LXC Description - Heretek-AI themed
DESCRIPTION=$(
cat <<EOF
<div align='center'>
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Heretek-AI Logo' style='width:81px;height:112px;'/>
</a>
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
<h2 style='font-size: 24px; margin: 20px 0; color: #dc2626;'>${APP} LXC</h2>
<p style='margin: 16px 0;'>
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/&#x2615;-Buy us a coffee-blue' alt='spend Coffee' />
<a href='https://discord.gg/3AnUqsXnmK' target='_blank' rel='noopener noreferrer'>
<img src='https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white' alt='Discord' />
</a>
</p>
<span style='margin: 0 10px;'>
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
<i class="fa fa-github fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>GitHub</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
<i class="fa fa-comments fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Discussions</a>
</span>
<span style='margin: 0 10px;'>
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
<a href='https://github.com/community-scripts/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
<i class="fa fa-exclamation-circle fa-fw" style="color: #dc2626;"></i>
<a href='https://github.com/Heretek-AI/ProxmoxVE/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #dc2626;'>Issues</a>
</span>
</div>
EOF
@@ -422,6 +422,10 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
## 2026-03-18
### 🆕 New Scripts
- 🔄 Upstream Sync - 2026-03-18 [@BillyOutlast](https://github.com/BillyOutlast) ([#62](https://github.com/Heretek-AI/ProxmoxVE/pull/62))
## 2026-03-16
## 2026-03-15
@@ -1,6 +1,6 @@
__ __ __ __ __
/ / / /___ _____/ /___ / /_/ /_
/ / / / __ \/ ___/ / __ \/ __/ __ \
/ /_/ / / / (__ ) / /_/ / /_/ / / /
\____/_/ /_/____/_/\____/\__/_/ /_/
____ __ __ ___
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
@@ -35,26 +35,22 @@ export default function Page() {
const [color, setColor] = useState("#000000");
useEffect(() => {
// Use Mechanicus-themed colors for particles
setColor(theme === "dark" ? "#b45309" : "#92400e");
// Use Heretek-themed colors - Blood Red
setColor(theme === "dark" ? "#dc2626" : "#b91c1c");
}, [theme]);
return (
<>
<div className="w-full mt-16 relative overflow-hidden">
{/* Mechanicus Background Effects */}
<div className="pointer-events-none absolute inset-0 scan-lines opacity-5" />
{/* Heretek Background Effects */}
<div className="pointer-events-none absolute inset-0 scan-lines opacity-10" />
<div className="pointer-events-none absolute inset-0 noise-overlay" />
{/* Themed Particles */}
<Particles
className="absolute inset-0 -z-40"
quantity={100}
ease={80}
color={color}
theme="mechanicus"
refresh
/>
{/* Circuit board pattern overlay */}
<div className="pointer-events-none absolute inset-0 circuit-board opacity-5" />
{/* Themed Particles - Blood Red */}
<Particles className="absolute inset-0 -z-40" quantity={100} ease={80} color={color} theme="heretek" refresh />
<div className="container mx-auto relative z-10">
<div className="flex h-[60vh] flex-col items-center justify-center gap-4 py-20 lg:py-32">
@@ -64,16 +60,14 @@ export default function Page() {
<AnimatedGradientText>
<div
className={cn(
`absolute inset-0 block size-full animate-gradient bg-gradient-to-r from-rust-500/50 via-corruption-500/50 to-brass-500/50 bg-[length:var(--bg-size)_100%] [border-radius:inherit] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]`,
`absolute inset-0 block size-full animate-gradient bg-gradient-to-r from-blood-500/50 via-corruption-500/50 to-blood-400/50 bg-[length:var(--bg-size)_100%] [border-radius:inherit] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]`,
`p-px ![mask-composite:subtract]`,
)}
/>
{" "}
<Separator className="mx-2 h-4" orientation="vertical" />
<Separator className="mx-2 h-4" orientation="vertical" />
<span
className={cn(
`animate-gradient bg-gradient-to-r from-rust-400 via-corruption-400 to-brass-400 bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent`,
`animate-gradient bg-gradient-to-r from-blood-400 via-corruption-400 to-blood-300 bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent`,
`inline`,
)}
>
@@ -82,27 +76,25 @@ export default function Page() {
</AnimatedGradientText>
</div>
</DialogTrigger>
<DialogContent className="mechanicus-panel">
<DialogContent className="heretek-panel border-blood-500/30">
<DialogHeader>
<DialogTitle className="font-[family-name:var(--font-cinzel)] brass-text">
<DialogTitle className="font-[family-name:var(--font-cinzel)] text-blood-400 glitch">
Praise the Omnissiah!
</DialogTitle>
<DialogDescription>
<DialogDescription className="text-muted-foreground">
A big thank you to tteck and the many contributors who have made this project possible. Your hard
work is truly appreciated by the entire Proxmox community!
</DialogDescription>
</DialogHeader>
<CardFooter className="flex flex-col gap-2">
<Button className="w-full" variant="mechanicus" asChild>
<Button className="w-full" variant="heretek" asChild>
<a
href="https://github.com/tteck"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center"
>
<FaGithub className="mr-2 h-4 w-4" />
{" "}
Tteck's GitHub
<FaGithub className="mr-2 h-4 w-4" /> Tteck's GitHub
</a>
</Button>
<Button className="w-full" variant="forge" asChild>
@@ -112,9 +104,7 @@ export default function Page() {
rel="noopener noreferrer"
className="flex items-center justify-center"
>
<ExternalLink className="mr-2 h-4 w-4" />
{" "}
Proxmox Helper Scripts
<ExternalLink className="mr-2 h-4 w-4" /> Proxmox Helper Scripts
</a>
</Button>
</CardFooter>
@@ -123,36 +113,27 @@ export default function Page() {
<div className="flex flex-col gap-4">
<h1 className="max-w-2xl text-center text-3xl font-semibold tracking-tighter md:text-7xl font-[family-name:var(--font-cinzel)]">
<span className="brass-text">Heretek-AI</span>
<span className="steel-text glitch">Heretek-AI</span>
</h1>
<div className="max-w-2xl gap-2 flex flex-col text-center sm:text-lg text-sm leading-relaxed tracking-tight text-muted-foreground md:text-xl">
<p className="text-rust-300 font-semibold">
Uncompliant scripts, made quicker.
</p>
<p className="text-blood-400 font-semibold animate-pulse">Uncompliant scripts, made quicker.</p>
<p>
Scripts that don't meet the strict guidelines of the official
{" "}
Scripts that don't meet the strict guidelines of the official{" "}
<a
href="https://github.com/community-scripts/ProxmoxVE"
target="_blank"
rel="noopener noreferrer"
className="underline text-corruption-400 hover:text-corruption-300 transition-colors"
className="underline text-blood-400 hover:text-blood-300 transition-colors"
>
Community-Scripts
</a>
{" "}
</a>{" "}
repository, but are updated faster and built with flexibility in mind.
</p>
</div>
</div>
<div className="flex flex-row gap-3">
<Link href="/scripts">
<Button
size="lg"
variant="mechanicus"
Icon={CustomArrowRightIcon}
iconPlacement="right"
>
<Button size="lg" variant="heretek" Icon={CustomArrowRightIcon} iconPlacement="right">
View Scripts
</Button>
</Link>
@@ -160,7 +141,7 @@ export default function Page() {
<Button
size="lg"
variant="outline"
className="border-rust-500/50 hover:border-rust-400 hover:bg-rust-500/10"
className="border-blood-500/50 hover:border-blood-400 hover:bg-blood-500/10"
>
Browse Categories
</Button>
@@ -183,7 +164,7 @@ export default function Page() {
<div className="max-w-4xl mx-auto px-4">
<div className="text-center mb-12">
<h2 className="text-3xl font-bold tracking-tighter md:text-5xl mb-4 font-[family-name:var(--font-cinzel)]">
<span className="brass-text">Frequently Asked Questions</span>
<span className="steel-text">Frequently Asked Questions</span>
</h2>
<p className="text-muted-foreground text-lg">
Find answers to common questions about our Proxmox VE scripts
@@ -36,7 +36,7 @@ const features = [
export function FeatureCards() {
return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{features.map(feature => (
{features.map((feature) => (
<Link
key={feature.title}
href={feature.href}
@@ -44,20 +44,37 @@ export function FeatureCards() {
rel={feature.external ? "noopener noreferrer" : undefined}
className="group"
>
<Card className="h-full transition-all duration-300 hover:border-rust-500/50 hover:shadow-lg hover:shadow-rust-500/10 rust-border">
<CardHeader>
<div className="mb-2 text-rust-400 group-hover:text-rust-300 transition-colors duration-300 group-hover:animate-heretic-glow">
<Card className="h-full transition-all duration-300 hover:border-blood-500/60 hover:shadow-lg hover:shadow-blood-500/20 heretek-card glitch relative overflow-hidden">
{/* Glitch overlay on hover */}
<div className="absolute inset-0 bg-gradient-to-r from-blood-500/5 via-transparent to-blood-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
{/* Scan line effect */}
<div className="absolute inset-0 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="absolute inset-0 bg-gradient-to-b from-transparent via-blood-500/5 to-transparent animate-scan-line" />
</div>
<CardHeader className="relative z-10">
<div className="mb-2 text-blood-400 group-hover:text-blood-300 transition-colors duration-300 group-hover:animate-heretic-glow">
{feature.icon}
</div>
<CardTitle className="text-lg font-[family-name:var(--font-cinzel)] group-hover:text-brass-400 transition-colors duration-300">
{feature.title}
<CardTitle className="text-lg font-[family-name:var(--font-cinzel)] group-hover:text-blood-400 transition-colors duration-300 relative">
<span className="relative inline-block">
{feature.title}
{/* Underline glitch effect */}
<span className="absolute bottom-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-blood-500 to-transparent transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300" />
</span>
</CardTitle>
</CardHeader>
<CardContent>
<CardContent className="relative z-10">
<CardDescription className="text-muted-foreground group-hover:text-foreground/80 transition-colors duration-300">
{feature.description}
</CardDescription>
</CardContent>
{/* Corner accent */}
<div className="absolute top-0 right-0 w-8 h-8 overflow-hidden">
<div className="absolute top-0 right-0 w-16 h-16 bg-gradient-to-br from-blood-500/20 to-transparent transform rotate-45 translate-x-8 -translate-y-8 group-hover:translate-x-4 group-hover:-translate-y-4 transition-transform duration-300" />
</div>
</Card>
</Link>
))}
@@ -8,17 +8,16 @@ import { buttonVariants } from "./ui/button";
export default function Footer() {
return (
<div className="supports-backdrop-blur:bg-background/90 mt-auto border-t w-full flex justify-between border-border bg-background/40 py-4 backdrop-blur-lg rust-border metal-surface">
<div className="supports-backdrop-blur:bg-background/90 mt-auto border-t w-full flex justify-between border-border bg-background/40 py-4 backdrop-blur-lg heretek-panel">
<div className="mx-6 w-full flex justify-between text-xs sm:text-sm text-muted-foreground">
<div className="flex items-center">
<p>
Website built by the community. The source code is available on
{" "}
Website built by the community. The source code is available on{" "}
<Link
href={`https://github.com/Heretek-AI/${basePath}/tree/main/frontend`}
target="_blank"
rel="noreferrer"
className="font-semibold underline-offset-2 duration-300 hover:underline text-rust-400 hover:text-rust-300"
className="font-semibold underline-offset-2 duration-300 hover:underline text-blood-400 hover:text-blood-300"
data-umami-event="View Website Source Code on Github"
>
GitHub
@@ -29,35 +28,50 @@ export default function Footer() {
<div className="sm:flex hidden gap-2">
<Link
href="/scripts"
className={cn(buttonVariants({ variant: "link" }), "text-muted-foreground flex items-center gap-2 hover:text-rust-400 transition-colors duration-300")}
className={cn(
buttonVariants({ variant: "link" }),
"text-muted-foreground flex items-center gap-2 hover:text-blood-400 transition-colors duration-300",
)}
>
<FileJson className="h-4 w-4" />
Scripts
</Link>
<Link
href="/categories"
className={cn(buttonVariants({ variant: "link" }), "text-muted-foreground flex items-center gap-2 hover:text-rust-400 transition-colors duration-300")}
className={cn(
buttonVariants({ variant: "link" }),
"text-muted-foreground flex items-center gap-2 hover:text-blood-400 transition-colors duration-300",
)}
>
<FolderOpen className="h-4 w-4" />
Categories
</Link>
<Link
href="/community"
className={cn(buttonVariants({ variant: "link" }), "text-muted-foreground flex items-center gap-2 hover:text-rust-400 transition-colors duration-300")}
className={cn(
buttonVariants({ variant: "link" }),
"text-muted-foreground flex items-center gap-2 hover:text-blood-400 transition-colors duration-300",
)}
>
<MessageCircle className="h-4 w-4" />
Community
</Link>
<Link
href="/json-editor"
className={cn(buttonVariants({ variant: "link" }), "text-muted-foreground flex items-center gap-2 hover:text-rust-400 transition-colors duration-300")}
className={cn(
buttonVariants({ variant: "link" }),
"text-muted-foreground flex items-center gap-2 hover:text-blood-400 transition-colors duration-300",
)}
>
<FileJson className="h-4 w-4" />
JSON Editor
</Link>
<Link
href="/data"
className={cn(buttonVariants({ variant: "link" }), "text-muted-foreground flex items-center gap-2 hover:text-rust-400 transition-colors duration-300")}
className={cn(
buttonVariants({ variant: "link" }),
"text-muted-foreground flex items-center gap-2 hover:text-blood-400 transition-colors duration-300",
)}
>
<Server className="h-4 w-4" />
API Data
@@ -32,10 +32,9 @@ function Navbar() {
return (
<>
<div
className={`fixed left-0 top-0 z-50 flex w-screen justify-center px-4 xl:px-0 transition-all duration-300 ${isScrolled
? "glass rust-border border-b bg-background/50"
: ""
}`}
className={`fixed left-0 top-0 z-50 flex w-screen justify-center px-4 xl:px-0 transition-all duration-300 ${
isScrolled ? "glass blood-border border-b bg-background/50" : ""
}`}
>
<div className="flex h-20 w-full max-w-[1440px] items-center justify-between sm:flex-row">
<Link
@@ -45,7 +44,7 @@ function Navbar() {
<div className="relative machine-icon">
<Image height={18} unoptimized width={18} alt="logo" src="/ProxmoxVE/logo.png" className="" />
</div>
<span className="font-[family-name:var(--font-cinzel)] tracking-wide text-foreground group-hover:text-rust-400 transition-colors duration-300">
<span className="font-[family-name:var(--font-cinzel)] tracking-wide text-foreground group-hover:text-blood-400 transition-colors duration-300 glitch">
Heretek AI
</span>
</Link>
@@ -56,38 +55,42 @@ function Navbar() {
</Suspense>
</div>
<div className="hidden sm:flex items-center gap-2">
{navbarLinks.filter(link => !link.external).map(({ href, event, icon, text }) => (
<Button
key={event}
variant="ghost"
size="sm"
asChild
className="text-muted-foreground hover:text-rust-400 hover:bg-rust-500/10 transition-colors duration-300"
>
<Link href={href} data-umami-event={event}>
{icon}
<span className="ml-2 hidden lg:inline">{text}</span>
</Link>
</Button>
))}
{navbarLinks
.filter((link) => !link.external)
.map(({ href, event, icon, text }) => (
<Button
key={event}
variant="ghost"
size="sm"
asChild
className="text-muted-foreground hover:text-blood-400 hover:bg-blood-500/10 transition-colors duration-300"
>
<Link href={href} data-umami-event={event}>
{icon}
<span className="ml-2 hidden lg:inline">{text}</span>
</Link>
</Button>
))}
</div>
<div className="flex sm:gap-2">
<CommandMenu />
<GitHubStarsButton username="Heretek-AI" repo="ProxmoxVE" className="hidden md:flex" />
{navbarLinks.filter(link => link.external).map(({ href, event, icon, text, mobileHidden }) => (
<Button
key={event}
variant="ghost"
size="icon"
asChild
className={`text-muted-foreground hover:text-rust-400 hover:bg-rust-500/10 transition-colors duration-300 ${mobileHidden ? "hidden lg:flex" : ""}`}
>
<Link target="_blank" href={href} data-umami-event={event}>
{icon}
<span className="sr-only">{text}</span>
</Link>
</Button>
))}
{navbarLinks
.filter((link) => link.external)
.map(({ href, event, icon, text, mobileHidden }) => (
<Button
key={event}
variant="ghost"
size="icon"
asChild
className={`text-muted-foreground hover:text-blood-400 hover:bg-blood-500/10 transition-colors duration-300 ${mobileHidden ? "hidden lg:flex" : ""}`}
>
<Link target="_blank" href={href} data-umami-event={event}>
{icon}
<span className="sr-only">{text}</span>
</Link>
</Button>
))}
<ThemeToggle />
</div>
</div>
@@ -12,16 +12,12 @@ const buttonVariants = cva(
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
expandIcon:
"group relative text-primary-foreground bg-primary hover:bg-primary/90",
expandIcon: "group relative text-primary-foreground bg-primary hover:bg-primary/90",
ringHover:
"bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2",
shine:
@@ -34,17 +30,23 @@ const buttonVariants = cva(
"relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 hover:after:origin-bottom-right hover:after:scale-x-0 after:transition-transform after:ease-in-out after:duration-300",
linkHover2:
"relative after:absolute after:bg-primary after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 hover:after:origin-bottom-left hover:after:scale-x-100 after:transition-transform after:ease-in-out after:duration-300",
// Mechanicus-themed variants
mechanicus:
"bg-gradient-to-r from-rust-600 to-rust-700 text-rust-100 border border-brass-500/30 hover:from-rust-500 hover:to-rust-600 hover:border-brass-400/50 hover:shadow-[0_0_15px_hsl(28_70%_45%_/_0.3)] transition-all duration-300",
brass:
"bg-gradient-to-r from-brass-500 to-brass-600 text-brass-100 border border-brass-400/30 hover:from-brass-400 hover:to-brass-500 hover:shadow-[0_0_15px_hsl(43_70%_50%_/_0.3)] transition-all duration-300",
// Heretek-themed variants - Blood & Steel
heretek:
"bg-gradient-to-r from-blood-600 to-blood-700 text-blood-100 border border-blood-500/30 hover:from-blood-500 hover:to-blood-600 hover:border-blood-400/50 hover:shadow-[0_0_15px_hsl(0_80%_50%_/_0.4)] transition-all duration-300",
blood:
"bg-gradient-to-r from-blood-500 to-blood-600 text-blood-100 border border-blood-400/30 hover:from-blood-400 hover:to-blood-500 hover:shadow-[0_0_20px_hsl(0_85%_55%_/_0.5)] transition-all duration-300",
void: "bg-gradient-to-r from-void-700 to-void-800 text-void-100 border border-void-500/30 hover:from-void-600 hover:to-void-700 hover:shadow-[0_0_15px_hsl(0_0%_20%_/_0.4)] transition-all duration-300",
steel:
"bg-gradient-to-r from-steel-600 to-steel-700 text-steel-100 border border-steel-500/30 hover:from-steel-500 hover:to-steel-600 hover:shadow-[0_0_10px_hsl(0_15%_35%_/_0.3)] transition-all duration-300",
corruption:
"bg-gradient-to-r from-corruption-600 to-corruption-700 text-corruption-100 border border-corruption-500/30 hover:from-corruption-500 hover:to-corruption-600 hover:shadow-[0_0_15px_hsl(145_60%_40%_/_0.3)] hover:animate-corrupted-pulse transition-all duration-300",
"bg-gradient-to-r from-corruption-600 to-corruption-700 text-corruption-100 border border-corruption-500/30 hover:from-corruption-500 hover:to-corruption-600 hover:shadow-[0_0_15px_hsl(0_70%_45%_/_0.4)] hover:animate-corrupted-pulse transition-all duration-300",
glitch:
"bg-gradient-to-r from-blood-600 to-void-700 text-void-100 border border-blood-500/30 hover:from-blood-500 hover:to-void-600 hover:border-blood-400/50 hover:shadow-[0_0_20px_hsl(0_80%_50%_/_0.5)] animate-glitch transition-all duration-300",
mechanicus:
"bg-gradient-to-r from-blood-600 to-blood-700 text-blood-100 border border-blood-500/30 hover:from-blood-500 hover:to-blood-600 hover:border-blood-400/50 hover:shadow-[0_0_15px_hsl(0_80%_50%_/_0.4)] transition-all duration-300",
forge:
"bg-gradient-to-r from-copper-600 to-copper-700 text-copper-100 border border-copper-500/30 hover:from-copper-500 hover:to-copper-600 hover:shadow-[0_0_20px_hsl(25_70%_45%_/_0.4)] transition-all duration-300",
iron:
"bg-gradient-to-r from-iron-700 to-iron-800 text-iron-100 border border-iron-500/30 hover:from-iron-600 hover:to-iron-700 hover:shadow-[0_0_10px_hsl(30_20%_30%_/_0.3)] transition-all duration-300",
"bg-gradient-to-r from-steel-600 to-blood-700 text-steel-100 border border-steel-500/30 hover:from-steel-500 hover:to-blood-600 hover:shadow-[0_0_20px_hsl(0_70%_45%_/_0.5)] transition-all duration-300",
iron: "bg-gradient-to-r from-iron-700 to-iron-800 text-iron-100 border border-iron-500/30 hover:from-iron-600 hover:to-iron-700 hover:shadow-[0_0_10px_hsl(0_10%_30%_/_0.3)] transition-all duration-300",
},
size: {
default: "h-10 px-4 py-2",
@@ -73,33 +75,16 @@ type IconRefProps = {
export type ButtonProps = {
asChild?: boolean;
} & React.ButtonHTMLAttributes<HTMLButtonElement> & VariantProps<typeof buttonVariants>;
} & React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants>;
export type ButtonIconProps = IconProps | IconRefProps;
const Button = React.forwardRef<
HTMLButtonElement,
ButtonProps & ButtonIconProps
>(
(
{
className,
variant,
size,
asChild = false,
Icon,
iconPlacement,
...props
},
ref,
) => {
const Button = React.forwardRef<HTMLButtonElement, ButtonProps & ButtonIconProps>(
({ className, variant, size, asChild = false, Icon, iconPlacement, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
>
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props}>
{Icon && iconPlacement === "left" && (
<div className="group-hover:translate-x-100 w-0 translate-x-[0%] pr-0 opacity-0 transition-all duration-200 group-hover:w-5 group-hover:pr-2 group-hover:opacity-100">
<Icon />
@@ -4,33 +4,33 @@
@layer base {
:root {
/* Heretek Light Theme - Worn Metal */
--background: 30 15% 12%;
--foreground: 35 20% 85%;
--card: 30 12% 10%;
--card-foreground: 35 20% 85%;
--popover: 30 15% 8%;
--popover-foreground: 35 20% 85%;
--primary: 28 80% 45%;
--primary-foreground: 30 15% 8%;
--secondary: 35 30% 25%;
--secondary-foreground: 35 20% 85%;
--muted: 30 20% 20%;
--muted-foreground: 35 15% 55%;
--accent: 145 65% 35%;
--accent-foreground: 145 10% 95%;
--destructive: 0 75% 45%;
--destructive-foreground: 0 10% 95%;
--border: 30 25% 22%;
--input: 30 25% 18%;
--ring: 28 80% 45%;
/* Heretek Light Theme - Dark Metal with Red Accents */
--background: 0 0% 8%;
--foreground: 0 0% 88%;
--card: 0 0% 6%;
--card-foreground: 0 0% 88%;
--popover: 0 0% 4%;
--popover-foreground: 0 0% 88%;
--primary: 0 75% 50%;
--primary-foreground: 0 0% 4%;
--secondary: 0 10% 18%;
--secondary-foreground: 0 0% 85%;
--muted: 0 8% 14%;
--muted-foreground: 0 5% 55%;
--accent: 0 70% 45%;
--accent-foreground: 0 0% 95%;
--destructive: 0 80% 50%;
--destructive-foreground: 0 0% 95%;
--border: 0 12% 20%;
--input: 0 10% 12%;
--ring: 0 75% 50%;
--radius: 0.25rem;
/* Heretek Chart Colors - Rust & Corruption */
--chart-1: 28 75% 45%;
--chart-2: 35 60% 40%;
--chart-3: 145 55% 35%;
--chart-4: 180 40% 30%;
--chart-5: 0 60% 40%;
/* Heretek Chart Colors - Blood & Steel */
--chart-1: 0 75% 50%;
--chart-2: 0 60% 35%;
--chart-3: 0 50% 45%;
--chart-4: 0 40% 30%;
--chart-5: 0 65% 40%;
}
::selection {
@@ -39,32 +39,32 @@
}
.dark {
/* Heretek Dark Theme - Corrupted Forge */
--background: 30 20% 4%;
--foreground: 35 15% 80%;
--card: 30 18% 6%;
--card-foreground: 35 15% 80%;
--popover: 30 20% 3%;
--popover-foreground: 35 15% 80%;
--primary: 28 85% 50%;
--primary-foreground: 30 25% 5%;
--secondary: 35 25% 15%;
--secondary-foreground: 35 15% 80%;
--muted: 30 20% 12%;
--muted-foreground: 35 12% 50%;
--accent: 145 70% 40%;
--accent-foreground: 145 15% 95%;
--destructive: 0 70% 40%;
--destructive-foreground: 0 15% 95%;
--border: 30 30% 18%;
--input: 30 25% 12%;
--ring: 28 85% 50%;
/* Heretek Chart Colors - Corruption Spread */
--chart-1: 28 80% 50%;
--chart-2: 35 55% 35%;
--chart-3: 145 60% 38%;
--chart-4: 60 45% 30%;
--chart-5: 0 65% 42%;
/* Heretek Dark Theme - The Void Stares Back */
--background: 0 0% 3%;
--foreground: 0 0% 85%;
--card: 0 0% 5%;
--card-foreground: 0 0% 85%;
--popover: 0 0% 2%;
--popover-foreground: 0 0% 85%;
--primary: 0 80% 55%;
--primary-foreground: 0 0% 3%;
--secondary: 0 8% 12%;
--secondary-foreground: 0 0% 82%;
--muted: 0 6% 10%;
--muted-foreground: 0 5% 50%;
--accent: 0 75% 50%;
--accent-foreground: 0 0% 95%;
--destructive: 0 85% 55%;
--destructive-foreground: 0 0% 95%;
--border: 0 15% 15%;
--input: 0 10% 8%;
--ring: 0 80% 55%;
/* Heretek Chart Colors - Corruption Spreads */
--chart-1: 0 80% 55%;
--chart-2: 0 65% 40%;
--chart-3: 0 55% 45%;
--chart-4: 0 45% 35%;
--chart-5: 0 70% 45%;
}
}
@@ -75,7 +75,9 @@
body {
@apply bg-background text-foreground;
font-feature-settings: "liga" 1, "calt" 1;
font-feature-settings:
"liga" 1,
"calt" 1;
}
}
@@ -83,45 +85,36 @@
-ms-overflow-style: none;
}
/* Heretek Scrollbar - Corrupted Metal */
/* Heretek Scrollbar - Dark Steel */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: hsl(30 20% 8%);
border-left: 1px solid hsl(30 30% 18%);
background: hsl(0 0% 4%);
border-left: 1px solid hsl(0 15% 12%);
}
::-webkit-scrollbar-thumb {
background: linear-gradient(180deg,
hsl(28 70% 35%) 0%,
hsl(35 50% 25%) 50%,
hsl(28 70% 35%) 100%);
background: linear-gradient(180deg, hsl(0 60% 30%) 0%, hsl(0 40% 20%) 50%, hsl(0 60% 30%) 100%);
border-radius: 4px;
border: 1px solid hsl(30 30% 20%);
border: 1px solid hsl(0 20% 15%);
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg,
hsl(28 80% 42%) 0%,
hsl(35 60% 32%) 50%,
hsl(28 80% 42%) 100%);
background: linear-gradient(180deg, hsl(0 70% 40%) 0%, hsl(0 50% 28%) 50%, hsl(0 70% 40%) 100%);
}
/* Glass Effect - Corrupted Viewport */
.glass {
backdrop-filter: blur(12px) saturate(120%);
-webkit-backdrop-filter: blur(12px) saturate(120%);
background: linear-gradient(135deg,
hsl(30 20% 8% / 0.85) 0%,
hsl(30 25% 12% / 0.75) 100%);
border: 1px solid hsl(30 30% 18% / 0.6);
background: linear-gradient(135deg, hsl(0 0% 5% / 0.9) 0%, hsl(0 5% 8% / 0.8) 100%);
border: 1px solid hsl(0 20% 15% / 0.6);
}
/* Heretek Corruption Effects */
@layer utilities {
/* Noise Texture Overlay */
.noise-overlay {
position: relative;
@@ -132,82 +125,88 @@
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.03;
opacity: 0.04;
pointer-events: none;
mix-blend-mode: overlay;
}
/* Rust Border Effect */
.rust-border {
/* Blood Border Effect */
.blood-border {
border: 1px solid transparent;
background:
linear-gradient(hsl(30 20% 6%), hsl(30 20% 6%)) padding-box,
linear-gradient(135deg,
hsl(28 70% 30%) 0%,
hsl(35 40% 20%) 25%,
hsl(28 60% 25%) 50%,
hsl(35 35% 18%) 75%,
hsl(28 70% 30%) 100%) border-box;
linear-gradient(hsl(0 0% 5%), hsl(0 0% 5%)) padding-box,
linear-gradient(
135deg,
hsl(0 70% 35%) 0%,
hsl(0 50% 20%) 25%,
hsl(0 60% 30%) 50%,
hsl(0 40% 18%) 75%,
hsl(0 70% 35%) 100%
)
border-box;
}
/* Corruption Glow */
/* Corruption Glow - Red */
.corruption-glow {
box-shadow:
0 0 20px hsl(145 60% 30% / 0.15),
0 0 40px hsl(145 50% 25% / 0.1),
inset 0 0 20px hsl(145 40% 20% / 0.05);
0 0 20px hsl(0 70% 40% / 0.2),
0 0 40px hsl(0 60% 30% / 0.15),
inset 0 0 20px hsl(0 50% 25% / 0.08);
}
/* Brass Text Effect */
.brass-text {
background: linear-gradient(180deg,
hsl(43 70% 55%) 0%,
hsl(35 60% 40%) 50%,
hsl(43 65% 45%) 100%);
/* Steel Text Effect */
.steel-text {
background: linear-gradient(180deg, hsl(0 0% 75%) 0%, hsl(0 5% 55%) 50%, hsl(0 0% 65%) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Copper Accent */
.copper-accent {
background: linear-gradient(135deg,
hsl(28 75% 45%) 0%,
hsl(25 65% 35%) 50%,
hsl(28 70% 40%) 100%);
/* Blood Accent */
.blood-accent {
background: linear-gradient(135deg, hsl(0 75% 50%) 0%, hsl(0 65% 40%) 50%, hsl(0 75% 45%) 100%);
}
/* Glitch Animation */
/* Enhanced Glitch Animation */
.glitch {
animation: glitch 4s infinite;
animation: glitch 3s infinite;
}
@keyframes glitch {
0%,
90%,
88%,
100% {
transform: translate(0);
filter: none;
}
89% {
transform: translate(-2px, 1px);
filter: hue-rotate(90deg) saturate(1.5);
}
90% {
transform: translate(2px, -1px);
filter: hue-rotate(-90deg) saturate(1.5);
}
91% {
transform: translate(-1px, 1px);
filter: hue-rotate(90deg);
transform: translate(-1px, -1px);
filter: hue-rotate(45deg) brightness(1.2);
}
92% {
transform: translate(1px, -1px);
filter: hue-rotate(-90deg);
transform: translate(1px, 1px);
filter: none;
}
93% {
transform: translate(-1px, -1px);
filter: hue-rotate(45deg);
transform: translate(-2px, 2px);
filter: hue-rotate(-45deg) saturate(1.3);
}
94% {
transform: translate(1px, 1px);
transform: translate(2px, -2px);
filter: none;
}
}
@@ -217,93 +216,95 @@
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(0deg,
transparent,
transparent 2px,
hsl(30 20% 2% / 0.3) 2px,
hsl(30 20% 2% / 0.3) 4px);
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
hsl(0 0% 2% / 0.4) 2px,
hsl(0 0% 2% / 0.4) 4px
);
pointer-events: none;
opacity: 0.5;
opacity: 0.6;
}
/* Flicker Animation */
.flicker {
animation: flicker 8s infinite;
animation: flicker 6s infinite;
}
@keyframes flicker {
0%,
100% {
opacity: 1;
}
90% {
opacity: 1;
}
91% {
opacity: 0.7;
}
92% {
opacity: 1;
}
93% {
opacity: 0.8;
opacity: 0.85;
}
94% {
opacity: 1;
}
96% {
95% {
opacity: 0.9;
}
97% {
96% {
opacity: 1;
}
}
/* Corrupted Pulse */
/* Corrupted Pulse - Red */
.corrupted-pulse {
animation: corrupted-pulse 3s ease-in-out infinite;
animation: corrupted-pulse 2.5s ease-in-out infinite;
}
@keyframes corrupted-pulse {
0%,
100% {
box-shadow:
0 0 10px hsl(145 50% 25% / 0.2),
0 0 20px hsl(145 40% 20% / 0.1);
0 0 10px hsl(0 65% 40% / 0.25),
0 0 20px hsl(0 55% 30% / 0.15);
}
50% {
box-shadow:
0 0 15px hsl(145 60% 30% / 0.3),
0 0 30px hsl(145 50% 25% / 0.15),
0 0 45px hsl(145 40% 20% / 0.1);
0 0 15px hsl(0 75% 45% / 0.35),
0 0 30px hsl(0 65% 35% / 0.2),
0 0 45px hsl(0 55% 25% / 0.1);
}
}
/* Metal Surface Gradient */
/* Dark Metal Surface Gradient */
.metal-surface {
background:
linear-gradient(145deg,
hsl(30 15% 15%) 0%,
hsl(30 20% 10%) 50%,
hsl(30 15% 12%) 100%);
background: linear-gradient(145deg, hsl(0 0% 10%) 0%, hsl(0 5% 6%) 50%, hsl(0 0% 8%) 100%);
box-shadow:
inset 1px 1px 0 hsl(30 20% 20%),
inset -1px -1px 0 hsl(30 25% 5%);
inset 1px 1px 0 hsl(0 10% 15%),
inset -1px -1px 0 hsl(0 15% 3%);
}
/* Tech Heresy Warning */
/* Heresy Warning - Enhanced */
.heresy-warning {
border-left: 3px solid hsl(0 70% 40%);
background: linear-gradient(90deg,
hsl(0 50% 10% / 0.3) 0%,
transparent 100%);
border-left: 3px solid hsl(0 80% 50%);
background: linear-gradient(90deg, hsl(0 60% 15% / 0.4) 0%, transparent 100%);
}
/* ===== NEW MECHANICUS EFFECTS ===== */
/* ===== ENHANCED HERETEK EFFECTS ===== */
/* Pulsing Circuit Lines */
/* Pulsing Circuit Lines - Red */
.circuit-pulse {
position: relative;
overflow: hidden;
@@ -313,11 +314,8 @@
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg,
transparent 0%,
hsl(145 60% 35% / 0.15) 50%,
transparent 100%);
animation: circuit-flow 3s linear infinite;
background: linear-gradient(90deg, transparent 0%, hsl(0 70% 45% / 0.2) 50%, transparent 100%);
animation: circuit-flow 2.5s linear infinite;
}
@keyframes circuit-flow {
@@ -330,19 +328,15 @@
}
}
/* Rusted Metal Texture */
.rusted-metal {
background:
linear-gradient(145deg,
hsl(30 15% 12%) 0%,
hsl(28 20% 8%) 50%,
hsl(30 12% 10%) 100%);
/* Dark Steel Texture */
.dark-steel {
background: linear-gradient(145deg, hsl(0 0% 8%) 0%, hsl(0 5% 5%) 50%, hsl(0 0% 6%) 100%);
box-shadow:
inset 2px 2px 4px hsl(30 20% 18%),
inset -2px -2px 4px hsl(30 25% 5%);
inset 2px 2px 4px hsl(0 10% 12%),
inset -2px -2px 4px hsl(0 15% 2%);
}
/* Tech-Priest Data Stream Effect */
/* Data Stream Effect - Red */
.data-stream {
position: relative;
overflow: hidden;
@@ -352,12 +346,14 @@
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(0deg,
transparent,
transparent 2px,
hsl(145 50% 30% / 0.05) 2px,
hsl(145 50% 30% / 0.05) 4px);
animation: stream-scroll 20s linear infinite;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
hsl(0 60% 40% / 0.06) 2px,
hsl(0 60% 40% / 0.06) 4px
);
animation: stream-scroll 15s linear infinite;
pointer-events: none;
}
@@ -371,7 +367,7 @@
}
}
/* Omnissiah's Blessing - Machine Spirit Glow */
/* Machine Spirit Glow - Red */
.machine-spirit {
position: relative;
}
@@ -380,39 +376,34 @@
content: "";
position: absolute;
inset: -2px;
background: linear-gradient(45deg,
hsl(28 80% 50% / 0.1),
hsl(43 70% 50% / 0.1),
hsl(28 80% 50% / 0.1));
background: linear-gradient(45deg, hsl(0 80% 50% / 0.15), hsl(0 70% 40% / 0.1), hsl(0 80% 50% / 0.15));
border-radius: inherit;
animation: spirit-pulse 4s ease-in-out infinite;
animation: spirit-pulse 3s ease-in-out infinite;
z-index: -1;
}
@keyframes spirit-pulse {
0%,
100% {
opacity: 0.3;
opacity: 0.4;
transform: scale(1);
}
50% {
opacity: 0.6;
opacity: 0.7;
transform: scale(1.02);
}
}
/* Binary Choir Text Effect */
/* Binary Choir Text Effect - Red */
.binary-choir {
text-shadow:
0 0 10px hsl(145 70% 40% / 0.5),
0 0 20px hsl(145 60% 30% / 0.3);
animation: binary-flicker 5s infinite;
0 0 10px hsl(0 75% 50% / 0.6),
0 0 20px hsl(0 65% 40% / 0.4);
animation: binary-flicker 4s infinite;
}
@keyframes binary-flicker {
0%,
100% {
opacity: 1;
@@ -431,51 +422,51 @@
}
}
/* Noosphere Connection Effect */
/* Noosphere Border - Red/Black */
.noosphere-border {
border: 1px solid transparent;
background:
linear-gradient(hsl(30 20% 6%), hsl(30 20% 6%)) padding-box,
linear-gradient(135deg,
hsl(145 60% 35%) 0%,
hsl(28 70% 40%) 25%,
hsl(43 60% 35%) 50%,
hsl(28 70% 40%) 75%,
hsl(145 60% 35%) 100%) border-box;
linear-gradient(hsl(0 0% 5%), hsl(0 0% 5%)) padding-box,
linear-gradient(
135deg,
hsl(0 70% 45%) 0%,
hsl(0 50% 30%) 25%,
hsl(0 60% 35%) 50%,
hsl(0 50% 25%) 75%,
hsl(0 70% 45%) 100%
)
border-box;
}
/* Mechanicus Panel */
.mechanicus-panel {
background: linear-gradient(145deg,
hsl(30 18% 8%) 0%,
hsl(30 22% 6%) 50%,
hsl(30 18% 10%) 100%);
border: 1px solid hsl(28 50% 20% / 0.5);
/* Heretek Panel */
.heretek-panel {
background: linear-gradient(145deg, hsl(0 5% 6%) 0%, hsl(0 8% 4%) 50%, hsl(0 5% 7%) 100%);
border: 1px solid hsl(0 40% 18% / 0.6);
box-shadow:
inset 0 1px 0 hsl(30 25% 15%),
inset 0 -1px 0 hsl(30 20% 5%),
0 4px 20px hsl(30 20% 5% / 0.3);
inset 0 1px 0 hsl(0 15% 12%),
inset 0 -1px 0 hsl(0 10% 3%),
0 4px 20px hsl(0 0% 3% / 0.4);
}
/* Sacred Machine Icon */
/* Sacred Machine Icon - Red */
.machine-icon {
filter: drop-shadow(0 0 8px hsl(28 70% 45% / 0.4));
filter: drop-shadow(0 0 8px hsl(0 70% 50% / 0.5));
transition: filter 0.3s ease;
}
.machine-icon:hover {
filter: drop-shadow(0 0 12px hsl(28 80% 50% / 0.6));
filter: drop-shadow(0 0 12px hsl(0 80% 55% / 0.7));
}
/* Heretek Input Focus */
/* Heretek Focus - Red */
.heretek-focus:focus {
outline: none;
box-shadow:
0 0 0 2px hsl(28 70% 40%),
0 0 15px hsl(28 60% 35% / 0.3);
0 0 0 2px hsl(0 70% 45%),
0 0 15px hsl(0 60% 40% / 0.4);
}
/* Corrupted Text Glitch */
/* Corrupted Text Glitch - Enhanced */
.text-glitch {
position: relative;
}
@@ -491,113 +482,106 @@
}
.text-glitch::before {
color: hsl(145 70% 40%);
animation: glitch-1 2s infinite linear alternate-reverse;
color: hsl(0 75% 50%);
animation: glitch-1 1.5s infinite linear alternate-reverse;
clip-path: polygon(0 0, 100% 0, 100% 35%, 0 35%);
}
.text-glitch::after {
color: hsl(0 70% 45%);
animation: glitch-2 2s infinite linear alternate-reverse;
color: hsl(0 85% 55%);
animation: glitch-2 1.5s infinite linear alternate-reverse;
clip-path: polygon(0 65%, 100% 65%, 100% 100%, 0 100%);
}
@keyframes glitch-1 {
0%,
100% {
transform: translate(0);
}
20% {
transform: translate(-2px, 2px);
transform: translate(-3px, 2px);
}
40% {
transform: translate(2px, -2px);
transform: translate(3px, -2px);
}
60% {
transform: translate(-1px, 1px);
transform: translate(-2px, 1px);
}
80% {
transform: translate(1px, -1px);
transform: translate(2px, -1px);
}
}
@keyframes glitch-2 {
0%,
100% {
transform: translate(0);
}
20% {
transform: translate(2px, -2px);
transform: translate(3px, -2px);
}
40% {
transform: translate(-2px, 2px);
transform: translate(-3px, 2px);
}
60% {
transform: translate(1px, -1px);
transform: translate(2px, -1px);
}
80% {
transform: translate(-1px, 1px);
transform: translate(-2px, 1px);
}
}
/* Forge Glow - for active elements */
/* Forge Glow - Red */
.forge-glow {
box-shadow:
0 0 10px hsl(28 70% 45% / 0.3),
0 0 20px hsl(28 60% 35% / 0.2),
inset 0 0 10px hsl(28 50% 25% / 0.1);
0 0 10px hsl(0 70% 50% / 0.35),
0 0 20px hsl(0 60% 40% / 0.25),
inset 0 0 10px hsl(0 50% 30% / 0.15);
}
/* Sacred Scroll Effect */
.sacred-scroll {
background:
linear-gradient(180deg,
hsl(30 20% 6%) 0%,
hsl(30 18% 8%) 10%,
hsl(30 20% 8%) 90%,
hsl(30 20% 6%) 100%);
border-top: 1px solid hsl(43 40% 25% / 0.3);
border-bottom: 1px solid hsl(43 40% 25% / 0.3);
background: linear-gradient(180deg, hsl(0 0% 4%) 0%, hsl(0 5% 6%) 10%, hsl(0 0% 5%) 90%, hsl(0 0% 4%) 100%);
border-top: 1px solid hsl(0 30% 20% / 0.4);
border-bottom: 1px solid hsl(0 30% 20% / 0.4);
}
/* Circuit Board Pattern */
/* Circuit Board Pattern - Red */
.circuit-board {
background-image:
linear-gradient(90deg, hsl(145 40% 20% / 0.1) 1px, transparent 1px),
linear-gradient(180deg, hsl(145 40% 20% / 0.1) 1px, transparent 1px);
linear-gradient(90deg, hsl(0 50% 25% / 0.12) 1px, transparent 1px),
linear-gradient(180deg, hsl(0 50% 25% / 0.12) 1px, transparent 1px);
background-size: 20px 20px;
}
/* Rust Particle Animation */
.rust-particles {
/* Blood Particle Animation */
.blood-particles {
position: relative;
overflow: hidden;
}
.rust-particles::after {
.blood-particles::after {
content: "";
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 20% 30%, hsl(28 60% 40% / 0.3) 1px, transparent 1px),
radial-gradient(circle at 80% 70%, hsl(35 50% 35% / 0.3) 1px, transparent 1px),
radial-gradient(circle at 50% 50%, hsl(28 70% 45% / 0.2) 1px, transparent 1px);
radial-gradient(circle at 20% 30%, hsl(0 70% 45% / 0.35) 1px, transparent 1px),
radial-gradient(circle at 80% 70%, hsl(0 60% 40% / 0.35) 1px, transparent 1px),
radial-gradient(circle at 50% 50%, hsl(0 75% 50% / 0.25) 1px, transparent 1px);
background-size: 100px 100px;
animation: rust-drift 30s linear infinite;
animation: blood-drift 25s linear infinite;
pointer-events: none;
}
@keyframes rust-drift {
@keyframes blood-drift {
0% {
transform: translateY(0) rotate(0deg);
}
@@ -606,4 +590,232 @@
transform: translateY(-100px) rotate(360deg);
}
}
/* Heretek Glitch Text - Advanced */
.heretek-glitch {
position: relative;
animation: heretek-glitch 4s infinite;
}
.heretek-glitch::before,
.heretek-glitch::after {
content: attr(data-text);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.8;
}
.heretek-glitch::before {
color: hsl(0 80% 50%);
animation: heretek-glitch-1 0.3s infinite;
clip-path: polygon(0 0, 100% 0, 100% 45%, 0 45%);
transform: translate(-2px, -2px);
}
.heretek-glitch::after {
color: hsl(0 90% 60%);
animation: heretek-glitch-2 0.3s infinite;
clip-path: polygon(0 55%, 100% 55%, 100% 100%, 0 100%);
transform: translate(2px, 2px);
}
@keyframes heretek-glitch {
0%,
85%,
100% {
transform: translate(0);
}
86% {
transform: translate(-2px, 1px);
}
87% {
transform: translate(2px, -1px);
}
88% {
transform: translate(-1px, -1px);
}
89% {
transform: translate(1px, 1px);
}
}
@keyframes heretek-glitch-1 {
0%,
100% {
transform: translate(-2px, -2px);
}
50% {
transform: translate(2px, 2px);
}
}
@keyframes heretek-glitch-2 {
0%,
100% {
transform: translate(2px, 2px);
}
50% {
transform: translate(-2px, -2px);
}
}
/* Void Background */
.void-bg {
background: radial-gradient(ellipse at center, hsl(0 0% 5%) 0%, hsl(0 0% 3%) 50%, hsl(0 0% 1%) 100%);
}
/* Blood Drip Effect */
.blood-drip {
position: relative;
}
.blood-drip::after {
content: "";
position: absolute;
bottom: 0;
left: 50%;
width: 2px;
height: 0;
background: linear-gradient(180deg, hsl(0 80% 50%), hsl(0 70% 40%), transparent);
animation: blood-drip 3s ease-in-out infinite;
transform: translateX(-50%);
}
@keyframes blood-drip {
0%,
100% {
height: 0;
opacity: 0;
}
50% {
height: 10px;
opacity: 1;
}
80% {
height: 15px;
opacity: 0.5;
}
}
/* Static Noise Effect */
.static-noise {
position: relative;
overflow: hidden;
}
.static-noise::before {
content: "";
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
opacity: 0;
animation: static-flash 8s infinite;
pointer-events: none;
}
@keyframes static-flash {
0%,
95%,
100% {
opacity: 0;
}
96% {
opacity: 0.08;
}
97% {
opacity: 0;
}
98% {
opacity: 0.05;
}
99% {
opacity: 0;
}
}
/* Chromatic Aberration */
.chromatic-aberration {
position: relative;
}
.chromatic-aberration::before,
.chromatic-aberration::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
.chromatic-aberration::before {
background: inherit;
transform: translate(-2px, 0);
opacity: 0.5;
mix-blend-mode: multiply;
filter: hue-rotate(90deg);
}
.chromatic-aberration::after {
background: inherit;
transform: translate(2px, 0);
opacity: 0.5;
mix-blend-mode: multiply;
filter: hue-rotate(-90deg);
}
/* Terminal Text Effect */
.terminal-text {
font-family: "Courier New", monospace;
text-shadow:
0 0 5px hsl(0 70% 50% / 0.8),
0 0 10px hsl(0 60% 40% / 0.5);
animation: terminal-flicker 0.1s infinite;
}
@keyframes terminal-flicker {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.98;
}
}
/* Heretek Card */
.heretek-card {
background: linear-gradient(145deg, hsl(0 5% 7%) 0%, hsl(0 8% 5%) 50%, hsl(0 5% 8%) 100%);
border: 1px solid hsl(0 30% 18%);
transition: all 0.3s ease;
}
.heretek-card:hover {
border-color: hsl(0 60% 35%);
box-shadow:
0 0 15px hsl(0 70% 40% / 0.2),
0 0 30px hsl(0 60% 30% / 0.1),
inset 0 0 10px hsl(0 50% 20% / 0.05);
}
/* Corruption Spread */
.corruption-spread {
position: relative;
overflow: hidden;
}
.corruption-spread::before {
content: "";
position: absolute;
inset: 0;
background: radial-gradient(circle at var(--x, 50%) var(--y, 50%), hsl(0 70% 40% / 0.15) 0%, transparent 50%);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.corruption-spread:hover::before {
opacity: 1;
}
}
@@ -2,19 +2,12 @@
//
import type { Config } from "tailwindcss";
const {
default: flattenColorPalette,
} = require("tailwindcss/lib/util/flattenColorPalette");
const { default: flattenColorPalette } = require("tailwindcss/lib/util/flattenColorPalette");
const svgToDataUri = require("mini-svg-data-uri");
const config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
prefix: "",
theme: {
container: {
@@ -59,71 +52,97 @@ const config = {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
// Heretek Custom Colors
// Heretek Custom Colors - Blood & Steel
blood: {
50: "hsl(0 60% 95%)",
100: "hsl(0 65% 90%)",
200: "hsl(0 70% 80%)",
300: "hsl(0 75% 65%)",
400: "hsl(0 80% 55%)",
500: "hsl(0 85% 45%)",
600: "hsl(0 80% 38%)",
700: "hsl(0 75% 30%)",
800: "hsl(0 70% 22%)",
900: "hsl(0 65% 15%)",
950: "hsl(0 60% 8%)",
},
steel: {
50: "hsl(0 5% 95%)",
100: "hsl(0 8% 88%)",
200: "hsl(0 10% 78%)",
300: "hsl(0 12% 65%)",
400: "hsl(0 15% 50%)",
500: "hsl(0 18% 42%)",
600: "hsl(0 20% 35%)",
700: "hsl(0 18% 28%)",
800: "hsl(0 15% 20%)",
900: "hsl(0 12% 12%)",
950: "hsl(0 10% 6%)",
},
void: {
50: "hsl(0 0% 95%)",
100: "hsl(0 0% 88%)",
200: "hsl(0 0% 78%)",
300: "hsl(0 0% 65%)",
400: "hsl(0 0% 50%)",
500: "hsl(0 0% 42%)",
600: "hsl(0 0% 35%)",
700: "hsl(0 0% 28%)",
800: "hsl(0 0% 20%)",
900: "hsl(0 0% 12%)",
950: "hsl(0 0% 6%)",
},
rust: {
50: "hsl(28 60% 95%)",
100: "hsl(28 60% 90%)",
200: "hsl(28 60% 80%)",
300: "hsl(28 65% 70%)",
400: "hsl(28 70% 55%)",
500: "hsl(28 75% 45%)",
600: "hsl(28 80% 38%)",
700: "hsl(28 75% 30%)",
800: "hsl(28 70% 22%)",
900: "hsl(28 65% 15%)",
950: "hsl(28 60% 8%)",
50: "hsl(0 60% 95%)",
100: "hsl(0 65% 90%)",
200: "hsl(0 70% 80%)",
300: "hsl(0 75% 70%)",
400: "hsl(0 80% 55%)",
500: "hsl(0 85% 45%)",
600: "hsl(0 80% 38%)",
700: "hsl(0 75% 30%)",
800: "hsl(0 70% 22%)",
900: "hsl(0 65% 15%)",
950: "hsl(0 60% 8%)",
},
brass: {
50: "hsl(43 50% 92%)",
100: "hsl(43 55% 85%)",
200: "hsl(43 60% 75%)",
300: "hsl(43 65% 62%)",
400: "hsl(43 70% 50%)",
500: "hsl(43 65% 45%)",
600: "hsl(43 60% 38%)",
700: "hsl(43 55% 30%)",
800: "hsl(43 50% 22%)",
900: "hsl(43 45% 15%)",
950: "hsl(43 40% 8%)",
},
copper: {
50: "hsl(25 50% 92%)",
100: "hsl(25 55% 85%)",
200: "hsl(25 60% 75%)",
300: "hsl(25 65% 62%)",
400: "hsl(25 70% 50%)",
500: "hsl(25 75% 42%)",
600: "hsl(25 80% 35%)",
700: "hsl(25 75% 28%)",
800: "hsl(25 70% 20%)",
900: "hsl(25 65% 14%)",
950: "hsl(25 60% 7%)",
50: "hsl(45 50% 92%)",
100: "hsl(45 55% 85%)",
200: "hsl(45 60% 75%)",
300: "hsl(45 65% 62%)",
400: "hsl(45 70% 50%)",
500: "hsl(45 65% 45%)",
600: "hsl(45 60% 38%)",
700: "hsl(45 55% 30%)",
800: "hsl(45 50% 22%)",
900: "hsl(45 45% 15%)",
950: "hsl(45 40% 8%)",
},
corruption: {
50: "hsl(145 40% 95%)",
100: "hsl(145 45% 88%)",
200: "hsl(145 50% 78%)",
300: "hsl(145 55% 65%)",
400: "hsl(145 60% 50%)",
500: "hsl(145 65% 40%)",
600: "hsl(145 70% 32%)",
700: "hsl(145 65% 25%)",
800: "hsl(145 60% 18%)",
900: "hsl(145 55% 12%)",
950: "hsl(145 50% 6%)",
50: "hsl(0 40% 95%)",
100: "hsl(0 45% 88%)",
200: "hsl(0 50% 78%)",
300: "hsl(0 55% 65%)",
400: "hsl(0 60% 50%)",
500: "hsl(0 65% 40%)",
600: "hsl(0 70% 32%)",
700: "hsl(0 65% 25%)",
800: "hsl(0 60% 18%)",
900: "hsl(0 55% 12%)",
950: "hsl(0 50% 6%)",
},
iron: {
50: "hsl(30 10% 95%)",
100: "hsl(30 12% 88%)",
200: "hsl(30 15% 78%)",
300: "hsl(30 18% 65%)",
400: "hsl(30 20% 50%)",
500: "hsl(30 22% 42%)",
600: "hsl(30 25% 35%)",
700: "hsl(30 22% 28%)",
800: "hsl(30 20% 20%)",
900: "hsl(30 18% 12%)",
950: "hsl(30 15% 6%)",
50: "hsl(0 5% 95%)",
100: "hsl(0 8% 88%)",
200: "hsl(0 10% 78%)",
300: "hsl(0 12% 65%)",
400: "hsl(0 15% 50%)",
500: "hsl(0 18% 42%)",
600: "hsl(0 20% 35%)",
700: "hsl(0 18% 28%)",
800: "hsl(0 15% 20%)",
900: "hsl(0 12% 12%)",
950: "hsl(0 10% 6%)",
},
},
borderRadius: {
@@ -140,11 +159,11 @@ const config = {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
"shine": {
shine: {
from: { backgroundPosition: "200% 0" },
to: { backgroundPosition: "-200% 0" },
},
"gradient": {
gradient: {
to: {
backgroundPosition: "var(--bg-size) 0",
},
@@ -156,11 +175,11 @@ const config = {
"50%": {
"background-position": "100% 100%",
},
"to": {
to: {
"background-position": "0% 0%",
},
},
"moveHorizontal": {
moveHorizontal: {
"0%": {
transform: "translateX(-50%) translateY(-10%)",
},
@@ -171,7 +190,7 @@ const config = {
transform: "translateX(-50%) translateY(-10%)",
},
},
"moveInCircle": {
moveInCircle: {
"0%": {
transform: "rotate(0deg)",
},
@@ -182,7 +201,7 @@ const config = {
transform: "rotate(360deg)",
},
},
"moveVertical": {
moveVertical: {
"0%": {
transform: "translateY(-50%)",
},
@@ -193,26 +212,34 @@ const config = {
transform: "translateY(-50%)",
},
},
// Heretek Glitch Animations
"glitch": {
"0%, 90%, 100%": {
// Heretek Glitch Animations - Enhanced
glitch: {
"0%, 88%, 100%": {
transform: "translate(0)",
filter: "none",
},
"91%": {
"89%": {
transform: "translate(-2px, 1px)",
filter: "hue-rotate(90deg) saturate(1.5)",
},
"92%": {
"90%": {
transform: "translate(2px, -1px)",
filter: "hue-rotate(-90deg) saturate(1.5)",
},
"93%": {
"91%": {
transform: "translate(-1px, -1px)",
filter: "hue-rotate(45deg)",
filter: "hue-rotate(45deg) brightness(1.2)",
},
"92%": {
transform: "translate(1px, 1px)",
filter: "none",
},
"93%": {
transform: "translate(-2px, 2px)",
filter: "hue-rotate(-45deg) saturate(1.3)",
},
"94%": {
transform: "translate(1px, 1px)",
transform: "translate(2px, -2px)",
filter: "none",
},
},
@@ -221,29 +248,32 @@ const config = {
"text-shadow": "none",
},
"1%": {
"text-shadow": "-2px 0 hsl(145 70% 40%), 2px 0 hsl(0 70% 45%)",
"text-shadow": "-2px 0 hsl(0 80% 50%), 2px 0 hsl(0 90% 60%)",
},
"2%": {
"text-shadow": "2px 0 hsl(145 70% 40%), -2px 0 hsl(0 70% 45%)",
"text-shadow": "2px 0 hsl(0 80% 50%), -2px 0 hsl(0 90% 60%)",
},
"3%": {
"text-shadow": "none",
},
},
"flicker": {
flicker: {
"0%, 100%": { opacity: "1" },
"90%": { opacity: "1" },
"91%": { opacity: "0.7" },
"92%": { opacity: "1" },
"93%": { opacity: "0.8" },
"93%": { opacity: "0.85" },
"94%": { opacity: "1" },
"96%": { opacity: "0.9" },
"97%": { opacity: "1" },
"95%": { opacity: "0.9" },
"96%": { opacity: "1" },
},
"corrupted-pulse": {
"0%, 100%": {
"box-shadow": "0 0 10px hsl(145 50% 25% / 0.2), 0 0 20px hsl(145 40% 20% / 0.1)",
"box-shadow": "0 0 10px hsl(0 65% 40% / 0.25), 0 0 20px hsl(0 55% 30% / 0.15)",
},
"50%": {
"box-shadow": "0 0 15px hsl(145 60% 30% / 0.3), 0 0 30px hsl(145 50% 25% / 0.15), 0 0 45px hsl(145 40% 20% / 0.1)",
"box-shadow":
"0 0 15px hsl(0 75% 45% / 0.35), 0 0 30px hsl(0 65% 35% / 0.2), 0 0 45px hsl(0 55% 25% / 0.1)",
},
},
"scan-line": {
@@ -254,20 +284,18 @@ const config = {
transform: "translateY(100vh)",
},
},
"rust-fall": {
"0%": {
transform: "translateY(-10%) rotate(0deg)",
"blood-drip": {
"0%, 100%": {
height: "0",
opacity: "0",
},
"10%": {
"50%": {
height: "10px",
opacity: "1",
},
"90%": {
opacity: "1",
},
"100%": {
transform: "translateY(100vh) rotate(720deg)",
opacity: "0",
"80%": {
height: "15px",
opacity: "0.5",
},
},
"metal-shine": {
@@ -280,13 +308,13 @@ const config = {
},
"heretic-glow": {
"0%, 100%": {
"filter": "brightness(1) saturate(1)",
filter: "brightness(1) saturate(1)",
},
"50%": {
"filter": "brightness(1.1) saturate(1.2)",
filter: "brightness(1.15) saturate(1.3)",
},
},
// New Mechanicus Animations
// Heretek Enhanced Animations
"binary-flicker": {
"0%, 100%": { opacity: "1" },
"50%": { opacity: "0.95" },
@@ -298,8 +326,8 @@ const config = {
"100%": { transform: "translateX(100%)" },
},
"spirit-pulse": {
"0%, 100%": { opacity: "0.3", transform: "scale(1)" },
"50%": { opacity: "0.6", transform: "scale(1.02)" },
"0%, 100%": { opacity: "0.4", transform: "scale(1)" },
"50%": { opacity: "0.7", transform: "scale(1.02)" },
},
"stream-scroll": {
"0%": { transform: "translateY(0)" },
@@ -307,50 +335,73 @@ const config = {
},
"forge-pulse": {
"0%, 100%": {
"box-shadow": "0 0 10px hsl(28 70% 45% / 0.3), 0 0 20px hsl(28 60% 35% / 0.2)",
"box-shadow": "0 0 10px hsl(0 70% 50% / 0.35), 0 0 20px hsl(0 60% 40% / 0.25)",
},
"50%": {
"box-shadow": "0 0 15px hsl(28 80% 50% / 0.4), 0 0 30px hsl(28 70% 40% / 0.3), 0 0 45px hsl(28 60% 35% / 0.2)",
"box-shadow":
"0 0 15px hsl(0 80% 55% / 0.45), 0 0 30px hsl(0 70% 45% / 0.35), 0 0 45px hsl(0 60% 35% / 0.2)",
},
},
"rust-drift": {
"blood-drift": {
"0%": { transform: "translateY(0) rotate(0deg)" },
"100%": { transform: "translateY(-100px) rotate(360deg)" },
},
"mechanicus-shimmer": {
"heretek-shimmer": {
"0%": { backgroundPosition: "-200% 0" },
"100%": { backgroundPosition: "200% 0" },
},
"static-flash": {
"0%, 95%, 100%": { opacity: "0" },
"96%": { opacity: "0.08" },
"97%": { opacity: "0" },
"98%": { opacity: "0.05" },
"99%": { opacity: "0" },
},
"terminal-flicker": {
"0%, 100%": { opacity: "1" },
"50%": { opacity: "0.98" },
},
"void-pulse": {
"0%, 100%": {
"box-shadow": "0 0 20px hsl(0 0% 0% / 0.5), inset 0 0 20px hsl(0 0% 0% / 0.3)",
},
"50%": {
"box-shadow": "0 0 40px hsl(0 0% 5% / 0.6), inset 0 0 30px hsl(0 0% 3% / 0.4)",
},
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"shine": "shine 8s ease-in-out infinite",
"gradient": "gradient 8s linear infinite",
shine: "shine 8s ease-in-out infinite",
gradient: "gradient 8s linear infinite",
// Heretek Animations
"glitch": "glitch 4s infinite",
"glitch-text": "glitch-text 8s infinite",
"flicker": "flicker 8s infinite",
"corrupted-pulse": "corrupted-pulse 3s ease-in-out infinite",
"scan-line": "scan-line 8s linear infinite",
"rust-fall": "rust-fall 10s linear infinite",
glitch: "glitch 3s infinite",
"glitch-text": "glitch-text 6s infinite",
flicker: "flicker 6s infinite",
"corrupted-pulse": "corrupted-pulse 2.5s ease-in-out infinite",
"scan-line": "scan-line 6s linear infinite",
"blood-drip": "blood-drip 3s ease-in-out infinite",
"metal-shine": "metal-shine 3s ease-in-out infinite",
"heretic-glow": "heretic-glow 4s ease-in-out infinite",
// New Mechanicus Animations
"binary-flicker": "binary-flicker 5s infinite",
"circuit-flow": "circuit-flow 3s linear infinite",
"spirit-pulse": "spirit-pulse 4s ease-in-out infinite",
"stream-scroll": "stream-scroll 20s linear infinite",
"forge-pulse": "forge-pulse 3s ease-in-out infinite",
"rust-drift": "rust-drift 30s linear infinite",
"mechanicus-shimmer": "mechanicus-shimmer 3s ease-in-out infinite",
// Heretek Enhanced Animations
"binary-flicker": "binary-flicker 4s infinite",
"circuit-flow": "circuit-flow 2.5s linear infinite",
"spirit-pulse": "spirit-pulse 3s ease-in-out infinite",
"stream-scroll": "stream-scroll 15s linear infinite",
"forge-pulse": "forge-pulse 2.5s ease-in-out infinite",
"blood-drift": "blood-drift 25s linear infinite",
"heretek-shimmer": "heretek-shimmer 3s ease-in-out infinite",
"static-flash": "static-flash 8s infinite",
"terminal-flicker": "terminal-flicker 0.1s infinite",
"void-pulse": "void-pulse 4s ease-in-out infinite",
},
backgroundImage: {
// Heretek Background Patterns
"rust-gradient": "linear-gradient(135deg, hsl(28 70% 35%) 0%, hsl(35 50% 25%) 50%, hsl(28 60% 30%) 100%)",
"corruption-gradient": "linear-gradient(180deg, hsl(145 50% 20%) 0%, hsl(145 60% 30%) 50%, hsl(145 50% 25%) 100%)",
"metal-surface": "linear-gradient(145deg, hsl(30 15% 15%) 0%, hsl(30 20% 10%) 50%, hsl(30 15% 12%) 100%)",
"brass-shine": "linear-gradient(90deg, hsl(43 60% 35%) 0%, hsl(43 75% 50%) 50%, hsl(43 60% 35%) 100%)",
"blood-gradient": "linear-gradient(135deg, hsl(0 70% 35%) 0%, hsl(0 50% 25%) 50%, hsl(0 60% 30%) 100%)",
"void-gradient": "linear-gradient(180deg, hsl(0 0% 5%) 0%, hsl(0 0% 3%) 50%, hsl(0 0% 8%) 100%)",
"steel-surface": "linear-gradient(145deg, hsl(0 5% 12%) 0%, hsl(0 8% 8%) 50%, hsl(0 5% 10%) 100%)",
"corruption-spread": "linear-gradient(90deg, hsl(0 0% 5%) 0%, hsl(0 60% 30%) 50%, hsl(0 0% 5%) 100%)",
},
},
},
@@ -382,9 +433,9 @@ const config = {
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" fill="none" stroke="${value}" stroke-width="0.5"><path d="M0 32h20m4 0h16m4 0h20M32 0v20m0 4v16m0 4v20"/><circle cx="32" cy="32" r="4" fill="${value}" fill-opacity="0.3"/><circle cx="24" cy="32" r="2" fill="${value}"/><circle cx="40" cy="32" r="2" fill="${value}"/><circle cx="32" cy="24" r="2" fill="${value}"/><circle cx="32" cy="40" r="2" fill="${value}"/></svg>`,
)}")`,
}),
"bg-rust-texture": (value: any) => ({
"bg-void-texture": (value: any) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><filter id="rust"><feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="4" result="noise"/><feColorMatrix type="saturate" values="0.3" in="noise" result="rust"/></filter><rect width="100" height="100" fill="${value}" style="filter:url(#rust)"/></svg>`,
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><filter id="void"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="4" result="noise"/><feColorMatrix type="saturate" values="0" in="noise" result="void"/></filter><rect width="100" height="100" fill="${value}" style="filter:url(#void)"/></svg>`,
)}")`,
}),
},
@@ -399,9 +450,7 @@ const config = {
function addVariablesForColors({ addBase, theme }: any) {
const allColors = flattenColorPalette(theme("colors"));
const newVars = Object.fromEntries(
Object.entries(allColors).map(([key, val]) => [`--${key}`, val]),
);
const newVars = Object.fromEntries(Object.entries(allColors).map(([key, val]) => [`--${key}`, val]));
addBase({
":root": newVars,
});
@@ -91,17 +91,18 @@ msg_info "Running Unsloth Studio Setup"
# This requires GPU access - set up environment for ROCm if installed
# Set up ROCm environment if available
# Use ${VAR:-} to handle unset variables (set -u causes errors otherwise)
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export ROCM_PATH="/opt/rocm-6.2"
fi
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: cobalt (cobaltgit)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://ntfy.sh/
APP="Alpine-ntfy"
var_tags="${var_tags:-notification}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-256}"
var_disk="${var_disk:-2}"
var_os="${var_os:-alpine}"
var_version="${var_version:-3.23}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /etc/ntfy ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
msg_info "Updating ntfy LXC"
$STD apk -U upgrade
setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy
msg_ok "Updated ntfy LXC"
msg_info "Restarting ntfy"
rc-service ntfy restart
msg_ok "Restarted ntfy"
msg_ok "Updated successfully!"
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://anytype.io
APP="Anytype-Server"
var_tags="${var_tags:-notes;productivity;sync}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-16}"
var_os="${var_os:-ubuntu}"
var_version="${var_version:-24.04}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /opt/anytype/any-sync-bundle ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "anytype" "grishy/any-sync-bundle"; then
msg_info "Stopping Service"
systemctl stop anytype
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/anytype/data /opt/anytype_data_backup
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "anytype" "grishy/any-sync-bundle" "prebuild" "latest" "/opt/anytype" "any-sync-bundle_*_linux_amd64.tar.gz"
chmod +x /opt/anytype/any-sync-bundle
msg_info "Restoring Data"
cp -r /opt/anytype_data_backup/. /opt/anytype/data
rm -rf /opt/anytype_data_backup
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start anytype
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:33010${CL}"
echo -e "${INFO}${YW} Client config file:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}/opt/anytype/data/client-config.yml${CL}"
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/qdm12/gluetun
APP="Gluetun"
var_tags="${var_tags:-vpn;wireguard;openvpn}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_tun="${var_tun:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /usr/local/bin/gluetun ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "gluetun" "qdm12/gluetun"; then
msg_info "Stopping Service"
systemctl stop gluetun
msg_ok "Stopped Service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "gluetun" "qdm12/gluetun" "tarball"
msg_info "Building Gluetun"
cd /opt/gluetun
$STD go mod download
CGO_ENABLED=0 $STD go build -trimpath -ldflags="-s -w" -o /usr/local/bin/gluetun ./cmd/gluetun/
msg_ok "Built Gluetun"
msg_info "Starting Service"
systemctl start gluetun
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}"
@@ -0,0 +1,6 @@
___ __ _____
/ | ____ __ __/ /___ ______ ___ / ___/___ ______ _____ _____
/ /| | / __ \/ / / / __/ / / / __ \/ _ \______\__ \/ _ \/ ___/ | / / _ \/ ___/
/ ___ |/ / / / /_/ / /_/ /_/ / /_/ / __/_____/__/ / __/ / | |/ / __/ /
/_/ |_/_/ /_/\__, /\__/\__, / .___/\___/ /____/\___/_/ |___/\___/_/
/____/ /____/_/
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: johanngrobe
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/oss-apps/split-pro
APP="Split-Pro"
var_tags="${var_tags:-finance;expense-sharing}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-6}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/split-pro ]]; then
msg_error "No Split Pro Installation Found!"
exit
fi
if check_for_gh_release "split-pro" "oss-apps/split-pro"; then
msg_info "Stopping Service"
systemctl stop split-pro
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp /opt/split-pro/.env /opt/split-pro.env
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball"
msg_info "Building Application"
cd /opt/split-pro
$STD pnpm install --frozen-lockfile
$STD pnpm build
cp /opt/split-pro.env /opt/split-pro/.env
rm -f /opt/split-pro.env
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
$STD pnpm exec prisma migrate deploy
msg_ok "Built Application"
msg_info "Starting Service"
systemctl start split-pro
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/FuzzyGrim/Yamtrack
APP="Yamtrack"
var_tags="${var_tags:-media;tracker;movies;anime}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/yamtrack ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "yamtrack" "FuzzyGrim/Yamtrack"; then
msg_info "Stopping Services"
systemctl stop yamtrack yamtrack-celery
msg_ok "Stopped Services"
msg_info "Backing up Data"
cp /opt/yamtrack/src/.env /opt/yamtrack_env.bak
msg_ok "Backed up Data"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "yamtrack" "FuzzyGrim/Yamtrack" "tarball"
msg_info "Installing Python Dependencies"
cd /opt/yamtrack
$STD uv venv .venv
$STD uv pip install --no-cache-dir -r requirements.txt
msg_ok "Installed Python Dependencies"
msg_info "Restoring Data"
cp /opt/yamtrack_env.bak /opt/yamtrack/src/.env
rm -f /opt/yamtrack_env.bak
msg_ok "Restored Data"
msg_info "Updating Yamtrack"
cd /opt/yamtrack/src
$STD /opt/yamtrack/.venv/bin/python manage.py migrate
$STD /opt/yamtrack/.venv/bin/python manage.py collectstatic --noinput
msg_ok "Updated Yamtrack"
msg_info "Updating Nginx Configuration"
cp /opt/yamtrack/nginx.conf /etc/nginx/nginx.conf
sed -i 's|user abc;|user www-data;|' /etc/nginx/nginx.conf
sed -i 's|/yamtrack/staticfiles/|/opt/yamtrack/src/staticfiles/|' /etc/nginx/nginx.conf
$STD systemctl reload nginx
msg_ok "Updated Nginx Configuration"
msg_info "Starting Services"
systemctl start yamtrack yamtrack-celery
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}"
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: cobalt (cobaltgit)
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
# Source: https://ntfy.sh/
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing ntfy"
$STD apk add --no-cache ntfy ntfy-openrc libcap
sed -i '/^listen-http/s/^\(.*\)$/#\1\n/' /etc/ntfy/server.yml
setcap 'cap_net_bind_service=+ep' /usr/bin/ntfy
$STD rc-update add ntfy default
$STD service ntfy start
msg_ok "Installed ntfy"
motd_ssh
customize
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://anytype.io
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
setup_mongodb
msg_info "Configuring MongoDB Replica Set"
cat <<EOF >>/etc/mongod.conf
replication:
replSetName: "rs0"
EOF
systemctl restart mongod
sleep 3
$STD mongosh --eval 'rs.initiate({_id: "rs0", members: [{_id: 0, host: "127.0.0.1:27017"}]})'
msg_ok "Configured MongoDB Replica Set"
msg_info "Installing Redis Stack"
setup_deb822_repo \
"redis-stack" \
"https://packages.redis.io/gpg" \
"https://packages.redis.io/deb" \
"jammy" \
"main"
$STD apt install -y redis-stack-server
systemctl enable -q --now redis-stack-server
msg_ok "Installed Redis Stack"
fetch_and_deploy_gh_release "anytype" "grishy/any-sync-bundle" "prebuild" "latest" "/opt/anytype" "any-sync-bundle_*_linux_amd64.tar.gz"
chmod +x /opt/anytype/any-sync-bundle
msg_info "Configuring Anytype"
mkdir -p /opt/anytype/data/storage
cat <<EOF >/opt/anytype/.env
ANY_SYNC_BUNDLE_CONFIG=/opt/anytype/data/bundle-config.yml
ANY_SYNC_BUNDLE_CLIENT_CONFIG=/opt/anytype/data/client-config.yml
ANY_SYNC_BUNDLE_INIT_STORAGE=/opt/anytype/data/storage/
ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=${LOCAL_IP}
ANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://127.0.0.1:27017/
ANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://127.0.0.1:6379/
ANY_SYNC_BUNDLE_LOG_LEVEL=info
EOF
msg_ok "Configured Anytype"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/anytype.service
[Unit]
Description=Anytype Sync Server (any-sync-bundle)
After=network-online.target mongod.service redis-stack-server.service
Wants=network-online.target
Requires=mongod.service redis-stack-server.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/anytype
EnvironmentFile=/opt/anytype/.env
ExecStart=/opt/anytype/any-sync-bundle start-bundle
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now anytype
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/qdm12/gluetun
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
openvpn \
wireguard-tools \
iptables
msg_ok "Installed Dependencies"
msg_info "Configuring iptables"
$STD update-alternatives --set iptables /usr/sbin/iptables-legacy
$STD update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
ln -sf /usr/sbin/openvpn /usr/sbin/openvpn2.6
msg_ok "Configured iptables"
setup_go
fetch_and_deploy_gh_release "gluetun" "qdm12/gluetun" "tarball"
msg_info "Building Gluetun"
cd /opt/gluetun
$STD go mod download
CGO_ENABLED=0 $STD go build -trimpath -ldflags="-s -w" -o /usr/local/bin/gluetun ./cmd/gluetun/
msg_ok "Built Gluetun"
msg_info "Configuring Gluetun"
mkdir -p /opt/gluetun-data
touch /etc/alpine-release
ln -sf /opt/gluetun-data /gluetun
cat <<EOF >/opt/gluetun-data/.env
VPN_SERVICE_PROVIDER=custom
VPN_TYPE=openvpn
OPENVPN_CUSTOM_CONFIG=/opt/gluetun-data/custom.ovpn
OPENVPN_USER=
OPENVPN_PASSWORD=
OPENVPN_PROCESS_USER=root
PUID=0
PGID=0
HTTP_CONTROL_SERVER_ADDRESS=:8000
HTTPPROXY=off
SHADOWSOCKS=off
PPROF_ENABLED=no
PPROF_BLOCK_PROFILE_RATE=0
PPROF_MUTEX_PROFILE_RATE=0
PPROF_HTTP_SERVER_ADDRESS=:6060
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on
HEALTH_SERVER_ADDRESS=127.0.0.1:9999
DNS_UPSTREAM_RESOLVERS=cloudflare
LOG_LEVEL=info
STORAGE_FILEPATH=/gluetun/servers.json
PUBLICIP_FILE=/gluetun/ip
VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port
TZ=UTC
EOF
msg_ok "Configured Gluetun"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/gluetun.service
[Unit]
Description=Gluetun VPN Client
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/gluetun-data
EnvironmentFile=/opt/gluetun-data/.env
UnsetEnvironment=USER
ExecStartPre=/bin/sh -c 'rm -f /etc/openvpn/target.ovpn'
ExecStart=/usr/local/bin/gluetun
Restart=on-failure
RestartSec=5
AmbientCapabilities=CAP_NET_ADMIN
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now gluetun
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: johanngrobe
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/oss-apps/split-pro
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
PG_VERSION="17" PG_MODULES="cron" setup_postgresql
msg_info "Installing Dependencies"
$STD apt install -y openssl
msg_ok "Installed Dependencies"
PG_DB_NAME="splitpro" PG_DB_USER="splitpro" PG_DB_EXTENSIONS="pg_cron" setup_postgresql_db
fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball"
msg_info "Installing Dependencies"
cd /opt/split-pro
$STD pnpm install --frozen-lockfile
msg_ok "Installed Dependencies"
msg_info "Building Split Pro"
cd /opt/split-pro
mkdir -p /opt/split-pro_data/uploads
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
NEXTAUTH_SECRET=$(openssl rand -base64 32)
cp .env.example .env
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\"|" .env
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\"${NEXTAUTH_SECRET}\"|" .env
sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=\"http://${LOCAL_IP}:3000\"|" .env
sed -i "s|^NEXTAUTH_URL_INTERNAL=.*|NEXTAUTH_URL_INTERNAL=\"http://localhost:3000\"|" .env
sed -i "/^POSTGRES_CONTAINER_NAME=/d" .env
sed -i "/^POSTGRES_USER=/d" .env
sed -i "/^POSTGRES_PASSWORD=/d" .env
sed -i "/^POSTGRES_DB=/d" .env
sed -i "/^POSTGRES_PORT=/d" .env
$STD pnpm build
$STD pnpm exec prisma migrate deploy
msg_ok "Built Split Pro"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/split-pro.service
[Unit]
Description=Split Pro
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/split-pro
EnvironmentFile=/opt/split-pro/.env
ExecStart=/usr/bin/pnpm start
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now split-pro
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (CanbiZ)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/FuzzyGrim/Yamtrack
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
nginx \
redis-server
msg_ok "Installed Dependencies"
PG_VERSION="16" setup_postgresql
PG_DB_NAME="yamtrack" PG_DB_USER="yamtrack" setup_postgresql_db
PYTHON_VERSION="3.12" setup_uv
fetch_and_deploy_gh_release "yamtrack" "FuzzyGrim/Yamtrack" "tarball"
msg_info "Installing Python Dependencies"
cd /opt/yamtrack
$STD uv venv .venv
$STD uv pip install --no-cache-dir -r requirements.txt
msg_ok "Installed Python Dependencies"
msg_info "Configuring Yamtrack"
SECRET=$(openssl rand -hex 32)
cat <<EOF >/opt/yamtrack/src/.env
SECRET=${SECRET}
DB_HOST=localhost
DB_NAME=${PG_DB_NAME}
DB_USER=${PG_DB_USER}
DB_PASSWORD=${PG_DB_PASS}
DB_PORT=5432
REDIS_URL=redis://localhost:6379
URLS=http://${LOCAL_IP}:8000
EOF
cd /opt/yamtrack/src
$STD /opt/yamtrack/.venv/bin/python manage.py migrate
$STD /opt/yamtrack/.venv/bin/python manage.py collectstatic --noinput
msg_ok "Configured Yamtrack"
msg_info "Configuring Nginx"
rm -f /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default
cp /opt/yamtrack/nginx.conf /etc/nginx/nginx.conf
sed -i 's|user abc;|user www-data;|' /etc/nginx/nginx.conf
sed -i 's|pid /tmp/nginx.pid;|pid /run/nginx.pid;|' /etc/nginx/nginx.conf
sed -i 's|/yamtrack/staticfiles/|/opt/yamtrack/src/staticfiles/|' /etc/nginx/nginx.conf
sed -i 's|error_log /dev/stderr|error_log /var/log/nginx/error.log|' /etc/nginx/nginx.conf
sed -i 's|access_log /dev/stdout|access_log /var/log/nginx/access.log|' /etc/nginx/nginx.conf
$STD nginx -t
systemctl enable -q nginx
$STD systemctl restart nginx
msg_ok "Configured Nginx"
msg_info "Creating Services"
cat <<EOF >/etc/systemd/system/yamtrack.service
[Unit]
Description=Yamtrack Gunicorn
After=network.target postgresql.service redis-server.service
Requires=postgresql.service redis-server.service
[Service]
Type=simple
WorkingDirectory=/opt/yamtrack/src
ExecStart=/opt/yamtrack/.venv/bin/gunicorn config.wsgi:application -b 127.0.0.1:8001 -w 2 --timeout 120
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/yamtrack-celery.service
[Unit]
Description=Yamtrack Celery Worker
After=network.target postgresql.service redis-server.service yamtrack.service
Requires=postgresql.service redis-server.service
[Service]
Type=simple
WorkingDirectory=/opt/yamtrack/src
ExecStart=/opt/yamtrack/.venv/bin/celery -A config worker --beat --scheduler django --loglevel INFO
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now redis-server yamtrack yamtrack-celery
msg_ok "Created Services"
motd_ssh
customize
cleanup_lxc
@@ -30,20 +30,55 @@ msg_ok "Installed Dependencies"
setup_hwaccel
# Setup Python virtual environment with uv (fast Python package manager)
PYTHON_VERSION="3.12" setup_uv
PYTHON_VERSION="3.13" setup_uv
msg_info "Creating Virtual Environment"
mkdir -p /opt/unsolth-studio
cd /opt/unsolth-studio || exit
$STD uv venv --python 3.12
$STD uv venv --python 3.13
source .venv/bin/activate
msg_ok "Created Virtual Environment"
msg_info "Installing PyTorch"
# Install PyTorch first (required by unsloth)
# Use CPU version for broader compatibility; GPU version can be installed manually if needed
$STD uv pip install torch --index-url https://download.pytorch.org/whl/cpu
msg_ok "Installed PyTorch"
msg_info "Detecting GPU Type for PyTorch Installation"
# Detect GPU type based on what setup_hwaccel installed
# setup_hwaccel runs before this and installs NVIDIA drivers or ROCm
GPU_TYPE="cpu"
# Check for NVIDIA GPU (nvidia-smi installed by setup_hwaccel)
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null; then
GPU_TYPE="nvidia"
msg_info "NVIDIA GPU detected - installing PyTorch with CUDA support"
# Check for AMD GPU (ROCm installed by setup_hwaccel at /opt/rocm)
elif [ -d "/opt/rocm" ] || [ -d "/opt/rocm-7.2" ] || [ -d "/opt/rocm-6.2" ]; then
GPU_TYPE="amd"
msg_info "AMD GPU detected (ROCm installed) - installing PyTorch with ROCm support"
# Check for AMD render devices (GPU passthrough configured)
elif ls /dev/dri/renderD* &>/dev/null 2>&1; then
# Check if any render device is AMD
for render_dev in /dev/dri/renderD*; do
if [ -e "$render_dev" ]; then
GPU_TYPE="amd"
msg_info "AMD GPU detected (render device) - installing PyTorch with ROCm support"
break
fi
done
fi
if [ "$GPU_TYPE" = "nvidia" ]; then
# NVIDIA GPU - install PyTorch with CUDA 12.4 support
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
msg_ok "Installed PyTorch with CUDA Support"
elif [ "$GPU_TYPE" = "amd" ]; then
# AMD GPU - install PyTorch with ROCm 7.2 support
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/test/rocm7.2
msg_ok "Installed PyTorch with ROCm Support"
else
# No GPU detected - install CPU version
msg_info "No GPU detected - installing PyTorch CPU version"
$STD uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
msg_ok "Installed PyTorch (CPU version)"
fi
msg_info "Installing Unsloth"
# Install unsloth and its dependencies
@@ -53,9 +88,36 @@ msg_ok "Installed Unsloth"
msg_info "Running Unsloth Studio Setup"
# Run the unsloth studio setup command to compile llama.cpp
# Use Python module invocation since uv pip install doesn't create entry points
$STD /opt/unsolth-studio/.venv/bin/python -m unsloth studio setup
msg_ok "Completed Unsloth Studio Setup"
# This requires GPU access - set up environment for ROCm if installed
# Set up ROCm environment if available
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib:$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib:$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib:$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-6.2"
fi
# Check if GPU is available (works for both CUDA and ROCm)
if /opt/unsolth-studio/.venv/bin/python -c "import torch; exit(0 if torch.cuda.is_available() else 1)" 2>/dev/null; then
$STD /opt/unsolth-studio/.venv/bin/python -m unsloth studio setup
msg_ok "Completed Unsloth Studio Setup"
else
msg_info "GPU not detected via torch.cuda - skipping Unsloth Studio setup"
msg_info "This may be normal if ROCm libraries need system restart to take effect"
echo ""
echo -e "${GN}Note: If you have GPU passthrough configured, try:${CL}"
echo -e "${GN} 1. Restart the container: pct stop <CTID> && pct start <CTID>${CL}"
echo -e "${GN} 2. Then run: source /opt/unsolth-studio/.venv/bin/activate && unsloth studio setup${CL}"
echo ""
fi
msg_info "Creating Directories"
mkdir -p /opt/unsolth-studio/models
@@ -65,6 +127,34 @@ chmod 755 /var/log/unsolth-studio
msg_ok "Created Directories"
msg_info "Creating Service"
# Create environment file for ROCm/CUDA paths
cat <<EOF >/opt/unsolth-studio/environment.sh
#!/bin/bash
# Set up GPU environment for Unsloth Studio
# ROCm environment (AMD GPUs)
if [ -d "/opt/rocm" ]; then
export PATH="/opt/rocm/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm"
elif [ -d "/opt/rocm-7.2" ]; then
export PATH="/opt/rocm-7.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-7.2/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-7.2"
elif [ -d "/opt/rocm-6.2" ]; then
export PATH="/opt/rocm-6.2/bin:\$PATH"
export LD_LIBRARY_PATH="/opt/rocm-6.2/lib:\$LD_LIBRARY_PATH"
export ROCM_PATH="/opt/rocm-6.2"
fi
# NVIDIA CUDA environment
if [ -d "/usr/local/cuda" ]; then
export PATH="/usr/local/cuda/bin:\$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64:\$LD_LIBRARY_PATH"
fi
EOF
chmod +x /opt/unsolth-studio/environment.sh
cat <<EOF >/etc/systemd/system/unsolth-studio.service
[Unit]
Description=Unsloth Studio - Local LLM Fine-tuning Web UI
@@ -75,6 +165,7 @@ Wants=network-online.target
Type=simple
WorkingDirectory=/opt/unsolth-studio
Environment="PATH=/opt/unsolth-studio/.venv/bin:/usr/local/bin:/usr/bin:/bin"
EnvironmentFile=/opt/unsolth-studio/environment.sh
ExecStart=/opt/unsolth-studio/.venv/bin/python -m unsloth studio -H 0.0.0.0 -p 8888
Restart=on-failure
RestartSec=10
@@ -90,8 +181,15 @@ TimeoutStopSec=60
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now unsolth-studio
# Don't auto-start the service since GPU passthrough may not be configured yet
# User needs to configure GPU passthrough first, then start the service manually
systemctl enable -q unsolth-studio
msg_ok "Created Service"
echo ""
echo -e "${GN}Note: The unsolth-studio service is enabled but not started.${CL}"
echo -e "${GN}Configure GPU passthrough first, then start with:${CL}"
echo -e "${GN} systemctl start unsolth-studio${CL}"
echo ""
# Create GPU passthrough info file
cat <<EOF >/opt/unsolth-studio/GPU_PASSTHROUGH.md
@@ -39,9 +39,16 @@ $STD uv venv --python 3.12
source .venv/bin/activate
msg_ok "Created Virtual Environment"
msg_info "Installing PyTorch"
# Install PyTorch first (required by unsloth)
# Use CPU version for broader compatibility; GPU version can be installed manually if needed
$STD uv pip install torch --index-url https://download.pytorch.org/whl/cpu
msg_ok "Installed PyTorch"
msg_info "Installing Unsloth"
# Install unsloth with torch backend auto-detection (GPU drivers must be installed first)
$STD uv pip install unsloth --torch-backend=auto
# Install unsloth and its dependencies
# packaging module is required but not declared as dependency
$STD uv pip install unsloth packaging
msg_ok "Installed Unsloth"
msg_info "Running Unsloth Studio Setup"
@@ -1,5 +1,5 @@
{
"generated": "2026-03-15T12:22:39Z",
"generated": "2026-03-16T18:42:54Z",
"versions": [
{
"slug": "agregarr",
@@ -18,9 +18,9 @@
{
"slug": "llamacpp",
"repo": "ggml-org/llama.cpp",
"version": "b8354",
"version": "b8373",
"pinned": false,
"date": "2026-03-15T10:06:38Z"
"date": "2026-03-16T10:55:12Z"
},
{
"slug": "localai",
@@ -46,9 +46,9 @@
{
"slug": "pegaprox",
"repo": "PegaProx/project-pegaprox",
"version": "v0.9.1.3",
"version": "v0.9.2",
"pinned": false,
"date": "2026-03-11T20:23:52Z"
"date": "2026-03-16T08:00:29Z"
},
{
"slug": "ragflow",
@@ -48,7 +48,8 @@ jobs:
const https = require('https');
const http = require('http');
const url = require('url');
function request(fullUrl, opts) {
function request(fullUrl, opts, redirectCount) {
redirectCount = redirectCount || 0;
return new Promise(function(resolve, reject) {
const u = url.parse(fullUrl);
const isHttps = u.protocol === 'https:';
@@ -63,6 +64,13 @@ jobs:
if (body) options.headers['Content-Length'] = Buffer.byteLength(body);
const lib = isHttps ? https : http;
const req = lib.request(options, function(res) {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
if (redirectCount >= 5) return reject(new Error('Too many redirects from ' + fullUrl));
const redirectUrl = url.resolve(fullUrl, res.headers.location);
res.resume();
resolve(request(redirectUrl, opts, redirectCount + 1));
return;
}
let data = '';
res.on('data', function(chunk) { data += chunk; });
res.on('end', function() {
@@ -125,15 +133,15 @@ jobs:
var osVersionToId = {};
try {
const res = await request(apiBase + '/collections/z_ref_note_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) noteTypeToId[item.type] = item.id; });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) { noteTypeToId[item.type] = item.id; noteTypeToId[item.type.toLowerCase()] = item.id; } });
} catch (e) { console.warn('z_ref_note_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_install_method_types/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) installMethodTypeToId[item.type] = item.id; });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.type != null) { installMethodTypeToId[item.type] = item.id; installMethodTypeToId[item.type.toLowerCase()] = item.id; } });
} catch (e) { console.warn('z_ref_install_method_types:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os/records?perPage=500', { headers: { 'Authorization': token } });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.os != null) osToId[item.os] = item.id; });
if (res.ok) JSON.parse(res.body).items?.forEach(function(item) { if (item.os != null) { osToId[item.os] = item.id; osToId[item.os.toLowerCase()] = item.id; } });
} catch (e) { console.warn('z_ref_os:', e.message); }
try {
const res = await request(apiBase + '/collections/z_ref_os_version/records?perPage=500&expand=os', { headers: { 'Authorization': token } });
@@ -154,7 +162,7 @@ jobs:
name: data.name,
slug: data.slug,
script_created: data.date_created || data.script_created,
script_updated: data.date_created || data.script_updated,
script_updated: new Date().toISOString().split('T')[0],
updateable: data.updateable,
privileged: data.privileged,
port: data.interface_port != null ? data.interface_port : data.port,
@@ -163,8 +171,8 @@ jobs:
logo: data.logo,
description: data.description,
config_path: data.config_path,
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user,
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd,
default_user: (data.default_credentials && data.default_credentials.username) || data.default_user || null,
default_passwd: (data.default_credentials && data.default_credentials.password) || data.default_passwd || null,
is_dev: false
};
var resolvedType = typeValueToId[data.type];
@@ -190,7 +198,7 @@ jobs:
var postRes = await request(notesCollUrl, {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ text: note.text || '', type: typeId })
body: JSON.stringify({ text: note.text || '', type: typeId, script: scriptId })
});
if (postRes.ok) noteIds.push(JSON.parse(postRes.body).id);
}
@@ -3,12 +3,12 @@ name: Sync Upstream (Git Merge Strategy)
on:
schedule:
# Runs automatically every day at 2:00 AM UTC
- cron: '0 2 * * *'
- cron: "0 2 * * *"
workflow_dispatch:
# Allows manual trigger from the Actions tab
inputs:
force_sync:
description: 'Force sync even if no new commits detected'
description: "Force sync even if no new commits detected"
required: false
default: false
type: boolean
@@ -48,7 +48,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Configure the 'ours' merge driver for fork-specific files
# This tells git to use our version for files marked with merge=ours in .gitattributes
git config merge.ours.name "ours merge driver"
@@ -64,17 +64,17 @@ jobs:
run: |
# Get the merge base between fork and upstream
MERGE_BASE=$(git merge-base HEAD upstream/${{ env.UPSTREAM_BRANCH }} 2>/dev/null || echo "")
if [ -z "$MERGE_BASE" ]; then
echo "No common ancestor found - this may be a fresh sync"
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "commit_count=unknown" >> $GITHUB_OUTPUT
exit 0
fi
# Count commits in upstream that are not in fork
UPSTREAM_COMMITS=$(git rev-list $MERGE_BASE..upstream/${{ env.UPSTREAM_BRANCH }} --count 2>/dev/null || echo "0")
if [ "$UPSTREAM_COMMITS" -eq 0 ] && [ "${{ github.event.inputs.force_sync }}" != "true" ]; then
echo "No new commits from upstream. Nothing to sync."
echo "has_changes=false" >> $GITHUB_OUTPUT
@@ -113,7 +113,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Configure the 'ours' merge driver
# Files with merge=ours in .gitattributes will automatically keep fork version
git config merge.ours.name "ours merge driver"
@@ -135,14 +135,14 @@ jobs:
id: merge
run: |
echo "Starting merge from upstream/${{ env.UPSTREAM_BRANCH }}..."
# Attempt merge with the 'ours' strategy for conflicts
# The .gitattributes file configures which files use merge=ours
git merge upstream/${{ env.UPSTREAM_BRANCH }} \
--no-edit \
-m "Merge upstream ${{ env.UPSTREAM_REPO }} into ${{ env.FORK_BRANCH }}" \
2>&1 || MERGE_STATUS=$?
if [ "${MERGE_STATUS:-0}" -eq 0 ]; then
echo "Merge completed successfully with no conflicts."
echo "merge_status=success" >> $GITHUB_OUTPUT
@@ -152,29 +152,70 @@ jobs:
echo "merge_status=conflicts" >> $GITHUB_OUTPUT
echo "has_conflicts=true" >> $GITHUB_OUTPUT
# List conflicted files
# List all conflicted files
CONFLICTS=$(git diff --name-only --diff-filter=U 2>/dev/null || echo "")
echo "conflicted_files<<EOF" >> $GITHUB_OUTPUT
echo "$CONFLICTS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Check if conflicts are only in fork-specific files
# These should have been handled by merge=ours, but check anyway
FORK_FILES=$(git diff --name-only --diff-filter=U \
-- 'install/' 'ct/' 'frontend/' 'misc/images/' '.github/workflows/' '.github/runner/' 'tools/addon/' 'README.md' 'CHANGELOG.md' 2>/dev/null || echo "")
echo "Resolving conflicts..."
if [ -n "$FORK_FILES" ]; then
echo "Warning: Fork-specific files have unexpected conflicts:"
echo "$FORK_FILES"
# For fork files, always take our version
for file in $FORK_FILES; do
# 1. Handle content conflicts in fork-specific files - always keep our version
# These are files that exist in both but have different content
FORK_CONTENT_FILES=$(git diff --name-only --diff-filter=U \
-- 'CHANGELOG.md' 'misc/tools.func' 'README.md' 2>/dev/null || echo "")
if [ -n "$FORK_CONTENT_FILES" ]; then
echo "Resolving content conflicts in fork-specific files:"
echo "$FORK_CONTENT_FILES"
for file in $FORK_CONTENT_FILES; do
echo " Keeping fork version: $file"
git checkout --ours "$file" 2>/dev/null || true
git add "$file" 2>/dev/null || true
done
fi
# For other conflicts, we leave them for manual resolution in the PR
# This allows reviewers to see what needs attention
# 2. Handle modify/delete conflicts - files deleted in fork but modified upstream
# We want to keep our deletion (remove the file)
MODIFY_DELETE_CONFLICTS=$(git status --porcelain | grep "^DU" | awk '{print $2}' 2>/dev/null || echo "")
if [ -n "$MODIFY_DELETE_CONFLICTS" ]; then
echo "Resolving modify/delete conflicts (keeping fork deletions):"
echo "$MODIFY_DELETE_CONFLICTS"
for file in $MODIFY_DELETE_CONFLICTS; do
echo " Removing file (fork deleted it): $file"
git rm --ignore-unmatch "$file" 2>/dev/null || true
done
fi
# 3. Handle any remaining content conflicts in fork directories
REMAINING_CONFLICTS=$(git diff --name-only --diff-filter=U 2>/dev/null || echo "")
if [ -n "$REMAINING_CONFLICTS" ]; then
echo "Resolving remaining conflicts in fork directories:"
echo "$REMAINING_CONFLICTS"
# For files in fork-specific directories, keep our version
FORK_DIR_CONFLICTS=$(echo "$REMAINING_CONFLICTS" | grep -E '^(install/|ct/|frontend/|misc/images/|tools/addon/)' || true)
if [ -n "$FORK_DIR_CONFLICTS" ]; then
for file in $FORK_DIR_CONFLICTS; do
echo " Keeping fork version: $file"
git checkout --ours "$file" 2>/dev/null || true
git add "$file" 2>/dev/null || true
done
fi
fi
# Check if there are still unresolved conflicts
REMAINING=$(git diff --name-only --diff-filter=U 2>/dev/null || echo "")
if [ -n "$REMAINING" ]; then
echo "Warning: Some conflicts could not be auto-resolved:"
echo "$REMAINING"
echo "These will need manual resolution in the PR."
else
echo "All conflicts resolved successfully."
fi
else
echo "Merge failed with status: ${MERGE_STATUS}"
echo "merge_status=failed" >> $GITHUB_OUTPUT
@@ -188,22 +229,28 @@ jobs:
echo "upstream_commits<<EOF" >> $GITHUB_OUTPUT
git log HEAD..upstream/${{ env.UPSTREAM_BRANCH }} --oneline --no-merges | head -30 >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Get changed files summary
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
git diff --name-only HEAD upstream/${{ env.UPSTREAM_BRANCH }} | head -50 >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Count files by category
TOTAL_CHANGED=$(git diff --name-only HEAD upstream/${{ env.UPSTREAM_BRANCH }} | wc -l)
echo "total_changed=$TOTAL_CHANGED" >> $GITHUB_OUTPUT
- name: Commit merge result
if: steps.merge.outputs.merge_status == 'conflicts'
run: |
# Commit the merge with resolved fork-specific conflicts
# Remaining conflicts will be visible in the PR for review
git commit -m "Merge upstream with partial conflict resolution" -m "Fork-specific files preserved, other conflicts need review" || true
# Check if there are staged changes to commit
if git diff --cached --quiet 2>/dev/null && git diff --quiet 2>/dev/null; then
echo "No changes to commit."
else
# Commit the merge with resolved conflicts
git commit -m "Merge upstream ${{ env.UPSTREAM_REPO }} with conflict resolution" \
-m "- Fork-specific files preserved (CHANGELOG.md, misc/tools.func)" \
-m "- Deleted files kept as deleted (fork deletions preserved)" \
-m "- New upstream scripts added" || echo "Commit may have already been made"
fi
- name: Push sync branch
run: |
@@ -216,56 +263,60 @@ jobs:
BRANCH="${{ steps.branch.outputs.branch_name }}"
HAS_CONFLICTS="${{ steps.merge.outputs.has_conflicts }}"
CONFLICTED_FILES="${{ steps.merge.outputs.conflicted_files }}"
# Build PR body
PR_BODY="## 🔄 Upstream Sync Summary
This PR syncs the latest changes from upstream [\`${{ env.UPSTREAM_REPO }}\`](https://github.com/${{ env.UPSTREAM_REPO }}) while preserving fork-specific customizations.
### 📊 Sync Statistics
- **Upstream commits:** ${{ needs.check-and-sync.outputs.commit_count }}
- **Files changed:** ${{ steps.summary.outputs.total_changed }}
- **Merge conflicts:** ${HAS_CONFLICTS:-none}
### 📋 Upstream Commits (last 30)
\`\`\`
${{ steps.summary.outputs.upstream_commits }}
\`\`\`
### 📁 Changed Files
<details>
<summary>Click to view changed files</summary>
\`\`\`
${{ steps.summary.outputs.changed_files }}
\`\`\`
</details>
"
# Add conflict warning if needed
# Add conflict resolution section
if [ "$HAS_CONFLICTS" = "true" ]; then
PR_BODY="$PR_BODY
### ⚠️ Merge Conflicts Detected
The following files have conflicts that need manual review:
### ⚠️ Conflicts Auto-Resolved
The following conflict resolution strategy was applied:
1. **Fork-specific content files** (CHANGELOG.md, misc/tools.func, README.md) - Kept fork version
2. **Modify/delete conflicts** - Kept fork's deletions (files removed in fork stay removed)
3. **Fork directory conflicts** (install/, ct/, frontend/) - Kept fork version
Original conflicted files:
\`\`\`
$CONFLICTED_FILES
\`\`\`
**Note:** Fork-specific files have been automatically preserved using the \`merge=ours\` strategy configured in \`.gitattributes\`.
"
else
PR_BODY="$PR_BODY
### ✅ Clean Merge
No conflicts detected. Fork-specific files were automatically preserved using the \`merge=ours\` strategy.
No conflicts detected. All changes merged cleanly.
"
fi
# Add preserved files section
PR_BODY="$PR_BODY
### 🔒 Fork-Specific Files Preserved
The following files/directories are configured in \`.gitattributes\` to always keep the fork version:
- \`install/\` - Custom install scripts
- \`ct/\` - Custom container scripts
@@ -276,25 +327,24 @@ jobs:
- \`tools/addon/\` - Custom addon tools
- \`README.md\` - Fork-specific documentation
- \`CHANGELOG.md\` - Fork-specific changelog
### ✅ Pre-Merge Checklist
- [ ] Review all changed files for unexpected modifications
- [ ] Verify fork-specific files are intact
- [ ] Resolve any remaining merge conflicts
- [ ] Test any new scripts or features from upstream
---
*This PR was automatically created by the [upstream-sync workflow](https://github.com/${{ github.repository }}/blob/main/.github/workflows/upstream-sync.yml).*
"
# Create PR title with conflict indicator
if [ "$HAS_CONFLICTS" = "true" ]; then
TITLE="🔄 Upstream Sync - $(date +%Y-%m-%d) ⚠️ CONFLICTS"
TITLE="🔄 Upstream Sync - $(date +%Y-%m-%d) ✅ Conflicts Resolved"
else
TITLE="🔄 Upstream Sync - $(date +%Y-%m-%d)"
fi
# Create the PR (labels are optional - will fail silently if labels don't exist)
# Create the PR
gh pr create \
--title "$TITLE" \
--body "$PR_BODY" \
@@ -1,6 +1,6 @@
__ __ __
/ /____ __ __________/ /___ _/ /___ _______ __
/ __/ _ \/ / / / ___/ __ / __ `/ __/ / / / ___/ / /
/ /_/ __/ /_/ / / / /_/ / /_/ / /_/ /_/ (__ )_/ /
\__/\___/\__,_/_/ \__,_/\__,_/\__/\__,_/____(_)
__ _ ____
_____/ /__(_) / /_______ ______ _____ _____
/ ___/ //_/ / / / ___/ _ \/ ___/ | / / _ \/ ___/
(__ ) ,< / / / (__ ) __/ / | |/ / __/ /
/____/_/|_/_/_/_/____/\___/_/ |___/\___/_/
@@ -1,5 +1,5 @@
{
"name": "Unsloth Studio",
"name": "Unsloth Studio (In Beta)",
"slug": "unsolth-studio",
"categories": [20],
"date_created": "2026-03-18",
@@ -26,14 +26,10 @@
}
],
"default_credentials": {
"username": "unsloth",
"password": "Check logs for generated password"
"username": null,
"password": null
},
"notes": [
{
"text": "Default credentials: username 'unsloth', password is randomly generated and shown in logs. Run 'journalctl -u unsolth-studio | grep password' to find it.",
"type": "warning"
},
{
"text": "Requires GPU passthrough for training. NVIDIA, AMD (ROCm), and Intel GPUs are supported.",
"type": "info"
@@ -0,0 +1,160 @@
#!/usr/bin/env bash
# Author: Heretek-AI
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/unslothai/unsloth
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt-get install -y \
curl \
wget \
git \
cmake \
build-essential \
python3 \
python3-pip \
python3-venv \
pciutils
msg_ok "Installed Dependencies"
# Setup GPU hardware acceleration FIRST (detects GPU, installs drivers, configures permissions)
# This must run before installing unsloth/torch so PyTorch can detect the GPU
setup_hwaccel
# Setup Python virtual environment with uv (fast Python package manager)
PYTHON_VERSION="3.12" setup_uv
msg_info "Creating Virtual Environment"
mkdir -p /opt/unsolth-studio
cd /opt/unsolth-studio || exit
$STD uv venv --python 3.12
source .venv/bin/activate
msg_ok "Created Virtual Environment"
msg_info "Installing Unsloth"
# Install unsloth with torch backend auto-detection (GPU drivers must be installed first)
$STD uv pip install unsloth --torch-backend=auto
msg_ok "Installed Unsloth"
msg_info "Running Unsloth Studio Setup"
# Run the unsloth studio setup command to compile llama.cpp
# Use Python module invocation since uv pip install doesn't create entry points
$STD /opt/unsolth-studio/.venv/bin/python -m unsloth studio setup
msg_ok "Completed Unsloth Studio Setup"
msg_info "Creating Directories"
mkdir -p /opt/unsolth-studio/models
mkdir -p /opt/unsolth-studio/datasets
mkdir -p /var/log/unsolth-studio
chmod 755 /var/log/unsolth-studio
msg_ok "Created Directories"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/unsolth-studio.service
[Unit]
Description=Unsloth Studio - Local LLM Fine-tuning Web UI
After=network.target network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/unsolth-studio
Environment="PATH=/opt/unsolth-studio/.venv/bin:/usr/local/bin:/usr/bin:/bin"
ExecStart=/opt/unsolth-studio/.venv/bin/python -m unsloth studio -H 0.0.0.0 -p 8888
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=unsolth-studio
# Resource limits
LimitNOFILE=65535
TimeoutStartSec=600
TimeoutStopSec=60
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now unsolth-studio
msg_ok "Created Service"
# Create GPU passthrough info file
cat <<EOF >/opt/unsolth-studio/GPU_PASSTHROUGH.md
# GPU Passthrough Configuration for Unsloth Studio
This container has been configured for GPU acceleration for LLM fine-tuning.
## Required Proxmox Configuration
Add the following lines to your container config file:
/etc/pve/lxc/<CTID>.conf
### For NVIDIA GPUs:
\`\`\`
# Requires nvidia-container-toolkit on host
lxc.cgroup2.devices.allow: c 195:* rwm
lxc.cgroup2.devices.allow: c 509:* rwm
dev0: /dev/nvidia0,gid=104
dev1: /dev/nvidiactl,gid=104
dev2: /dev/nvidia-uvm,gid=104
dev3: /dev/nvidia-uvm-tools,gid=104
\`\`\`
### For AMD GPUs (ROCm):
\`\`\`
dev0: /dev/kfd,gid=104
dev1: /dev/dri/renderD128,gid=104
lxc.cgroup2.devices.allow: c 226:0 rwm
lxc.cgroup2.devices.allow: c 226:128 rwm
\`\`\`
### For Intel GPUs:
\`\`\`
dev0: /dev/dri/renderD128,gid=104
lxc.cgroup2.devices.allow: c 226:128 rwm
\`\`\`
## Verify GPU Access
Run these commands inside the container:
- nvidia-smi (NVIDIA GPUs)
- rocminfo (AMD GPUs)
- python -c "import torch; print(torch.cuda.is_available())"
## Usage
Access the web UI at: http://<IP>:8888
On first launch:
1. Create a password to secure your account
2. Follow the onboarding wizard to select a model and dataset
3. Configure training parameters
4. Start fine-tuning!
## Supported Models
Unsloth Studio supports fine-tuning many LLM models including:
- Llama 3.x
- Qwen 2.x / 3.x
- Mistral
- Gemma
- Phi-3
- And many more...
## Documentation
- Official Docs: https://unsloth.ai/docs/new/studio/start
- GitHub: https://github.com/unslothai/unsloth
EOF
motd_ssh
customize
cleanup_lxc
@@ -101,26 +101,9 @@ EOF
systemctl enable -q --now llamacpp
msg_ok "Created Service"
msg_info "Configuring GPU Permissions"
# Add render and video groups for GPU access
usermod -aG render,video root 2>/dev/null || true
# Configure /dev/kfd and /dev/dri permissions for AMD
if [[ -e /dev/kfd ]]; then
chgrp render /dev/kfd 2>/dev/null || true
chmod 660 /dev/kfd 2>/dev/null || true
fi
if [[ -d /dev/dri ]]; then
chmod 755 /dev/dri 2>/dev/null || true
for render_dev in /dev/dri/renderD*; do
if [[ -e "$render_dev" ]]; then
chgrp render "$render_dev" 2>/dev/null || true
chmod 660 "$render_dev" 2>/dev/null || true
fi
done
fi
msg_ok "Configured GPU Permissions"
# Setup GPU hardware acceleration (detects GPU, installs drivers, configures permissions)
# This handles NVIDIA, AMD/ROCm, and Intel GPU detection and driver installation
setup_hwaccel
# Create GPU passthrough info file
cat <<EOF >/opt/llamacpp/GPU_PASSTHROUGH.md
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/mudler/skillserver
APP="skillserver"
var_tags="${var_tags:-ai;mcp;skills;agents}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-8}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -f /usr/local/bin/skillserver ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "skillserver" "mudler/skillserver"; then
msg_info "Stopping Service"
systemctl stop skillserver
msg_ok "Stopped Service"
msg_info "Backing up Data"
cp -r /opt/skillserver/skills /opt/skillserver_skills_backup
msg_ok "Backed up Data"
setup_go
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "skillserver" "mudler/skillserver" "tarball" "latest" "/opt/skillserver"
msg_info "Building Application"
cd /opt/skillserver || exit
$STD go build -o /usr/local/bin/skillserver ./cmd/skillserver
msg_ok "Built Application"
msg_info "Restoring Data"
cp -r /opt/skillserver_skills_backup/. /opt/skillserver/skills
rm -rf /opt/skillserver_skills_backup
msg_ok "Restored Data"
msg_info "Starting Service"
systemctl start skillserver
msg_ok "Started Service"
msg_ok "Updated Successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed Successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"
@@ -0,0 +1,50 @@
{
"name": "SkillServer (In Development)",
"slug": "skillserver",
"categories": [20],
"date_created": "2026-03-17",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 8080,
"documentation": "https://github.com/mudler/skillserver",
"website": "https://github.com/mudler/skillserver",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/skillserver.webp",
"config_path": "",
"description": "An MCP/REST server with WebUI serving as a centralized skills database for AI Agents. Manages 'Skills' (directory-based with SKILL.md files) stored locally, following the Agent Skills specification. Features MCP server integration, web interface for skill management, Git synchronization, and full-text search.",
"install_methods": [
{
"type": "default",
"script": "ct/skillserver.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 8,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": [
{
"text": "Access the WebUI at http://SERVER_IP:8080",
"type": "info"
},
{
"text": "Skills are stored in /opt/skillserver/skills",
"type": "info"
},
{
"text": "Configure Git repositories to sync skills via SKILLSERVER_GIT_REPOS environment variable",
"type": "info"
},
{
"text": "MCP server runs over stdio for integration with AI clients",
"type": "info"
}
]
}
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/mudler/skillserver
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt-get install -y \
git \
ca-certificates
msg_ok "Installed Dependencies"
setup_go
fetch_and_deploy_gh_release "skillserver" "mudler/skillserver" "tarball" "latest" "/opt/skillserver"
msg_info "Building Application"
cd /opt/skillserver || exit
$STD go build -o /usr/local/bin/skillserver ./cmd/skillserver
msg_ok "Built Application"
msg_info "Creating Service"
mkdir -p /opt/skillserver/skills
cat <<EOF >/etc/systemd/system/skillserver.service
[Unit]
Description=SkillServer - MCP/REST server for AI agent skills
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/skillserver
# Use tail -f /dev/null to keep stdin open for the MCP stdio server
# skillserver runs both MCP stdio (main thread) and web server (goroutine)
# The MCP server needs stdin to stay open, otherwise it exits immediately
ExecStart=/bin/sh -c 'tail -f /dev/null | /usr/local/bin/skillserver --enable-logging'
StandardOutput=journal
StandardError=journal
Restart=on-failure
RestartSec=5
Environment=SKILLSERVER_DIR=/opt/skillserver/skills
Environment=SKILLSERVER_PORT=8080
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now skillserver
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
source <(curl -fsSL ""${COMMUNITY_SCRIPTS_URL"}"/misc/build.func)
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
# Author: BillyOutlast
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/mcmonkeyprojects/SwarmUI

Some files were not shown because too many files have changed in this diff Show More