Compare commits
12 Commits
delete_fil
...
MickLesk-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b0fcf7d9d | ||
|
|
66fd840baa | ||
|
|
6b58bd1bf9 | ||
|
|
f7a42823a4 | ||
|
|
11c34baf7a | ||
|
|
1f2d8159d0 | ||
|
|
d382697368 | ||
|
|
817b0ea517 | ||
|
|
9413782993 | ||
|
|
f727d7979c | ||
|
|
6fd1b2e96d | ||
|
|
b3ca9efbed |
@@ -1,67 +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://anytype.io
|
||||
|
||||
APP="Anytype-Server"
|
||||
var_tags="${var_tags:-notes;productivity;sync}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-4096}"
|
||||
var_disk="${var_disk:-16}"
|
||||
var_os="${var_os:-ubuntu}"
|
||||
var_version="${var_version:-24.04}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -f /opt/anytype/any-sync-bundle ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "anytype" "grishy/any-sync-bundle"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop anytype
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp -r /opt/anytype/data /opt/anytype_data_backup
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "anytype" "grishy/any-sync-bundle" "prebuild" "latest" "/opt/anytype" "any-sync-bundle_*_linux_amd64.tar.gz"
|
||||
chmod +x /opt/anytype/any-sync-bundle
|
||||
|
||||
msg_info "Restoring Data"
|
||||
cp -r /opt/anytype_data_backup/. /opt/anytype/data
|
||||
rm -rf /opt/anytype_data_backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start anytype
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:33010${CL}"
|
||||
echo -e "${INFO}${YW} Client config file:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}/opt/anytype/data/client-config.yml${CL}"
|
||||
@@ -1,61 +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/qdm12/gluetun
|
||||
|
||||
APP="Gluetun"
|
||||
var_tags="${var_tags:-vpn;wireguard;openvpn}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-2048}"
|
||||
var_disk="${var_disk:-8}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
var_tun="${var_tun:-yes}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -f /usr/local/bin/gluetun ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "gluetun" "qdm12/gluetun"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop gluetun
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "gluetun" "qdm12/gluetun" "tarball"
|
||||
|
||||
msg_info "Building Gluetun"
|
||||
cd /opt/gluetun
|
||||
$STD go mod download
|
||||
CGO_ENABLED=0 $STD go build -trimpath -ldflags="-s -w" -o /usr/local/bin/gluetun ./cmd/gluetun/
|
||||
msg_ok "Built Gluetun"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start gluetun
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8000${CL}"
|
||||
@@ -1,69 +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: johanngrobe
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/oss-apps/split-pro
|
||||
|
||||
APP="Split-Pro"
|
||||
var_tags="${var_tags:-finance;expense-sharing}"
|
||||
var_cpu="${var_cpu:-2}"
|
||||
var_ram="${var_ram:-4096}"
|
||||
var_disk="${var_disk:-6}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
|
||||
if [[ ! -d /opt/split-pro ]]; then
|
||||
msg_error "No Split Pro Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "split-pro" "oss-apps/split-pro"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop split-pro
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
msg_info "Backing up Data"
|
||||
cp /opt/split-pro/.env /opt/split-pro.env
|
||||
msg_ok "Backed up Data"
|
||||
|
||||
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball" "latest" "/opt/split-pro"
|
||||
|
||||
msg_info "Building Application"
|
||||
cd /opt/split-pro
|
||||
$STD pnpm install --frozen-lockfile
|
||||
$STD pnpm build
|
||||
cp /opt/split-pro.env /opt/split-pro/.env
|
||||
rm -f /opt/split-pro.env
|
||||
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
|
||||
$STD pnpm exec prisma migrate deploy
|
||||
msg_ok "Built Application"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start split-pro
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
msg_ok "Completed successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"
|
||||
echo -e "${INFO}${YW} Before first use, configure auth in:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}/opt/split-pro/.env${CL}"
|
||||
@@ -1,81 +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://anytype.io
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
setup_mongodb
|
||||
|
||||
msg_info "Configuring MongoDB Replica Set"
|
||||
cat <<EOF >>/etc/mongod.conf
|
||||
|
||||
replication:
|
||||
replSetName: "rs0"
|
||||
EOF
|
||||
systemctl restart mongod
|
||||
sleep 3
|
||||
$STD mongosh --eval 'rs.initiate({_id: "rs0", members: [{_id: 0, host: "127.0.0.1:27017"}]})'
|
||||
msg_ok "Configured MongoDB Replica Set"
|
||||
|
||||
msg_info "Installing Redis Stack"
|
||||
setup_deb822_repo \
|
||||
"redis-stack" \
|
||||
"https://packages.redis.io/gpg" \
|
||||
"https://packages.redis.io/deb" \
|
||||
"jammy" \
|
||||
"main"
|
||||
$STD apt install -y redis-stack-server
|
||||
systemctl enable -q --now redis-stack-server
|
||||
msg_ok "Installed Redis Stack"
|
||||
|
||||
fetch_and_deploy_gh_release "anytype" "grishy/any-sync-bundle" "prebuild" "latest" "/opt/anytype" "any-sync-bundle_*_linux_amd64.tar.gz"
|
||||
chmod +x /opt/anytype/any-sync-bundle
|
||||
|
||||
msg_info "Configuring Anytype"
|
||||
mkdir -p /opt/anytype/data/storage
|
||||
cat <<EOF >/opt/anytype/.env
|
||||
ANY_SYNC_BUNDLE_CONFIG=/opt/anytype/data/bundle-config.yml
|
||||
ANY_SYNC_BUNDLE_CLIENT_CONFIG=/opt/anytype/data/client-config.yml
|
||||
ANY_SYNC_BUNDLE_INIT_STORAGE=/opt/anytype/data/storage/
|
||||
ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS=${LOCAL_IP}
|
||||
ANY_SYNC_BUNDLE_INIT_MONGO_URI=mongodb://127.0.0.1:27017/
|
||||
ANY_SYNC_BUNDLE_INIT_REDIS_URI=redis://127.0.0.1:6379/
|
||||
ANY_SYNC_BUNDLE_LOG_LEVEL=info
|
||||
EOF
|
||||
msg_ok "Configured Anytype"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/anytype.service
|
||||
[Unit]
|
||||
Description=Anytype Sync Server (any-sync-bundle)
|
||||
After=network-online.target mongod.service redis-stack-server.service
|
||||
Wants=network-online.target
|
||||
Requires=mongod.service redis-stack-server.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/anytype
|
||||
EnvironmentFile=/opt/anytype/.env
|
||||
ExecStart=/opt/anytype/any-sync-bundle start-bundle
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now anytype
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -16,12 +16,11 @@ update_os
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
build-essential \
|
||||
git \
|
||||
libssl-dev \
|
||||
libreadline-dev \
|
||||
zlib1g-dev \
|
||||
libyaml-dev \
|
||||
curl \
|
||||
git \
|
||||
imagemagick \
|
||||
gsfonts \
|
||||
brotli \
|
||||
@@ -38,7 +37,7 @@ DISCOURSE_DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
|
||||
PG_HBA=$(find /etc/postgresql -name pg_hba.conf 2>/dev/null | head -n1)
|
||||
sed -i 's/^local\s\+all\s\+all\s\+peer$/local all all md5/' "$PG_HBA"
|
||||
$STD systemctl restart postgresql
|
||||
PG_DB_NAME="discourse" PG_DB_USER="discourse" PG_DB_PASS="$DISCOURSE_DB_PASS" setup_postgresql_db
|
||||
PG_DB_NAME="discourse" PG_DB_USER="discourse" PG_DB_PASS="$DISCOURSE_DB_PASS" PG_DB_EXTENSIONS="vector" setup_postgresql_db
|
||||
msg_ok "Configured PostgreSQL for Discourse"
|
||||
|
||||
msg_info "Configuring Discourse"
|
||||
@@ -62,8 +61,12 @@ DISCOURSE_SMTP_ADDRESS=localhost
|
||||
DISCOURSE_SMTP_PORT=25
|
||||
DISCOURSE_SMTP_AUTHENTICATION=none
|
||||
DISCOURSE_NOTIFICATION_EMAIL=noreply@${LOCAL_IP}
|
||||
APP_ROOT=/opt/discourse
|
||||
EOF
|
||||
|
||||
mkdir -p /opt/discourse/tmp/sockets /opt/discourse/tmp/pids /opt/discourse/log
|
||||
sed -i 's|bind "unix://#{APP_ROOT}/tmp/sockets/puma.sock"|bind "tcp://127.0.0.1:3000"|' /opt/discourse/config/puma.rb
|
||||
sed -i 's|stdout_redirect.*|# logging handled by systemd|' /opt/discourse/config/puma.rb
|
||||
chown -R root:root /opt/discourse
|
||||
chmod 755 /opt/discourse
|
||||
msg_ok "Configured Discourse"
|
||||
@@ -90,7 +93,6 @@ 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"
|
||||
|
||||
@@ -105,10 +107,7 @@ set +a
|
||||
$STD bundle exec rails assets:precompile
|
||||
msg_ok "Built Discourse Assets"
|
||||
|
||||
msg_info "Preparing Admin Onboarding"
|
||||
msg_ok "Automatic admin bootstrap skipped (use first signup in UI with admin@local)"
|
||||
|
||||
msg_info "Creating Service"
|
||||
msg_info "Creating Services"
|
||||
cat <<EOF >/etc/systemd/system/discourse.service
|
||||
[Unit]
|
||||
Description=Discourse Forum
|
||||
@@ -118,7 +117,7 @@ After=network.target postgresql.service redis-server.service
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/discourse
|
||||
Environment=RAILS_ENV=production
|
||||
EnvironmentFile=/opt/discourse/.env
|
||||
Environment=PATH=/root/.rbenv/shims:/root/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
ExecStart=/root/.rbenv/shims/bundle exec puma -w 2
|
||||
Restart=on-failure
|
||||
@@ -127,8 +126,27 @@ RestartSec=5
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now discourse
|
||||
msg_ok "Created Service"
|
||||
|
||||
cat <<EOF >/etc/systemd/system/discourse-sidekiq.service
|
||||
[Unit]
|
||||
Description=Discourse Sidekiq
|
||||
After=network.target postgresql.service redis-server.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/discourse
|
||||
EnvironmentFile=/opt/discourse/.env
|
||||
Environment=PATH=/root/.rbenv/shims:/root/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
ExecStart=/root/.rbenv/shims/bundle exec sidekiq -q critical -q low -q default
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now discourse discourse-sidekiq
|
||||
msg_ok "Created Services"
|
||||
|
||||
msg_info "Configuring Nginx"
|
||||
cat <<EOF >/etc/nginx/sites-available/discourse
|
||||
|
||||
@@ -1,92 +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/qdm12/gluetun
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
openvpn \
|
||||
wireguard-tools \
|
||||
iptables
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Configuring iptables"
|
||||
$STD update-alternatives --set iptables /usr/sbin/iptables-legacy
|
||||
$STD update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
|
||||
ln -sf /usr/sbin/openvpn /usr/sbin/openvpn2.6
|
||||
msg_ok "Configured iptables"
|
||||
|
||||
setup_go
|
||||
|
||||
fetch_and_deploy_gh_release "gluetun" "qdm12/gluetun" "tarball"
|
||||
|
||||
msg_info "Building Gluetun"
|
||||
cd /opt/gluetun
|
||||
$STD go mod download
|
||||
CGO_ENABLED=0 $STD go build -trimpath -ldflags="-s -w" -o /usr/local/bin/gluetun ./cmd/gluetun/
|
||||
msg_ok "Built Gluetun"
|
||||
|
||||
msg_info "Configuring Gluetun"
|
||||
mkdir -p /opt/gluetun-data
|
||||
touch /etc/alpine-release
|
||||
ln -sf /opt/gluetun-data /gluetun
|
||||
cat <<EOF >/opt/gluetun-data/.env
|
||||
VPN_SERVICE_PROVIDER=custom
|
||||
VPN_TYPE=openvpn
|
||||
OPENVPN_CUSTOM_CONFIG=/opt/gluetun-data/custom.ovpn
|
||||
OPENVPN_USER=
|
||||
OPENVPN_PASSWORD=
|
||||
HTTP_CONTROL_SERVER_ADDRESS=:8000
|
||||
HTTPPROXY=off
|
||||
SHADOWSOCKS=off
|
||||
PPROF_ENABLED=no
|
||||
PPROF_BLOCK_PROFILE_RATE=0
|
||||
PPROF_MUTEX_PROFILE_RATE=0
|
||||
PPROF_HTTP_SERVER_ADDRESS=:6060
|
||||
FIREWALL_ENABLED_DISABLING_IT_SHOOTS_YOU_IN_YOUR_FOOT=on
|
||||
HEALTH_SERVER_ADDRESS=127.0.0.1:9999
|
||||
DNS_UPSTREAM_RESOLVERS=cloudflare
|
||||
LOG_LEVEL=info
|
||||
STORAGE_FILEPATH=/gluetun/servers.json
|
||||
PUBLICIP_FILE=/gluetun/ip
|
||||
VPN_PORT_FORWARDING_STATUS_FILE=/gluetun/forwarded_port
|
||||
TZ=UTC
|
||||
EOF
|
||||
msg_ok "Configured Gluetun"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/gluetun.service
|
||||
[Unit]
|
||||
Description=Gluetun VPN Client
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/gluetun-data
|
||||
EnvironmentFile=/opt/gluetun-data/.env
|
||||
UnsetEnvironment=USER
|
||||
ExecStart=/usr/local/bin/gluetun
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
AmbientCapabilities=CAP_NET_ADMIN
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now gluetun
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -23,7 +23,6 @@ $STD apt install -y \
|
||||
libpq-dev \
|
||||
cmake \
|
||||
pkg-config \
|
||||
git \
|
||||
redis-server \
|
||||
nginx \
|
||||
postfix \
|
||||
@@ -31,7 +30,7 @@ $STD apt install -y \
|
||||
opendkim-tools
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PG_VERSION="16" setup_postgresql
|
||||
PG_VERSION="17" setup_postgresql
|
||||
APPLICATION="simplelogin" PG_DB_NAME="simplelogin" PG_DB_USER="simplelogin" setup_postgresql_db
|
||||
PYTHON_VERSION="3.12" setup_uv
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
@@ -72,27 +71,45 @@ $STD openssl rsa -in /opt/simplelogin/openid-rsa.key -pubout -out /opt/simplelog
|
||||
mkdir -p /opt/simplelogin/uploads /opt/simplelogin/.gnupg
|
||||
chmod 700 /opt/simplelogin/.gnupg
|
||||
|
||||
cat <<EOF >/opt/simplelogin/.env
|
||||
URL=http://${LOCAL_IP}
|
||||
EMAIL_DOMAIN=example.com
|
||||
SUPPORT_EMAIL=support@example.com
|
||||
EMAIL_SERVERS_WITH_PRIORITY=[(10, "localhost.")]
|
||||
POSTFIX_SERVER=localhost
|
||||
DB_URI=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}
|
||||
FLASK_SECRET=${FLASK_SECRET}
|
||||
DKIM_PRIVATE_KEY_PATH=/opt/simplelogin/dkim/dkim.private
|
||||
GNUPGHOME=/opt/simplelogin/.gnupg
|
||||
LOCAL_FILE_UPLOAD=true
|
||||
UPLOAD_DIR=/opt/simplelogin/uploads
|
||||
DISABLE_ALIAS_SUFFIX=1
|
||||
WORDS_FILE_PATH=/opt/simplelogin/local_data/words.txt
|
||||
NAMESERVERS=1.1.1.1
|
||||
MEM_STORE_URI=redis://localhost:6379/1
|
||||
OPENID_PRIVATE_KEY_PATH=/opt/simplelogin/openid-rsa.key
|
||||
OPENID_PUBLIC_KEY_PATH=/opt/simplelogin/openid-rsa.pub
|
||||
EOF
|
||||
{
|
||||
echo "URL=http://${LOCAL_IP}"
|
||||
echo "EMAIL_DOMAIN=example.com"
|
||||
echo "SUPPORT_EMAIL=support@example.com"
|
||||
echo 'EMAIL_SERVERS_WITH_PRIORITY=[(10, "localhost.")]'
|
||||
echo "POSTFIX_SERVER=localhost"
|
||||
echo "DB_URI=postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
|
||||
echo "FLASK_SECRET=${FLASK_SECRET}"
|
||||
echo "DKIM_PRIVATE_KEY_PATH=/opt/simplelogin/dkim/dkim.private"
|
||||
echo "GNUPGHOME=/opt/simplelogin/.gnupg"
|
||||
echo "LOCAL_FILE_UPLOAD=true"
|
||||
echo "UPLOAD_DIR=/opt/simplelogin/uploads"
|
||||
echo "DISABLE_ALIAS_SUFFIX=1"
|
||||
echo "WORDS_FILE_PATH=/opt/simplelogin/local_data/words.txt"
|
||||
echo "NAMESERVERS=1.1.1.1"
|
||||
echo "MEM_STORE_URI=redis://localhost:6379/1"
|
||||
echo "OPENID_PRIVATE_KEY_PATH=/opt/simplelogin/openid-rsa.key"
|
||||
echo "OPENID_PUBLIC_KEY_PATH=/opt/simplelogin/openid-rsa.pub"
|
||||
} >/opt/simplelogin/.env
|
||||
|
||||
cd /opt/simplelogin
|
||||
export FLASK_APP=server
|
||||
export URL="http://${LOCAL_IP}"
|
||||
export EMAIL_DOMAIN="example.com"
|
||||
export SUPPORT_EMAIL="support@example.com"
|
||||
export EMAIL_SERVERS_WITH_PRIORITY='[(10, "localhost.")]'
|
||||
export POSTFIX_SERVER="localhost"
|
||||
export DB_URI="postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
|
||||
export FLASK_SECRET="${FLASK_SECRET}"
|
||||
export DKIM_PRIVATE_KEY_PATH="/opt/simplelogin/dkim/dkim.private"
|
||||
export GNUPGHOME="/opt/simplelogin/.gnupg"
|
||||
export LOCAL_FILE_UPLOAD="true"
|
||||
export UPLOAD_DIR="/opt/simplelogin/uploads"
|
||||
export DISABLE_ALIAS_SUFFIX="1"
|
||||
export WORDS_FILE_PATH="/opt/simplelogin/local_data/words.txt"
|
||||
export NAMESERVERS="1.1.1.1"
|
||||
export MEM_STORE_URI="redis://localhost:6379/1"
|
||||
export OPENID_PRIVATE_KEY_PATH="/opt/simplelogin/openid-rsa.key"
|
||||
export OPENID_PUBLIC_KEY_PATH="/opt/simplelogin/openid-rsa.pub"
|
||||
$STD .venv/bin/flask db upgrade
|
||||
$STD .venv/bin/python init_app.py
|
||||
msg_ok "Configured SimpleLogin"
|
||||
@@ -137,6 +154,7 @@ Requires=postgresql.service redis-server.service
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/simplelogin
|
||||
EnvironmentFile=/opt/simplelogin/.env
|
||||
ExecStart=/opt/simplelogin/.venv/bin/gunicorn wsgi:app -b 127.0.0.1:7777 -w 2 --timeout 120
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
@@ -154,6 +172,7 @@ Requires=postgresql.service redis-server.service
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/simplelogin
|
||||
EnvironmentFile=/opt/simplelogin/.env
|
||||
ExecStart=/opt/simplelogin/.venv/bin/python email_handler.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
@@ -171,6 +190,7 @@ Requires=postgresql.service redis-server.service
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/simplelogin
|
||||
EnvironmentFile=/opt/simplelogin/.env
|
||||
ExecStart=/opt/simplelogin/.venv/bin/python job_runner.py
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: johanngrobe
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/oss-apps/split-pro
|
||||
|
||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
|
||||
color
|
||||
verb_ip6
|
||||
catch_errors
|
||||
setting_up_container
|
||||
network_check
|
||||
update_os
|
||||
|
||||
NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
|
||||
PG_VERSION="17" PG_MODULES="cron" setup_postgresql
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
openssl
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
PG_DB_NAME="splitpro" PG_DB_USER="splitpro" PG_DB_EXTENSIONS="pg_cron" setup_postgresql_db
|
||||
|
||||
fetch_and_deploy_gh_release "split-pro" "oss-apps/split-pro" "tarball" "latest" "/opt/split-pro"
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
cd /opt/split-pro
|
||||
$STD pnpm install --frozen-lockfile
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
msg_info "Building Split Pro"
|
||||
cd /opt/split-pro
|
||||
mkdir -p /opt/split-pro_data/uploads
|
||||
ln -sf /opt/split-pro_data/uploads /opt/split-pro/uploads
|
||||
NEXTAUTH_SECRET=$(openssl rand -base64 32)
|
||||
cp .env.example .env
|
||||
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"postgresql://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}\"|" .env
|
||||
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\"${NEXTAUTH_SECRET}\"|" .env
|
||||
sed -i "s|^NEXTAUTH_URL=.*|NEXTAUTH_URL=\"http://${LOCAL_IP}:3000\"|" .env
|
||||
sed -i "s|^NEXTAUTH_URL_INTERNAL=.*|NEXTAUTH_URL_INTERNAL=\"http://localhost:3000\"|" .env
|
||||
sed -i "/^POSTGRES_CONTAINER_NAME=/d" .env
|
||||
sed -i "/^POSTGRES_USER=/d" .env
|
||||
sed -i "/^POSTGRES_PASSWORD=/d" .env
|
||||
sed -i "/^POSTGRES_DB=/d" .env
|
||||
sed -i "/^POSTGRES_PORT=/d" .env
|
||||
$STD pnpm build
|
||||
$STD pnpm exec prisma migrate deploy
|
||||
msg_ok "Built Split Pro"
|
||||
|
||||
msg_info "Creating Service"
|
||||
cat <<EOF >/etc/systemd/system/split-pro.service
|
||||
[Unit]
|
||||
Description=Split Pro
|
||||
After=network.target postgresql.service
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=/opt/split-pro
|
||||
EnvironmentFile=/opt/split-pro/.env
|
||||
ExecStart=/usr/bin/pnpm start
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
systemctl enable -q --now split-pro
|
||||
msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"name": "Gluetun",
|
||||
"slug": "gluetun",
|
||||
"categories": [
|
||||
4
|
||||
],
|
||||
"date_created": "2026-03-10",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 8000,
|
||||
"documentation": "https://github.com/qdm12/gluetun-wiki",
|
||||
"config_path": "/opt/gluetun-data/.env",
|
||||
"website": "https://github.com/qdm12/gluetun",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/gluetun.webp",
|
||||
"description": "Gluetun is a lightweight VPN client supporting multiple providers (Mullvad, NordVPN, PIA, ProtonVPN, Surfshark, etc.) with OpenVPN and WireGuard, built-in DNS over TLS, firewall kill switch, HTTP proxy, and Shadowsocks proxy.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/gluetun.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 2048,
|
||||
"hdd": 8,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "You must configure your VPN provider credentials in /opt/gluetun-data/.env before the service will connect",
|
||||
"type": "warning"
|
||||
},
|
||||
{
|
||||
"text": "TUN device support is required and enabled by default during container creation",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Port 8000 provides the HTTP control server API",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Supports 30+ VPN providers - see https://github.com/qdm12/gluetun-wiki for provider-specific setup",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "Split Pro",
|
||||
"slug": "split-pro",
|
||||
"categories": [
|
||||
12
|
||||
],
|
||||
"date_created": "2026-02-12",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 3000,
|
||||
"documentation": "https://github.com/oss-apps/split-pro/blob/main/docker/README.md",
|
||||
"website": "https://github.com/oss-apps/split-pro",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/splitpro.webp",
|
||||
"config_path": "/opt/split-pro/.env",
|
||||
"description": "SplitPro is a self-hosted, open source way to share expenses with friends. It is designed as a replacement for Splitwise.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/split-pro.sh",
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"ram": 4096,
|
||||
"hdd": 6,
|
||||
"os": "debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Before first use you must configure email credentials or authentication (OAuth/OIDC) provider in `/opt/split-pro/.env` and restart the service `systemctl restart split-pro`.",
|
||||
"type": "warning"
|
||||
},
|
||||
{
|
||||
"text": "Receipt uploads are stored in `/opt/split-pro_data/uploads`",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
609
misc/tools.func
609
misc/tools.func
@@ -105,11 +105,13 @@ curl_with_retry() {
|
||||
fi
|
||||
fi
|
||||
|
||||
debug_log "curl attempt $attempt failed, waiting ${backoff}s before retry..."
|
||||
debug_log "curl attempt $attempt failed (timeout=${timeout}s), waiting ${backoff}s before retry..."
|
||||
sleep "$backoff"
|
||||
# Exponential backoff: 1, 2, 4, 8... capped at 30s
|
||||
backoff=$((backoff * 2))
|
||||
((backoff > 30)) && backoff=30
|
||||
# Double --max-time on each retry so slow connections can finish
|
||||
timeout=$((timeout * 2))
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
@@ -172,8 +174,10 @@ curl_api_with_retry() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
debug_log "curl API attempt $attempt failed (HTTP $http_code), waiting ${attempt}s..."
|
||||
debug_log "curl API attempt $attempt failed (HTTP $http_code, timeout=${timeout}s), waiting ${attempt}s..."
|
||||
sleep "$attempt"
|
||||
# Double --max-time on each retry so slow connections can finish
|
||||
timeout=$((timeout * 2))
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
@@ -934,7 +938,11 @@ upgrade_package() {
|
||||
# ------------------------------------------------------------------------------
|
||||
# Repository availability check with caching
|
||||
# ------------------------------------------------------------------------------
|
||||
declare -A _REPO_CACHE 2>/dev/null || true
|
||||
# Note: Must use -gA (global) because tools.func is sourced inside update_os()
|
||||
# function scope. Plain 'declare -A' would create a local variable that gets
|
||||
# destroyed when update_os() returns, causing "unbound variable" errors later
|
||||
# when setup_postgresql/verify_repo_available tries to access the cache key.
|
||||
declare -gA _REPO_CACHE 2>/dev/null || declare -A _REPO_CACHE 2>/dev/null || true
|
||||
|
||||
verify_repo_available() {
|
||||
local repo_url="$1"
|
||||
@@ -965,13 +973,43 @@ verify_repo_available() {
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Ensure dependencies are installed (with apt update caching)
|
||||
# Ensure dependencies are installed (with apt/apk update caching)
|
||||
# Supports both Debian (apt/dpkg) and Alpine (apk) systems
|
||||
# ------------------------------------------------------------------------------
|
||||
ensure_dependencies() {
|
||||
local deps=("$@")
|
||||
local missing=()
|
||||
|
||||
# Fast batch check using dpkg-query (much faster than individual checks)
|
||||
# Detect Alpine Linux
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
for dep in "${deps[@]}"; do
|
||||
if command -v "$dep" &>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if apk info -e "$dep" &>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
missing+=("$dep")
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
$STD apk add --no-cache "${missing[@]}" || {
|
||||
local failed=()
|
||||
for pkg in "${missing[@]}"; do
|
||||
if ! $STD apk add --no-cache "$pkg" 2>/dev/null; then
|
||||
failed+=("$pkg")
|
||||
fi
|
||||
done
|
||||
if [[ ${#failed[@]} -gt 0 ]]; then
|
||||
msg_error "Failed to install dependencies: ${failed[*]}"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Debian/Ubuntu: Fast batch check using dpkg-query
|
||||
local installed_pkgs
|
||||
installed_pkgs=$(dpkg-query -W -f='${Package}\n' 2>/dev/null | sort -u)
|
||||
|
||||
@@ -1068,11 +1106,53 @@ create_temp_dir() {
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Check if package is installed (faster than dpkg -l | grep)
|
||||
# Check if package is installed (supports both Debian and Alpine)
|
||||
# ------------------------------------------------------------------------------
|
||||
is_package_installed() {
|
||||
local package="$1"
|
||||
dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "^install ok installed$"
|
||||
if [[ -f /etc/alpine-release ]]; then
|
||||
apk info -e "$package" &>/dev/null
|
||||
else
|
||||
dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "^install ok installed$"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Prompt user to enter a GitHub Personal Access Token (PAT) interactively
|
||||
# Returns 0 if a valid token was provided, 1 otherwise
|
||||
# ------------------------------------------------------------------------------
|
||||
prompt_for_github_token() {
|
||||
if [[ ! -t 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local reply
|
||||
read -rp "${TAB}Would you like to enter a GitHub Personal Access Token (PAT)? [y/N]: " reply
|
||||
reply="${reply:-n}"
|
||||
|
||||
if [[ ! "${reply,,}" =~ ^(y|yes)$ ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local token
|
||||
while true; do
|
||||
read -rp "${TAB}Enter your GitHub PAT: " token
|
||||
# Trim leading/trailing whitespace
|
||||
token="$(echo "$token" | xargs)"
|
||||
if [[ -z "$token" ]]; then
|
||||
msg_warn "Token cannot be empty. Please try again."
|
||||
continue
|
||||
fi
|
||||
if [[ "$token" =~ [[:space:]] ]]; then
|
||||
msg_warn "Token must not contain spaces. Please try again."
|
||||
continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
|
||||
export GITHUB_TOKEN="$token"
|
||||
msg_ok "GitHub token has been set."
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -1087,7 +1167,8 @@ github_api_call() {
|
||||
local header_args=()
|
||||
[[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||
|
||||
for attempt in $(seq 1 $max_retries); do
|
||||
local attempt=1
|
||||
while ((attempt <= max_retries)); do
|
||||
local http_code
|
||||
http_code=$(curl -sSL -w "%{http_code}" -o "$output_file" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
@@ -1104,7 +1185,11 @@ github_api_call() {
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||
else
|
||||
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
|
||||
msg_error "The repository may require authentication."
|
||||
fi
|
||||
if prompt_for_github_token; then
|
||||
header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||
continue
|
||||
fi
|
||||
return 1
|
||||
;;
|
||||
@@ -1114,9 +1199,16 @@ github_api_call() {
|
||||
msg_warn "GitHub API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)"
|
||||
sleep "$retry_delay"
|
||||
retry_delay=$((retry_delay * 2))
|
||||
((attempt++))
|
||||
continue
|
||||
fi
|
||||
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||
if prompt_for_github_token; then
|
||||
header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||
retry_delay=2
|
||||
attempt=1
|
||||
continue
|
||||
fi
|
||||
msg_error "To increase the limit, export a GitHub token before running the script:"
|
||||
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
|
||||
return 1
|
||||
@@ -1128,6 +1220,7 @@ github_api_call() {
|
||||
000 | "")
|
||||
if [[ $attempt -lt $max_retries ]]; then
|
||||
sleep "$retry_delay"
|
||||
((attempt++))
|
||||
continue
|
||||
fi
|
||||
msg_error "GitHub API connection failed (no response)."
|
||||
@@ -1137,12 +1230,14 @@ github_api_call() {
|
||||
*)
|
||||
if [[ $attempt -lt $max_retries ]]; then
|
||||
sleep "$retry_delay"
|
||||
((attempt++))
|
||||
continue
|
||||
fi
|
||||
msg_error "GitHub API call failed (HTTP $http_code)."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
msg_error "GitHub API call failed after ${max_retries} attempts: ${url}"
|
||||
@@ -1732,6 +1827,13 @@ setup_deb822_repo() {
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
else
|
||||
# Already binary — copy directly
|
||||
cp -f "$tmp_gpg" "/etc/apt/keyrings/${name}.gpg" || {
|
||||
msg_error "Failed to install GPG key for ${name}"
|
||||
rm -f "$tmp_gpg"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
rm -f "$tmp_gpg"
|
||||
chmod 644 "/etc/apt/keyrings/${name}.gpg"
|
||||
@@ -1877,6 +1979,47 @@ extract_version_from_json() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Get latest GitHub tag (for repos that only publish tags, not releases).
|
||||
#
|
||||
# Usage:
|
||||
# get_latest_gh_tag "owner/repo" [prefix]
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - GitHub repo (owner/repo)
|
||||
# $2 - Optional prefix filter (e.g., "v" to only match tags starting with "v")
|
||||
#
|
||||
# Returns:
|
||||
# Latest tag name (stdout), or returns 1 on failure
|
||||
# ------------------------------------------------------------------------------
|
||||
get_latest_gh_tag() {
|
||||
local repo="$1"
|
||||
local prefix="${2:-}"
|
||||
local temp_file
|
||||
temp_file=$(mktemp)
|
||||
|
||||
if ! github_api_call "https://api.github.com/repos/${repo}/tags?per_page=50" "$temp_file"; then
|
||||
rm -f "$temp_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tag=""
|
||||
if [[ -n "$prefix" ]]; then
|
||||
tag=$(jq -r --arg p "$prefix" '[.[] | select(.name | startswith($p))][0].name // empty' "$temp_file")
|
||||
else
|
||||
tag=$(jq -r '.[0].name // empty' "$temp_file")
|
||||
fi
|
||||
|
||||
rm -f "$temp_file"
|
||||
|
||||
if [[ -z "$tag" ]]; then
|
||||
msg_error "No tags found for ${repo}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$tag"
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Get latest GitHub release version with fallback to tags
|
||||
# Usage: get_latest_github_release "owner/repo" [strip_v] [include_prerelease]
|
||||
@@ -1975,103 +2118,131 @@ verify_gpg_fingerprint() {
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Get latest GitHub tag for a repository.
|
||||
# Fetches and deploys a GitHub tag-based source tarball.
|
||||
#
|
||||
# Description:
|
||||
# - Queries the GitHub API for tags (not releases)
|
||||
# - Useful for repos that only create tags, not full releases
|
||||
# - Supports optional prefix filter and version-only extraction
|
||||
# - Returns the latest tag name (printed to stdout)
|
||||
# - Downloads the source tarball for a given tag from GitHub
|
||||
# - Extracts to the target directory
|
||||
# - Writes the version to ~/.<app>
|
||||
#
|
||||
# Usage:
|
||||
# MONGO_VERSION=$(get_latest_gh_tag "mongodb/mongo-tools")
|
||||
# LATEST=$(get_latest_gh_tag "owner/repo" "v") # only tags starting with "v"
|
||||
# LATEST=$(get_latest_gh_tag "owner/repo" "" "true") # strip leading "v"
|
||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server"
|
||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "latest" "/opt/guacamole-server"
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - GitHub repo (owner/repo)
|
||||
# $2 - Tag prefix filter (optional, e.g. "v" or "100.")
|
||||
# $3 - Strip prefix from result (optional, "true" to strip $2 prefix)
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success (tag printed to stdout), 1 on failure
|
||||
# $1 - App name (used for version file ~/.<app>)
|
||||
# $2 - GitHub repo (owner/repo)
|
||||
# $3 - Tag version (default: "latest" → auto-detect via get_latest_gh_tag)
|
||||
# $4 - Target directory (default: /opt/$app)
|
||||
#
|
||||
# Notes:
|
||||
# - Skips tags containing "rc", "alpha", "beta", "dev", "test"
|
||||
# - Sorts by version number (sort -V) to find the latest
|
||||
# - Respects GITHUB_TOKEN for rate limiting
|
||||
# - Supports CLEAN_INSTALL=1 to wipe target before extracting
|
||||
# - For repos that only publish tags, not GitHub Releases
|
||||
# ------------------------------------------------------------------------------
|
||||
get_latest_gh_tag() {
|
||||
local repo="$1"
|
||||
local prefix="${2:-}"
|
||||
local strip_prefix="${3:-false}"
|
||||
fetch_and_deploy_gh_tag() {
|
||||
local app="$1"
|
||||
local repo="$2"
|
||||
local version="${3:-latest}"
|
||||
local target="${4:-/opt/$app}"
|
||||
local app_lc=""
|
||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
||||
local version_file="$HOME/.${app_lc}"
|
||||
|
||||
local header_args=()
|
||||
[[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||
if [[ "$version" == "latest" ]]; then
|
||||
version=$(get_latest_gh_tag "$repo") || {
|
||||
msg_error "Failed to determine latest tag for ${repo}"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
local http_code=""
|
||||
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_tags.json \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"${header_args[@]}" \
|
||||
"https://api.github.com/repos/${repo}/tags?per_page=100" 2>/dev/null) || true
|
||||
local current_version=""
|
||||
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
|
||||
|
||||
if [[ "$http_code" == "401" ]]; then
|
||||
msg_error "GitHub API authentication failed (HTTP 401)."
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||
else
|
||||
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
|
||||
fi
|
||||
rm -f /tmp/gh_tags.json
|
||||
if [[ "$current_version" == "$version" ]]; then
|
||||
msg_ok "$app is already up-to-date ($version)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local tmpdir
|
||||
tmpdir=$(mktemp -d) || return 1
|
||||
local tarball_url="https://github.com/${repo}/archive/refs/tags/${version}.tar.gz"
|
||||
local filename="${app_lc}-${version}.tar.gz"
|
||||
|
||||
msg_info "Fetching GitHub tag: ${app} (${version})"
|
||||
|
||||
download_file "$tarball_url" "$tmpdir/$filename" || {
|
||||
msg_error "Download failed: $tarball_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
}
|
||||
|
||||
mkdir -p "$target"
|
||||
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
|
||||
rm -rf "${target:?}/"*
|
||||
fi
|
||||
|
||||
if [[ "$http_code" == "403" ]]; then
|
||||
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||
msg_error "To increase the limit, export a GitHub token before running the script:"
|
||||
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
|
||||
rm -f /tmp/gh_tags.json
|
||||
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
|
||||
msg_error "Failed to extract tarball"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$http_code" == "000" || -z "$http_code" ]]; then
|
||||
msg_error "GitHub API connection failed (no response)."
|
||||
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
|
||||
rm -f /tmp/gh_tags.json
|
||||
return 1
|
||||
fi
|
||||
local unpack_dir
|
||||
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
|
||||
|
||||
if [[ "$http_code" != "200" ]] || [[ ! -s /tmp/gh_tags.json ]]; then
|
||||
msg_error "Unable to fetch tags for ${repo} (HTTP ${http_code})"
|
||||
rm -f /tmp/gh_tags.json
|
||||
return 1
|
||||
fi
|
||||
shopt -s dotglob nullglob
|
||||
cp -r "$unpack_dir"/* "$target/"
|
||||
shopt -u dotglob nullglob
|
||||
|
||||
local tags_json
|
||||
tags_json=$(</tmp/gh_tags.json)
|
||||
rm -f /tmp/gh_tags.json
|
||||
|
||||
# Extract tag names, filter by prefix, exclude pre-release patterns, sort by version
|
||||
local latest=""
|
||||
latest=$(echo "$tags_json" | grep -oP '"name":\s*"\K[^"]+' |
|
||||
{ [[ -n "$prefix" ]] && grep "^${prefix}" || cat; } |
|
||||
grep -viE '(rc|alpha|beta|dev|test|preview|snapshot)' |
|
||||
sort -V | tail -n1)
|
||||
|
||||
if [[ -z "$latest" ]]; then
|
||||
msg_warn "No matching tags found for ${repo}${prefix:+ (prefix: $prefix)}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$strip_prefix" == "true" && -n "$prefix" ]]; then
|
||||
latest="${latest#"$prefix"}"
|
||||
fi
|
||||
|
||||
echo "$latest"
|
||||
rm -rf "$tmpdir"
|
||||
echo "$version" >"$version_file"
|
||||
msg_ok "Deployed ${app} ${version} to ${target}"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Checks for new GitHub tag (for repos without releases).
|
||||
#
|
||||
# Description:
|
||||
# - Uses get_latest_gh_tag to fetch the latest tag
|
||||
# - Compares it to a local cached version (~/.<app>)
|
||||
# - If newer, sets global CHECK_UPDATE_RELEASE and returns 0
|
||||
#
|
||||
# Usage:
|
||||
# if check_for_gh_tag "guacd" "apache/guacamole-server"; then
|
||||
# fetch_and_deploy_gh_tag "guacd" "apache/guacamole-server" "/opt/guacamole-server"
|
||||
# fi
|
||||
#
|
||||
# Notes:
|
||||
# - For repos that only publish tags, not GitHub Releases
|
||||
# - Same interface as check_for_gh_release
|
||||
# ------------------------------------------------------------------------------
|
||||
check_for_gh_tag() {
|
||||
local app="$1"
|
||||
local repo="$2"
|
||||
local prefix="${3:-}"
|
||||
local app_lc=""
|
||||
app_lc="$(echo "${app,,}" | tr -d ' ')"
|
||||
local current_file="$HOME/.${app_lc}"
|
||||
|
||||
msg_info "Checking for update: ${app}"
|
||||
|
||||
local latest=""
|
||||
latest=$(get_latest_gh_tag "$repo" "$prefix") || return 1
|
||||
|
||||
local current=""
|
||||
[[ -f "$current_file" ]] && current="$(<"$current_file")"
|
||||
|
||||
if [[ -z "$current" || "$current" != "$latest" ]]; then
|
||||
CHECK_UPDATE_RELEASE="$latest"
|
||||
msg_ok "Update available: ${app} ${current:-not installed} → ${latest}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
msg_ok "No update available: ${app} (${latest})"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL FUNCTIONS
|
||||
# ==============================================================================
|
||||
@@ -2121,6 +2292,35 @@ check_for_gh_release() {
|
||||
# Try /latest endpoint for non-pinned versions (most efficient)
|
||||
local releases_json="" http_code=""
|
||||
|
||||
# For pinned versions, query the specific release tag directly
|
||||
if [[ -n "$pinned_version_in" ]]; then
|
||||
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_check.json \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"${header_args[@]}" \
|
||||
"https://api.github.com/repos/${source}/releases/tags/${pinned_version_in}" 2>/dev/null) || true
|
||||
|
||||
if [[ "$http_code" == "200" ]] && [[ -s /tmp/gh_check.json ]]; then
|
||||
releases_json="[$(</tmp/gh_check.json)]"
|
||||
elif [[ "$http_code" == "401" ]]; then
|
||||
msg_error "GitHub API authentication failed (HTTP 401)."
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||
else
|
||||
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
|
||||
fi
|
||||
rm -f /tmp/gh_check.json
|
||||
return 1
|
||||
elif [[ "$http_code" == "403" ]]; then
|
||||
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||
msg_error "To increase the limit, export a GitHub token before running the script:"
|
||||
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
|
||||
rm -f /tmp/gh_check.json
|
||||
return 1
|
||||
fi
|
||||
rm -f /tmp/gh_check.json
|
||||
fi
|
||||
|
||||
if [[ -z "$pinned_version_in" ]]; then
|
||||
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_check.json \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
@@ -2388,6 +2588,8 @@ check_for_codeberg_release() {
|
||||
# ------------------------------------------------------------------------------
|
||||
create_self_signed_cert() {
|
||||
local APP_NAME="${1:-${APPLICATION}}"
|
||||
local HOSTNAME="$(hostname -f)"
|
||||
local IP="$(hostname -I | awk '{print $1}')"
|
||||
local APP_NAME_LC=$(echo "${APP_NAME,,}" | tr -d ' ')
|
||||
local CERT_DIR="/etc/ssl/${APP_NAME_LC}"
|
||||
local CERT_KEY="${CERT_DIR}/${APP_NAME_LC}.key"
|
||||
@@ -2405,8 +2607,8 @@ create_self_signed_cert() {
|
||||
|
||||
mkdir -p "$CERT_DIR"
|
||||
$STD openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
|
||||
-subj "/CN=${APP_NAME}" \
|
||||
-addext "subjectAltName=DNS:${APP_NAME}" \
|
||||
-subj "/CN=${HOSTNAME}" \
|
||||
-addext "subjectAltName=DNS:${HOSTNAME},DNS:localhost,IP:${IP},IP:127.0.0.1" \
|
||||
-keyout "$CERT_KEY" \
|
||||
-out "$CERT_CRT" || {
|
||||
msg_error "Failed to create self-signed certificate"
|
||||
@@ -2476,6 +2678,30 @@ function ensure_usr_local_bin_persist() {
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# curl_download - Downloads a file with automatic retry and exponential backoff.
|
||||
#
|
||||
# Usage: curl_download <output_file> <url>
|
||||
#
|
||||
# Retries up to 5 times with increasing --max-time (60/120/240/480/960s).
|
||||
# Returns 0 on success, 1 if all attempts fail.
|
||||
# ------------------------------------------------------------------------------
|
||||
function curl_download() {
|
||||
local output="$1"
|
||||
local url="$2"
|
||||
local timeouts=(60 120 240 480 960)
|
||||
|
||||
for i in "${!timeouts[@]}"; do
|
||||
if curl --connect-timeout 15 --max-time "${timeouts[$i]}" -fsSL -o "$output" "$url"; then
|
||||
return 0
|
||||
fi
|
||||
if ((i < ${#timeouts[@]} - 1)); then
|
||||
msg_warn "Download timed out after ${timeouts[$i]}s, retrying... (attempt $((i + 2))/${#timeouts[@]})"
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Downloads and deploys latest Codeberg release (source, binary, tarball, asset).
|
||||
#
|
||||
@@ -2533,8 +2759,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
local app_lc=$(echo "${app,,}" | tr -d ' ')
|
||||
local version_file="$HOME/.${app_lc}"
|
||||
|
||||
local api_timeout="--connect-timeout 10 --max-time 60"
|
||||
local download_timeout="--connect-timeout 15 --max-time 900"
|
||||
local api_timeouts=(60 120 240)
|
||||
|
||||
local current_version=""
|
||||
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
|
||||
@@ -2574,7 +2799,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
|
||||
# Codeberg archive URL format: https://codeberg.org/{owner}/{repo}/archive/{tag}.tar.gz
|
||||
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
|
||||
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
|
||||
if curl_download "$tmpdir/$filename" "$archive_url"; then
|
||||
download_success=true
|
||||
fi
|
||||
|
||||
@@ -2621,16 +2846,18 @@ function fetch_and_deploy_codeberg_release() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local max_retries=3 retry_delay=2 attempt=1 success=false resp http_code
|
||||
local attempt=0 success=false resp http_code
|
||||
|
||||
while ((attempt <= max_retries)); do
|
||||
resp=$(curl $api_timeout -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break
|
||||
sleep "$retry_delay"
|
||||
while ((attempt < ${#api_timeouts[@]})); do
|
||||
resp=$(curl --connect-timeout 10 --max-time "${api_timeouts[$attempt]}" -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break
|
||||
((attempt++))
|
||||
if ((attempt < ${#api_timeouts[@]})); then
|
||||
msg_warn "API request timed out after ${api_timeouts[$((attempt - 1))]}s, retrying... (attempt $((attempt + 1))/${#api_timeouts[@]})"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! $success; then
|
||||
msg_error "Failed to fetch release metadata from $api_url after $max_retries attempts"
|
||||
msg_error "Failed to fetch release metadata from $api_url after ${#api_timeouts[@]} attempts"
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -2671,7 +2898,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
|
||||
# Codeberg archive URL format
|
||||
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
|
||||
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
|
||||
if curl_download "$tmpdir/$filename" "$archive_url"; then
|
||||
download_success=true
|
||||
fi
|
||||
|
||||
@@ -2745,7 +2972,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
fi
|
||||
|
||||
filename="${url_match##*/}"
|
||||
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$url_match" || {
|
||||
curl_download "$tmpdir/$filename" "$url_match" || {
|
||||
msg_error "Download failed: $url_match"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -2788,7 +3015,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
}
|
||||
|
||||
filename="${asset_url##*/}"
|
||||
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$asset_url" || {
|
||||
curl_download "$tmpdir/$filename" "$asset_url" || {
|
||||
msg_error "Download failed: $asset_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -2889,7 +3116,7 @@ function fetch_and_deploy_codeberg_release() {
|
||||
local target_file="$app"
|
||||
[[ "$use_filename" == "true" ]] && target_file="$filename"
|
||||
|
||||
curl $download_timeout -fsSL -o "$target/$target_file" "$asset_url" || {
|
||||
curl_download "$target/$target_file" "$asset_url" || {
|
||||
msg_error "Download failed: $asset_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -3084,8 +3311,7 @@ function fetch_and_deploy_gh_release() {
|
||||
local app_lc=$(echo "${app,,}" | tr -d ' ')
|
||||
local version_file="$HOME/.${app_lc}"
|
||||
|
||||
local api_timeout="--connect-timeout 10 --max-time 60"
|
||||
local download_timeout="--connect-timeout 15 --max-time 900"
|
||||
local api_timeouts=(60 120 240)
|
||||
|
||||
local current_version=""
|
||||
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
|
||||
@@ -3105,18 +3331,37 @@ function fetch_and_deploy_gh_release() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
local max_retries=3 retry_delay=2 attempt=1 success=false http_code
|
||||
local max_retries=${#api_timeouts[@]} retry_delay=2 attempt=1 success=false http_code
|
||||
|
||||
while ((attempt <= max_retries)); do
|
||||
http_code=$(curl $api_timeout -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
|
||||
http_code=$(curl --connect-timeout 10 --max-time "${api_timeouts[$((attempt - 1))]:-240}" -sSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url" 2>/dev/null) || true
|
||||
if [[ "$http_code" == "200" ]]; then
|
||||
success=true
|
||||
break
|
||||
elif [[ "$http_code" == "401" ]]; then
|
||||
msg_error "GitHub API authentication failed (HTTP 401)."
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||
else
|
||||
msg_error "The repository may require authentication."
|
||||
fi
|
||||
if prompt_for_github_token; then
|
||||
header=(-H "Authorization: token $GITHUB_TOKEN")
|
||||
continue
|
||||
fi
|
||||
break
|
||||
elif [[ "$http_code" == "403" ]]; then
|
||||
if ((attempt < max_retries)); then
|
||||
msg_warn "GitHub API rate limit hit, retrying in ${retry_delay}s... (attempt $attempt/$max_retries)"
|
||||
sleep "$retry_delay"
|
||||
retry_delay=$((retry_delay * 2))
|
||||
else
|
||||
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||
if prompt_for_github_token; then
|
||||
header=(-H "Authorization: token $GITHUB_TOKEN")
|
||||
retry_delay=2
|
||||
attempt=0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
sleep "$retry_delay"
|
||||
@@ -3125,21 +3370,10 @@ function fetch_and_deploy_gh_release() {
|
||||
done
|
||||
|
||||
if ! $success; then
|
||||
if [[ "$http_code" == "401" ]]; then
|
||||
msg_error "GitHub API authentication failed (HTTP 401)."
|
||||
if [[ -n "${GITHUB_TOKEN:-}" ]]; then
|
||||
msg_error "Your GITHUB_TOKEN appears to be invalid or expired."
|
||||
else
|
||||
msg_error "The repository may require authentication. Try: export GITHUB_TOKEN=\"ghp_your_token\""
|
||||
fi
|
||||
elif [[ "$http_code" == "403" ]]; then
|
||||
msg_error "GitHub API rate limit exceeded (HTTP 403)."
|
||||
msg_error "To increase the limit, export a GitHub token before running the script:"
|
||||
msg_error " export GITHUB_TOKEN=\"ghp_your_token_here\""
|
||||
elif [[ "$http_code" == "000" || -z "$http_code" ]]; then
|
||||
if [[ "$http_code" == "000" || -z "$http_code" ]]; then
|
||||
msg_error "GitHub API connection failed (no response)."
|
||||
msg_error "Check your network/DNS: curl -sSL https://api.github.com/rate_limit"
|
||||
else
|
||||
elif [[ "$http_code" != "401" ]]; then
|
||||
msg_error "Failed to fetch release metadata (HTTP $http_code)"
|
||||
fi
|
||||
return 1
|
||||
@@ -3174,7 +3408,7 @@ function fetch_and_deploy_gh_release() {
|
||||
local direct_tarball_url="https://github.com/$repo/archive/refs/tags/$tag_name.tar.gz"
|
||||
filename="${app_lc}-${version_safe}.tar.gz"
|
||||
|
||||
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$direct_tarball_url" || {
|
||||
curl_download "$tmpdir/$filename" "$direct_tarball_url" || {
|
||||
msg_error "Download failed: $direct_tarball_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -3277,7 +3511,7 @@ function fetch_and_deploy_gh_release() {
|
||||
fi
|
||||
|
||||
filename="${url_match##*/}"
|
||||
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$url_match" || {
|
||||
curl_download "$tmpdir/$filename" "$url_match" || {
|
||||
msg_error "Download failed: $url_match"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -3344,7 +3578,7 @@ function fetch_and_deploy_gh_release() {
|
||||
}
|
||||
|
||||
filename="${asset_url##*/}"
|
||||
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$asset_url" || {
|
||||
curl_download "$tmpdir/$filename" "$asset_url" || {
|
||||
msg_error "Download failed: $asset_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -3465,7 +3699,7 @@ function fetch_and_deploy_gh_release() {
|
||||
local target_file="$app"
|
||||
[[ "$use_filename" == "true" ]] && target_file="$filename"
|
||||
|
||||
curl $download_timeout -fsSL -o "$target/$target_file" "$asset_url" || {
|
||||
curl_download "$target/$target_file" "$asset_url" || {
|
||||
msg_error "Download failed: $asset_url"
|
||||
rm -rf "$tmpdir"
|
||||
return 1
|
||||
@@ -4022,6 +4256,8 @@ function setup_gs() {
|
||||
# - NVIDIA requires matching host driver version
|
||||
# ------------------------------------------------------------------------------
|
||||
function setup_hwaccel() {
|
||||
local service_user="${1:-}"
|
||||
|
||||
# Check if user explicitly disabled GPU in advanced settings
|
||||
# ENABLE_GPU is exported from build.func
|
||||
if [[ "${ENABLE_GPU:-no}" == "no" ]]; then
|
||||
@@ -4273,7 +4509,7 @@ function setup_hwaccel() {
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Device Permissions
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
_setup_gpu_permissions "$in_ct"
|
||||
_setup_gpu_permissions "$in_ct" "$service_user"
|
||||
|
||||
cache_installed_version "hwaccel" "1.0"
|
||||
msg_ok "Setup Hardware Acceleration"
|
||||
@@ -4440,9 +4676,8 @@ _setup_amd_gpu() {
|
||||
fi
|
||||
# Ubuntu includes AMD firmware in linux-firmware by default
|
||||
|
||||
# ROCm for compute (optional - large download)
|
||||
# Uncomment if needed:
|
||||
# $STD apt -y install rocm-opencl-runtime 2>/dev/null || true
|
||||
# ROCm compute stack (OpenCL + HIP)
|
||||
_setup_rocm "$os_id" "$os_codename"
|
||||
|
||||
msg_ok "AMD GPU configured"
|
||||
}
|
||||
@@ -4470,6 +4705,109 @@ _setup_amd_apu() {
|
||||
msg_ok "AMD APU configured"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# AMD ROCm Compute Setup
|
||||
# Adds ROCm repository and installs the ROCm compute stack for AMD GPUs/APUs.
|
||||
# Provides: OpenCL, HIP, rocm-smi, rocminfo
|
||||
# Supported: Debian 12/13, Ubuntu 22.04/24.04 (amd64 only)
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
_setup_rocm() {
|
||||
local os_id="$1" os_codename="$2"
|
||||
|
||||
# Only amd64 is supported
|
||||
if [[ "$(dpkg --print-architecture 2>/dev/null)" != "amd64" ]]; then
|
||||
msg_warn "ROCm is only available for amd64 — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ROCM_VERSION="7.2"
|
||||
local ROCM_REPO_CODENAME
|
||||
|
||||
# Map OS codename to ROCm repository codename (Ubuntu-based repos)
|
||||
case "${os_id}-${os_codename}" in
|
||||
debian-bookworm) ROCM_REPO_CODENAME="jammy" ;;
|
||||
debian-trixie | debian-sid) ROCM_REPO_CODENAME="noble" ;;
|
||||
ubuntu-jammy) ROCM_REPO_CODENAME="jammy" ;;
|
||||
ubuntu-noble) ROCM_REPO_CODENAME="noble" ;;
|
||||
*)
|
||||
msg_warn "ROCm not supported on ${os_id} ${os_codename} — skipping"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
msg_info "Installing ROCm ${ROCM_VERSION} compute stack"
|
||||
|
||||
# ROCm main repository (userspace compute libs)
|
||||
setup_deb822_repo \
|
||||
"rocm" \
|
||||
"https://repo.radeon.com/rocm/rocm.gpg.key" \
|
||||
"https://repo.radeon.com/rocm/apt/${ROCM_VERSION}" \
|
||||
"${ROCM_REPO_CODENAME}" \
|
||||
"main" \
|
||||
"amd64" || {
|
||||
msg_warn "Failed to add ROCm repository — skipping ROCm"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Note: The amdgpu/latest/ubuntu repo (kernel driver packages) is intentionally
|
||||
# omitted — kernel drivers are managed by the Proxmox host, not the LXC container.
|
||||
# Only the ROCm userspace compute stack is needed inside the container.
|
||||
|
||||
# Pin ROCm packages to prefer radeon repo
|
||||
cat <<EOF >/etc/apt/preferences.d/rocm-pin-600
|
||||
Package: *
|
||||
Pin: release o=repo.radeon.com
|
||||
Pin-Priority: 600
|
||||
EOF
|
||||
|
||||
# apt update with retry — repo.radeon.com CDN can be mid-sync (transient size mismatches).
|
||||
# Run with ERR trap disabled so a transient failure does not abort the entire install.
|
||||
local _apt_ok=0
|
||||
for _attempt in 1 2 3; do
|
||||
if (
|
||||
set +e
|
||||
apt-get update -qq 2>&1
|
||||
exit $?
|
||||
) 2>/dev/null; then
|
||||
_apt_ok=1
|
||||
break
|
||||
fi
|
||||
msg_warn "apt update failed (attempt ${_attempt}/3) — AMD repo may be temporarily unavailable, retrying in 30s…"
|
||||
sleep 30
|
||||
done
|
||||
if [[ $_apt_ok -eq 0 ]]; then
|
||||
msg_warn "apt update still failing after 3 attempts — skipping ROCm install"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Install only runtime packages — full 'rocm' meta-package includes 15GB+ dev tools
|
||||
$STD apt install -y rocm-opencl-runtime rocm-hip-runtime rocm-smi-lib 2>/dev/null || {
|
||||
msg_warn "ROCm runtime install failed — trying minimal set"
|
||||
$STD apt install -y rocm-opencl-runtime rocm-smi-lib 2>/dev/null || msg_warn "ROCm minimal install also failed"
|
||||
}
|
||||
|
||||
# Group membership for GPU access
|
||||
usermod -aG render,video root 2>/dev/null || true
|
||||
|
||||
# Environment (PATH + LD_LIBRARY_PATH)
|
||||
if [[ -d /opt/rocm ]]; then
|
||||
cat <<'ENVEOF' >/etc/profile.d/rocm.sh
|
||||
export PATH="$PATH:/opt/rocm/bin"
|
||||
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}/opt/rocm/lib"
|
||||
ENVEOF
|
||||
chmod +x /etc/profile.d/rocm.sh
|
||||
# Also make available for current session / systemd services
|
||||
echo "/opt/rocm/lib" >/etc/ld.so.conf.d/rocm.conf
|
||||
ldconfig 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -x /opt/rocm/bin/rocminfo ]]; then
|
||||
msg_ok "ROCm ${ROCM_VERSION} installed"
|
||||
else
|
||||
msg_warn "ROCm installed but rocminfo not found — GPU may not be available in container"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# NVIDIA GPU Setup
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -4486,10 +4824,10 @@ _setup_nvidia_gpu() {
|
||||
# Format varies by driver type:
|
||||
# Proprietary: "NVRM version: NVIDIA UNIX x86_64 Kernel Module 550.54.14 Thu..."
|
||||
# Open: "NVRM version: NVIDIA UNIX Open Kernel Module for x86_64 590.48.01 Release..."
|
||||
# Use regex to extract version number (###.##.## pattern)
|
||||
# Use regex to extract version number (###.##.## or ###.## pattern)
|
||||
local nvidia_host_version=""
|
||||
if [[ -f /proc/driver/nvidia/version ]]; then
|
||||
nvidia_host_version=$(grep -oP '\d{3,}\.\d+\.\d+' /proc/driver/nvidia/version 2>/dev/null | head -1)
|
||||
nvidia_host_version=$(grep -oP '\d{3,}\.\d+(\.\d+)?' /proc/driver/nvidia/version 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
if [[ -z "$nvidia_host_version" ]]; then
|
||||
@@ -4805,6 +5143,7 @@ EOF
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
_setup_gpu_permissions() {
|
||||
local in_ct="$1"
|
||||
local service_user="${2:-}"
|
||||
|
||||
# /dev/dri permissions (Intel/AMD)
|
||||
if [[ "$in_ct" == "0" && -d /dev/dri ]]; then
|
||||
@@ -4871,6 +5210,12 @@ _setup_gpu_permissions() {
|
||||
chmod 666 /dev/kfd 2>/dev/null || true
|
||||
msg_info "AMD ROCm compute device configured"
|
||||
fi
|
||||
|
||||
# Add service user to render and video groups for GPU hardware acceleration
|
||||
if [[ -n "$service_user" ]]; then
|
||||
$STD usermod -aG render "$service_user" 2>/dev/null || true
|
||||
$STD usermod -aG video "$service_user" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
@@ -5142,7 +5487,7 @@ current_ip="$(get_current_ip)"
|
||||
|
||||
if [[ -z "$current_ip" ]]; then
|
||||
echo "[ERROR] Could not detect local IP" >&2
|
||||
exit 1
|
||||
exit 123
|
||||
fi
|
||||
|
||||
if [[ -f "$IP_FILE" ]]; then
|
||||
@@ -5643,20 +5988,20 @@ function setup_mongodb() {
|
||||
# - Handles Debian Trixie libaio1t64 transition
|
||||
#
|
||||
# Variables:
|
||||
# USE_MYSQL_REPO - Set to "true" to use official MySQL repository
|
||||
# (default: false, uses distro packages)
|
||||
# USE_MYSQL_REPO - Use official MySQL repository (default: true)
|
||||
# Set to "false" to use distro packages instead
|
||||
# MYSQL_VERSION - MySQL version to install when using official repo
|
||||
# (e.g. 8.0, 8.4) (default: 8.0)
|
||||
#
|
||||
# Examples:
|
||||
# setup_mysql # Uses distro package (recommended)
|
||||
# USE_MYSQL_REPO=true setup_mysql # Uses official MySQL repo
|
||||
# USE_MYSQL_REPO=true MYSQL_VERSION="8.4" setup_mysql # Specific version
|
||||
# setup_mysql # Uses official MySQL repo, 8.0
|
||||
# MYSQL_VERSION="8.4" setup_mysql # Specific version from MySQL repo
|
||||
# USE_MYSQL_REPO=false setup_mysql # Uses distro package instead
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
function setup_mysql() {
|
||||
local MYSQL_VERSION="${MYSQL_VERSION:-8.0}"
|
||||
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-false}"
|
||||
local USE_MYSQL_REPO="${USE_MYSQL_REPO:-true}"
|
||||
local DISTRO_ID DISTRO_CODENAME
|
||||
DISTRO_ID=$(awk -F= '/^ID=/{print $2}' /etc/os-release | tr -d '"')
|
||||
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)
|
||||
|
||||
Reference in New Issue
Block a user