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_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"
|
||||
|
||||
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
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
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_info "Restoring Data"
|
||||
cp /opt/calibre-web/app.db_backup /opt/calibre-web/app.db 2>/dev/null
|
||||
rm -f /opt/calibre-web/app.db_backup
|
||||
cp /opt/app.db_backup /opt/calibre-web/app.db 2>/dev/null
|
||||
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_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_info "Updating Discourse"
|
||||
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
|
||||
cd /opt/discourse
|
||||
git pull origin main
|
||||
$STD bundle install --deployment --without test development
|
||||
$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 db:migrate
|
||||
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 "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}${CL}"
|
||||
echo -e "${INFO}${YW} Default Credentials:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}Username: admin${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}Password: Check /opt/discourse/.env${CL}"
|
||||
echo -e "${INFO}${YW} Admin Setup:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}Create the first account in the web UI (use admin@local to match developer emails)${CL}"
|
||||
|
||||
@ -93,7 +93,9 @@ function update_script() {
|
||||
msg_ok "Updated Gramps Web Frontend"
|
||||
|
||||
msg_info "Applying Database Migration"
|
||||
cd /opt/gramps-web-api
|
||||
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
||||
ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \
|
||||
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
||||
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
||||
$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,
|
||||
"hdd": null,
|
||||
"os": null,
|
||||
"version": null
|
||||
"version": "PVE 8.x / 9.x"
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -36,6 +36,10 @@
|
||||
"text": "Execute within the Proxmox shell",
|
||||
"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.",
|
||||
"type": "info"
|
||||
@ -45,4 +49,4 @@
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,12 @@ update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
build-essential \
|
||||
python3 \
|
||||
python3-dev \
|
||||
libldap2-dev \
|
||||
libsasl2-dev \
|
||||
libssl-dev \
|
||||
imagemagick \
|
||||
libpango-1.0-0 \
|
||||
libharfbuzz0b \
|
||||
@ -27,12 +32,14 @@ msg_info "Installing Calibre (for eBook conversion)"
|
||||
$STD apt install -y 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
|
||||
|
||||
msg_info "Installing Python Dependencies"
|
||||
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_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 \
|
||||
imagemagick \
|
||||
gsfonts \
|
||||
brotli \
|
||||
nginx \
|
||||
redis-server
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PG_VERSION="16" setup_postgresql
|
||||
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
RUBY_VERSION="3.4.4" setup_ruby
|
||||
|
||||
msg_info "Configuring PostgreSQL for Discourse"
|
||||
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"
|
||||
sed -i 's/^local\s\+all\s\+all\s\+peer$/local all all md5/' "$PG_HBA"
|
||||
$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
|
||||
msg_ok "Configured PostgreSQL for Discourse"
|
||||
|
||||
msg_info "Configuring Discourse"
|
||||
DISCOURSE_SECRET_KEY=$(openssl rand -hex 64)
|
||||
|
||||
git clone --depth 1 https://github.com/discourse/discourse.git /opt/discourse
|
||||
$STD git clone --depth 1 https://github.com/discourse/discourse.git /opt/discourse
|
||||
cd /opt/discourse
|
||||
|
||||
cat <<EOF >/opt/discourse/.env
|
||||
RAILS_ENV=production
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
@ -93,6 +90,7 @@ export RAILS_ENV=production
|
||||
set -a
|
||||
source /opt/discourse/.env
|
||||
set +a
|
||||
$STD runuser -u postgres -- psql -d discourse -c "CREATE EXTENSION IF NOT EXISTS vector;"
|
||||
$STD bundle exec rails db:migrate
|
||||
msg_ok "Set Up Database"
|
||||
|
||||
@ -107,16 +105,8 @@ set +a
|
||||
$STD bundle exec rails assets:precompile
|
||||
msg_ok "Built Discourse Assets"
|
||||
|
||||
msg_info "Creating Discourse Admin User"
|
||||
cd /opt/discourse
|
||||
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 "Preparing Admin Onboarding"
|
||||
msg_ok "Automatic admin bootstrap skipped (use first signup in UI with admin@local)"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/discourse.service
|
||||
|
||||
@ -94,7 +94,9 @@ corepack enable
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
|
||||
cd /opt/gramps-web-api
|
||||
GRAMPS_API_CONFIG=/opt/gramps-web/config/config.cfg \
|
||||
ALEMBIC_CONFIG=/opt/gramps-web-api/alembic.ini \
|
||||
GRAMPSHOME=/opt/gramps-web/data/gramps \
|
||||
GRAMPS_DATABASE_PATH=/opt/gramps-web/data/gramps/grampsdb \
|
||||
$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" ;;
|
||||
"")
|
||||
# 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"
|
||||
;;
|
||||
*)
|
||||
@ -135,24 +135,50 @@ explain_exit_code() {
|
||||
# --- Generic / Shell ---
|
||||
1) echo "General error / Operation not permitted" ;;
|
||||
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) ---
|
||||
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)" ;;
|
||||
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+)" ;;
|
||||
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)" ;;
|
||||
30) echo "curl: FTP port command failed" ;;
|
||||
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 ---
|
||||
100) echo "APT: Package manager error (broken packages / dependency problems)" ;;
|
||||
101) echo "APT: Configuration error (bad sources.list, malformed config)" ;;
|
||||
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 ---
|
||||
124) echo "Command timed out (timeout command)" ;;
|
||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||
127) echo "Command not found" ;;
|
||||
128) echo "Invalid argument to exit" ;;
|
||||
129) echo "Killed by SIGHUP (terminal closed / hangup)" ;;
|
||||
130) echo "Aborted by user (SIGINT)" ;;
|
||||
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
||||
137) echo "Killed (SIGKILL / Out of memory?)" ;;
|
||||
@ -237,16 +263,21 @@ explain_exit_code() {
|
||||
# json_escape()
|
||||
#
|
||||
# - Escapes a string for safe JSON embedding
|
||||
# - Strips ANSI escape sequences and non-printable control characters
|
||||
# - Handles backslashes, quotes, newlines, tabs, and carriage returns
|
||||
# ------------------------------------------------------------------------------
|
||||
json_escape() {
|
||||
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//$'\n'/\\n}
|
||||
s=${s//$'\r'/}
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -369,18 +426,19 @@ detect_cpu() {
|
||||
# - Detects RAM speed using dmidecode
|
||||
# - Sets RAM_SPEED global (e.g., "4800" for DDR5-4800)
|
||||
# - Requires root access for dmidecode
|
||||
# - Returns empty if not available
|
||||
# - Returns empty if not available or if speed is "Unknown" (nested VMs)
|
||||
# ------------------------------------------------------------------------------
|
||||
detect_ram() {
|
||||
RAM_SPEED=""
|
||||
|
||||
if command -v dmidecode &>/dev/null; then
|
||||
# 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
|
||||
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
|
||||
|
||||
@ -592,6 +650,8 @@ EOF
|
||||
curl -fsS -m "${TELEMETRY_TIMEOUT}" -X POST "${TELEMETRY_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" &>/dev/null || true
|
||||
|
||||
POST_TO_API_DONE=true
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -665,13 +725,12 @@ post_update_to_api() {
|
||||
else
|
||||
exit_code=1
|
||||
fi
|
||||
# Get log lines and build structured error string
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
short_error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
@ -814,31 +873,52 @@ EOF
|
||||
categorize_error() {
|
||||
local code="$1"
|
||||
case "$code" in
|
||||
# Network errors
|
||||
6 | 7 | 22 | 28 | 35) echo "network" ;;
|
||||
# Network errors (curl/wget)
|
||||
6 | 7 | 22 | 35) echo "network" ;;
|
||||
|
||||
# Storage errors
|
||||
214 | 217 | 219) echo "storage" ;;
|
||||
# Timeout errors
|
||||
28 | 124 | 211) echo "timeout" ;;
|
||||
|
||||
# Dependency/Package errors
|
||||
100 | 101 | 102 | 127 | 160 | 161 | 162) echo "dependency" ;;
|
||||
# Storage errors (Proxmox storage)
|
||||
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
|
||||
126 | 152) echo "permission" ;;
|
||||
|
||||
# Timeout errors
|
||||
124 | 28 | 211) echo "timeout" ;;
|
||||
# Configuration errors (Proxmox config, invalid args)
|
||||
128 | 203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
|
||||
# Configuration errors
|
||||
203 | 204 | 205 | 206 | 207 | 208) echo "config" ;;
|
||||
# Proxmox container/template errors
|
||||
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
|
||||
130) echo "aborted" ;;
|
||||
|
||||
# Resource errors (OOM, etc)
|
||||
137 | 134) echo "resource" ;;
|
||||
# Resource errors (OOM, SIGKILL, SIGABRT)
|
||||
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" ;;
|
||||
esac
|
||||
}
|
||||
@ -901,11 +981,9 @@ post_tool_to_api() {
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@ -968,11 +1046,9 @@ post_addon_to_api() {
|
||||
[[ ! "$exit_code" =~ ^[0-9]+$ ]] && exit_code=1
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
fi
|
||||
|
||||
@ -1067,11 +1143,9 @@ post_update_to_api_extended() {
|
||||
fi
|
||||
local error_text=""
|
||||
error_text=$(get_error_text)
|
||||
if [[ -n "$error_text" ]]; then
|
||||
error=$(json_escape "$error_text")
|
||||
else
|
||||
error=$(json_escape "$(explain_exit_code "$exit_code")")
|
||||
fi
|
||||
local full_error
|
||||
full_error=$(build_error_string "$exit_code" "$error_text")
|
||||
error=$(json_escape "$full_error")
|
||||
error_category=$(categorize_error "$exit_code")
|
||||
[[ -z "$error" ]] && error="Unknown error"
|
||||
fi
|
||||
|
||||
@ -1641,4 +1641,17 @@ function get_lxc_ip() {
|
||||
# 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
|
||||
|
||||
@ -49,6 +49,7 @@ if ! declare -f explain_exit_code &>/dev/null; then
|
||||
126) echo "Command invoked cannot execute (permission problem?)" ;;
|
||||
127) echo "Command not found" ;;
|
||||
128) echo "Invalid argument to exit" ;;
|
||||
129) echo "Killed by SIGHUP (terminal closed / hangup)" ;;
|
||||
130) echo "Terminated by Ctrl+C (SIGINT)" ;;
|
||||
134) echo "Process aborted (SIGABRT - possibly Node.js heap overflow)" ;;
|
||||
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
|
||||
|
||||
# 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
|
||||
# Copyright (c) 2023-2026 community-scripts ORG
|
||||
# Author: MickLesk | Maintainer: community-scripts
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://www.proxmox.com/
|
||||
|
||||
function header_info {
|
||||
clear
|
||||
@ -18,195 +17,220 @@ function header_info {
|
||||
EOF
|
||||
}
|
||||
|
||||
# Color variables
|
||||
YW=$(echo "\033[33m")
|
||||
GN=$(echo "\033[1;92m")
|
||||
RD=$(echo "\033[01;31m")
|
||||
BL=$(echo "\033[36m")
|
||||
CL=$(echo "\033[m")
|
||||
BFR="\\r\\033[K"
|
||||
HOLD=" "
|
||||
CM="${GN}✓${CL}"
|
||||
CROSS="${RD}✗${CL}"
|
||||
CM="${GN}✔️${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() {
|
||||
echo -ne " ${YW}›${CL} $1..."
|
||||
local msg="$1"
|
||||
echo -e "${INFO} ${YW}${msg}...${CL}"
|
||||
}
|
||||
|
||||
msg_ok() {
|
||||
echo -e "${BFR} ${CM} $1${CL}"
|
||||
local msg="$1"
|
||||
echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
|
||||
}
|
||||
|
||||
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() {
|
||||
msg_info "Creating dependency-check hookscript"
|
||||
mkdir -p /var/lib/vz/snippets
|
||||
cat <<'EOF' > /var/lib/vz/snippets/dependency-check.sh
|
||||
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"
|
||||
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
|
||||
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"
|
||||
CONFIG_CMD=(qm config "$VMID")
|
||||
log "Guest type is QEMU (VM)."
|
||||
elif pct config "$VMID" >/dev/null 2>&1; then
|
||||
GUEST_TYPE="lxc"
|
||||
CONFIG_CMD="pct config"
|
||||
CONFIG_CMD=(pct config "$VMID")
|
||||
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")
|
||||
GUEST_CONFIG=$("${CONFIG_CMD[@]}")
|
||||
|
||||
# --- 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)
|
||||
STORAGE_IDS=$(echo "$GUEST_CONFIG" | awk -F':' '
|
||||
/^(scsi|sata|virtio|ide|efidisk|tpmstate|unused|rootfs|mp)[0-9]*:/ {
|
||||
val=$2
|
||||
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
|
||||
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
|
||||
STATUS=$(pvesm status 2>/dev/null | awk -v id="$STORAGE_ID" '$1 == id { print $3; exit }')
|
||||
|
||||
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
|
||||
if [ -z "$STATUS" ]; then
|
||||
log "WARNING: Storage '$STORAGE_ID' not found in 'pvesm status'. Skipping this dependency."
|
||||
continue
|
||||
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
|
||||
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
|
||||
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}')
|
||||
TAGS=$(echo "$GUEST_CONFIG" | awk -F': ' '/^tags:/ {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]}"
|
||||
IFS='_' read -r _ DEP_TYPE HOST PORT EXTRA <<< "$TAG"
|
||||
|
||||
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
|
||||
case "$DEP_TYPE" in
|
||||
ping)
|
||||
if [ -z "$HOST" ]; then
|
||||
log "WARNING: Malformed ping dependency tag '$TAG'. Ignoring."
|
||||
continue
|
||||
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."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@ -215,17 +239,16 @@ 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"
|
||||
chmod +x "$HOOKSCRIPT_FILE"
|
||||
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
|
||||
msg_info "Creating exclusion configuration file"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
msg_ok "Exclusion file already exists, skipping."
|
||||
else
|
||||
cat <<'EOF' >/etc/default/pve-auto-hook
|
||||
#
|
||||
# Configuration for the Proxmox Automatic Hookscript Applicator
|
||||
#
|
||||
@ -238,71 +261,99 @@ create_exclusion_config() {
|
||||
|
||||
IGNORE_IDS=""
|
||||
EOF
|
||||
msg_ok "Created exclusion configuration file"
|
||||
fi
|
||||
chmod 0644 "$CONFIG_FILE"
|
||||
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
|
||||
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_TAG="pve-auto-hook"
|
||||
IGNORE_IDS=""
|
||||
|
||||
log() {
|
||||
systemd-cat -t "$LOG_TAG" <<< "$1"
|
||||
}
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
source "$CONFIG_FILE"
|
||||
IGNORE_IDS=$(grep -E '^IGNORE_IDS=' "$CONFIG_FILE" | head -n1 | cut -d'=' -f2- | tr -d '"')
|
||||
fi
|
||||
|
||||
# Process QEMU VMs
|
||||
qm list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||
is_ignored=false
|
||||
is_ignored() {
|
||||
local vmid="$1"
|
||||
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
|
||||
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"
|
||||
return 1
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
pct list | awk 'NR>1 {print $1}' | while read -r VMID; do
|
||||
if is_ignored "$VMID"; then
|
||||
continue
|
||||
fi
|
||||
ensure_hookscript "lxc" "$VMID"
|
||||
done
|
||||
EOF
|
||||
chmod +x "$APPLICATOR_FILE"
|
||||
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
|
||||
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/
|
||||
PathExistsGlob=/etc/pve/qemu-server/*.conf
|
||||
PathExistsGlob=/etc/pve/lxc/*.conf
|
||||
Unit=pve-auto-hook.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat <<'EOF' > /etc/systemd/system/pve-auto-hook.service
|
||||
cat <<'EOF' >/etc/systemd/system/pve-auto-hook.service
|
||||
[Unit]
|
||||
Description=Automatically add hookscript to new Proxmox guests
|
||||
|
||||
@ -310,54 +361,149 @@ Description=Automatically add hookscript to new Proxmox guests
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/pve-apply-hookscript.sh
|
||||
EOF
|
||||
msg_ok "Created systemd units"
|
||||
chmod 0644 "$PATH_UNIT_FILE" "$SERVICE_UNIT_FILE"
|
||||
msg_ok "Created systemd units"
|
||||
}
|
||||
|
||||
remove_hookscript_assignments() {
|
||||
msg_info "Removing hookscript assignment from guests using dependency-check"
|
||||
|
||||
qm list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||
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
|
||||
|
||||
pct list | awk 'NR>1 {print $1}' | while read -r vmid; do
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
# --- Main Execution ---
|
||||
header_info
|
||||
ensure_supported_pve
|
||||
|
||||
if ! command -v pveversion >/dev/null 2>&1; then
|
||||
msg_error "This script must be run on a Proxmox VE host."
|
||||
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 "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"
|
||||
|
||||
if ! confirm_action "Do you want to proceed with the installation?"; then
|
||||
msg_error "Installation cancelled"
|
||||
exit 1
|
||||
fi
|
||||
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"
|
||||
echo ""
|
||||
install_stack
|
||||
|
||||
read -p "Do you want to proceed with the installation? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
msg_error "Installation cancelled."
|
||||
echo -e "\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 ${YW}IGNORE_IDS${CL} in:"
|
||||
echo -e " ${YW}${CONFIG_FILE}${CL}"
|
||||
echo -e "\nMonitor activity with:"
|
||||
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
|
||||
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"
|
||||
echo ""
|
||||
uninstall_stack
|
||||
;;
|
||||
*)
|
||||
msg_error "Unknown option: $1"
|
||||
print_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user