mirror of
https://github.com/Heretek-AI/Heretek-ProxmoxVE.git
synced 2026-07-01 12:25:27 -04:00
Sync from Heretek-AI/ProxmoxVE - 2026-03-20
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
+46
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+4
-4
@@ -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
|
||||
+2
-1
@@ -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
@@ -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
|
||||
|
||||
+27
-1
@@ -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
|
||||
|
||||
+5
-92
@@ -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
|
||||
|
||||
+675
@@ -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
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
__ __ __ __ ___
|
||||
__ ______ _____/ /___ / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ / __ \/ __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) / /_/ / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/_/\____/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
+10
-2
@@ -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}"
|
||||
+21
-3
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+8
-4
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+175
-5
@@ -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"
|
||||
|
||||
+118
-33
@@ -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
|
||||
+6
@@ -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
|
||||
|
||||
+30
@@ -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"
|
||||
|
||||
+6
@@ -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
|
||||
|
||||
+30
@@ -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"
|
||||
|
||||
+6
-8
@@ -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)
|
||||
|
||||
+12
-5
@@ -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",
|
||||
|
||||
+7
-3
@@ -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"
|
||||
|
||||
+12
-12
@@ -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/☕-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
|
||||
|
||||
+12
-12
@@ -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/☕-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
|
||||
|
||||
+22
-22
@@ -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
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
-62
@@ -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}"
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
-62
@@ -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}"
|
||||
+7
-7
@@ -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
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
-266
@@ -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
-1
@@ -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>
|
||||
|
||||
+5
-11
@@ -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 path—beyond 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>
|
||||
|
||||
+18
-37
@@ -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>
|
||||
);
|
||||
|
||||
+37
-33
@@ -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/☕-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
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
__ __ __ __ __
|
||||
/ / / /___ _____/ /___ / /_/ /_
|
||||
/ / / / __ \/ ___/ / __ \/ __/ __ \
|
||||
/ /_/ / / / (__ ) / /_/ / /_/ / / /
|
||||
\____/_/ /_/____/_/\____/\__/_/ /_/
|
||||
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
|
||||
+26
-45
@@ -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
|
||||
|
||||
+24
-7
@@ -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>
|
||||
))}
|
||||
|
||||
+23
-9
@@ -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
|
||||
|
||||
+36
-33
@@ -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>
|
||||
|
||||
+24
-39
@@ -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 />
|
||||
|
||||
+450
-238
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+182
-133
@@ -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,
|
||||
});
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
|
||||
+50
@@ -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}"
|
||||
+67
@@ -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}"
|
||||
+61
@@ -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}"
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
___ __ _ __ ____
|
||||
/ | / /___ (_)___ ___ ____ / /_/ __/_ __
|
||||
/ /| | / / __ \/ / __ \/ _ \______/ __ \/ __/ /_/ / / /
|
||||
/ ___ |/ / /_/ / / / / / __/_____/ / / / /_/ __/ /_/ /
|
||||
/_/ |_/_/ .___/_/_/ /_/\___/ /_/ /_/\__/_/ \__, /
|
||||
/_/ /____/
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
___ __ _____
|
||||
/ | ____ __ __/ /___ ______ ___ / ___/___ ______ _____ _____
|
||||
/ /| | / __ \/ / / / __/ / / / __ \/ _ \______\__ \/ _ \/ ___/ | / / _ \/ ___/
|
||||
/ ___ |/ / / / /_/ / /_/ /_/ / /_/ / __/_____/__/ / __/ / | |/ / __/ /
|
||||
/_/ |_/_/ /_/\__, /\__/\__, / .___/\___/ /____/\___/_/ |___/\___/_/
|
||||
/____/ /____/_/
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
________ __
|
||||
/ ____/ /_ _____ / /___ ______
|
||||
/ / __/ / / / / _ \/ __/ / / / __ \
|
||||
/ /_/ / / /_/ / __/ /_/ /_/ / / / /
|
||||
\____/_/\__,_/\___/\__/\__,_/_/ /_/
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
_____ ___ __ ____
|
||||
/ ___/____ / (_) /_ / __ \_________
|
||||
\__ \/ __ \/ / / __/_____/ /_/ / ___/ __ \
|
||||
___/ / /_/ / / / /_/_____/ ____/ / / /_/ /
|
||||
/____/ .___/_/_/\__/ /_/ /_/ \____/
|
||||
/_/
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
__ __ __ __
|
||||
\ \/ /___ _____ ___ / /__________ ______/ /__
|
||||
\ / __ `/ __ `__ \/ __/ ___/ __ `/ ___/ //_/
|
||||
/ / /_/ / / / / / / /_/ / / /_/ / /__/ ,<
|
||||
/_/\__,_/_/ /_/ /_/\__/_/ \__,_/\___/_/|_|
|
||||
|
||||
+68
@@ -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}"
|
||||
+83
@@ -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}"
|
||||
+25
@@ -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
|
||||
+81
@@ -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
|
||||
+96
@@ -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
|
||||
+74
@@ -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
|
||||
+105
@@ -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
|
||||
+109
-11
@@ -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
|
||||
|
||||
+9
-2
@@ -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"
|
||||
|
||||
+5
-5
@@ -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",
|
||||
|
||||
+16
-8
@@ -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);
|
||||
}
|
||||
|
||||
+100
-50
@@ -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" \
|
||||
|
||||
+2
@@ -420,6 +420,8 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-03-18
|
||||
|
||||
## 2026-03-16
|
||||
|
||||
## 2026-03-15
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
__ __ __
|
||||
/ /____ __ __________/ /___ _/ /___ _______ __
|
||||
/ __/ _ \/ / / / ___/ __ / __ `/ __/ / / / ___/ / /
|
||||
/ /_/ __/ /_/ / / / /_/ / /_/ / /_/ /_/ (__ )_/ /
|
||||
\__/\___/\__,_/_/ \__,_/\__,_/\__/\__,_/____(_)
|
||||
|
||||
__ _ ____
|
||||
_____/ /__(_) / /_______ ______ _____ _____
|
||||
/ ___/ //_/ / / / ___/ _ \/ ___/ | / / _ \/ ___/
|
||||
(__ ) ,< / / / (__ ) __/ / | |/ / __/ /
|
||||
/____/_/|_/_/_/_/____/\___/_/ |___/\___/_/
|
||||
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
__ __ __ __ __
|
||||
/ / / /___ _____/ /___ / /_/ /_
|
||||
/ / / / __ \/ ___/ / __ \/ __/ __ \
|
||||
/ /_/ / / / (__ ) / /_/ / /_/ / / /
|
||||
\____/_/ /_/____/_/\____/\__/_/ /_/
|
||||
|
||||
+3
-7
@@ -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"
|
||||
+160
@@ -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
|
||||
+4
@@ -12,6 +12,10 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
# Setup GPU hardware acceleration (detects GPU, installs drivers, configures permissions)
|
||||
# This handles NVIDIA, AMD/ROCm, and Intel GPU detection and driver installation
|
||||
setup_hwaccel
|
||||
|
||||
fetch_and_deploy_gh_release "lemonade" "lemonade-sdk/lemonade" "binary"
|
||||
|
||||
msg_info "Configuring Service"
|
||||
|
||||
+3
-20
@@ -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
|
||||
|
||||
+4
@@ -13,6 +13,10 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
# Setup GPU hardware acceleration (detects GPU, installs drivers, configures permissions)
|
||||
# This handles NVIDIA, AMD/ROCm, and Intel GPU detection and driver installation
|
||||
setup_hwaccel
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt-get install -y curl
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
+4
@@ -12,6 +12,10 @@ setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
# Setup GPU hardware acceleration (detects GPU, installs drivers, configures permissions)
|
||||
# This handles NVIDIA, AMD/ROCm, and Intel GPU detection and driver installation
|
||||
setup_hwaccel
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
setup_deb822_repo \
|
||||
"microsoft" \
|
||||
|
||||
+1
@@ -3586,6 +3586,7 @@ build_container() {
|
||||
fi
|
||||
|
||||
# Core exports for install.func
|
||||
export COMMUNITY_SCRIPTS_URL="$COMMUNITY_SCRIPTS_URL"
|
||||
export DIAGNOSTICS="$DIAGNOSTICS"
|
||||
export RANDOM_UUID="$RANDOM_UUID"
|
||||
export EXECUTION_ID="$EXECUTION_ID"
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
__ __ __
|
||||
/ /____ __ __________/ /___ _/ /___ _______ __
|
||||
/ __/ _ \/ / / / ___/ __ / __ `/ __/ / / / ___/ / /
|
||||
/ /_/ __/ /_/ / / / /_/ / /_/ / /_/ /_/ (__ )_/ /
|
||||
\__/\___/\__,_/_/ \__,_/\__,_/\__/\__,_/____(_)
|
||||
|
||||
+70
@@ -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}"
|
||||
+50
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+61
@@ -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
-1
@@ -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
Reference in New Issue
Block a user