Merge branch 'community-scripts:main' into truenas-vm-whiptail-box-size-fix
This commit is contained in:
commit
edb291bb29
@ -35,20 +35,24 @@ function update_script() {
|
|||||||
msg_ok "Stopped Service"
|
msg_ok "Stopped Service"
|
||||||
|
|
||||||
msg_info "Backing up Data"
|
msg_info "Backing up Data"
|
||||||
cp -r /opt/calibre-web/app.db /opt/calibre-web/app.db_backup 2>/dev/null
|
cp -r /opt/calibre-web/app.db /opt/app.db_backup
|
||||||
|
cp -r /opt/calibre-web/data /opt/data_backup
|
||||||
msg_ok "Backed up Data"
|
msg_ok "Backed up Data"
|
||||||
|
|
||||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "calibre-web" "janeczku/calibre-web" "tarball" "latest" "/opt/calibre-web"
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "calibre-web" "janeczku/calibre-web" "prebuild" "latest" "/opt/calibre-web" "calibre-web*.tar.gz"
|
||||||
setup_uv
|
setup_uv
|
||||||
|
|
||||||
msg_info "Installing Dependencies"
|
msg_info "Installing Dependencies"
|
||||||
cd /opt/calibre-web
|
cd /opt/calibre-web
|
||||||
$STD uv sync --no-dev
|
$STD uv venv
|
||||||
|
$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir --upgrade pip setuptools wheel
|
||||||
|
$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir -r requirements.txt
|
||||||
msg_ok "Installed Dependencies"
|
msg_ok "Installed Dependencies"
|
||||||
|
|
||||||
msg_info "Restoring Data"
|
msg_info "Restoring Data"
|
||||||
cp /opt/calibre-web/app.db_backup /opt/calibre-web/app.db 2>/dev/null
|
cp /opt/app.db_backup /opt/calibre-web/app.db 2>/dev/null
|
||||||
rm -f /opt/calibre-web/app.db_backup
|
cp -r /opt/data_backup /opt/calibre-web/data 2>/dev/null
|
||||||
|
rm -rf /opt/app.db_backup /opt/data_backup
|
||||||
msg_ok "Restored Data"
|
msg_ok "Restored Data"
|
||||||
|
|
||||||
msg_info "Starting Service"
|
msg_info "Starting Service"
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/build.func)
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: MickLesk (CanbiZ)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source:
|
|
||||||
|
|
||||||
APP="CRONMASTER"
|
|
||||||
var_tags="${var_tags:-}"
|
|
||||||
var_cpu="${var_cpu:-2}"
|
|
||||||
var_ram="${var_ram:-4096}"
|
|
||||||
var_disk="${var_disk:-8}"
|
|
||||||
var_os="${var_os:-debian}"
|
|
||||||
var_version="${var_version:-13}"
|
|
||||||
var_unprivileged="${var_unprivileged:-1}"
|
|
||||||
#var_fuse="${var_fuse:-no}"
|
|
||||||
#var_tun="${var_tun:-no}"
|
|
||||||
|
|
||||||
header_info "$APP"
|
|
||||||
variables
|
|
||||||
color
|
|
||||||
catch_errors
|
|
||||||
|
|
||||||
function update_script() {
|
|
||||||
header_info
|
|
||||||
check_container_storage
|
|
||||||
check_container_resources
|
|
||||||
if [[ ! -d /opt/cronmaster ]]; then
|
|
||||||
msg_error "No ${APP} Installation Found!"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
msg_info "Updating Debian LXC"
|
|
||||||
$STD apt update
|
|
||||||
$STD apt upgrade -y
|
|
||||||
msg_ok "Updated Debian LXC"
|
|
||||||
cleanup_lxc
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
start
|
|
||||||
build_container
|
|
||||||
description
|
|
||||||
|
|
||||||
msg_ok "Completed successfully!"
|
|
||||||
msg_custom "🚀" "${GN}" "${APP} setup has been successfully initialized!"
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/build.func)
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: MickLesk (CanbiZ)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source: https://github.com/databasus/databasus
|
|
||||||
|
|
||||||
APP="Databasus"
|
|
||||||
var_tags="${var_tags:-backup;postgresql;database}"
|
|
||||||
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 /opt/databasus/databasus ]]; then
|
|
||||||
msg_error "No ${APP} Installation Found!"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
if check_for_gh_release "databasus" "databasus/databasus"; then
|
|
||||||
msg_info "Stopping Databasus"
|
|
||||||
$STD systemctl stop databasus
|
|
||||||
msg_ok "Stopped Databasus"
|
|
||||||
|
|
||||||
msg_info "Backing up Configuration"
|
|
||||||
cp /opt/databasus/.env /tmp/databasus.env.bak
|
|
||||||
msg_ok "Backed up Configuration"
|
|
||||||
|
|
||||||
msg_info "Updating Databasus"
|
|
||||||
fetch_and_deploy_gh_release "databasus" "databasus/databasus" "tarball" "latest" "/opt/databasus"
|
|
||||||
|
|
||||||
cd /opt/databasus/frontend
|
|
||||||
$STD npm ci
|
|
||||||
$STD npm run build
|
|
||||||
|
|
||||||
cd /opt/databasus/backend
|
|
||||||
$STD go mod download
|
|
||||||
$STD /root/go/bin/swag init -g cmd/main.go -o swagger
|
|
||||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go
|
|
||||||
mv /opt/databasus/backend/databasus /opt/databasus/databasus
|
|
||||||
|
|
||||||
cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/
|
|
||||||
cp -r /opt/databasus/backend/migrations /opt/databasus/
|
|
||||||
chown -R postgres:postgres /opt/databasus
|
|
||||||
msg_ok "Updated Databasus"
|
|
||||||
|
|
||||||
msg_info "Restoring Configuration"
|
|
||||||
cp /tmp/databasus.env.bak /opt/databasus/.env
|
|
||||||
rm -f /tmp/databasus.env.bak
|
|
||||||
chown postgres:postgres /opt/databasus/.env
|
|
||||||
msg_ok "Restored Configuration"
|
|
||||||
|
|
||||||
msg_info "Starting Databasus"
|
|
||||||
$STD systemctl start databasus
|
|
||||||
msg_ok "Started Databasus"
|
|
||||||
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}${CL}"
|
|
||||||
@ -43,10 +43,12 @@ function update_script() {
|
|||||||
msg_ok "Backed up Data"
|
msg_ok "Backed up Data"
|
||||||
|
|
||||||
msg_info "Updating Discourse"
|
msg_info "Updating Discourse"
|
||||||
|
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
|
||||||
cd /opt/discourse
|
cd /opt/discourse
|
||||||
git pull origin main
|
git pull origin main
|
||||||
$STD bundle install --deployment --without test development
|
$STD bundle install --deployment --without test development
|
||||||
$STD yarn install
|
$STD yarn install
|
||||||
|
$STD runuser -u postgres -- psql -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||||
$STD bundle exec rails assets:precompile
|
$STD bundle exec rails assets:precompile
|
||||||
$STD bundle exec rails db:migrate
|
$STD bundle exec rails db:migrate
|
||||||
msg_ok "Updated Discourse"
|
msg_ok "Updated Discourse"
|
||||||
@ -70,6 +72,5 @@ msg_ok "Completed Successfully!\n"
|
|||||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
||||||
echo -e "${INFO}${YW} Default Credentials:${CL}"
|
echo -e "${INFO}${YW} Admin Setup:${CL}"
|
||||||
echo -e "${TAB}${GATEWAY}${BGN}Username: admin${CL}"
|
echo -e "${TAB}${GATEWAY}${BGN}Create the first account in the web UI (use admin@local to match developer emails)${CL}"
|
||||||
echo -e "${TAB}${GATEWAY}${BGN}Password: Check /opt/discourse/.env${CL}"
|
|
||||||
|
|||||||
@ -93,7 +93,9 @@ function update_script() {
|
|||||||
msg_ok "Updated Gramps Web Frontend"
|
msg_ok "Updated Gramps Web Frontend"
|
||||||
|
|
||||||
msg_info "Applying Database Migration"
|
msg_info "Applying Database Migration"
|
||||||
|
cd /opt/gramps-web-api
|
||||||
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
||||||
|
ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \
|
||||||
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
||||||
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
||||||
$STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate
|
$STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate
|
||||||
|
|||||||
35
frontend/public/json/cronmaster.json
Normal file
35
frontend/public/json/cronmaster.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "CronMaster",
|
||||||
|
"slug": "cronmaster",
|
||||||
|
"categories": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"date_created": "2026-02-17",
|
||||||
|
"type": "pve",
|
||||||
|
"updateable": true,
|
||||||
|
"privileged": false,
|
||||||
|
"interface_port": 3000,
|
||||||
|
"documentation": "https://github.com/fccview/cronmaster",
|
||||||
|
"website": "https://github.com/fccview/cronmaster",
|
||||||
|
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/cr-nmaster.webp",
|
||||||
|
"config_path": "/opt/cronmaster/.env",
|
||||||
|
"description": "Self-hosted cron job scheduler with web UI, live logs, auth and prebuilt binaries provided upstream.",
|
||||||
|
"install_methods": [
|
||||||
|
{
|
||||||
|
"type": "default",
|
||||||
|
"script": "tools/addon/cronmaster.sh",
|
||||||
|
"resources": {
|
||||||
|
"cpu": null,
|
||||||
|
"ram": null,
|
||||||
|
"hdd": null,
|
||||||
|
"os": null,
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_credentials": {
|
||||||
|
"username": null,
|
||||||
|
"password": null
|
||||||
|
},
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Databasus",
|
|
||||||
"slug": "databasus",
|
|
||||||
"categories": [
|
|
||||||
7
|
|
||||||
],
|
|
||||||
"date_created": "2025-01-14",
|
|
||||||
"type": "ct",
|
|
||||||
"updateable": true,
|
|
||||||
"privileged": false,
|
|
||||||
"interface_port": 80,
|
|
||||||
"documentation": "https://github.com/databasus/databasus",
|
|
||||||
"website": "https://github.com/databasus/databasus",
|
|
||||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/databasus.webp",
|
|
||||||
"config_path": "/opt/databasus/.env",
|
|
||||||
"description": "Free, open source and self-hosted solution for automated PostgreSQL backups. With multiple storage options, notifications, scheduling, and a beautiful web interface for managing database backups across multiple PostgreSQL instances.",
|
|
||||||
"install_methods": [
|
|
||||||
{
|
|
||||||
"type": "default",
|
|
||||||
"script": "ct/databasus.sh",
|
|
||||||
"resources": {
|
|
||||||
"cpu": 2,
|
|
||||||
"ram": 2048,
|
|
||||||
"hdd": 8,
|
|
||||||
"os": "Debian",
|
|
||||||
"version": "13"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"default_credentials": {
|
|
||||||
"username": "admin@localhost",
|
|
||||||
"password": "See /root/databasus.creds"
|
|
||||||
},
|
|
||||||
"notes": [
|
|
||||||
{
|
|
||||||
"text": "Supports PostgreSQL versions 12-18 with cloud and self-hosted instances",
|
|
||||||
"type": "info"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "Features: Scheduled backups, multiple storage providers, notifications, encryption",
|
|
||||||
"type": "info"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"ram": null,
|
"ram": null,
|
||||||
"hdd": null,
|
"hdd": null,
|
||||||
"os": null,
|
"os": null,
|
||||||
"version": null
|
"version": "PVE 8.x / 9.x"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -36,6 +36,10 @@
|
|||||||
"text": "Execute within the Proxmox shell",
|
"text": "Execute within the Proxmox shell",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"text": "The script supports --install (default), --status and --uninstall for clean lifecycle management.",
|
||||||
|
"type": "info"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"text": "To wait until a certain host is available, tag the VM or container with `dep_ping_<hostname>` where `<hostname>` is the name or IP of the host to ping. The script will wait until the host is reachable before proceeding with the startup.",
|
"text": "To wait until a certain host is available, tag the VM or container with `dep_ping_<hostname>` where `<hostname>` is the name or IP of the host to ping. The script will wait until the host is reachable before proceeding with the startup.",
|
||||||
"type": "info"
|
"type": "info"
|
||||||
|
|||||||
@ -15,7 +15,12 @@ update_os
|
|||||||
|
|
||||||
msg_info "Installing Dependencies"
|
msg_info "Installing Dependencies"
|
||||||
$STD apt install -y \
|
$STD apt install -y \
|
||||||
|
build-essential \
|
||||||
python3 \
|
python3 \
|
||||||
|
python3-dev \
|
||||||
|
libldap2-dev \
|
||||||
|
libsasl2-dev \
|
||||||
|
libssl-dev \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
libpango-1.0-0 \
|
libpango-1.0-0 \
|
||||||
libharfbuzz0b \
|
libharfbuzz0b \
|
||||||
@ -27,12 +32,14 @@ msg_info "Installing Calibre (for eBook conversion)"
|
|||||||
$STD apt install -y calibre
|
$STD apt install -y calibre
|
||||||
msg_ok "Installed Calibre"
|
msg_ok "Installed Calibre"
|
||||||
|
|
||||||
fetch_and_deploy_gh_release "calibre-web" "janeczku/calibre-web" "tarball" "latest" "/opt/calibre-web"
|
fetch_and_deploy_gh_release "calibre-web" "janeczku/calibre-web" "prebuild" "latest" "/opt/calibre-web" "calibre-web*.tar.gz"
|
||||||
setup_uv
|
setup_uv
|
||||||
|
|
||||||
msg_info "Installing Python Dependencies"
|
msg_info "Installing Python Dependencies"
|
||||||
cd /opt/calibre-web
|
cd /opt/calibre-web
|
||||||
$STD uv sync --no-dev
|
$STD uv venv
|
||||||
|
$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir --upgrade pip setuptools wheel
|
||||||
|
$STD uv pip install --python /opt/calibre-web/.venv/bin/python --no-cache-dir -r requirements.txt
|
||||||
msg_ok "Installed Python Dependencies"
|
msg_ok "Installed Python Dependencies"
|
||||||
|
|
||||||
msg_info "Creating Service"
|
msg_info "Creating Service"
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: Slaviša Arežina (tremor021)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source: https://github.com/fccview/cronmaster
|
|
||||||
|
|
||||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
|
||||||
color
|
|
||||||
verb_ip6
|
|
||||||
catch_errors
|
|
||||||
setting_up_container
|
|
||||||
network_check
|
|
||||||
update_os
|
|
||||||
setup_hwaccel
|
|
||||||
|
|
||||||
msg_info "Installing dependencies"
|
|
||||||
$STD apt install -y pciutils
|
|
||||||
msg_ok "Installed dependencies"
|
|
||||||
|
|
||||||
NODE_VERSION="24" NODE_MODULE="yarn" setup_nodejs
|
|
||||||
|
|
||||||
setup_deb822_repo \
|
|
||||||
"docker" \
|
|
||||||
"https://download.docker.com/linux/debian/gpg" \
|
|
||||||
"https://download.docker.com/linux/debian" \
|
|
||||||
"trixie" \
|
|
||||||
"stable"
|
|
||||||
$STD apt install -y docker-ce-cli
|
|
||||||
fetch_and_deploy_gh_release "cronmaster" "fccview/cronmaster" "tarball"
|
|
||||||
|
|
||||||
msg_info "Setting up CronMaster"
|
|
||||||
AUTH_PASS="$(openssl rand -base64 18 | cut -c1-13)"
|
|
||||||
cd /opt/cronmaster
|
|
||||||
$STD yarn --frozen-lockfile
|
|
||||||
export NEXT_TELEMETRY_DISABLED=1
|
|
||||||
$STD yarn build
|
|
||||||
cat <<EOF >/opt/cronmaster/.env
|
|
||||||
NODE_ENV=production
|
|
||||||
APP_URL=
|
|
||||||
LOCALE=
|
|
||||||
HOME=
|
|
||||||
AUTH_PASSWORD=${AUTH_PASS}
|
|
||||||
PORT=3000
|
|
||||||
HOSTNAME="0.0.0.0"
|
|
||||||
NEXT_TELEMETRY_DISABLED=1
|
|
||||||
EOF
|
|
||||||
{
|
|
||||||
echo "CronMaster Credentials:"
|
|
||||||
echo ""
|
|
||||||
echo "Password: $AUTH_PASS"
|
|
||||||
}>>~/cronmaster.creds
|
|
||||||
msg_ok "Setup CronMaster"
|
|
||||||
|
|
||||||
msg_info "Creating Service"
|
|
||||||
cat <<EOF >/etc/systemd/system/cronmaster.service
|
|
||||||
[Unit]
|
|
||||||
Description=CronMaster Service
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
EnvironmentFile=/opt/cronmaster/.env
|
|
||||||
WorkingDirectory=/opt/cronmaster
|
|
||||||
ExecStart=/usr/bin/yarn start
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl start --now -q cronmaster
|
|
||||||
msg_info "Created Service"
|
|
||||||
|
|
||||||
motd_ssh
|
|
||||||
customize
|
|
||||||
cleanup_lxc
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Copyright (c) 2021-2026 community-scripts ORG
|
|
||||||
# Author: MickLesk (CanbiZ)
|
|
||||||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
|
||||||
# Source: https://github.com/databasus/databasus
|
|
||||||
|
|
||||||
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 \
|
|
||||||
valkey
|
|
||||||
msg_ok "Installed Dependencies"
|
|
||||||
|
|
||||||
PG_VERSION="17" setup_postgresql
|
|
||||||
setup_go
|
|
||||||
NODE_VERSION="24" setup_nodejs
|
|
||||||
|
|
||||||
fetch_and_deploy_gh_release "databasus" "databasus/databasus" "tarball" "latest" "/opt/databasus"
|
|
||||||
|
|
||||||
msg_info "Building Databasus (Patience)"
|
|
||||||
cd /opt/databasus/frontend
|
|
||||||
$STD npm ci
|
|
||||||
$STD npm run build
|
|
||||||
cd /opt/databasus/backend
|
|
||||||
$STD go mod tidy
|
|
||||||
$STD go mod download
|
|
||||||
$STD go install github.com/swaggo/swag/cmd/swag@latest
|
|
||||||
$STD /root/go/bin/swag init -g cmd/main.go -o swagger
|
|
||||||
$STD env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o databasus ./cmd/main.go
|
|
||||||
mv /opt/databasus/backend/databasus /opt/databasus/databasus
|
|
||||||
mkdir -p /databasus-data/{pgdata,temp,backups,data,logs}
|
|
||||||
mkdir -p /opt/databasus/ui/build
|
|
||||||
mkdir -p /opt/databasus/migrations
|
|
||||||
cp -r /opt/databasus/frontend/dist/* /opt/databasus/ui/build/
|
|
||||||
cp -r /opt/databasus/backend/migrations/* /opt/databasus/migrations/
|
|
||||||
chown -R postgres:postgres /databasus-data
|
|
||||||
msg_ok "Built Databasus"
|
|
||||||
|
|
||||||
msg_info "Configuring Databasus"
|
|
||||||
JWT_SECRET=$(openssl rand -hex 32)
|
|
||||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
|
||||||
# Create PostgreSQL version symlinks for compatibility
|
|
||||||
for v in 12 13 14 15 16 18; do
|
|
||||||
ln -sf /usr/lib/postgresql/17 /usr/lib/postgresql/$v
|
|
||||||
done
|
|
||||||
# Install goose for migrations
|
|
||||||
$STD go install github.com/pressly/goose/v3/cmd/goose@latest
|
|
||||||
ln -sf /root/go/bin/goose /usr/local/bin/goose
|
|
||||||
cat <<EOF >/opt/databasus/.env
|
|
||||||
# Environment
|
|
||||||
ENV_MODE=production
|
|
||||||
|
|
||||||
# Server
|
|
||||||
SERVER_PORT=4005
|
|
||||||
SERVER_HOST=0.0.0.0
|
|
||||||
|
|
||||||
# Database
|
|
||||||
DATABASE_DSN=host=localhost user=postgres password=postgres dbname=databasus port=5432 sslmode=disable
|
|
||||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable
|
|
||||||
|
|
||||||
# Migrations
|
|
||||||
GOOSE_DRIVER=postgres
|
|
||||||
GOOSE_DBSTRING=postgres://postgres:postgres@localhost:5432/databasus?sslmode=disable
|
|
||||||
GOOSE_MIGRATION_DIR=/opt/databasus/migrations
|
|
||||||
|
|
||||||
# Valkey (Redis-compatible cache)
|
|
||||||
VALKEY_HOST=localhost
|
|
||||||
VALKEY_PORT=6379
|
|
||||||
|
|
||||||
# Security
|
|
||||||
JWT_SECRET=${JWT_SECRET}
|
|
||||||
ENCRYPTION_KEY=${ENCRYPTION_KEY}
|
|
||||||
|
|
||||||
# Paths
|
|
||||||
DATA_DIR=/databasus-data/data
|
|
||||||
BACKUP_DIR=/databasus-data/backups
|
|
||||||
LOG_DIR=/databasus-data/logs
|
|
||||||
EOF
|
|
||||||
chown postgres:postgres /opt/databasus/.env
|
|
||||||
chmod 600 /opt/databasus/.env
|
|
||||||
msg_ok "Configured Databasus"
|
|
||||||
|
|
||||||
msg_info "Configuring Valkey"
|
|
||||||
cat >/etc/valkey/valkey.conf <<EOF
|
|
||||||
port 6379
|
|
||||||
bind 127.0.0.1
|
|
||||||
protected-mode yes
|
|
||||||
save ""
|
|
||||||
maxmemory 256mb
|
|
||||||
maxmemory-policy allkeys-lru
|
|
||||||
EOF
|
|
||||||
systemctl enable -q --now valkey-server
|
|
||||||
systemctl restart valkey-server
|
|
||||||
msg_ok "Configured Valkey"
|
|
||||||
|
|
||||||
msg_info "Creating Database"
|
|
||||||
# Configure PostgreSQL to allow local password auth for databasus
|
|
||||||
PG_HBA="/etc/postgresql/17/main/pg_hba.conf"
|
|
||||||
if ! grep -q "databasus" "$PG_HBA"; then
|
|
||||||
sed -i '/^local\s*all\s*all/i local databasus postgres trust' "$PG_HBA"
|
|
||||||
sed -i '/^host\s*all\s*all\s*127/i host databasus postgres 127.0.0.1/32 trust' "$PG_HBA"
|
|
||||||
systemctl reload postgresql
|
|
||||||
fi
|
|
||||||
$STD sudo -u postgres psql -c "CREATE DATABASE databasus;" 2>/dev/null || true
|
|
||||||
$STD sudo -u postgres psql -c "ALTER USER postgres WITH SUPERUSER CREATEROLE CREATEDB;" 2>/dev/null || true
|
|
||||||
msg_ok "Created Database"
|
|
||||||
|
|
||||||
msg_info "Creating Databasus Service"
|
|
||||||
cat <<EOF >/etc/systemd/system/databasus.service
|
|
||||||
[Unit]
|
|
||||||
Description=Databasus - Database Backup Management
|
|
||||||
After=network.target postgresql.service valkey.service
|
|
||||||
Requires=postgresql.service valkey.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
WorkingDirectory=/opt/databasus
|
|
||||||
EnvironmentFile=/opt/databasus/.env
|
|
||||||
ExecStart=/opt/databasus/databasus
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
$STD systemctl daemon-reload
|
|
||||||
$STD systemctl enable -q --now databasus
|
|
||||||
msg_ok "Created Databasus Service"
|
|
||||||
|
|
||||||
msg_info "Configuring Nginx"
|
|
||||||
cat <<EOF >/etc/nginx/sites-available/databasus
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://127.0.0.1:4005;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade \$http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host \$host;
|
|
||||||
proxy_set_header X-Real-IP \$remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
||||||
proxy_cache_bypass \$http_upgrade;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_read_timeout 86400s;
|
|
||||||
proxy_send_timeout 86400s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
ln -sf /etc/nginx/sites-available/databasus /etc/nginx/sites-enabled/databasus
|
|
||||||
rm -f /etc/nginx/sites-enabled/default
|
|
||||||
$STD nginx -t
|
|
||||||
$STD systemctl enable -q --now nginx
|
|
||||||
$STD systemctl reload nginx
|
|
||||||
msg_ok "Configured Nginx"
|
|
||||||
|
|
||||||
motd_ssh
|
|
||||||
customize
|
|
||||||
cleanup_lxc
|
|
||||||
@ -24,30 +24,27 @@ $STD apt install -y \
|
|||||||
git \
|
git \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
gsfonts \
|
gsfonts \
|
||||||
|
brotli \
|
||||||
nginx \
|
nginx \
|
||||||
redis-server
|
redis-server
|
||||||
msg_ok "Installed Dependencies"
|
msg_ok "Installed Dependencies"
|
||||||
|
|
||||||
PG_VERSION="16" setup_postgresql
|
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
|
||||||
NODE_VERSION="22" setup_nodejs
|
NODE_VERSION="22" setup_nodejs
|
||||||
RUBY_VERSION="3.4.4" setup_ruby
|
RUBY_VERSION="3.4.4" setup_ruby
|
||||||
|
|
||||||
msg_info "Configuring PostgreSQL for Discourse"
|
msg_info "Configuring PostgreSQL for Discourse"
|
||||||
DISCOURSE_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
DISCOURSE_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
||||||
# Configure pg_hba.conf for md5 authentication
|
|
||||||
PG_HBA="/etc/postgresql/16/main/pg_hba.conf"
|
PG_HBA="/etc/postgresql/16/main/pg_hba.conf"
|
||||||
sed -i 's/^local\s\+all\s\+all\s\+peer$/local all all md5/' "$PG_HBA"
|
sed -i 's/^local\s\+all\s\+all\s\+peer$/local all all md5/' "$PG_HBA"
|
||||||
$STD systemctl restart postgresql
|
$STD systemctl restart postgresql
|
||||||
# Create user + database explicitly for reliable bootstrap
|
|
||||||
PG_DB_NAME="discourse" PG_DB_USER="discourse" PG_DB_PASS="$DISCOURSE_DB_PASS" setup_postgresql_db
|
PG_DB_NAME="discourse" PG_DB_USER="discourse" PG_DB_PASS="$DISCOURSE_DB_PASS" setup_postgresql_db
|
||||||
msg_ok "Configured PostgreSQL for Discourse"
|
msg_ok "Configured PostgreSQL for Discourse"
|
||||||
|
|
||||||
msg_info "Configuring Discourse"
|
msg_info "Configuring Discourse"
|
||||||
DISCOURSE_SECRET_KEY=$(openssl rand -hex 64)
|
DISCOURSE_SECRET_KEY=$(openssl rand -hex 64)
|
||||||
|
$STD git clone --depth 1 https://github.com/discourse/discourse.git /opt/discourse
|
||||||
git clone --depth 1 https://github.com/discourse/discourse.git /opt/discourse
|
|
||||||
cd /opt/discourse
|
cd /opt/discourse
|
||||||
|
|
||||||
cat <<EOF >/opt/discourse/.env
|
cat <<EOF >/opt/discourse/.env
|
||||||
RAILS_ENV=production
|
RAILS_ENV=production
|
||||||
RAILS_LOG_TO_STDOUT=true
|
RAILS_LOG_TO_STDOUT=true
|
||||||
@ -93,6 +90,7 @@ export RAILS_ENV=production
|
|||||||
set -a
|
set -a
|
||||||
source /opt/discourse/.env
|
source /opt/discourse/.env
|
||||||
set +a
|
set +a
|
||||||
|
$STD runuser -u postgres -- psql -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||||
$STD bundle exec rails db:migrate
|
$STD bundle exec rails db:migrate
|
||||||
msg_ok "Set Up Database"
|
msg_ok "Set Up Database"
|
||||||
|
|
||||||
@ -107,16 +105,8 @@ set +a
|
|||||||
$STD bundle exec rails assets:precompile
|
$STD bundle exec rails assets:precompile
|
||||||
msg_ok "Built Discourse Assets"
|
msg_ok "Built Discourse Assets"
|
||||||
|
|
||||||
msg_info "Creating Discourse Admin User"
|
msg_info "Preparing Admin Onboarding"
|
||||||
cd /opt/discourse
|
msg_ok "Automatic admin bootstrap skipped (use first signup in UI with admin@local)"
|
||||||
export PATH="$HOME/.rbenv/bin:$HOME/.rbenv/shims:$PATH"
|
|
||||||
eval "$(rbenv init - bash)" 2>/dev/null || true
|
|
||||||
export RAILS_ENV=production
|
|
||||||
set -a
|
|
||||||
source /opt/discourse/.env
|
|
||||||
set +a
|
|
||||||
$STD bundle exec rails runner "User.create!(email: 'admin@local', username: 'admin', password: '${DISCOURSE_DB_PASS}', admin: true)" || true
|
|
||||||
msg_ok "Created Discourse Admin User"
|
|
||||||
|
|
||||||
msg_info "Creating Service"
|
msg_info "Creating Service"
|
||||||
cat <<EOF >/etc/systemd/system/discourse.service
|
cat <<EOF >/etc/systemd/system/discourse.service
|
||||||
|
|||||||
@ -94,7 +94,9 @@ corepack enable
|
|||||||
$STD npm install
|
$STD npm install
|
||||||
$STD npm run build
|
$STD npm run build
|
||||||
|
|
||||||
|
cd /opt/gramps-web-api
|
||||||
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
||||||
|
ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \
|
||||||
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
||||||
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
||||||
$STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate
|
$STD /opt/gramps-web/venv/bin/python3 -m gramps_webapi user migrate
|
||||||
|
|||||||
152
misc/api.func
152
misc/api.func
@ -91,7 +91,7 @@ detect_repo_source() {
|
|||||||
community-scripts/ProxmoxVED) REPO_SOURCE="ProxmoxVED" ;;
|
community-scripts/ProxmoxVED) REPO_SOURCE="ProxmoxVED" ;;
|
||||||
"")
|
"")
|
||||||
# No URL detected — use hardcoded fallback
|
# No URL detected — use hardcoded fallback
|
||||||
# CI sed transforms this on promotion: ProxmoxVED → ProxmoxVE
|
# This value must match the repo: ProxmoxVE for production, ProxmoxVED for dev
|
||||||
REPO_SOURCE="ProxmoxVED"
|
REPO_SOURCE="ProxmoxVED"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@ -135,24 +135,50 @@ explain_exit_code() {
|
|||||||
# --- Generic / Shell ---
|
# --- Generic / Shell ---
|
||||||
1) echo "General error / Operation not permitted" ;;
|
1) echo "General error / Operation not permitted" ;;
|
||||||
2) echo "Misuse of shell builtins (e.g. syntax error)" ;;
|
2) echo "Misuse of shell builtins (e.g. syntax error)" ;;
|
||||||
|
10) echo "Docker / privileged mode required (unsupported environment)" ;;
|
||||||
|
|
||||||
# --- curl / wget errors (commonly seen in downloads) ---
|
# --- curl / wget errors (commonly seen in downloads) ---
|
||||||
|
4) echo "curl: Feature not supported or protocol error" ;;
|
||||||
|
5) echo "curl: Could not resolve proxy" ;;
|
||||||
6) echo "curl: DNS resolution failed (could not resolve host)" ;;
|
6) echo "curl: DNS resolution failed (could not resolve host)" ;;
|
||||||
7) echo "curl: Failed to connect (network unreachable / host down)" ;;
|
7) echo "curl: Failed to connect (network unreachable / host down)" ;;
|
||||||
|
8) echo "curl: FTP server reply error" ;;
|
||||||
22) echo "curl: HTTP error returned (404, 429, 500+)" ;;
|
22) echo "curl: HTTP error returned (404, 429, 500+)" ;;
|
||||||
|
23) echo "curl: Write error (disk full or permissions)" ;;
|
||||||
|
25) echo "curl: Upload failed" ;;
|
||||||
28) echo "curl: Operation timeout (network slow or server not responding)" ;;
|
28) echo "curl: Operation timeout (network slow or server not responding)" ;;
|
||||||
|
30) echo "curl: FTP port command failed" ;;
|
||||||
35) echo "curl: SSL/TLS handshake failed (certificate error)" ;;
|
35) echo "curl: SSL/TLS handshake failed (certificate error)" ;;
|
||||||
|
56) echo "curl: Receive error (connection reset by peer)" ;;
|
||||||
|
75) echo "Temporary failure (retry later)" ;;
|
||||||
|
78) echo "curl: Remote file not found (404 on FTP/file)" ;;
|
||||||
|
|
||||||
# --- Package manager / APT / DPKG ---
|
# --- Package manager / APT / DPKG ---
|
||||||
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
||||||
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||||
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
|
102) echo "APT: Lock held by another process (dpkg/apt still running)" ;;
|
||||||
|
|
||||||
|
# --- BSD sysexits.h (64-78) ---
|
||||||
|
64) echo "Usage error (wrong arguments)" ;;
|
||||||
|
65) echo "Data format error (bad input data)" ;;
|
||||||
|
66) echo "Input file not found (cannot open input)" ;;
|
||||||
|
67) echo "User not found (addressee unknown)" ;;
|
||||||
|
68) echo "Host not found (hostname unknown)" ;;
|
||||||
|
69) echo "Service unavailable" ;;
|
||||||
|
70) echo "Internal software error" ;;
|
||||||
|
71) echo "System error (OS-level failure)" ;;
|
||||||
|
72) echo "Critical OS file missing" ;;
|
||||||
|
73) echo "Cannot create output file" ;;
|
||||||
|
74) echo "I/O error" ;;
|
||||||
|
76) echo "Remote protocol error" ;;
|
||||||
|
77) echo "Permission denied" ;;
|
||||||
|
|
||||||
# --- Common shell/system errors ---
|
# --- Common shell/system errors ---
|
||||||
124) echo "Command timed out (timeout command)" ;;
|
124) echo "Command timed out (timeout command)" ;;
|
||||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||||
127) echo "Command not found" ;;
|
127) echo "Command not found" ;;
|
||||||
128) echo "Invalid argument to exit" ;;
|
128) echo "Invalid argument to exit" ;;
|
||||||
|
129) echo "Killed by SIGHUP (terminal closed / hangup)" ;;
|
||||||
130) echo "Aborted by user (SIGINT)" ;;
|
130) echo "Aborted by user (SIGINT)" ;;
|
||||||
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
||||||
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||||
@ -237,16 +263,21 @@ explain_exit_code() {
|
|||||||
# json_escape()
|
# json_escape()
|
||||||
#
|
#
|
||||||
# - Escapes a string for safe JSON embedding
|
# - Escapes a string for safe JSON embedding
|
||||||
|
# - Strips ANSI escape sequences and non-printable control characters
|
||||||
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
json_escape() {
|
json_escape() {
|
||||||
local s="$1"
|
local s="$1"
|
||||||
|
# Strip ANSI escape sequences (color codes etc.)
|
||||||
|
s=$(printf '%s' "$s" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g')
|
||||||
s=${s//\\/\\\\}
|
s=${s//\\/\\\\}
|
||||||
s=${s//"/\\"/}
|
s=${s//"/\\"/}
|
||||||
s=${s//$'\n'/\\n}
|
s=${s//$'\n'/\\n}
|
||||||
s=${s//$'\r'/}
|
s=${s//$'\r'/}
|
||||||
s=${s//$'\t'/\\t}
|
s=${s//$'\t'/\\t}
|
||||||
echo "$s"
|
# Remove any remaining control characters (0x00-0x1F except those already handled)
|
||||||
|
s=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037')
|
||||||
|
printf '%s' "$s"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -283,7 +314,33 @@ get_error_text() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
if [[ -n "$logfile" && -s "$logfile" ]]; then
|
||||||
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//'
|
tail -n 20 "$logfile" 2>/dev/null | sed 's/\r$//' | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# build_error_string()
|
||||||
|
#
|
||||||
|
# - Builds a structured error string for telemetry reporting
|
||||||
|
# - Format: "exit_code=<N> | <explanation>\n---\n<last 20 log lines>"
|
||||||
|
# - If no log lines available, returns just the explanation
|
||||||
|
# - Arguments:
|
||||||
|
# * $1: exit_code (numeric)
|
||||||
|
# * $2: log_text (optional, output from get_error_text)
|
||||||
|
# - Returns structured error string via stdout
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
build_error_string() {
|
||||||
|
local exit_code="${1:-1}"
|
||||||
|
local log_text="${2:-}"
|
||||||
|
local explanation
|
||||||
|
explanation=$(explain_exit_code "$exit_code")
|
||||||
|
|
||||||
|
if [[ -n "$log_text" ]]; then
|
||||||
|
# Structured format: header + separator + log lines
|
||||||
|
printf 'exit_code=%s | %s\n---\n%s' "$exit_code" "$explanation" "$log_text"
|
||||||
|
else
|
||||||
|
# No log available - just the explanation with exit code
|
||||||
|
printf 'exit_code=%s | %s' "$exit_code" "$explanation"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,18 +426,19 @@ detect_cpu() {
|
|||||||
# - Detects RAM speed using dmidecode
|
# - Detects RAM speed using dmidecode
|
||||||
# - Sets RAM_SPEED global (e.g., "4800" for DDR5-4800)
|
# - Sets RAM_SPEED global (e.g., "4800" for DDR5-4800)
|
||||||
# - Requires root access for dmidecode
|
# - Requires root access for dmidecode
|
||||||
# - Returns empty if not available
|
# - Returns empty if not available or if speed is "Unknown" (nested VMs)
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
detect_ram() {
|
detect_ram() {
|
||||||
RAM_SPEED=""
|
RAM_SPEED=""
|
||||||
|
|
||||||
if command -v dmidecode &>/dev/null; then
|
if command -v dmidecode &>/dev/null; then
|
||||||
# Get configured memory speed (actual running speed)
|
# Get configured memory speed (actual running speed)
|
||||||
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1)
|
# Use || true to handle "Unknown" values in nested VMs (no numeric match)
|
||||||
|
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Configured Memory Speed:" | grep -oE "[0-9]+" | head -1) || true
|
||||||
|
|
||||||
# Fallback to Speed: if Configured not available
|
# Fallback to Speed: if Configured not available
|
||||||
if [[ -z "$RAM_SPEED" ]]; then
|
if [[ -z "$RAM_SPEED" ]]; then
|
||||||
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1)
|
RAM_SPEED=$(dmidecode -t memory 2>/dev/null | grep -m1 "Speed:" | grep -oE "[0-9]+" | head -1) || true
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -592,6 +650,8 @@ EOF
|
|||||||
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$JSON_PAYLOAD" &>/dev/null || true
|
-d "$JSON_PAYLOAD" &>/dev/null || true
|
||||||
|
|
||||||
|
POST_TO_API_DONE=true
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -665,13 +725,12 @@ post_update_to_api() {
|
|||||||
else
|
else
|
||||||
exit_code=1
|
exit_code=1
|
||||||
fi
|
fi
|
||||||
|
# Get log lines and build structured error string
|
||||||
local error_text=""
|
local error_text=""
|
||||||
error_text=$(get_error_text)
|
error_text=$(get_error_text)
|
||||||
if [[ -n "$error_text" ]]; then
|
local full_error
|
||||||
error=$(json_escape "$error_text")
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||||
else
|
error=$(json_escape "$full_error")
|
||||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
|
||||||
fi
|
|
||||||
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||||
error_category=$(categorize_error "$exit_code")
|
error_category=$(categorize_error "$exit_code")
|
||||||
[[ -z "$error" ]] && error="Unknown error"
|
[[ -z "$error" ]] && error="Unknown error"
|
||||||
@ -814,31 +873,52 @@ EOF
|
|||||||
categorize_error() {
|
categorize_error() {
|
||||||
local code="$1"
|
local code="$1"
|
||||||
case "$code" in
|
case "$code" in
|
||||||
# Network errors
|
# Network errors (curl/wget)
|
||||||
6 | 7 | 22 | 28 | 35) echo "network" ;;
|
6 | 7 | 22 | 35) echo "network" ;;
|
||||||
|
|
||||||
# Storage errors
|
# Timeout errors
|
||||||
214 | 217 | 219) echo "storage" ;;
|
28 | 124 | 211) echo "timeout" ;;
|
||||||
|
|
||||||
# Dependency/Package errors
|
# Storage errors (Proxmox storage)
|
||||||
100 | 101 | 102 | 127 | 160 | 161 | 162) echo "dependency" ;;
|
214 | 217 | 219 | 224) echo "storage" ;;
|
||||||
|
|
||||||
|
# Dependency/Package errors (APT, DPKG, pip, commands)
|
||||||
|
100 | 101 | 102 | 127 | 160 | 161 | 162 | 255) echo "dependency" ;;
|
||||||
|
|
||||||
# Permission errors
|
# Permission errors
|
||||||
126 | 152) echo "permission" ;;
|
126 | 152) echo "permission" ;;
|
||||||
|
|
||||||
# Timeout errors
|
# Configuration errors (Proxmox config, invalid args)
|
||||||
124 | 28 | 211) echo "timeout" ;;
|
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||||
|
|
||||||
# Configuration errors
|
# Proxmox container/template errors
|
||||||
203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
200 | 209 | 210 | 212 | 213 | 215 | 216 | 218 | 220 | 221 | 222 | 223 | 225 | 231) echo "proxmox" ;;
|
||||||
|
|
||||||
|
# Service/Systemd errors
|
||||||
|
150 | 151 | 153 | 154) echo "service" ;;
|
||||||
|
|
||||||
|
# Database errors (PostgreSQL, MySQL, MongoDB)
|
||||||
|
170 | 171 | 172 | 173 | 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193) echo "database" ;;
|
||||||
|
|
||||||
|
# Node.js / JavaScript runtime errors
|
||||||
|
243 | 245 | 246 | 247 | 248 | 249) echo "runtime" ;;
|
||||||
|
|
||||||
|
# Python environment errors
|
||||||
|
# (already covered: 160-162 under dependency)
|
||||||
|
|
||||||
# Aborted by user
|
# Aborted by user
|
||||||
130) echo "aborted" ;;
|
130) echo "aborted" ;;
|
||||||
|
|
||||||
# Resource errors (OOM, etc)
|
# Resource errors (OOM, SIGKILL, SIGABRT)
|
||||||
137 | 134) echo "resource" ;;
|
134 | 137) echo "resource" ;;
|
||||||
|
|
||||||
# Default
|
# Signal/Process errors (SIGTERM, SIGPIPE, SIGSEGV)
|
||||||
|
129 | 139 | 141 | 143) echo "signal" ;;
|
||||||
|
|
||||||
|
# Shell errors (general error, syntax error)
|
||||||
|
1 | 2) echo "shell" ;;
|
||||||
|
|
||||||
|
# Default - truly unknown
|
||||||
*) echo "unknown" ;;
|
*) echo "unknown" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@ -901,11 +981,9 @@ post_tool_to_api() {
|
|||||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||||
local error_text=""
|
local error_text=""
|
||||||
error_text=$(get_error_text)
|
error_text=$(get_error_text)
|
||||||
if [[ -n "$error_text" ]]; then
|
local full_error
|
||||||
error=$(json_escape "$error_text")
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||||
else
|
error=$(json_escape "$full_error")
|
||||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
|
||||||
fi
|
|
||||||
error_category=$(categorize_error "$exit_code")
|
error_category=$(categorize_error "$exit_code")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -968,11 +1046,9 @@ post_addon_to_api() {
|
|||||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||||
local error_text=""
|
local error_text=""
|
||||||
error_text=$(get_error_text)
|
error_text=$(get_error_text)
|
||||||
if [[ -n "$error_text" ]]; then
|
local full_error
|
||||||
error=$(json_escape "$error_text")
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||||
else
|
error=$(json_escape "$full_error")
|
||||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
|
||||||
fi
|
|
||||||
error_category=$(categorize_error "$exit_code")
|
error_category=$(categorize_error "$exit_code")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -1067,11 +1143,9 @@ post_update_to_api_extended() {
|
|||||||
fi
|
fi
|
||||||
local error_text=""
|
local error_text=""
|
||||||
error_text=$(get_error_text)
|
error_text=$(get_error_text)
|
||||||
if [[ -n "$error_text" ]]; then
|
local full_error
|
||||||
error=$(json_escape "$error_text")
|
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||||
else
|
error=$(json_escape "$full_error")
|
||||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
|
||||||
fi
|
|
||||||
error_category=$(categorize_error "$exit_code")
|
error_category=$(categorize_error "$exit_code")
|
||||||
[[ -z "$error" ]] && error="Unknown error"
|
[[ -z "$error" ]] && error="Unknown error"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -1641,4 +1641,17 @@ function get_lxc_ip() {
|
|||||||
# SIGNAL TRAPS
|
# SIGNAL TRAPS
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# on_hup_keepalive()
|
||||||
|
#
|
||||||
|
# - SIGHUP (terminal hangup) trap handler
|
||||||
|
# - Keeps long-running scripts alive if terminal/SSH session disconnects
|
||||||
|
# - Stops spinner safely and writes warning to active log
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
on_hup_keepalive() {
|
||||||
|
stop_spinner
|
||||||
|
log_msg "[WARN] Received SIGHUP (terminal hangup). Continuing execution in background."
|
||||||
|
}
|
||||||
|
|
||||||
|
trap 'on_hup_keepalive' HUP
|
||||||
trap 'stop_spinner' EXIT INT TERM
|
trap 'stop_spinner' EXIT INT TERM
|
||||||
|
|||||||
@ -49,6 +49,7 @@ if ! declare -f explain_exit_code &>/dev/null; then
|
|||||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||||
127) echo "Command not found" ;;
|
127) echo "Command not found" ;;
|
||||||
128) echo "Invalid argument to exit" ;;
|
128) echo "Invalid argument to exit" ;;
|
||||||
|
129) echo "Killed by SIGHUP (terminal closed / hangup)" ;;
|
||||||
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
||||||
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
||||||
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||||
|
|||||||
226
tools/addon/cronmaster.sh
Normal file
226
tools/addon/cronmaster.sh
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2026 community-scripts ORG
|
||||||
|
# Author: MickLesk (CanbiZ)
|
||||||
|
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
||||||
|
# Source: https://github.com/fccview/cronmaster
|
||||||
|
|
||||||
|
if ! command -v curl &>/dev/null; then
|
||||||
|
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||||
|
apt-get update >/dev/null 2>&1
|
||||||
|
apt-get install -y curl >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/core.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/tools.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/error_handler.func)
|
||||||
|
|
||||||
|
# Enable error handling
|
||||||
|
set -Eeuo pipefail
|
||||||
|
trap 'error_handler' ERR
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ==============================================================================
|
||||||
|
APP="CronMaster"
|
||||||
|
APP_TYPE="addon"
|
||||||
|
INSTALL_PATH="/opt/cronmaster"
|
||||||
|
CONFIG_PATH="/opt/cronmaster/.env"
|
||||||
|
DEFAULT_PORT=3000
|
||||||
|
|
||||||
|
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||||
|
load_functions
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# HEADER
|
||||||
|
# ==============================================================================
|
||||||
|
function header_info {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
______ __ ___ __
|
||||||
|
/ ____/________ ____ / |/ /___ ______/ /____ _____
|
||||||
|
/ / / ___/ __ \/ __ \/ /|_/ / __ `/ ___/ __/ _ \/ ___/
|
||||||
|
/ /___/ / / /_/ / / / / / / / /_/ (__ ) /_/ __/ /
|
||||||
|
\____/_/ \____/_/ /_/_/ /_/\__,_/____/\__/\___/_/
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# OS DETECTION
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ -f "/etc/alpine-release" ]]; then
|
||||||
|
msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu."
|
||||||
|
exit 1
|
||||||
|
elif [[ -f "/etc/debian_version" ]]; then
|
||||||
|
OS="Debian"
|
||||||
|
SERVICE_PATH="/etc/systemd/system/cronmaster.service"
|
||||||
|
else
|
||||||
|
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# UNINSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function uninstall() {
|
||||||
|
msg_info "Uninstalling ${APP}"
|
||||||
|
systemctl disable --now cronmaster.service &>/dev/null || true
|
||||||
|
rm -f "$SERVICE_PATH"
|
||||||
|
rm -rf "$INSTALL_PATH"
|
||||||
|
rm -f "/usr/local/bin/update_cronmaster"
|
||||||
|
rm -f "$HOME/.cronmaster"
|
||||||
|
msg_ok "${APP} has been uninstalled"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# UPDATE
|
||||||
|
# ==============================================================================
|
||||||
|
function update() {
|
||||||
|
if check_for_gh_release "cronmaster" "fccview/cronmaster"; then
|
||||||
|
msg_info "Stopping service"
|
||||||
|
systemctl stop cronmaster.service &>/dev/null || true
|
||||||
|
msg_ok "Stopped service"
|
||||||
|
|
||||||
|
msg_info "Backing up configuration"
|
||||||
|
cp "$CONFIG_PATH" /tmp/cronmaster.env.bak 2>/dev/null || true
|
||||||
|
msg_ok "Backed up configuration"
|
||||||
|
|
||||||
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "cronmaster" "fccview/cronmaster" "prebuild" "latest" "$INSTALL_PATH" "cronmaster_*_prebuild.tar.gz"
|
||||||
|
|
||||||
|
msg_info "Restoring configuration"
|
||||||
|
cp /tmp/cronmaster.env.bak "$CONFIG_PATH" 2>/dev/null || true
|
||||||
|
rm -f /tmp/cronmaster.env.bak
|
||||||
|
msg_ok "Restored configuration"
|
||||||
|
|
||||||
|
msg_info "Starting service"
|
||||||
|
systemctl start cronmaster
|
||||||
|
msg_ok "Started service"
|
||||||
|
msg_ok "Updated successfully"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# INSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function install() {
|
||||||
|
# Setup Node.js (only installs if not present or different version)
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
msg_ok "Node.js already installed ($(node -v))"
|
||||||
|
else
|
||||||
|
NODE_VERSION="22" setup_nodejs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force fresh download by removing version cache
|
||||||
|
rm -f "$HOME/.cronmaster"
|
||||||
|
fetch_and_deploy_gh_release "cronmaster" "fccview/cronmaster" "prebuild" "latest" "$INSTALL_PATH" "cronmaster_*_prebuild.tar.gz"
|
||||||
|
|
||||||
|
local AUTH_PASS
|
||||||
|
AUTH_PASS="$(openssl rand -base64 18 | cut -c1-13)"
|
||||||
|
|
||||||
|
msg_info "Creating configuration"
|
||||||
|
cat <<EOF >"$CONFIG_PATH"
|
||||||
|
NODE_ENV=production
|
||||||
|
AUTH_PASSWORD=${AUTH_PASS}
|
||||||
|
PORT=${DEFAULT_PORT}
|
||||||
|
HOSTNAME=0.0.0.0
|
||||||
|
NEXT_TELEMETRY_DISABLED=1
|
||||||
|
EOF
|
||||||
|
chmod 600 "$CONFIG_PATH"
|
||||||
|
msg_ok "Created configuration"
|
||||||
|
|
||||||
|
msg_info "Creating service"
|
||||||
|
cat <<EOF >"$SERVICE_PATH"
|
||||||
|
[Unit]
|
||||||
|
Description=CronMaster Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=${INSTALL_PATH}
|
||||||
|
EnvironmentFile=${CONFIG_PATH}
|
||||||
|
ExecStart=/usr/bin/node ${INSTALL_PATH}/server.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-target.target
|
||||||
|
EOF
|
||||||
|
systemctl enable --now cronmaster &>/dev/null
|
||||||
|
msg_ok "Created and started service"
|
||||||
|
|
||||||
|
# Create update script
|
||||||
|
msg_info "Creating update script"
|
||||||
|
cat <<'UPDATEEOF' >/usr/local/bin/update_cronmaster
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# CronMaster Update Script
|
||||||
|
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/tools/addon/cronmaster.sh)"
|
||||||
|
UPDATEEOF
|
||||||
|
chmod +x /usr/local/bin/update_cronmaster
|
||||||
|
msg_ok "Created update script (/usr/local/bin/update_cronmaster)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||||
|
msg_ok "Password: ${BL}${AUTH_PASS}${CL}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# MAIN
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Handle type=update (called from update script)
|
||||||
|
if [[ "${type:-}" == "update" ]]; then
|
||||||
|
header_info
|
||||||
|
if [[ -d "$INSTALL_PATH" ]]; then
|
||||||
|
update
|
||||||
|
else
|
||||||
|
msg_error "${APP} is not installed. Nothing to update."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
header_info
|
||||||
|
get_lxc_ip
|
||||||
|
|
||||||
|
# Check if already installed
|
||||||
|
if [[ -d "$INSTALL_PATH" && -n "$(ls -A "$INSTALL_PATH" 2>/dev/null)" ]]; then
|
||||||
|
msg_warn "${APP} is already installed."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||||
|
read -r uninstall_prompt
|
||||||
|
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
uninstall
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||||
|
read -r update_prompt
|
||||||
|
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
update
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_warn "No action selected. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fresh installation
|
||||||
|
msg_warn "${APP} is not installed."
|
||||||
|
echo ""
|
||||||
|
echo -e "${TAB}${INFO} This will install:"
|
||||||
|
echo -e "${TAB} - Node.js 22"
|
||||||
|
echo -e "${TAB} - CronMaster (prebuild)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||||
|
read -r install_prompt
|
||||||
|
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
install
|
||||||
|
else
|
||||||
|
msg_warn "Installation cancelled. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
374
tools/addon/jellystat.sh
Normal file
374
tools/addon/jellystat.sh
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
#!/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/CyferShepard/Jellystat
|
||||||
|
|
||||||
|
if ! command -v curl &>/dev/null; then
|
||||||
|
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||||
|
apt-get update >/dev/null 2>&1
|
||||||
|
apt-get install -y curl >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||||
|
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||||
|
|
||||||
|
# Enable error handling
|
||||||
|
set -Eeuo pipefail
|
||||||
|
trap 'error_handler' ERR
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# CONFIGURATION
|
||||||
|
# ==============================================================================
|
||||||
|
APP="Jellystat"
|
||||||
|
APP_TYPE="addon"
|
||||||
|
INSTALL_PATH="/opt/jellystat"
|
||||||
|
CONFIG_PATH="/opt/jellystat/.env"
|
||||||
|
DEFAULT_PORT=3000
|
||||||
|
|
||||||
|
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||||
|
load_functions
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# HEADER
|
||||||
|
# ==============================================================================
|
||||||
|
function header_info {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
__ ____ __ __
|
||||||
|
/ /__ / / /_ _______/ /_____ _/ /_
|
||||||
|
__ / / _ \/ / / / / / ___/ __/ __ `/ __/
|
||||||
|
/ /_/ / __/ / / /_/ (__ ) /_/ /_/ / /_
|
||||||
|
\____/\___/_/_/\__, /____/\__/\__,_/\__/
|
||||||
|
/____/
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# OS DETECTION
|
||||||
|
# ==============================================================================
|
||||||
|
if [[ -f "/etc/alpine-release" ]]; then
|
||||||
|
msg_error "Alpine is not supported for ${APP}. Use Debian/Ubuntu."
|
||||||
|
exit 1
|
||||||
|
elif [[ -f "/etc/debian_version" ]]; then
|
||||||
|
OS="Debian"
|
||||||
|
SERVICE_PATH="/etc/systemd/system/jellystat.service"
|
||||||
|
else
|
||||||
|
echo -e "${CROSS} Unsupported OS detected. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# UNINSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function uninstall() {
|
||||||
|
msg_info "Uninstalling ${APP}"
|
||||||
|
systemctl disable --now jellystat.service &>/dev/null || true
|
||||||
|
rm -f "$SERVICE_PATH"
|
||||||
|
rm -rf "$INSTALL_PATH"
|
||||||
|
rm -f "/usr/local/bin/update_jellystat"
|
||||||
|
rm -f "$HOME/.jellystat"
|
||||||
|
msg_ok "${APP} has been uninstalled"
|
||||||
|
|
||||||
|
# Ask about PostgreSQL database removal
|
||||||
|
echo ""
|
||||||
|
echo -n "${TAB}Also remove PostgreSQL database 'jellystat'? (y/N): "
|
||||||
|
read -r db_prompt
|
||||||
|
if [[ "${db_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
if command -v psql &>/dev/null; then
|
||||||
|
msg_info "Removing PostgreSQL database and user"
|
||||||
|
$STD sudo -u postgres psql -c "DROP DATABASE IF EXISTS jellystat;" &>/dev/null || true
|
||||||
|
$STD sudo -u postgres psql -c "DROP USER IF EXISTS jellystat;" &>/dev/null || true
|
||||||
|
msg_ok "Removed PostgreSQL database 'jellystat' and user 'jellystat'"
|
||||||
|
else
|
||||||
|
msg_warn "PostgreSQL not found - database may have been removed already"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
msg_warn "PostgreSQL database was NOT removed. Remove manually if needed:"
|
||||||
|
echo -e "${TAB} sudo -u postgres psql -c \"DROP DATABASE jellystat;\""
|
||||||
|
echo -e "${TAB} sudo -u postgres psql -c \"DROP USER jellystat;\""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# UPDATE
|
||||||
|
# ==============================================================================
|
||||||
|
function update() {
|
||||||
|
if check_for_gh_release "jellystat" "CyferShepard/Jellystat"; then
|
||||||
|
msg_info "Stopping service"
|
||||||
|
systemctl stop jellystat.service &>/dev/null || true
|
||||||
|
msg_ok "Stopped service"
|
||||||
|
|
||||||
|
msg_info "Backing up configuration"
|
||||||
|
cp "$CONFIG_PATH" /tmp/jellystat.env.bak 2>/dev/null || true
|
||||||
|
msg_ok "Backed up configuration"
|
||||||
|
|
||||||
|
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "jellystat" "CyferShepard/Jellystat" "tarball" "latest" "$INSTALL_PATH"
|
||||||
|
|
||||||
|
msg_info "Restoring configuration"
|
||||||
|
cp /tmp/jellystat.env.bak "$CONFIG_PATH" 2>/dev/null || true
|
||||||
|
rm -f /tmp/jellystat.env.bak
|
||||||
|
msg_ok "Restored configuration"
|
||||||
|
|
||||||
|
msg_info "Installing dependencies"
|
||||||
|
cd "$INSTALL_PATH"
|
||||||
|
$STD npm install
|
||||||
|
msg_ok "Installed dependencies"
|
||||||
|
|
||||||
|
msg_info "Building ${APP}"
|
||||||
|
$STD npm run build
|
||||||
|
msg_ok "Built ${APP}"
|
||||||
|
|
||||||
|
msg_info "Starting service"
|
||||||
|
systemctl start jellystat
|
||||||
|
msg_ok "Started service"
|
||||||
|
msg_ok "Updated successfully"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# INSTALL
|
||||||
|
# ==============================================================================
|
||||||
|
function install() {
|
||||||
|
# Setup Node.js (only installs if not present or different version)
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
msg_ok "Node.js already installed ($(node -v))"
|
||||||
|
else
|
||||||
|
NODE_VERSION="22" setup_nodejs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setup PostgreSQL (only installs if not present)
|
||||||
|
if command -v psql &>/dev/null; then
|
||||||
|
msg_ok "PostgreSQL already installed"
|
||||||
|
else
|
||||||
|
PG_VERSION="17" setup_postgresql
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create database and user (skip if already exists)
|
||||||
|
local DB_NAME="jellystat"
|
||||||
|
local DB_USER="jellystat"
|
||||||
|
local DB_PASS
|
||||||
|
|
||||||
|
msg_info "Setting up PostgreSQL database"
|
||||||
|
|
||||||
|
# Check if database already exists
|
||||||
|
if sudo -u postgres psql -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw "$DB_NAME"; then
|
||||||
|
msg_warn "Database '${DB_NAME}' already exists - skipping creation"
|
||||||
|
echo -n "${TAB}Enter existing database password for '${DB_USER}': "
|
||||||
|
read -rs DB_PASS
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
# Generate new password
|
||||||
|
DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c16)
|
||||||
|
|
||||||
|
# Check if user exists, create if not
|
||||||
|
if sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${DB_USER}'" 2>/dev/null | grep -q 1; then
|
||||||
|
msg_info "User '${DB_USER}' exists, updating password"
|
||||||
|
$STD sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASS}';" || {
|
||||||
|
msg_error "Failed to update PostgreSQL user"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
else
|
||||||
|
$STD sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASS}';" || {
|
||||||
|
msg_error "Failed to create PostgreSQL user"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create database (use template0 for UTF8 encoding compatibility)
|
||||||
|
$STD sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} WITH OWNER ${DB_USER} ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE template0;" || {
|
||||||
|
msg_error "Failed to create PostgreSQL database"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
$STD sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" || {
|
||||||
|
msg_error "Failed to grant privileges"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grant schema permissions (required for PostgreSQL 15+)
|
||||||
|
$STD sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};" || true
|
||||||
|
|
||||||
|
# Configure pg_hba.conf for password authentication on localhost
|
||||||
|
local PG_HBA
|
||||||
|
PG_HBA=$(sudo -u postgres psql -tAc "SHOW hba_file;" 2>/dev/null | tr -d ' ')
|
||||||
|
if [[ -n "$PG_HBA" && -f "$PG_HBA" ]]; then
|
||||||
|
# Check if md5/scram-sha-256 auth is already configured for local connections
|
||||||
|
if ! grep -qE "^host\s+${DB_NAME}\s+${DB_USER}\s+127.0.0.1" "$PG_HBA"; then
|
||||||
|
msg_info "Configuring PostgreSQL authentication"
|
||||||
|
# Add password auth for jellystat user on localhost (before the default rules)
|
||||||
|
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} 127.0.0.1/32 scram-sha-256" "$PG_HBA"
|
||||||
|
sed -i "/^# IPv4 local connections:/a host ${DB_NAME} ${DB_USER} ::1/128 scram-sha-256" "$PG_HBA"
|
||||||
|
# Reload PostgreSQL to apply changes
|
||||||
|
systemctl reload postgresql
|
||||||
|
msg_ok "Configured PostgreSQL authentication"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Created PostgreSQL database '${DB_NAME}'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate JWT Secret
|
||||||
|
local JWT_SECRET
|
||||||
|
JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||||
|
|
||||||
|
# Force fresh download by removing version cache
|
||||||
|
rm -f "$HOME/.jellystat"
|
||||||
|
fetch_and_deploy_gh_release "jellystat" "CyferShepard/Jellystat" "tarball" "latest" "$INSTALL_PATH"
|
||||||
|
|
||||||
|
msg_info "Installing dependencies"
|
||||||
|
cd "$INSTALL_PATH"
|
||||||
|
$STD npm install
|
||||||
|
msg_ok "Installed dependencies"
|
||||||
|
|
||||||
|
msg_info "Building ${APP}"
|
||||||
|
$STD npm run build
|
||||||
|
msg_ok "Built ${APP}"
|
||||||
|
|
||||||
|
msg_info "Creating configuration"
|
||||||
|
cat <<EOF >"$CONFIG_PATH"
|
||||||
|
# Jellystat Configuration
|
||||||
|
# Database
|
||||||
|
POSTGRES_USER=${DB_USER}
|
||||||
|
POSTGRES_PASSWORD=${DB_PASS}
|
||||||
|
POSTGRES_IP=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=${DB_NAME}
|
||||||
|
|
||||||
|
# Security
|
||||||
|
JWT_SECRET=${JWT_SECRET}
|
||||||
|
|
||||||
|
# Server
|
||||||
|
JS_LISTEN_IP=0.0.0.0
|
||||||
|
JS_BASE_URL=/
|
||||||
|
TZ=$(cat /etc/timezone 2>/dev/null || echo "UTC")
|
||||||
|
|
||||||
|
# Optional: GeoLite for IP Geolocation
|
||||||
|
# JS_GEOLITE_ACCOUNT_ID=
|
||||||
|
# JS_GEOLITE_LICENSE_KEY=
|
||||||
|
|
||||||
|
# Optional: Master Override (if you forget your password)
|
||||||
|
# JS_USER=admin
|
||||||
|
# JS_PASSWORD=admin
|
||||||
|
|
||||||
|
# Optional: Minimum playback duration to record (seconds)
|
||||||
|
# MINIMUM_SECONDS_TO_INCLUDE_PLAYBACK=1
|
||||||
|
|
||||||
|
# Optional: Self-signed certificates
|
||||||
|
REJECT_SELF_SIGNED_CERTIFICATES=true
|
||||||
|
EOF
|
||||||
|
chmod 600 "$CONFIG_PATH"
|
||||||
|
msg_ok "Created configuration"
|
||||||
|
|
||||||
|
msg_info "Creating service"
|
||||||
|
cat <<EOF >"$SERVICE_PATH"
|
||||||
|
[Unit]
|
||||||
|
Description=Jellystat - Statistics for Jellyfin
|
||||||
|
After=network.target postgresql.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=${INSTALL_PATH}/backend
|
||||||
|
EnvironmentFile=${CONFIG_PATH}
|
||||||
|
ExecStart=/usr/bin/node ${INSTALL_PATH}/backend/server.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
systemctl enable --now jellystat &>/dev/null
|
||||||
|
msg_ok "Created and started service"
|
||||||
|
|
||||||
|
# Create update script (simple wrapper that calls this addon with type=update)
|
||||||
|
msg_info "Creating update script"
|
||||||
|
cat <<'UPDATEEOF' >/usr/local/bin/update_jellystat
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Jellystat Update Script
|
||||||
|
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/jellystat.sh)"
|
||||||
|
UPDATEEOF
|
||||||
|
chmod +x /usr/local/bin/update_jellystat
|
||||||
|
msg_ok "Created update script (/usr/local/bin/update_jellystat)"
|
||||||
|
|
||||||
|
# Save credentials
|
||||||
|
local CREDS_FILE="/root/jellystat.creds"
|
||||||
|
cat <<EOF >"$CREDS_FILE"
|
||||||
|
Jellystat Credentials
|
||||||
|
=====================
|
||||||
|
Database User: ${DB_USER}
|
||||||
|
Database Password: ${DB_PASS}
|
||||||
|
Database Name: ${DB_NAME}
|
||||||
|
JWT Secret: ${JWT_SECRET}
|
||||||
|
|
||||||
|
Web UI: http://${LOCAL_IP}:${DEFAULT_PORT}
|
||||||
|
EOF
|
||||||
|
chmod 600 "$CREDS_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||||
|
msg_ok "Credentials saved to: ${BL}${CREDS_FILE}${CL}"
|
||||||
|
echo ""
|
||||||
|
msg_warn "On first access, you'll need to configure your Jellyfin server connection."
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==============================================================================
|
||||||
|
# MAIN
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# Handle type=update (called from update script)
|
||||||
|
if [[ "${type:-}" == "update" ]]; then
|
||||||
|
header_info
|
||||||
|
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/package.json" ]]; then
|
||||||
|
update
|
||||||
|
else
|
||||||
|
msg_error "${APP} is not installed. Nothing to update."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
header_info
|
||||||
|
get_lxc_ip
|
||||||
|
|
||||||
|
# Check if already installed
|
||||||
|
if [[ -d "$INSTALL_PATH" && -f "$INSTALL_PATH/package.json" ]]; then
|
||||||
|
msg_warn "${APP} is already installed."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||||
|
read -r uninstall_prompt
|
||||||
|
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
uninstall
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||||
|
read -r update_prompt
|
||||||
|
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
update
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_warn "No action selected. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fresh installation
|
||||||
|
msg_warn "${APP} is not installed."
|
||||||
|
echo ""
|
||||||
|
echo -e "${TAB}${INFO} This will install:"
|
||||||
|
echo -e "${TAB} - Node.js 22"
|
||||||
|
echo -e "${TAB} - PostgreSQL 17"
|
||||||
|
echo -e "${TAB} - Jellystat"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||||
|
read -r install_prompt
|
||||||
|
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||||
|
install
|
||||||
|
else
|
||||||
|
msg_warn "Installation cancelled. Exiting."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
361
tools/pve/dependency-check copy.sh
Normal file
361
tools/pve/dependency-check copy.sh
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Copyright (c) 2023 community-scripts ORG
|
||||||
|
# This script is designed to install the Proxmox Dependency Check Hookscript.
|
||||||
|
# It sets up a dependency-checking hookscript and automates its
|
||||||
|
# application to all new and existing guests using a systemd watcher.
|
||||||
|
# License: MIT
|
||||||
|
|
||||||
|
function header_info {
|
||||||
|
clear
|
||||||
|
cat <<"EOF"
|
||||||
|
____ _ ____ _ _
|
||||||
|
| _ \ ___ _ __ ___ _ __ __| | ___ _ __ ___ _ _ / ___| |__ ___ ___| | __
|
||||||
|
| | | |/ _ \ '_ \ / _ \ '_ \ / _` |/ _ \ '_ \ / __| | | | | | '_ \ / _ \/ __| |/ /
|
||||||
|
| |_| | __/ |_) | __/ | | | (_| | __/ | | | (__| |_| | |___| | | | __/ (__| <
|
||||||
|
|____/ \___| .__/ \___|_| |_|\__,_|\___|_| |_|\___|\__, |\____|_| |_|\___|\___|_|\_\
|
||||||
|
|_| |___/
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Color variables
|
||||||
|
YW=$(echo "\033[33m")
|
||||||
|
GN=$(echo "\033[1;92m")
|
||||||
|
RD=$(echo "\033[01;31m")
|
||||||
|
CL=$(echo "\033[m")
|
||||||
|
BFR="\\r\\033[K"
|
||||||
|
HOLD=" "
|
||||||
|
CM="${GN}✓${CL}"
|
||||||
|
CROSS="${RD}✗${CL}"
|
||||||
|
|
||||||
|
# Spinner for progress indication (simplified)
|
||||||
|
spinner() {
|
||||||
|
local pid=$!
|
||||||
|
local delay=0.1
|
||||||
|
local spinstr='|/-\'
|
||||||
|
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
||||||
|
local temp=${spinstr#?}
|
||||||
|
printf " [%c] " "$spinstr"
|
||||||
|
local spinstr=$temp${spinstr%"$temp"}
|
||||||
|
sleep $delay
|
||||||
|
printf "\b\b\b\b\b\b"
|
||||||
|
done
|
||||||
|
printf " \b\b\b\b"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message functions
|
||||||
|
msg_info() {
|
||||||
|
echo -ne " ${YW}›${CL} $1..."
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_ok() {
|
||||||
|
echo -e "${BFR} ${CM} $1${CL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
msg_error() {
|
||||||
|
echo -e "${BFR} ${CROSS} $1${CL}"
|
||||||
|
}
|
||||||
|
# --- End of base script functions ---
|
||||||
|
|
||||||
|
# --- Installation Functions ---
|
||||||
|
|
||||||
|
# Function to create the actual hookscript that runs before guest startup
|
||||||
|
create_dependency_hookscript() {
|
||||||
|
msg_info "Creating dependency-check hookscript"
|
||||||
|
mkdir -p /var/lib/vz/snippets
|
||||||
|
cat <<'EOF' >/var/lib/vz/snippets/dependency-check.sh
|
||||||
|
#!/bin/bash
|
||||||
|
# Proxmox Hookscript for Pre-Start Dependency Checking
|
||||||
|
# Works for both QEMU VMs and LXC Containers
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
POLL_INTERVAL=5 # Seconds to wait between checks
|
||||||
|
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
||||||
|
# --- End Configuration ---
|
||||||
|
|
||||||
|
VMID=$1
|
||||||
|
PHASE=$2
|
||||||
|
|
||||||
|
# Function for logging to syslog with a consistent format
|
||||||
|
log() {
|
||||||
|
echo "[hookscript-dep-check] VMID $VMID: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# This script only runs in the 'pre-start' phase
|
||||||
|
if [ "$PHASE" != "pre-start" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "--- Starting Pre-Start Dependency Check ---"
|
||||||
|
|
||||||
|
# --- Determine Guest Type (QEMU or LXC) ---
|
||||||
|
GUEST_TYPE=""
|
||||||
|
CONFIG_CMD=""
|
||||||
|
if qm config "$VMID" >/dev/null 2>&1; then
|
||||||
|
GUEST_TYPE="qemu"
|
||||||
|
CONFIG_CMD="qm config"
|
||||||
|
log "Guest type is QEMU (VM)."
|
||||||
|
elif pct config "$VMID" >/dev/null 2>&1; then
|
||||||
|
GUEST_TYPE="lxc"
|
||||||
|
CONFIG_CMD="pct config"
|
||||||
|
log "Guest type is LXC (Container)."
|
||||||
|
else
|
||||||
|
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GUEST_CONFIG=$($CONFIG_CMD "$VMID")
|
||||||
|
|
||||||
|
# --- 1. Storage Availability Check ---
|
||||||
|
log "Checking storage availability..."
|
||||||
|
# Grep for all disk definitions (scsi, sata, virtio, ide, rootfs, mp)
|
||||||
|
# and extract the storage identifier (the field between the colons).
|
||||||
|
# Sort -u gets the unique list of storage pools.
|
||||||
|
STORAGE_IDS=$(echo "$GUEST_CONFIG" | grep -E '^(scsi|sata|virtio|ide|rootfs|mp)[0-9]*:' | awk -F'[:]' '{print $2}' | awk '{print$1}' | sort -u)
|
||||||
|
|
||||||
|
if [ -z "$STORAGE_IDS" ]; then
|
||||||
|
log "No storage dependencies found to check."
|
||||||
|
else
|
||||||
|
for STORAGE_ID in $STORAGE_IDS; do
|
||||||
|
log "Checking status of storage: '$STORAGE_ID'"
|
||||||
|
ATTEMPTS=0
|
||||||
|
while true; do
|
||||||
|
# Grep for the storage ID line in pvesm status and check the 'Active' column (3rd column)
|
||||||
|
STATUS=$(pvesm status | grep "^\s*$STORAGE_ID\s" | awk '{print $3}')
|
||||||
|
if [ "$STATUS" == "active" ]; then
|
||||||
|
log "Storage '$STORAGE_ID' is active."
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTEMPTS=$((ATTEMPTS + 1))
|
||||||
|
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
||||||
|
log "ERROR: Timeout waiting for storage '$STORAGE_ID' to become active. Aborting start."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Storage '$STORAGE_ID' is not active (current status: '${STATUS:-inactive/unknown}'). Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
log "All storage dependencies are met."
|
||||||
|
|
||||||
|
|
||||||
|
# --- 2. Custom Tag-Based Dependency Check ---
|
||||||
|
log "Checking for custom tag-based dependencies..."
|
||||||
|
TAGS=$(echo "$GUEST_CONFIG" | grep '^tags:' | awk '{print $2}')
|
||||||
|
|
||||||
|
if [ -z "$TAGS" ]; then
|
||||||
|
log "No tags found. Skipping custom dependency check."
|
||||||
|
else
|
||||||
|
# Replace colons with spaces to loop through tags
|
||||||
|
for TAG in ${TAGS//;/ }; do
|
||||||
|
# Check if the tag matches our dependency format 'dep_*'
|
||||||
|
if [[ $TAG == dep_* ]]; then
|
||||||
|
log "Found dependency tag: '$TAG'"
|
||||||
|
|
||||||
|
# Split tag into parts using underscore as delimiter
|
||||||
|
IFS='_' read -ra PARTS <<< "$TAG"
|
||||||
|
DEP_TYPE="${PARTS[1]}"
|
||||||
|
|
||||||
|
ATTEMPTS=0
|
||||||
|
while true; do
|
||||||
|
CHECK_PASSED=false
|
||||||
|
case "$DEP_TYPE" in
|
||||||
|
"tcp")
|
||||||
|
HOST="${PARTS[2]}"
|
||||||
|
PORT="${PARTS[3]}"
|
||||||
|
if [ -z "$HOST" ] || [ -z "$PORT" ]; then
|
||||||
|
log "ERROR: Malformed TCP dependency tag '$TAG'. Skipping."
|
||||||
|
CHECK_PASSED=true # Skip to avoid infinite loop
|
||||||
|
# nc -z is great for this. -w sets a timeout.
|
||||||
|
elif nc -z -w 2 "$HOST" "$PORT"; then
|
||||||
|
log "TCP dependency met: Host $HOST port $PORT is open."
|
||||||
|
CHECK_PASSED=true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
"ping")
|
||||||
|
HOST="${PARTS[2]}"
|
||||||
|
if [ -z "$HOST" ]; then
|
||||||
|
log "ERROR: Malformed PING dependency tag '$TAG'. Skipping."
|
||||||
|
CHECK_PASSED=true # Skip to avoid infinite loop
|
||||||
|
# ping -c 1 (one packet) -W 2 (2-second timeout)
|
||||||
|
elif ping -c 1 -W 2 "$HOST" >/dev/null 2>&1; then
|
||||||
|
log "Ping dependency met: Host $HOST is reachable."
|
||||||
|
CHECK_PASSED=true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
||||||
|
CHECK_PASSED=true # Mark as passed to avoid getting stuck
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if $CHECK_PASSED; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
ATTEMPTS=$((ATTEMPTS + 1))
|
||||||
|
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
||||||
|
log "ERROR: Timeout waiting for dependency '$TAG'. Aborting start."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Dependency '$TAG' not met. Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "All custom dependencies are met."
|
||||||
|
log "--- Dependency Check Complete. Proceeding with start. ---"
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x /var/lib/vz/snippets/dependency-check.sh
|
||||||
|
msg_ok "Created dependency-check hookscript"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create the config file for exclusions
|
||||||
|
create_exclusion_config() {
|
||||||
|
msg_info "Creating exclusion configuration file"
|
||||||
|
if [ -f /etc/default/pve-auto-hook ]; then
|
||||||
|
msg_ok "Exclusion file already exists, skipping."
|
||||||
|
else
|
||||||
|
cat <<'EOF' >/etc/default/pve-auto-hook
|
||||||
|
#
|
||||||
|
# Configuration for the Proxmox Automatic Hookscript Applicator
|
||||||
|
#
|
||||||
|
# Add VM or LXC IDs here to prevent the hookscript from being added.
|
||||||
|
# Separate IDs with spaces.
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# IGNORE_IDS="9000 9001 105"
|
||||||
|
#
|
||||||
|
|
||||||
|
IGNORE_IDS=""
|
||||||
|
EOF
|
||||||
|
msg_ok "Created exclusion configuration file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create the script that applies the hook
|
||||||
|
create_applicator_script() {
|
||||||
|
msg_info "Creating the hookscript applicator script"
|
||||||
|
cat <<'EOF' >/usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
#!/bin/bash
|
||||||
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
|
LOG_TAG="pve-auto-hook-list"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
systemd-cat -t "$LOG_TAG" <<< "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Process QEMU VMs
|
||||||
|
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
is_ignored=false
|
||||||
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
|
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
||||||
|
done
|
||||||
|
if $is_ignored; then continue; fi
|
||||||
|
if qm config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
||||||
|
log "Hookscript not found for VM $VMID. Applying..."
|
||||||
|
qm set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Process LXC Containers
|
||||||
|
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
is_ignored=false
|
||||||
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
|
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
||||||
|
done
|
||||||
|
if $is_ignored; then continue; fi
|
||||||
|
if pct config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
||||||
|
log "Hookscript not found for LXC $VMID. Applying..."
|
||||||
|
pct set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
||||||
|
done
|
||||||
|
EOF
|
||||||
|
chmod +x /usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
msg_ok "Created applicator script"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to set up the systemd watcher and service
|
||||||
|
create_systemd_units() {
|
||||||
|
msg_info "Creating systemd watcher and service units"
|
||||||
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.path
|
||||||
|
[Unit]
|
||||||
|
Description=Watch for new Proxmox guest configs to apply hookscript
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathModified=/etc/pve/qemu-server/
|
||||||
|
PathModified=/etc/pve/lxc/
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.service
|
||||||
|
[Unit]
|
||||||
|
Description=Automatically add hookscript to new Proxmox guests
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
||||||
|
EOF
|
||||||
|
msg_ok "Created systemd units"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main Execution ---
|
||||||
|
header_info
|
||||||
|
|
||||||
|
if ! command -v pveversion >/dev/null 2>&1; then
|
||||||
|
msg_error "This script must be run on a Proxmox VE host."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\nThis script will install a service to automatically apply a"
|
||||||
|
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
||||||
|
echo -e "${YW}This includes creating files in:${CL}"
|
||||||
|
echo -e " - /var/lib/vz/snippets/"
|
||||||
|
echo -e " - /usr/local/bin/"
|
||||||
|
echo -e " - /etc/default/"
|
||||||
|
echo -e " - /etc/systemd/system/\n"
|
||||||
|
|
||||||
|
read -p "Do you want to proceed with the installation? (y/n): " -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
msg_error "Installation cancelled."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n"
|
||||||
|
create_dependency_hookscript
|
||||||
|
create_exclusion_config
|
||||||
|
create_applicator_script
|
||||||
|
create_systemd_units
|
||||||
|
|
||||||
|
msg_info "Reloading systemd and enabling the watcher"
|
||||||
|
(systemctl daemon-reload && systemctl enable --now pve-auto-hook.path) >/dev/null 2>&1 &
|
||||||
|
spinner
|
||||||
|
msg_ok "Systemd watcher enabled and running"
|
||||||
|
|
||||||
|
msg_info "Performing initial run to update existing guests"
|
||||||
|
/usr/local/bin/pve-apply-hookscript.sh >/dev/null 2>&1 &
|
||||||
|
spinner
|
||||||
|
msg_ok "Initial run complete"
|
||||||
|
|
||||||
|
echo -e "\n\n${GN}Installation successful!${CL}"
|
||||||
|
echo -e "The service is now active and will monitor for new guests."
|
||||||
|
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to the ${YW}IGNORE_IDS${CL} variable in:"
|
||||||
|
echo -e " ${YW}/etc/default/pve-auto-hook${CL}"
|
||||||
|
echo -e "\nYou can monitor the service's activity with:"
|
||||||
|
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
||||||
|
|
||||||
|
exit 0
|
||||||
@ -1,10 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Copyright (c) 2023 community-scripts ORG
|
# Copyright (c) 2023-2026 community-scripts ORG
|
||||||
# This script is designed to install the Proxmox Dependency Check Hookscript.
|
# Author: MickLesk | Maintainer: community-scripts
|
||||||
# It sets up a dependency-checking hookscript and automates its
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||||
# application to all new and existing guests using a systemd watcher.
|
# Source: https://www.proxmox.com/
|
||||||
# License: MIT
|
|
||||||
|
|
||||||
function header_info {
|
function header_info {
|
||||||
clear
|
clear
|
||||||
@ -18,49 +17,83 @@ function header_info {
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Color variables
|
|
||||||
YW=$(echo "\033[33m")
|
YW=$(echo "\033[33m")
|
||||||
GN=$(echo "\033[1;92m")
|
GN=$(echo "\033[1;92m")
|
||||||
RD=$(echo "\033[01;31m")
|
RD=$(echo "\033[01;31m")
|
||||||
|
BL=$(echo "\033[36m")
|
||||||
CL=$(echo "\033[m")
|
CL=$(echo "\033[m")
|
||||||
BFR="\\r\\033[K"
|
BFR="\\r\\033[K"
|
||||||
HOLD=" "
|
CM="${GN}✔️${CL}"
|
||||||
CM="${GN}✓${CL}"
|
CROSS="${RD}✖️${CL}"
|
||||||
CROSS="${RD}✗${CL}"
|
INFO="${BL}ℹ️${CL}"
|
||||||
|
|
||||||
# Spinner for progress indication (simplified)
|
|
||||||
spinner() {
|
|
||||||
local pid=$!
|
|
||||||
local delay=0.1
|
|
||||||
local spinstr='|/-\'
|
|
||||||
while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
|
|
||||||
local temp=${spinstr#?}
|
|
||||||
printf " [%c] " "$spinstr"
|
|
||||||
local spinstr=$temp${spinstr%"$temp"}
|
|
||||||
sleep $delay
|
|
||||||
printf "\b\b\b\b\b\b"
|
|
||||||
done
|
|
||||||
printf " \b\b\b\b"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Message functions
|
|
||||||
msg_info() {
|
msg_info() {
|
||||||
echo -ne " ${YW}›${CL} $1..."
|
local msg="$1"
|
||||||
|
echo -e "${INFO} ${YW}${msg}...${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_ok() {
|
msg_ok() {
|
||||||
echo -e "${BFR} ${CM} $1${CL}"
|
local msg="$1"
|
||||||
|
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_error() {
|
msg_error() {
|
||||||
echo -e "${BFR} ${CROSS} $1${CL}"
|
local msg="$1"
|
||||||
|
echo -e "${BFR} ${CROSS} ${RD}${msg}${CL}"
|
||||||
}
|
}
|
||||||
# --- End of base script functions ---
|
|
||||||
|
|
||||||
|
SCRIPT_NAME="$(basename "$0")"
|
||||||
|
HOOKSCRIPT_FILE="/var/lib/vz/snippets/dependency-check.sh"
|
||||||
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
|
APPLICATOR_FILE="/usr/local/bin/pve-apply-hookscript.sh"
|
||||||
|
PATH_UNIT_FILE="/etc/systemd/system/pve-auto-hook.path"
|
||||||
|
SERVICE_UNIT_FILE="/etc/systemd/system/pve-auto-hook.service"
|
||||||
|
|
||||||
# --- Installation Functions ---
|
function print_usage {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: ${SCRIPT_NAME} [OPTIONS]
|
||||||
|
|
||||||
|
Install or remove the Proxmox startup dependency-check hook system.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--install Install/update hookscript automation (default)
|
||||||
|
--uninstall Remove automation and cleanup hookscript assignments
|
||||||
|
--status Show current installation state
|
||||||
|
--help, -h Show this help message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensure_supported_pve {
|
||||||
|
if ! command -v pveversion >/dev/null 2>&1; then
|
||||||
|
msg_error "This script must be run on a Proxmox VE host"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pve_version major
|
||||||
|
pve_version=$(pveversion | grep -oE 'pve-manager/[0-9.]+' | cut -d'/' -f2)
|
||||||
|
major=$(echo "$pve_version" | cut -d'.' -f1)
|
||||||
|
|
||||||
|
if [[ -z "$major" ]] || ! [[ "$major" =~ ^[0-9]+$ ]]; then
|
||||||
|
msg_error "Unable to detect a supported Proxmox version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$major" -lt 8 ]] || [[ "$major" -gt 9 ]]; then
|
||||||
|
msg_error "Supported on Proxmox VE 8.x and 9.x (detected: $pve_version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Proxmox VE $pve_version detected"
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm_action {
|
||||||
|
local prompt="$1"
|
||||||
|
read -r -p "$prompt (y/n): " -n 1 REPLY
|
||||||
|
echo
|
||||||
|
[[ "$REPLY" =~ ^[Yy]$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
# Function to create the actual hookscript that runs before guest startup
|
|
||||||
create_dependency_hookscript() {
|
create_dependency_hookscript() {
|
||||||
msg_info "Creating dependency-check hookscript"
|
msg_info "Creating dependency-check hookscript"
|
||||||
mkdir -p /var/lib/vz/snippets
|
mkdir -p /var/lib/vz/snippets
|
||||||
@ -69,144 +102,135 @@ create_dependency_hookscript() {
|
|||||||
# Proxmox Hookscript for Pre-Start Dependency Checking
|
# Proxmox Hookscript for Pre-Start Dependency Checking
|
||||||
# Works for both QEMU VMs and LXC Containers
|
# Works for both QEMU VMs and LXC Containers
|
||||||
|
|
||||||
# --- Configuration ---
|
|
||||||
POLL_INTERVAL=5 # Seconds to wait between checks
|
POLL_INTERVAL=5 # Seconds to wait between checks
|
||||||
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
MAX_ATTEMPTS=60 # Max number of attempts before failing (60 * 5s = 5 minutes)
|
||||||
# --- End Configuration ---
|
|
||||||
|
|
||||||
VMID=$1
|
VMID=$1
|
||||||
PHASE=$2
|
PHASE=$2
|
||||||
|
|
||||||
# Function for logging to syslog with a consistent format
|
|
||||||
log() {
|
log() {
|
||||||
echo "[hookscript-dep-check] VMID $VMID: $1"
|
logger -t hookscript-dep-check "VMID $VMID: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
has_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_tcp() {
|
||||||
|
local host="$1"
|
||||||
|
local port="$2"
|
||||||
|
|
||||||
|
if has_cmd nc; then
|
||||||
|
nc -z -w 2 "$host" "$port" >/dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
timeout 2 bash -c "</dev/tcp/${host}/${port}" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_until() {
|
||||||
|
local description="$1"
|
||||||
|
local check_cmd="$2"
|
||||||
|
|
||||||
|
local attempts=0
|
||||||
|
while true; do
|
||||||
|
if eval "$check_cmd"; then
|
||||||
|
log "$description"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
attempts=$((attempts + 1))
|
||||||
|
if [ "$attempts" -ge "$MAX_ATTEMPTS" ]; then
|
||||||
|
log "ERROR: Timeout waiting for condition: $description"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Waiting ${POLL_INTERVAL}s for condition: $description (Attempt ${attempts}/${MAX_ATTEMPTS})"
|
||||||
|
sleep "$POLL_INTERVAL"
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# This script only runs in the 'pre-start' phase
|
|
||||||
if [ "$PHASE" != "pre-start" ]; then
|
if [ "$PHASE" != "pre-start" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "--- Starting Pre-Start Dependency Check ---"
|
log "--- Starting Pre-Start Dependency Check ---"
|
||||||
|
|
||||||
# --- Determine Guest Type (QEMU or LXC) ---
|
|
||||||
GUEST_TYPE=""
|
|
||||||
CONFIG_CMD=""
|
|
||||||
if qm config "$VMID" >/dev/null 2>&1; then
|
if qm config "$VMID" >/dev/null 2>&1; then
|
||||||
GUEST_TYPE="qemu"
|
CONFIG_CMD=(qm config "$VMID")
|
||||||
CONFIG_CMD="qm config"
|
|
||||||
log "Guest type is QEMU (VM)."
|
log "Guest type is QEMU (VM)."
|
||||||
elif pct config "$VMID" >/dev/null 2>&1; then
|
elif pct config "$VMID" >/dev/null 2>&1; then
|
||||||
GUEST_TYPE="lxc"
|
CONFIG_CMD=(pct config "$VMID")
|
||||||
CONFIG_CMD="pct config"
|
|
||||||
log "Guest type is LXC (Container)."
|
log "Guest type is LXC (Container)."
|
||||||
else
|
else
|
||||||
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
log "ERROR: Could not determine guest type for $VMID. Aborting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
GUEST_CONFIG=$($CONFIG_CMD "$VMID")
|
GUEST_CONFIG=$("${CONFIG_CMD[@]}")
|
||||||
|
|
||||||
# --- 1. Storage Availability Check ---
|
|
||||||
log "Checking storage availability..."
|
log "Checking storage availability..."
|
||||||
# Grep for all disk definitions (scsi, sata, virtio, ide, rootfs, mp)
|
STORAGE_IDS=$(echo "$GUEST_CONFIG" | awk -F':' '
|
||||||
# and extract the storage identifier (the field between the colons).
|
/^(scsi|sata|virtio|ide|efidisk|tpmstate|unused|rootfs|mp)[0-9]*:/ {
|
||||||
# Sort -u gets the unique list of storage pools.
|
val=$2
|
||||||
STORAGE_IDS=$(echo "$GUEST_CONFIG" | grep -E '^(scsi|sata|virtio|ide|rootfs|mp)[0-9]*:' | awk -F'[:]' '{print $2}' | awk '{print$1}' | sort -u)
|
gsub(/^[[:space:]]+/, "", val)
|
||||||
|
split(val, parts, ",")
|
||||||
|
storage=parts[1]
|
||||||
|
|
||||||
|
# Skip bind-mount style paths and empty values
|
||||||
|
if (storage == "" || storage ~ /^\//) next
|
||||||
|
|
||||||
|
print storage
|
||||||
|
}
|
||||||
|
' | sort -u)
|
||||||
|
|
||||||
if [ -z "$STORAGE_IDS" ]; then
|
if [ -z "$STORAGE_IDS" ]; then
|
||||||
log "No storage dependencies found to check."
|
log "No storage dependencies found to check."
|
||||||
else
|
else
|
||||||
for STORAGE_ID in $STORAGE_IDS; do
|
for STORAGE_ID in $STORAGE_IDS; do
|
||||||
log "Checking status of storage: '$STORAGE_ID'"
|
STATUS=$(pvesm status 2>/dev/null | awk -v id="$STORAGE_ID" '$1 == id { print $3; exit }')
|
||||||
ATTEMPTS=0
|
|
||||||
while true; do
|
if [ -z "$STATUS" ]; then
|
||||||
# Grep for the storage ID line in pvesm status and check the 'Active' column (3rd column)
|
log "WARNING: Storage '$STORAGE_ID' not found in 'pvesm status'. Skipping this dependency."
|
||||||
STATUS=$(pvesm status | grep "^\s*$STORAGE_ID\s" | awk '{print $3}')
|
continue
|
||||||
if [ "$STATUS" == "active" ]; then
|
|
||||||
log "Storage '$STORAGE_ID' is active."
|
|
||||||
break
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ATTEMPTS=$((ATTEMPTS + 1))
|
wait_until "Storage '$STORAGE_ID' is active." "[ \"\$(pvesm status 2>/dev/null | awk -v id=\"$STORAGE_ID\" '\$1 == id { print \$3; exit }')\" = \"active\" ]" || exit 1
|
||||||
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
|
||||||
log "ERROR: Timeout waiting for storage '$STORAGE_ID' to become active. Aborting start."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Storage '$STORAGE_ID' is not active (current status: '${STATUS:-inactive/unknown}'). Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
|
||||||
sleep $POLL_INTERVAL
|
|
||||||
done
|
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
log "All storage dependencies are met."
|
log "All storage dependencies are met."
|
||||||
|
|
||||||
|
|
||||||
# --- 2. Custom Tag-Based Dependency Check ---
|
|
||||||
log "Checking for custom tag-based dependencies..."
|
log "Checking for custom tag-based dependencies..."
|
||||||
TAGS=$(echo "$GUEST_CONFIG" | grep '^tags:' | awk '{print $2}')
|
TAGS=$(echo "$GUEST_CONFIG" | awk -F': ' '/^tags:/ {print $2}')
|
||||||
|
|
||||||
if [ -z "$TAGS" ]; then
|
if [ -z "$TAGS" ]; then
|
||||||
log "No tags found. Skipping custom dependency check."
|
log "No tags found. Skipping custom dependency check."
|
||||||
else
|
else
|
||||||
# Replace colons with spaces to loop through tags
|
|
||||||
for TAG in ${TAGS//;/ }; do
|
for TAG in ${TAGS//;/ }; do
|
||||||
# Check if the tag matches our dependency format 'dep_*'
|
|
||||||
if [[ $TAG == dep_* ]]; then
|
if [[ $TAG == dep_* ]]; then
|
||||||
log "Found dependency tag: '$TAG'"
|
log "Found dependency tag: '$TAG'"
|
||||||
|
|
||||||
# Split tag into parts using underscore as delimiter
|
IFS='_' read -r _ DEP_TYPE HOST PORT EXTRA <<< "$TAG"
|
||||||
IFS='_' read -ra PARTS <<< "$TAG"
|
|
||||||
DEP_TYPE="${PARTS[1]}"
|
|
||||||
|
|
||||||
ATTEMPTS=0
|
|
||||||
while true; do
|
|
||||||
CHECK_PASSED=false
|
|
||||||
case "$DEP_TYPE" in
|
case "$DEP_TYPE" in
|
||||||
"tcp")
|
ping)
|
||||||
HOST="${PARTS[2]}"
|
|
||||||
PORT="${PARTS[3]}"
|
|
||||||
if [ -z "$HOST" ] || [ -z "$PORT" ]; then
|
|
||||||
log "ERROR: Malformed TCP dependency tag '$TAG'. Skipping."
|
|
||||||
CHECK_PASSED=true # Skip to avoid infinite loop
|
|
||||||
# nc -z is great for this. -w sets a timeout.
|
|
||||||
elif nc -z -w 2 "$HOST" "$PORT"; then
|
|
||||||
log "TCP dependency met: Host $HOST port $PORT is open."
|
|
||||||
CHECK_PASSED=true
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
|
|
||||||
"ping")
|
|
||||||
HOST="${PARTS[2]}"
|
|
||||||
if [ -z "$HOST" ]; then
|
if [ -z "$HOST" ]; then
|
||||||
log "ERROR: Malformed PING dependency tag '$TAG'. Skipping."
|
log "WARNING: Malformed ping dependency tag '$TAG'. Ignoring."
|
||||||
CHECK_PASSED=true # Skip to avoid infinite loop
|
continue
|
||||||
# ping -c 1 (one packet) -W 2 (2-second timeout)
|
|
||||||
elif ping -c 1 -W 2 "$HOST" >/dev/null 2>&1; then
|
|
||||||
log "Ping dependency met: Host $HOST is reachable."
|
|
||||||
CHECK_PASSED=true
|
|
||||||
fi
|
fi
|
||||||
|
wait_until "Ping dependency met: Host $HOST is reachable." "ping -c 1 -W 2 \"$HOST\" >/dev/null 2>&1" || exit 1
|
||||||
|
;;
|
||||||
|
tcp)
|
||||||
|
if [ -z "$HOST" ] || [ -z "$PORT" ] || ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
|
||||||
|
log "WARNING: Malformed TCP dependency tag '$TAG'. Expected dep_tcp_<host>_<port>. Ignoring."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
wait_until "TCP dependency met: Host $HOST port $PORT is open." "check_tcp \"$HOST\" \"$PORT\"" || exit 1
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
log "WARNING: Unknown dependency type '$DEP_TYPE' in tag '$TAG'. Ignoring."
|
||||||
CHECK_PASSED=true # Mark as passed to avoid getting stuck
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if $CHECK_PASSED; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
ATTEMPTS=$((ATTEMPTS + 1))
|
|
||||||
if [ $ATTEMPTS -ge $MAX_ATTEMPTS ]; then
|
|
||||||
log "ERROR: Timeout waiting for dependency '$TAG'. Aborting start."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Dependency '$TAG' not met. Waiting ${POLL_INTERVAL}s... (Attempt ${ATTEMPTS}/${MAX_ATTEMPTS})"
|
|
||||||
sleep $POLL_INTERVAL
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@ -215,14 +239,13 @@ log "All custom dependencies are met."
|
|||||||
log "--- Dependency Check Complete. Proceeding with start. ---"
|
log "--- Dependency Check Complete. Proceeding with start. ---"
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
chmod +x /var/lib/vz/snippets/dependency-check.sh
|
chmod +x "$HOOKSCRIPT_FILE"
|
||||||
msg_ok "Created dependency-check hookscript"
|
msg_ok "Created dependency-check hookscript"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the config file for exclusions
|
|
||||||
create_exclusion_config() {
|
create_exclusion_config() {
|
||||||
msg_info "Creating exclusion configuration file"
|
msg_info "Creating exclusion configuration file"
|
||||||
if [ -f /etc/default/pve-auto-hook ]; then
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
msg_ok "Exclusion file already exists, skipping."
|
msg_ok "Exclusion file already exists, skipping."
|
||||||
else
|
else
|
||||||
cat <<'EOF' >/etc/default/pve-auto-hook
|
cat <<'EOF' >/etc/default/pve-auto-hook
|
||||||
@ -238,56 +261,83 @@ create_exclusion_config() {
|
|||||||
|
|
||||||
IGNORE_IDS=""
|
IGNORE_IDS=""
|
||||||
EOF
|
EOF
|
||||||
|
chmod 0644 "$CONFIG_FILE"
|
||||||
msg_ok "Created exclusion configuration file"
|
msg_ok "Created exclusion configuration file"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to create the script that applies the hook
|
|
||||||
create_applicator_script() {
|
create_applicator_script() {
|
||||||
msg_info "Creating the hookscript applicator script"
|
msg_info "Creating the hookscript applicator script"
|
||||||
cat <<'EOF' >/usr/local/bin/pve-apply-hookscript.sh
|
cat <<'EOF' >/usr/local/bin/pve-apply-hookscript.sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
HOOKSCRIPT_VOLUME_ID="local:snippets/dependency-check.sh"
|
||||||
CONFIG_FILE="/etc/default/pve-auto-hook"
|
CONFIG_FILE="/etc/default/pve-auto-hook"
|
||||||
LOG_TAG="pve-auto-hook-list"
|
LOG_TAG="pve-auto-hook"
|
||||||
|
IGNORE_IDS=""
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
systemd-cat -t "$LOG_TAG" <<< "$1"
|
systemd-cat -t "$LOG_TAG" <<< "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
source "$CONFIG_FILE"
|
IGNORE_IDS=$(grep -E '^IGNORE_IDS=' "$CONFIG_FILE" | head -n1 | cut -d'=' -f2- | tr -d '"')
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Process QEMU VMs
|
is_ignored() {
|
||||||
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
local vmid="$1"
|
||||||
is_ignored=false
|
|
||||||
for id_to_ignore in $IGNORE_IDS; do
|
for id_to_ignore in $IGNORE_IDS; do
|
||||||
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
if [ "$id_to_ignore" = "$vmid" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
if $is_ignored; then continue; fi
|
return 1
|
||||||
if qm config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
}
|
||||||
log "Hookscript not found for VM $VMID. Applying..."
|
|
||||||
qm set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
ensure_hookscript() {
|
||||||
|
local guest_type="$1"
|
||||||
|
local vmid="$2"
|
||||||
|
local current_hook=""
|
||||||
|
|
||||||
|
if [ "$guest_type" = "qemu" ]; then
|
||||||
|
current_hook=$(qm config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
else
|
||||||
|
current_hook=$(pct config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$current_hook" ]; then
|
||||||
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
log "Guest $guest_type/$vmid already has another hookscript ($current_hook). Leaving unchanged."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Applying hookscript to $guest_type/$vmid"
|
||||||
|
if [ "$guest_type" = "qemu" ]; then
|
||||||
|
qm set "$vmid" --hookscript "$HOOKSCRIPT_VOLUME_ID" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
pct set "$vmid" --hookscript "$HOOKSCRIPT_VOLUME_ID" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
|
if is_ignored "$VMID"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ensure_hookscript "qemu" "$VMID"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Process LXC Containers
|
|
||||||
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||||
is_ignored=false
|
if is_ignored "$VMID"; then
|
||||||
for id_to_ignore in $IGNORE_IDS; do
|
continue
|
||||||
if [ "$id_to_ignore" == "$VMID" ]; then is_ignored=true; break; fi
|
fi
|
||||||
done
|
ensure_hookscript "lxc" "$VMID"
|
||||||
if $is_ignored; then continue; fi
|
|
||||||
if pct config "$VMID" | grep -q '^hookscript:'; then continue; fi
|
|
||||||
log "Hookscript not found for LXC $VMID. Applying..."
|
|
||||||
pct set "$VMID" --hookscript "$HOOKSCRIPT_VOLUME_ID"
|
|
||||||
done
|
done
|
||||||
EOF
|
EOF
|
||||||
chmod +x /usr/local/bin/pve-apply-hookscript.sh
|
chmod +x "$APPLICATOR_FILE"
|
||||||
msg_ok "Created applicator script"
|
msg_ok "Created applicator script"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to set up the systemd watcher and service
|
|
||||||
create_systemd_units() {
|
create_systemd_units() {
|
||||||
msg_info "Creating systemd watcher and service units"
|
msg_info "Creating systemd watcher and service units"
|
||||||
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.path
|
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.path
|
||||||
@ -295,8 +345,9 @@ create_systemd_units() {
|
|||||||
Description=Watch for new Proxmox guest configs to apply hookscript
|
Description=Watch for new Proxmox guest configs to apply hookscript
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathModified=/etc/pve/qemu-server/
|
PathExistsGlob=/etc/pve/qemu-server/*.conf
|
||||||
PathModified=/etc/pve/lxc/
|
PathExistsGlob=/etc/pve/lxc/*.conf
|
||||||
|
Unit=pve-auto-hook.service
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
@ -310,18 +361,106 @@ Description=Automatically add hookscript to new Proxmox guests
|
|||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
||||||
EOF
|
EOF
|
||||||
|
chmod 0644 "$PATH_UNIT_FILE" "$SERVICE_UNIT_FILE"
|
||||||
msg_ok "Created systemd units"
|
msg_ok "Created systemd units"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remove_hookscript_assignments() {
|
||||||
|
msg_info "Removing hookscript assignment from guests using dependency-check"
|
||||||
|
|
||||||
# --- Main Execution ---
|
qm list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||||
header_info
|
current_hook=$(qm config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
|
qm set "$vmid" --delete hookscript >/dev/null 2>&1 && msg_ok "Removed hookscript from VM $vmid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
if ! command -v pveversion >/dev/null 2>&1; then
|
pct list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||||
msg_error "This script must be run on a Proxmox VE host."
|
current_hook=$(pct config "$vmid" | awk '/^hookscript:/ {print $2}')
|
||||||
|
if [ "$current_hook" = "$HOOKSCRIPT_VOLUME_ID" ]; then
|
||||||
|
pct set "$vmid" --delete hookscript >/dev/null 2>&1 && msg_ok "Removed hookscript from LXC $vmid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
install_stack() {
|
||||||
|
create_dependency_hookscript
|
||||||
|
create_exclusion_config
|
||||||
|
create_applicator_script
|
||||||
|
create_systemd_units
|
||||||
|
|
||||||
|
msg_info "Reloading systemd and enabling watcher"
|
||||||
|
if systemctl daemon-reload && systemctl enable --now pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
msg_ok "Systemd watcher enabled and running"
|
||||||
|
else
|
||||||
|
msg_error "Could not enable pve-auto-hook.path"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
msg_info "Performing initial run to update existing guests"
|
||||||
|
if "$APPLICATOR_FILE" >/dev/null 2>&1; then
|
||||||
|
msg_ok "Initial run complete"
|
||||||
|
else
|
||||||
|
msg_error "Initial run failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
uninstall_stack() {
|
||||||
|
remove_hookscript_assignments
|
||||||
|
|
||||||
|
msg_info "Stopping and disabling systemd units"
|
||||||
|
systemctl disable --now pve-auto-hook.path >/dev/null 2>&1 || true
|
||||||
|
systemctl disable --now pve-auto-hook.service >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
msg_info "Removing installed files"
|
||||||
|
rm -f "$HOOKSCRIPT_FILE" "$APPLICATOR_FILE" "$PATH_UNIT_FILE" "$SERVICE_UNIT_FILE" "$CONFIG_FILE"
|
||||||
|
|
||||||
|
if systemctl daemon-reload >/dev/null 2>&1; then
|
||||||
|
msg_ok "systemd daemon reloaded"
|
||||||
|
else
|
||||||
|
msg_error "Failed to reload systemd daemon"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg_ok "Dependency-check stack successfully removed"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
echo -e "\n${BL}Dependency-check status${CL}"
|
||||||
|
echo -e "--------------------------------"
|
||||||
|
[ -f "$HOOKSCRIPT_FILE" ] && echo -e "Hookscript file: ${GN}present${CL}" || echo -e "Hookscript file: ${RD}missing${CL}"
|
||||||
|
[ -f "$APPLICATOR_FILE" ] && echo -e "Applicator script: ${GN}present${CL}" || echo -e "Applicator script: ${RD}missing${CL}"
|
||||||
|
[ -f "$CONFIG_FILE" ] && echo -e "Config file: ${GN}present${CL}" || echo -e "Config file: ${RD}missing${CL}"
|
||||||
|
[ -f "$PATH_UNIT_FILE" ] && echo -e "Path unit: ${GN}present${CL}" || echo -e "Path unit: ${RD}missing${CL}"
|
||||||
|
[ -f "$SERVICE_UNIT_FILE" ] && echo -e "Service unit: ${GN}present${CL}" || echo -e "Service unit: ${RD}missing${CL}"
|
||||||
|
|
||||||
|
if systemctl is-enabled pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
echo -e "Watcher enabled: ${GN}yes${CL}"
|
||||||
|
else
|
||||||
|
echo -e "Watcher enabled: ${YW}no${CL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if systemctl is-active pve-auto-hook.path >/dev/null 2>&1; then
|
||||||
|
echo -e "Watcher active: ${GN}yes${CL}"
|
||||||
|
else
|
||||||
|
echo -e "Watcher active: ${YW}no${CL}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
header_info
|
||||||
|
ensure_supported_pve
|
||||||
|
|
||||||
|
case "${1:---install}" in
|
||||||
|
--help | -h)
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--status)
|
||||||
|
show_status
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--install)
|
||||||
echo -e "\nThis script will install a service to automatically apply a"
|
echo -e "\nThis script will install a service to automatically apply a"
|
||||||
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
echo -e "dependency-checking hookscript to all new and existing Proxmox guests."
|
||||||
echo -e "${YW}This includes creating files in:${CL}"
|
echo -e "${YW}This includes creating files in:${CL}"
|
||||||
@ -330,34 +469,41 @@ echo -e " - /usr/local/bin/"
|
|||||||
echo -e " - /etc/default/"
|
echo -e " - /etc/default/"
|
||||||
echo -e " - /etc/systemd/system/\n"
|
echo -e " - /etc/systemd/system/\n"
|
||||||
|
|
||||||
read -p "Do you want to proceed with the installation? (y/n): " -n 1 -r
|
if ! confirm_action "Do you want to proceed with the installation?"; then
|
||||||
echo
|
msg_error "Installation cancelled"
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
msg_error "Installation cancelled."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n"
|
echo ""
|
||||||
create_dependency_hookscript
|
install_stack
|
||||||
create_exclusion_config
|
|
||||||
create_applicator_script
|
|
||||||
create_systemd_units
|
|
||||||
|
|
||||||
msg_info "Reloading systemd and enabling the watcher"
|
echo -e "\n${GN}Installation successful!${CL}"
|
||||||
(systemctl daemon-reload && systemctl enable --now pve-auto-hook.path) >/dev/null 2>&1 &
|
|
||||||
spinner
|
|
||||||
msg_ok "Systemd watcher enabled and running"
|
|
||||||
|
|
||||||
msg_info "Performing initial run to update existing guests"
|
|
||||||
/usr/local/bin/pve-apply-hookscript.sh >/dev/null 2>&1 &
|
|
||||||
spinner
|
|
||||||
msg_ok "Initial run complete"
|
|
||||||
|
|
||||||
echo -e "\n\n${GN}Installation successful!${CL}"
|
|
||||||
echo -e "The service is now active and will monitor for new guests."
|
echo -e "The service is now active and will monitor for new guests."
|
||||||
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to the ${YW}IGNORE_IDS${CL} variable in:"
|
echo -e "To ${YW}exclude${CL} a VM or LXC, add its ID to ${YW}IGNORE_IDS${CL} in:"
|
||||||
echo -e " ${YW}/etc/default/pve-auto-hook${CL}"
|
echo -e " ${YW}${CONFIG_FILE}${CL}"
|
||||||
echo -e "\nYou can monitor the service's activity with:"
|
echo -e "\nMonitor activity with:"
|
||||||
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
echo -e " ${YW}journalctl -fu pve-auto-hook.service${CL}\n"
|
||||||
|
;;
|
||||||
|
--uninstall)
|
||||||
|
echo -e "\nThis will completely remove the dependency-check stack:"
|
||||||
|
echo -e " - hookscript and applicator"
|
||||||
|
echo -e " - systemd path/service units"
|
||||||
|
echo -e " - exclusion config"
|
||||||
|
echo -e " - hookscript assignment from guests using ${HOOKSCRIPT_VOLUME_ID}\n"
|
||||||
|
|
||||||
|
if ! confirm_action "Do you want to proceed with uninstall?"; then
|
||||||
|
msg_error "Uninstall cancelled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
uninstall_stack
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
msg_error "Unknown option: $1"
|
||||||
|
print_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user