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:
@@ -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}"
|
||||
@@ -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,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://wakapi.dev/ | https://github.com/muety/wakapi
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata
|
||||
$STD update-ca-certificates
|
||||
$STD apk add --no-cache go --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
fetch_and_deploy_gh_release "wakapi" "muety/wakapi" "tarball"
|
||||
|
||||
msg_info "Configuring Wakapi"
|
||||
LOCAL_IP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1)
|
||||
cd /opt/wakapi
|
||||
$STD go mod download
|
||||
$STD go build -o wakapi
|
||||
cp config.default.yml config.yml
|
||||
sed -i 's/listen_ipv6: ::1/listen_ipv6: "-"/g' config.yml
|
||||
sed -i 's/listen_ipv4: 127.0.0.1/listen_ipv4: "0.0.0.0"/g' config.yml
|
||||
sed -i "s/public_url: http:\/\/localhost:3000/public_url: http:\/\/$LOCAL_IP:3000/g" config.yml
|
||||
msg_ok "Configured Wakapi"
|
||||
|
||||
msg_info "Enabling Wakapi Service"
|
||||
cat <<EOF >/etc/init.d/wakapi
|
||||
#!/sbin/openrc-run
|
||||
description="Wakapi Service"
|
||||
directory="/opt/wakapi"
|
||||
command="/opt/wakapi/wakapi"
|
||||
command_args="-config config.yml"
|
||||
command_background="true"
|
||||
command_user="root"
|
||||
pidfile="/var/run/wakapi.pid"
|
||||
|
||||
depend() {
|
||||
use net
|
||||
}
|
||||
EOF
|
||||
chmod +x /etc/init.d/wakapi
|
||||
$STD rc-update add wakapi default
|
||||
msg_ok "Enabled Wakapi Service"
|
||||
|
||||
msg_info "Starting Wakapi"
|
||||
$STD rc-service wakapi start
|
||||
msg_ok "Started Wakapi"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Matthew Stern (sternma) | MickLesk (CanbiZ)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/dmunozv04/iSponsorBlockTV
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
fetch_and_deploy_gh_release "isponsorblocktv" "dmunozv04/iSponsorBlockTV" "singlefile" "latest" "/opt/isponsorblocktv" "iSponsorBlockTV-*-linux"
|
||||
|
||||
msg_info "Setting up iSponsorBlockTV"
|
||||
install -d /var/lib/isponsorblocktv
|
||||
msg_ok "Set up iSponsorBlockTV"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/isponsorblocktv.service
|
||||
[Unit]
|
||||
Description=iSponsorBlockTV
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
Environment=iSPBTV_data_dir=/var/lib/isponsorblocktv
|
||||
ExecStart=/opt/isponsorblocktv/isponsorblocktv
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q isponsorblocktv
|
||||
msg_ok "Created Service"
|
||||
|
||||
msg_info "Creating CLI wrapper"
|
||||
cat <<EOF >/usr/local/bin/iSponsorBlockTV
|
||||
#!/usr/bin/env bash
|
||||
export iSPBTV_data_dir="/var/lib/isponsorblocktv"
|
||||
|
||||
set +e
|
||||
/opt/isponsorblocktv/isponsorblocktv "$@"
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
case "${1:-}" in
|
||||
setup|setup-cli)
|
||||
systemctl restart isponsorblocktv >/dev/null 2>&1 || true
|
||||
;;
|
||||
esac
|
||||
|
||||
exit $status
|
||||
EOF
|
||||
chmod +x /usr/local/bin/iSponsorBlockTV
|
||||
ln -sf /usr/local/bin/iSponsorBlockTV /usr/bin/iSponsorBlockTV
|
||||
msg_ok "Created CLI wrapper"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: Slaviša Arežina (tremor021)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://goteleport.com/
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
setup_deb822_repo \
|
||||
"teleport" \
|
||||
"https://deb.releases.teleport.dev/teleport-pubkey.asc" \
|
||||
"https://apt.releases.teleport.dev/debian" \
|
||||
"trixie" \
|
||||
"stable/v18"
|
||||
|
||||
msg_info "Configuring Teleport"
|
||||
$STD apt install -y teleport
|
||||
$STD teleport configure -o /etc/teleport.yaml
|
||||
systemctl enable -q --now teleport
|
||||
sleep 10
|
||||
tctl users add teleport-admin --roles=editor,access --logins=root >~/teleportadmin.txt
|
||||
sed -i "s|https://[^:]*:3080|https://${LOCAL_IP}:3080|g" ~/teleportadmin.txt
|
||||
msg_ok "Configured Teleport"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -285,7 +285,10 @@ EOF
|
||||
post_progress_to_api
|
||||
|
||||
local tools_content
|
||||
tools_content=$(curl -fsSL ${COMMUNITY_SCRIPTS_URL}/misc/tools.func) || {
|
||||
# Add cache-busting query parameter to ensure we get the latest version
|
||||
# GitHub's CDN caches raw content, which can cause stale scripts to be served
|
||||
local cache_bust="?_=$(date +%s)"
|
||||
tools_content=$(curl -fsSL "${COMMUNITY_SCRIPTS_URL}/misc/tools.func${cache_bust}") || {
|
||||
msg_error "Failed to download tools.func"
|
||||
exit 115
|
||||
}
|
||||
|
||||
@@ -2198,6 +2198,7 @@ check_for_gh_release() {
|
||||
local app="$1"
|
||||
local source="$2"
|
||||
local pinned_version_in="${3:-}" # optional
|
||||
local pin_reason="${4:-}" # optional reason shown to user
|
||||
local app_lc=""
|
||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
||||
local current_file="$HOME/.${app_lc}"
|
||||
@@ -2342,7 +2343,11 @@ check_for_gh_release() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_ok "No update available: ${app} is already on pinned version (${current})"
|
||||
if [[ -n "$pin_reason" ]]; then
|
||||
msg_ok "No update available: ${app} (${current}) - update held back: ${pin_reason}"
|
||||
else
|
||||
msg_ok "No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -2381,6 +2386,7 @@ check_for_codeberg_release() {
|
||||
local app="$1"
|
||||
local source="$2"
|
||||
local pinned_version_in="${3:-}" # optional
|
||||
local pin_reason="${4:-}" # optional reason shown to user
|
||||
local app_lc="${app,,}"
|
||||
local current_file="$HOME/.${app_lc}"
|
||||
|
||||
@@ -2460,7 +2466,11 @@ check_for_codeberg_release() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_ok "No update available: ${app} is already on pinned version (${current})"
|
||||
if [[ -n "$pin_reason" ]]; then
|
||||
msg_ok "No update available: ${app} (${current}) - update held back: ${pin_reason}"
|
||||
else
|
||||
msg_ok "No update available: ${app} (${current}) - update temporarily held back due to issues with newer releases"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -3057,6 +3067,61 @@ function fetch_and_deploy_codeberg_release() {
|
||||
# (60s timeout, default yes) to use a previous version that has the asset.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Wait for HTTPS connectivity to be ready
|
||||
# This is needed because DNS resolution and ping can work before HTTPS is ready
|
||||
# during container startup (race condition with network stack initialization)
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - URL to test (default: https://api.github.com/rate_limit)
|
||||
# $2 - Max wait time in seconds (default: 60)
|
||||
#
|
||||
# Returns: 0 if HTTPS is ready, 1 if timeout
|
||||
# ------------------------------------------------------------------------------
|
||||
wait_for_https_readiness() {
|
||||
local test_url="${1:-https://api.github.com/rate_limit}"
|
||||
local max_wait="${2:-60}"
|
||||
local waited=0
|
||||
local interval=2
|
||||
local success_count=0
|
||||
local required_successes=3
|
||||
|
||||
# Check if we're in a container (need longer warmup)
|
||||
if [[ -f /.dockerenv ]] || grep -q 'lxc\|docker\|container' /proc/1/cgroup 2>/dev/null; then
|
||||
# Container detected - start with 5 second delay for network stack to stabilize
|
||||
msg_info "Container detected, waiting for network stack to stabilize..."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
msg_info "Waiting for HTTPS connectivity..."
|
||||
|
||||
while ((waited < max_wait)); do
|
||||
if curl -fsSL --connect-timeout 3 --max-time 10 "$test_url" &>/dev/null; then
|
||||
((success_count++))
|
||||
msg_ok "HTTPS check ${success_count}/${required_successes} passed"
|
||||
if ((success_count >= required_successes)); then
|
||||
msg_ok "HTTPS connectivity verified after ${waited}s"
|
||||
# Additional stabilization delay after successful checks
|
||||
# Network can be intermittent during container startup
|
||||
msg_info "Waiting 5s for network to stabilize..."
|
||||
sleep 5
|
||||
return 0
|
||||
fi
|
||||
# Wait between successful checks to ensure stability
|
||||
sleep 2
|
||||
((waited += 2))
|
||||
else
|
||||
success_count=0
|
||||
msg_warn "HTTPS check failed, retrying... (${waited}s/${max_wait}s)"
|
||||
fi
|
||||
sleep $interval
|
||||
((waited += interval))
|
||||
done
|
||||
|
||||
msg_error "HTTPS connectivity NOT ready after ${max_wait}s timeout"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Scans older GitHub releases for a matching asset when the latest release
|
||||
# is missing the expected file. Used internally by fetch_and_deploy_gh_release.
|
||||
@@ -3195,7 +3260,7 @@ function fetch_and_deploy_gh_release() {
|
||||
local header=()
|
||||
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
|
||||
|
||||
# dns pre check
|
||||
# DNS pre-check
|
||||
local gh_host
|
||||
gh_host=$(awk -F/ '{print $3}' <<<"$api_url")
|
||||
if ! getent hosts "$gh_host" &>/dev/null; then
|
||||
@@ -3203,7 +3268,17 @@ function fetch_and_deploy_gh_release() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local max_retries=3 retry_delay=2 attempt=1 success=false http_code
|
||||
# Wait for HTTPS connectivity to be ready before making API calls
|
||||
# This prevents "no response" errors during container startup when network
|
||||
# stack isn't fully initialized yet (DNS and ping work before HTTPS is ready)
|
||||
# Use 60 second timeout for containers with slow network initialization
|
||||
if ! wait_for_https_readiness "https://api.github.com/rate_limit" 60; then
|
||||
msg_error "HTTPS connectivity not available after 60 seconds"
|
||||
msg_error "Check your network configuration and firewall rules"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local max_retries=5 retry_delay=2 attempt=1 success=false http_code
|
||||
|
||||
while ((attempt <= max_retries)); do
|
||||
http_code=$(curl "$api_timeout" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
|
||||
@@ -3235,6 +3310,28 @@ function fetch_and_deploy_gh_release() {
|
||||
attempt=0
|
||||
fi
|
||||
fi
|
||||
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
|
||||
# Network failure - use exponential backoff
|
||||
if ((attempt < max_retries)); then
|
||||
msg_warn "Network failure (no response), retrying in ${retry_delay}s... (attempt $attempt/$max_retries)"
|
||||
# Debug: test connectivity during retry
|
||||
if ((attempt == 1)); then
|
||||
msg_info "Testing network connectivity..."
|
||||
if curl -fsSL --connect-timeout 5 --max-time 10 "https://api.github.com/rate_limit" &>/dev/null; then
|
||||
msg_ok "Network connectivity test passed - API is reachable"
|
||||
else
|
||||
msg_warn "Network connectivity test FAILED - API is NOT reachable"
|
||||
msg_info "This suggests intermittent network issues during container startup"
|
||||
fi
|
||||
fi
|
||||
sleep "$retry_delay"
|
||||
retry_delay=$((retry_delay * 2))
|
||||
((retry_delay > 30)) && retry_delay=30
|
||||
else
|
||||
msg_error "GitHub API connection failed (no response after $max_retries attempts)."
|
||||
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
|
||||
msg_error "If network works manually, this may be a Proxmox/LXC network timing issue"
|
||||
fi
|
||||
else
|
||||
sleep "$retry_delay"
|
||||
fi
|
||||
@@ -5207,24 +5304,7 @@ function setup_java() {
|
||||
|
||||
# Get currently installed version
|
||||
local INSTALLED_VERSION=""
|
||||
if dpkg -l | grep -q "temurin-.*-jdk" 2>/dev/null; then
|
||||
INSTALLED_VERSION=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+' | head -n1 || echo "")
|
||||
fi
|
||||
|
||||
# Validate INSTALLED_VERSION is not empty if JDK package found
|
||||
local JDK_COUNT=0
|
||||
JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || true)
|
||||
if [[ -z "$INSTALLED_VERSION" && "${JDK_COUNT:-0}" -gt 0 ]]; then
|
||||
msg_warn "Found Temurin JDK but cannot determine version - attempting reinstall"
|
||||
# Try to get actual package name for purge
|
||||
local OLD_PACKAGE
|
||||
OLD_PACKAGE=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | head -n1 || echo "")
|
||||
if [[ -n "$OLD_PACKAGE" ]]; then
|
||||
msg_info "Removing existing package: $OLD_PACKAGE"
|
||||
$STD apt purge -y "$OLD_PACKAGE" || true
|
||||
fi
|
||||
INSTALLED_VERSION="" # Reset to trigger fresh install
|
||||
fi
|
||||
INSTALLED_VERSION=$(dpkg-query -W -f '${Package}\n' 2>/dev/null | grep -oP '^temurin-\K[0-9]+(?=-jdk$)' | head -n1 || echo "")
|
||||
|
||||
# Scenario 1: Already at correct version
|
||||
if [[ "$INSTALLED_VERSION" == "$JAVA_VERSION" ]]; then
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
__ ______ _____
|
||||
/ / / / __ `/ __ \
|
||||
/ /_/ / /_/ / /_/ /
|
||||
\__, /\__,_/\____/
|
||||
/____/
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/Heretek-AI/ProxmoxVE/refs/heads/main}"
|
||||
source <(curl -fsSL "${COMMUNITY_SCRIPTS_URL}"/misc/build.func)
|
||||
# Author: Heretek-AI
|
||||
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/YaoApp/yao
|
||||
|
||||
APP="yao"
|
||||
var_tags="${var_tags:-ai;agents;automation;low-code}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-4096}"
|
||||
var_disk="${var_disk:-10}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -f /usr/local/bin/yao ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "yao" "YaoApp/yao"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop yao
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/yao/data /opt/yao_data_backup 2>/dev/null || true
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
fetch_and_deploy_gh_release "yao" "YaoApp/yao" "singlefile" "latest" "/usr/local/bin" "yao-v*-linux-*"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/yao_data_backup/. /opt/yao/data 2>/dev/null || true
|
||||
rm -rf /opt/yao_data_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start yao
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:5099${CL}"
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "Yao (In Development)",
|
||||
"slug": "yao",
|
||||
"categories": [20],
|
||||
"date_created": "2026-03-20",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 5099,
|
||||
"documentation": "https://yaoapps.com/docs",
|
||||
"website": "https://yaoapps.com",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/yao.webp",
|
||||
"config_path": "/opt/yao/.env",
|
||||
"description": "Yao is an open-source engine for autonomous agents - event-driven, proactive, and self-scheduling. Build agents that work like real team members with three trigger modes: Clock (scheduled), Human (email/message), and Event (webhook/database).",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/yao.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 4096,
|
||||
"hdd": 10,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "After installation, create a new project: mkdir -p /opt/yao/myapp && cd /opt/yao/myapp && yao start",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Access the web interface at http://IP:5099",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Yao includes built-in SQLite database, V8 JavaScript engine, and TypeScript runtime - no additional dependencies required.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
+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 @@
|
||||
__ __ __ __ ___
|
||||
__ ______ _____/ /___ / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ / __ \/ __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) / /_/ / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/_/\____/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
+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"
|
||||
|
||||
+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"
|
||||
|
||||
+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"
|
||||
|
||||
+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
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
____ __ __ ___
|
||||
__ ______ _________ / / /_/ /_ _____/ /___ ______/ (_)___
|
||||
/ / / / __ \/ ___/ __ \/ / __/ __ \______/ ___/ __/ / / / __ / / __ \
|
||||
/ /_/ / / / (__ ) /_/ / / /_/ / / /_____(__ ) /_/ /_/ / /_/ / / /_/ /
|
||||
\__,_/_/ /_/____/\____/_/\__/_/ /_/ /____/\__/\__,_/\__,_/_/\____/
|
||||
|
||||
+34
-34
@@ -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
|
||||
@@ -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 - Heretek-AI themed
|
||||
# Generate LXC Description
|
||||
DESCRIPTION=$(
|
||||
cat <<EOF
|
||||
<div align='center'>
|
||||
<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 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>
|
||||
|
||||
<h2 style='font-size: 24px; margin: 20px 0; color: #dc2626;'>${APP} LXC</h2>
|
||||
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
|
||||
|
||||
<p style='margin: 16px 0;'>
|
||||
<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 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>
|
||||
</p>
|
||||
|
||||
<span style='margin: 0 10px;'>
|
||||
<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>
|
||||
<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>
|
||||
</span>
|
||||
<span style='margin: 0 10px;'>
|
||||
<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>
|
||||
<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>
|
||||
</span>
|
||||
<span style='margin: 0 10px;'>
|
||||
<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>
|
||||
<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>
|
||||
</span>
|
||||
</div>
|
||||
EOF
|
||||
|
||||
-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
|
||||
|
||||
+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
|
||||
|
||||
+2
@@ -420,6 +420,8 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
</details>
|
||||
|
||||
## 2026-03-16
|
||||
|
||||
## 2026-03-15
|
||||
|
||||
## 2026-03-14
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
_____ _______ __ __
|
||||
/__ //_ __/ | / /__ / /_
|
||||
/ / / / / |/ / _ \/ __/
|
||||
/ /__/ / / /| / __/ /_
|
||||
/____/_/ /_/ |_/\___/\__/
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#!/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://ztnet.network
|
||||
|
||||
APP="ZTNet"
|
||||
var_tags="${var_tags:-network;vpn;zerotier}"
|
||||
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/ztnet ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop ztnet
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/ztnet/data /opt/ztnet_data_backup 2>/dev/null || true
|
||||
cp /opt/ztnet/.env /opt/ztnet_env_backup 2>/dev/null || true
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
msg_info "Updating ZTNet"
|
||||
curl -s http://install.ztnet.network | bash
|
||||
msg_ok "Updated ZTNet"
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/ztnet_data_backup/. /opt/ztnet/data 2>/dev/null || true
|
||||
cp /opt/ztnet_env_backup /opt/ztnet/.env 2>/dev/null || true
|
||||
rm -rf /opt/ztnet_data_backup /opt/ztnet_env_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start ztnet
|
||||
msg_ok "Started Service"
|
||||
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}:3000${CL}"
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Author: BillyOutlast
|
||||
# License: MIT | https://github.com/Heretek-AI/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://ztnet.network
|
||||
|
||||
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 \
|
||||
jq \
|
||||
git \
|
||||
openssl \
|
||||
gnupg \
|
||||
lsb-release \
|
||||
postgresql \
|
||||
postgresql-contrib
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Installing ZeroTier"
|
||||
curl -s 'https://raw.githubusercontent.com/zerotier/ZeroTierOne/main/doc/contact%40zerotier.com.gpg' | gpg --import
|
||||
if z=$(curl -s 'https://install.zerotier.com/' | gpg); then
|
||||
echo "$z" | bash
|
||||
fi
|
||||
$STD systemctl enable --now zerotier-one
|
||||
msg_ok "Installed ZeroTier"
|
||||
|
||||
msg_info "Installing ZTNet"
|
||||
curl -s http://install.ztnet.network | bash
|
||||
msg_ok "Installed ZTNet"
|
||||
|
||||
msg_info "Enabling ZTNet Service"
|
||||
$STD systemctl enable --now ztnet
|
||||
msg_ok "Started ZTNet"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
+14813
File diff suppressed because it is too large
Load Diff
+542
@@ -0,0 +1,542 @@
|
||||
"use client";
|
||||
|
||||
import { Suspense, useEffect, useState, useMemo } from "react";
|
||||
import { Loader2, Copy, Check, Terminal, Settings2, Server, Cpu, HardDrive, Network, Shield, Play } from "lucide-react";
|
||||
import { useQueryState } from "nuqs";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import type { Category, Script } from "@/lib/types";
|
||||
import { fetchCategories } from "@/lib/data";
|
||||
import { Search } from "@/components/search";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { basePath } from "@/config/site-config";
|
||||
import {
|
||||
generateInstallCommand,
|
||||
validateConfig,
|
||||
getAvailableOS,
|
||||
getDefaultResources,
|
||||
getScriptTypeDisplay,
|
||||
type GeneratorConfig,
|
||||
DEFAULT_CONFIG,
|
||||
} from "@/lib/generate-command";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function GeneratorContent() {
|
||||
const [categories, setCategories] = useState<Category[]>([]);
|
||||
const [selectedScript, setSelectedScript] = useState<Script | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [search, setSearch] = useQueryState("search");
|
||||
|
||||
// Configuration state
|
||||
const [config, setConfig] = useState<GeneratorConfig>(DEFAULT_CONFIG);
|
||||
|
||||
// Get all scripts from all categories
|
||||
const allScripts = useMemo(() => {
|
||||
if (!categories.length) return [];
|
||||
const scripts = categories.flatMap((category) => category.scripts || []);
|
||||
// Remove duplicates by slug
|
||||
const uniqueScripts = new Map<string, Script>();
|
||||
scripts.forEach((script) => {
|
||||
if (!uniqueScripts.has(script.slug)) {
|
||||
uniqueScripts.set(script.slug, script);
|
||||
}
|
||||
});
|
||||
return Array.from(uniqueScripts.values());
|
||||
}, [categories]);
|
||||
|
||||
// Filter scripts by search
|
||||
const filteredScripts = useMemo(() => {
|
||||
if (!search) return allScripts;
|
||||
const searchLower = search.toLowerCase();
|
||||
return allScripts.filter(
|
||||
(script) =>
|
||||
script.name.toLowerCase().includes(searchLower) ||
|
||||
script.description.toLowerCase().includes(searchLower)
|
||||
);
|
||||
}, [allScripts, search]);
|
||||
|
||||
// Available OS options for selected script
|
||||
const availableOS = useMemo(() => getAvailableOS(selectedScript), [selectedScript]);
|
||||
|
||||
// Default resources for selected script
|
||||
const defaultResources = useMemo(() => getDefaultResources(selectedScript), [selectedScript]);
|
||||
|
||||
// Load categories on mount
|
||||
useEffect(() => {
|
||||
fetchCategories()
|
||||
.then((data) => setCategories(data))
|
||||
.catch((error) => console.error(error));
|
||||
}, []);
|
||||
|
||||
// Update config when script is selected
|
||||
useEffect(() => {
|
||||
if (selectedScript) {
|
||||
const defaults = getDefaultResources(selectedScript);
|
||||
const os = getAvailableOS(selectedScript)[0] || "";
|
||||
setConfig((prev) => ({
|
||||
...prev,
|
||||
script: selectedScript,
|
||||
os,
|
||||
cpuCores: defaults.cpu || prev.cpuCores,
|
||||
ram: defaults.ram || prev.ram,
|
||||
diskSize: defaults.disk || prev.diskSize,
|
||||
}));
|
||||
}
|
||||
}, [selectedScript]);
|
||||
|
||||
// Generate command
|
||||
const command = useMemo(() => generateInstallCommand(config), [config]);
|
||||
|
||||
// Validation
|
||||
const validation = useMemo(() => validateConfig(config), [config]);
|
||||
|
||||
// Copy command to clipboard
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// Update config helper
|
||||
const updateConfig = <K extends keyof GeneratorConfig>(key: K, value: GeneratorConfig[K]) => {
|
||||
setConfig((prev) => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-8 mt-16">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-foreground mb-2">Unattended Script Generator</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Generate installation commands for automated deployments with custom configurations
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
{/* Script Selection */}
|
||||
<Card className="border-rust/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-brass">
|
||||
<Server className="h-5 w-5" />
|
||||
Select Script
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Choose a script to configure for unattended installation
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<Search
|
||||
placeholder="Search scripts..."
|
||||
value={search || ""}
|
||||
onChange={(e) => setSearch(e.target.value || null)}
|
||||
/>
|
||||
|
||||
<div className="max-h-[400px] overflow-y-auto space-y-2">
|
||||
{filteredScripts.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
No scripts found
|
||||
</div>
|
||||
) : (
|
||||
filteredScripts.slice(0, 50).map((script) => (
|
||||
<button
|
||||
key={script.slug}
|
||||
onClick={() => setSelectedScript(script)}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-3 p-3 rounded-lg border transition-colors text-left",
|
||||
selectedScript?.slug === script.slug
|
||||
? "border-brass bg-brass/10"
|
||||
: "border-rust/30 hover:border-brass/50"
|
||||
)}
|
||||
>
|
||||
<div className="flex h-12 w-12 min-w-12 items-center justify-center rounded-lg bg-accent p-1">
|
||||
<Image
|
||||
src={script.logo || `/${basePath}/logo.png`}
|
||||
unoptimized
|
||||
height={48}
|
||||
width={48}
|
||||
alt=""
|
||||
className="h-10 w-10 object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium truncate">{script.name}</span>
|
||||
<Badge variant="outline" className="text-xs border-copper/50">
|
||||
{getScriptTypeDisplay(script.type)}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground line-clamp-1">
|
||||
{script.description}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Configuration */}
|
||||
<Card className="border-rust/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-brass">
|
||||
<Settings2 className="h-5 w-5" />
|
||||
Configuration
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Customize the installation parameters
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="basic" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="basic">Basic</TabsTrigger>
|
||||
<TabsTrigger value="network">Network</TabsTrigger>
|
||||
<TabsTrigger value="resources">Resources</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Basic Tab */}
|
||||
<TabsContent value="basic" className="space-y-4 mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="hostname" className="text-copper">Hostname</Label>
|
||||
<Input
|
||||
id="hostname"
|
||||
placeholder="e.g., my-container"
|
||||
value={config.hostname}
|
||||
onChange={(e) => updateConfig("hostname", e.target.value)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="os" className="text-copper">Operating System</Label>
|
||||
<Select
|
||||
value={config.os}
|
||||
onValueChange={(value) => updateConfig("os", value)}
|
||||
disabled={!selectedScript || availableOS.length === 0}
|
||||
>
|
||||
<SelectTrigger className="border-rust/30 focus:border-brass">
|
||||
<SelectValue placeholder="Select OS" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableOS.map((os) => (
|
||||
<SelectItem key={os} value={os}>
|
||||
{os}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="unprivileged" className="text-copper">Unprivileged Container</Label>
|
||||
<Switch
|
||||
id="unprivileged"
|
||||
checked={config.unprivileged}
|
||||
onCheckedChange={(checked) => updateConfig("unprivileged", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="startAfter" className="text-copper">Start After Creation</Label>
|
||||
<Switch
|
||||
id="startAfter"
|
||||
checked={config.startAfterCreation}
|
||||
onCheckedChange={(checked) => updateConfig("startAfterCreation", checked)}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Network Tab */}
|
||||
<TabsContent value="network" className="space-y-4 mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="text-copper">Network Type</Label>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant={config.networkType === "dhcp" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => updateConfig("networkType", "dhcp")}
|
||||
className={cn(
|
||||
"flex-1",
|
||||
config.networkType === "dhcp"
|
||||
? "bg-brass text-background hover:bg-brass/90"
|
||||
: "border-rust/30 hover:border-brass"
|
||||
)}
|
||||
>
|
||||
DHCP
|
||||
</Button>
|
||||
<Button
|
||||
variant={config.networkType === "static" ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => updateConfig("networkType", "static")}
|
||||
className={cn(
|
||||
"flex-1",
|
||||
config.networkType === "static"
|
||||
? "bg-brass text-background hover:bg-brass/90"
|
||||
: "border-rust/30 hover:border-brass"
|
||||
)}
|
||||
>
|
||||
Static
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{config.networkType === "static" && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ip" className="text-copper">IP Address</Label>
|
||||
<Input
|
||||
id="ip"
|
||||
placeholder="e.g., 192.168.1.100"
|
||||
value={config.ip}
|
||||
onChange={(e) => updateConfig("ip", e.target.value)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="gateway" className="text-copper">Gateway</Label>
|
||||
<Input
|
||||
id="gateway"
|
||||
placeholder="e.g., 192.168.1.1"
|
||||
value={config.gateway}
|
||||
onChange={(e) => updateConfig("gateway", e.target.value)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="dns" className="text-copper">DNS Servers (comma-separated)</Label>
|
||||
<Input
|
||||
id="dns"
|
||||
placeholder="e.g., 8.8.8.8, 8.8.4.4"
|
||||
value={config.dns.join(", ")}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
"dns",
|
||||
e.target.value.split(",").map((s) => s.trim()).filter(Boolean)
|
||||
)
|
||||
}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="ssh" className="text-copper">Enable SSH</Label>
|
||||
<Switch
|
||||
id="ssh"
|
||||
checked={config.sshEnabled}
|
||||
onCheckedChange={(checked) => updateConfig("sshEnabled", checked)}
|
||||
/>
|
||||
</div>
|
||||
{config.sshEnabled && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="sshPort" className="text-copper">SSH Port</Label>
|
||||
<Input
|
||||
id="sshPort"
|
||||
type="number"
|
||||
min={1}
|
||||
max={65535}
|
||||
value={config.sshPort}
|
||||
onChange={(e) => updateConfig("sshPort", parseInt(e.target.value) || 22)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Resources Tab */}
|
||||
<TabsContent value="resources" className="space-y-4 mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="cpu" className="text-copper flex items-center gap-2">
|
||||
<Cpu className="h-4 w-4" />
|
||||
CPU Cores
|
||||
</Label>
|
||||
<Input
|
||||
id="cpu"
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder={defaultResources.cpu?.toString() || "e.g., 2"}
|
||||
value={config.cpuCores || ""}
|
||||
onChange={(e) => updateConfig("cpuCores", e.target.value ? parseInt(e.target.value) : null)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
{defaultResources.cpu && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: {defaultResources.cpu} cores
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="ram" className="text-copper flex items-center gap-2">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
RAM (MB)
|
||||
</Label>
|
||||
<Input
|
||||
id="ram"
|
||||
type="number"
|
||||
min={128}
|
||||
placeholder={defaultResources.ram?.toString() || "e.g., 512"}
|
||||
value={config.ram || ""}
|
||||
onChange={(e) => updateConfig("ram", e.target.value ? parseInt(e.target.value) : null)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
{defaultResources.ram && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: {defaultResources.ram} MB
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="disk" className="text-copper flex items-center gap-2">
|
||||
<HardDrive className="h-4 w-4" />
|
||||
Disk Size (GB)
|
||||
</Label>
|
||||
<Input
|
||||
id="disk"
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder={defaultResources.disk?.toString() || "e.g., 8"}
|
||||
value={config.diskSize || ""}
|
||||
onChange={(e) => updateConfig("diskSize", e.target.value ? parseInt(e.target.value) : null)}
|
||||
className="border-rust/30 focus:border-brass"
|
||||
/>
|
||||
{defaultResources.disk && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Default: {defaultResources.disk} GB
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Generated Command */}
|
||||
<Card className="mt-6 border-rust/30">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-brass">
|
||||
<Terminal className="h-5 w-5" />
|
||||
Generated Command
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Copy and run this command in your Proxmox VE shell
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{!validation.valid && (
|
||||
<div className="mb-4 p-3 rounded-lg bg-corruption/10 border border-corruption/30">
|
||||
<ul className="text-sm text-corruption">
|
||||
{validation.errors.map((error, i) => (
|
||||
<li key={i}>• {error}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
<Textarea
|
||||
value={command}
|
||||
readOnly
|
||||
className="font-mono text-sm bg-background border-rust/30 min-h-[120px] pr-12"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleCopy}
|
||||
disabled={!validation.valid}
|
||||
className="absolute top-2 right-2 border-rust/30 hover:border-brass"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4 mr-1" />
|
||||
Copied
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
Copy
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{selectedScript && (
|
||||
<div className="mt-4 flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Play className="h-4 w-4" />
|
||||
<span>
|
||||
Run this command in your Proxmox VE shell to deploy {selectedScript.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setSelectedScript(null);
|
||||
setConfig(DEFAULT_CONFIG);
|
||||
setSearch(null);
|
||||
}}
|
||||
className="border-rust/30 hover:border-brass"
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
asChild
|
||||
disabled={!selectedScript}
|
||||
className="bg-brass text-background hover:bg-brass/90"
|
||||
>
|
||||
<Link
|
||||
href={{
|
||||
pathname: "/scripts",
|
||||
query: { id: selectedScript?.slug },
|
||||
}}
|
||||
>
|
||||
View Script Details
|
||||
</Link>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function GeneratorPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex h-screen w-full flex-col items-center justify-center gap-5 bg-background">
|
||||
<Loader2 className="h-10 w-10 animate-spin" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<GeneratorContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user