Compare commits

...

17 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
2667f603e5 fix(misc): replace generic return 1 with specific exit codes in remaining .func files
Replace 80 generic 'return 1' error returns with specific exit codes
across alpine-tools.func, build.func, core.func, cloud-init.func,
and vm-core.func, matching the EXIT_CODES.md schema.

alpine-tools.func (51 replaced):
- check_for_gh_release, fetch_and_deploy_gh: API/DNS/download errors
- setup_yq, setup_adminer, setup_uv, setup_java, setup_go, setup_composer
- need_tool, download_with_progress

build.func (13 replaced):
- install_ssh_keys_into_ct: file operation errors (252)
- choose_and_set_storage_for_file: parameter/service errors (65/150)
- _find_default_vars, default_var_settings: file not found (252)
- destroy_lxc, resolve_storage_preselect, select_storage: param errors (65)
- validate_storage_space: hardware/space errors (236)

core.func (9 replaced):
- get_header: download failed (250)
- prompt_select: no options (65)
- check_or_create_swap: dd/mkswap/swapon failures (150), invalid size (65)
- get_current_ip, get_lxc_ip: IP detection failed (6)

cloud-init.func (6 replaced):
- setup_cloud_init: invalid IP/gateway format (65)
- configure_cloud_init_interactive: whiptail missing (127)
- get_vm_ip, wait_for_cloud_init: timeout/connection errors (7/150)

vm-core.func (1 replaced):
- get_header: download failed (250)

Boolean returns (validate_*, is_*, prompt_*, 'no update') kept as return 1.
2026-03-26 15:06:48 +01:00
CanbiZ (MickLesk)
a53fef912c fix(tools.func): replace generic return 1 with specific exit codes
Replace 300 generic 'return 1' error returns with specific exit codes
matching the EXIT_CODES.md schema for better telemetry and debugging:

- 6: DNS resolution failed
- 7: Connection/curl failed
- 22: HTTP/API error (401, 403, 404, etc.)
- 65: Data format/parameter error
- 100: APT package manager error
- 127: Command not found
- 150: Service/build failed
- 236: Hardware not detected
- 238: OS not supported
- 250: Download/version determination failed
- 251: File extraction failed
- 252: File not found

Boolean returns (is_tool_installed, should_update_tool, verify_tool_version,
verify_repo_available, prompt_for_github_token, should_upgrade, is_lts_version,
check_for_*_release 'no update') intentionally kept as return 1.

Affected functions: curl_with_retry, curl_api_with_retry, download_gpg_key,
install_packages_with_retry, upgrade_packages_with_retry, manage_tool_repository,
upgrade_package, github_api_call, codeberg_api_call, setup_deb822_repo,
get_latest_gh_tag, get_latest_github_release, check_for_gh_release,
check_for_codeberg_release, fetch_and_deploy_gh_release,
fetch_and_deploy_codeberg_release, fetch_and_deploy_from_url,
setup_mongodb, setup_mysql, setup_mariadb, setup_nodejs, setup_postgresql,
setup_php, setup_java, setup_go, setup_ruby, setup_rust, setup_uv,
setup_clickhouse, setup_adminer, setup_composer, setup_ffmpeg,
setup_imagemagick, setup_gs, setup_yq, setup_meilisearch, setup_docker,
setup_postgresql_db, setup_mariadb_db, and helper functions.
2026-03-26 14:50:06 +01:00
community-scripts-pr-app[bot]
d06a70819d Update CHANGELOG.md (#13307)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 11:35:20 +00:00
community-scripts-pr-app[bot]
53e73e2f1a Update CHANGELOG.md (#13306)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 11:34:51 +00:00
Tom Frenzel
ee66508879 SparkyFitness: add garmin microservice (#12642)
* feat(sparkyfitness): add garmin microservice

* chore(sparkyfitness): add note to json

* chore(sparkyfitness): set garmin url

* feat(sparkyfitness): add garmin install option to update menu

* fix(sparkyfitness): typo

* chore(sparkyfitness): streamline naming and refactor menu

* feat: add sparlkyfitness garmin addon

* fix: remove EOF

* fix: update uninstall

* chore: update telemetry

* fix: app name

* chore: update env vars

* fix: typo

* chore: update default port

* fix: message format

* fix: update uninstall

* fix: rename functions

* fix: remove custom header_info

* fix: add header file

* chore: revert function naming
2026-03-26 12:34:28 +01:00
community-scripts-pr-app[bot]
83cfa0b5b4 Update CHANGELOG.md (#13305)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 11:24:51 +00:00
CanbiZ (MickLesk)
0a458c0a11 Immich: Bump to 2.6.2 | use start.sh in service, ensure DB_HOSTNAME in .env | Fix Rights Issue with ZFS Shares (#13199)
* fix(immich): use start.sh in service, ensure DB_HOSTNAME in .env

* Bump Immich to v2.6.2 and adjust chown handling

Update Immich release references from v2.6.1 to v2.6.2 in ct/immich.sh and install/immich-install.sh. Replace broad recursive chown -R on the install dir with a safer approach that avoids recursing into the upload directory (which may be a mounted volume with restricted permissions): set ownership on the install dir itself, chown each top-level entry except 'upload', and attempt to chown the upload path while ignoring errors. Also adjust ordering for /var/log/immich chown to avoid permission issues when enabling services.
2026-03-26 12:24:26 +01:00
community-scripts-pr-app[bot]
6703fca0e4 Update CHANGELOG.md (#13301)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 09:12:14 +00:00
CanbiZ (MickLesk)
42fbf1afc5 core: use /usr/bin/install to prevent function shadowing (#13299) 2026-03-26 10:11:47 +01:00
community-scripts-pr-app[bot]
d1c5b03fa7 Update CHANGELOG.md (#13298)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 09:04:24 +00:00
CanbiZ (MickLesk)
b9a39db667 fix(tools.func): pin npm to 11.11.0 to work around Node.js 22.22.2 regression (#13296)
Node.js 22.22.2 ships with a broken npm self-upgrade path where 'npm install -g npm@latest' fails with MODULE_NOT_FOUND for promise-retry. Pin to npm@11.11.0 as a known-good version until the upstream issue is resolved. Ref: nodejs/node#62425, npm/cli#9151
2026-03-26 10:04:00 +01:00
community-scripts-pr-app[bot]
0872f086ef Update CHANGELOG.md (#13297)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-26 08:54:51 +00:00
CanbiZ (MickLesk)
4eecca8aea fix(tools.func): use absolute path for install in setup_uv
Using bare 'install' command gets shadowed when scripts define their own install() function, causing setup_uv to hang. Use /usr/bin/install instead.
2026-03-26 09:54:17 +01:00
CanbiZ (MickLesk)
d915dee103 Update website link from .com to .org 2026-03-25 17:52:46 +01:00
CanbiZ (MickLesk)
97bf744e96 fix typo (org instead of com) 2026-03-25 17:48:03 +01:00
community-scripts-pr-app[bot]
7425f5d8fe Update CHANGELOG.md (#13284)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-03-25 12:06:51 +00:00
CanbiZ (MickLesk)
53bc492fdb Make shell command substitutions safe with || true (#13279)
Add defensive fallbacks (|| true) to multiple command substitutions to prevent non-zero exits when commands produce no output or are unavailable. Changes touch misc/api.func, misc/build.func and misc/tools.func and cover places like lspci, /proc/cpuinfo parsing, /etc/os-release reads, hostname -I usage, grep reads from vars files and maps, pct config parsing, storage/template lookups, tool version detection, NVIDIA driver version extraction, and MeiliSearch config parsing. These edits do not change functional behavior aside from ensuring the scripts continue running (variables will be empty) instead of failing in stricter shells or when commands return non-zero status.
2026-03-25 13:06:21 +01:00
45 changed files with 693 additions and 462 deletions

View File

@@ -426,6 +426,28 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details> </details>
## 2026-03-26
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Immich: Bump to 2.6.2 | use start.sh in service, ensure DB_HOSTNAME in .env | Fix Rights Issue with ZFS Shares [@MickLesk](https://github.com/MickLesk) ([#13199](https://github.com/community-scripts/ProxmoxVE/pull/13199))
- #### ✨ New Features
- SparkyFitness: add garmin microservice as addon [@tomfrenzel](https://github.com/tomfrenzel) ([#12642](https://github.com/community-scripts/ProxmoxVE/pull/12642))
### 💾 Core
- #### 🐞 Bug Fixes
- tools.func: pin npm to 11.11.0 to work around Node.js 22.22.2 regression [@MickLesk](https://github.com/MickLesk) ([#13296](https://github.com/community-scripts/ProxmoxVE/pull/13296))
- #### 🔧 Refactor
- core: use /usr/bin/install to prevent function shadowing [@MickLesk](https://github.com/MickLesk) ([#13299](https://github.com/community-scripts/ProxmoxVE/pull/13299))
## 2026-03-25 ## 2026-03-25
### 🚀 Updated Scripts ### 🚀 Updated Scripts
@@ -434,6 +456,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- Komodo v2: migrate env vars to v2 and update source [@MickLesk](https://github.com/MickLesk) ([#13262](https://github.com/community-scripts/ProxmoxVE/pull/13262)) - Komodo v2: migrate env vars to v2 and update source [@MickLesk](https://github.com/MickLesk) ([#13262](https://github.com/community-scripts/ProxmoxVE/pull/13262))
### 💾 Core
- #### 🔧 Refactor
- core: make shell command substitutions safe with || true [@MickLesk](https://github.com/MickLesk) ([#13279](https://github.com/community-scripts/ProxmoxVE/pull/13279))
## 2026-03-24 ## 2026-03-24
### 🆕 New Scripts ### 🆕 New Scripts

View File

@@ -5,7 +5,7 @@
<p><em>A Community Legacy in Memory of @tteck</em></p> <p><em>A Community Legacy in Memory of @tteck</em></p>
<p> <p>
<a href="https://community-scripts.com"> <a href="https://community-scripts.org">
<img src="https://img.shields.io/badge/🌐_Website-Visit-4c9b3f?style=for-the-badge&labelColor=2d3748" alt="Website" /> <img src="https://img.shields.io/badge/🌐_Website-Visit-4c9b3f?style=for-the-badge&labelColor=2d3748" alt="Website" />
</a> </a>
<a href="https://discord.gg/3AnUqsXnmK"> <a href="https://discord.gg/3AnUqsXnmK">

View File

@@ -73,7 +73,7 @@ function update_script() {
$STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-amd64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin $STD curl -fsSL https://github.com/filebrowser/filebrowser/releases/download/v2.23.0/linux-amd64-filebrowser.tar.gz | tar -xzv -C /usr/local/bin
$STD filebrowser config init -a '0.0.0.0' $STD filebrowser config init -a '0.0.0.0'
$STD filebrowser config set -a '0.0.0.0' $STD filebrowser config set -a '0.0.0.0'
$STD filebrowser users add admin community-scripts.com --perm.admin $STD filebrowser users add admin community-scripts.org --perm.admin
msg_ok "Installed FileBrowser" msg_ok "Installed FileBrowser"
msg_info "Creating Service" msg_info "Creating Service"
@@ -93,7 +93,7 @@ WantedBy=default.target" >$service_path
msg_ok "Completed successfully!\n" msg_ok "Completed successfully!\n"
echo -e "FileBrowser should be reachable by going to the following URL. echo -e "FileBrowser should be reachable by going to the following URL.
${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.com\n" ${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.org\n"
exit exit
fi fi
} }

View File

@@ -109,7 +109,7 @@ EOF
msg_ok "Image-processing libraries up to date" msg_ok "Image-processing libraries up to date"
fi fi
RELEASE="v2.6.1" RELEASE="v2.6.2"
if check_for_gh_release "Immich" "immich-app/immich" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then if check_for_gh_release "Immich" "immich-app/immich" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then
if [[ $(cat ~/.immich) > "2.5.1" ]]; then if [[ $(cat ~/.immich) > "2.5.1" ]]; then
msg_info "Enabling Maintenance Mode" msg_info "Enabling Maintenance Mode"
@@ -214,7 +214,10 @@ EOF
cd "$SRC_DIR"/machine-learning cd "$SRC_DIR"/machine-learning
mkdir -p "$ML_DIR" mkdir -p "$ML_DIR"
chown -R immich:immich "$INSTALL_DIR" # chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR"
find "$INSTALL_DIR" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +
chown immich:immich "${UPLOAD_DIR:-$INSTALL_DIR/upload}" 2>/dev/null || true
chown immich:immich ./uv.lock chown immich:immich ./uv.lock
export VIRTUAL_ENV="${ML_DIR}"/ml-venv export VIRTUAL_ENV="${ML_DIR}"/ml-venv
export UV_HTTP_TIMEOUT=300 export UV_HTTP_TIMEOUT=300
@@ -263,7 +266,20 @@ EOF
[[ ! -f /usr/bin/immich ]] && ln -sf "$APP_DIR"/cli/bin/immich /usr/bin/immich [[ ! -f /usr/bin/immich ]] && ln -sf "$APP_DIR"/cli/bin/immich /usr/bin/immich
[[ ! -f /usr/bin/immich-admin ]] && ln -sf "$APP_DIR"/bin/immich-admin /usr/bin/immich-admin [[ ! -f /usr/bin/immich-admin ]] && ln -sf "$APP_DIR"/bin/immich-admin /usr/bin/immich-admin
chown -R immich:immich "$INSTALL_DIR" if ! grep -q '^DB_HOSTNAME=' "$INSTALL_DIR"/.env; then
sed -i '/^DB_DATABASE_NAME/a DB_HOSTNAME=127.0.0.1' "$INSTALL_DIR"/.env
fi
if grep -q 'ExecStart=/usr/bin/node' /etc/systemd/system/immich-web.service; then
sed -i '/^EnvironmentFile=/d' /etc/systemd/system/immich-web.service
sed -i "s|^ExecStart=.*|ExecStart=${APP_DIR}/bin/start.sh|" /etc/systemd/system/immich-web.service
systemctl daemon-reload
fi
# chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR"
find "$INSTALL_DIR" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +
chown immich:immich "${UPLOAD_DIR:-$INSTALL_DIR/upload}" 2>/dev/null || true
if [[ "${MAINT_MODE:-0}" == 1 ]]; then if [[ "${MAINT_MODE:-0}" == 1 ]]; then
msg_info "Disabling Maintenance Mode" msg_info "Disabling Maintenance Mode"
cd /opt/immich/app/bin cd /opt/immich/app/bin

View File

@@ -68,7 +68,7 @@ function update_script() {
$STD curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash $STD curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
$STD filebrowser config init -a '0.0.0.0' $STD filebrowser config init -a '0.0.0.0'
$STD filebrowser config set -a '0.0.0.0' $STD filebrowser config set -a '0.0.0.0'
$STD filebrowser users add admin community-scripts.com --perm.admin $STD filebrowser users add admin community-scripts.org --perm.admin
msg_ok "Installed FileBrowser" msg_ok "Installed FileBrowser"
msg_info "Creating Service" msg_info "Creating Service"
@@ -90,7 +90,7 @@ EOF
msg_ok "Completed successfully!\n" msg_ok "Completed successfully!\n"
echo -e "FileBrowser should be reachable by going to the following URL. echo -e "FileBrowser should be reachable by going to the following URL.
${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.com\n" ${BL}http://$LOCAL_IP:8080${CL} admin|community-scripts.org\n"
exit exit
fi fi
if [ "$UPD" == "4" ]; then if [ "$UPD" == "4" ]; then

View File

@@ -50,7 +50,7 @@ function update_script() {
/opt/semaphore/config.json /opt/semaphore/config.json
SEM_PW=$(cat ~/semaphore.creds) SEM_PW=$(cat ~/semaphore.creds)
systemctl start semaphore systemctl start semaphore
$STD semaphore user add --admin --login admin --email admin@community-scripts.com --name Administrator --password "${SEM_PW}" --config /opt/semaphore/config.json $STD semaphore user add --admin --login admin --email admin@community-scripts.org --name Administrator --password "${SEM_PW}" --config /opt/semaphore/config.json
msg_ok "Moved from BoltDB to SQLite" msg_ok "Moved from BoltDB to SQLite"
fi fi

View File

@@ -62,10 +62,10 @@ expect "Email address"
send "\r" send "\r"
expect "Password" expect "Password"
send "community-scripts.com\r" send "community-scripts.org\r"
expect "Password (again)" expect "Password (again)"
send "community-scripts.com\r" send "community-scripts.org\r"
expect eof expect eof
EOF EOF

View File

@@ -58,7 +58,7 @@ service:
use_prerelease: false use_prerelease: false
dashboard: dashboard:
icon: https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/images/logo.png icon: https://raw.githubusercontent.com/community-scripts/ProxmoxVE/refs/heads/main/misc/images/logo.png
icon_link_to: https://community-scripts.com/ icon_link_to: https://community-scripts.org/
web_url: https://github.com/community-scripts/ProxmoxVE/releases web_url: https://github.com/community-scripts/ProxmoxVE/releases
EOF EOF
msg_ok "Setup Config" msg_ok "Setup Config"

View File

@@ -35,7 +35,7 @@ PG_DB_NAME="healthchecks_db" PG_DB_USER="hc_user" PG_DB_PASS=$(openssl rand -bas
msg_info "Setup Keys (Admin / Secret)" msg_info "Setup Keys (Admin / Secret)"
SECRET_KEY="$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)" SECRET_KEY="$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | cut -c1-32)"
ADMIN_EMAIL="admin@community-scripts.com" ADMIN_EMAIL="admin@community-scripts.org"
ADMIN_PASSWORD="$PG_DB_PASS" ADMIN_PASSWORD="$PG_DB_PASS"
{ {
echo "healthchecks Admin Email: $ADMIN_EMAIL" echo "healthchecks Admin Email: $ADMIN_EMAIL"

View File

@@ -295,7 +295,7 @@ ML_DIR="${APP_DIR}/machine-learning"
GEO_DIR="${INSTALL_DIR}/geodata" GEO_DIR="${INSTALL_DIR}/geodata"
mkdir -p {"${APP_DIR}","${UPLOAD_DIR}","${GEO_DIR}","${INSTALL_DIR}"/cache} mkdir -p {"${APP_DIR}","${UPLOAD_DIR}","${GEO_DIR}","${INSTALL_DIR}"/cache}
fetch_and_deploy_gh_release "Immich" "immich-app/immich" "tarball" "v2.6.1" "$SRC_DIR" fetch_and_deploy_gh_release "Immich" "immich-app/immich" "tarball" "v2.6.2" "$SRC_DIR"
PNPM_VERSION="$(jq -r '.packageManager | split("@")[1] | split("+")[0]' ${SRC_DIR}/package.json)" PNPM_VERSION="$(jq -r '.packageManager | split("@")[1] | split("+")[0]' ${SRC_DIR}/package.json)"
NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs NODE_VERSION="24" NODE_MODULE="pnpm@${PNPM_VERSION}" setup_nodejs
@@ -344,7 +344,11 @@ msg_ok "Installed Immich Server, Web and Plugin Components"
cd "$SRC_DIR"/machine-learning cd "$SRC_DIR"/machine-learning
$STD useradd -U -s /usr/sbin/nologin -r -M -d "$INSTALL_DIR" immich $STD useradd -U -s /usr/sbin/nologin -r -M -d "$INSTALL_DIR" immich
mkdir -p "$ML_DIR" && chown -R immich:immich "$INSTALL_DIR" mkdir -p "$ML_DIR"
# chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR"
find "$INSTALL_DIR" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +
chown immich:immich "$UPLOAD_DIR" 2>/dev/null || true
export VIRTUAL_ENV="${ML_DIR}/ml-venv" export VIRTUAL_ENV="${ML_DIR}/ml-venv"
export UV_HTTP_TIMEOUT=300 export UV_HTTP_TIMEOUT=300
if [[ -f ~/.openvino ]]; then if [[ -f ~/.openvino ]]; then
@@ -469,8 +473,7 @@ User=immich
Group=immich Group=immich
UMask=0077 UMask=0077
WorkingDirectory=${APP_DIR} WorkingDirectory=${APP_DIR}
EnvironmentFile=${INSTALL_DIR}/.env ExecStart=${APP_DIR}/bin/start.sh
ExecStart=/usr/bin/node ${APP_DIR}/dist/main
Restart=on-failure Restart=on-failure
SyslogIdentifier=immich-web SyslogIdentifier=immich-web
StandardOutput=append:/var/log/immich/web.log StandardOutput=append:/var/log/immich/web.log
@@ -500,7 +503,11 @@ StandardError=append:/var/log/immich/ml.log
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
chown -R immich:immich "$INSTALL_DIR" /var/log/immich chown -R immich:immich /var/log/immich
# chown excluding upload dir contents (may be a mount with restricted permissions)
chown immich:immich "$INSTALL_DIR"
find "$INSTALL_DIR" -maxdepth 1 -mindepth 1 ! -name upload -exec chown -R immich:immich {} +
chown immich:immich "$UPLOAD_DIR" 2>/dev/null || true
systemctl enable -q --now immich-ml.service immich-web.service systemctl enable -q --now immich-ml.service immich-web.service
msg_ok "Modified user, created env file, scripts and services" msg_ok "Modified user, created env file, scripts and services"

View File

@@ -17,7 +17,7 @@ fetch_and_deploy_gh_release "inspircd" "inspircd/inspircd" "binary" "latest" "/o
msg_info "Configuring InspIRCd" msg_info "Configuring InspIRCd"
cat <<EOF >/etc/inspircd/inspircd.conf cat <<EOF >/etc/inspircd/inspircd.conf
<define name="networkDomain" value="community-scripts.com"> <define name="networkDomain" value="community-scripts.org">
<define name="networkName" value="Proxmox VE Helper-Scripts"> <define name="networkName" value="Proxmox VE Helper-Scripts">
<server <server

View File

@@ -55,10 +55,10 @@ $STD expect <<EOF
set timeout -1 set timeout -1
log_user 0 log_user 0
spawn bin/console kimai:user:create admin admin@community-scripts.com ROLE_SUPER_ADMIN spawn bin/console kimai:user:create admin admin@community-scripts.org ROLE_SUPER_ADMIN
expect "Please enter the password:" expect "Please enter the password:"
send "community-scripts.com\r" send "community-scripts.org\r"
expect eof expect eof
EOF EOF

View File

@@ -33,7 +33,7 @@ $STD yarn config set ignore-engines true
$STD yarn install $STD yarn install
$STD yarn run production $STD yarn run production
$STD php artisan key:generate $STD php artisan key:generate
$STD php artisan setup:production --email=admin@community-scripts.com --password=community-scripts.com --force $STD php artisan setup:production --email=admin@community-scripts.org --password=community-scripts.org --force
chown -R www-data:www-data /opt/monica chown -R www-data:www-data /opt/monica
chmod -R 775 /opt/monica/storage chmod -R 775 /opt/monica/storage
echo "* * * * * root php /opt/monica/artisan schedule:run >> /dev/null 2>&1" >>/etc/crontab echo "* * * * * root php /opt/monica/artisan schedule:run >> /dev/null 2>&1" >>/etc/crontab

View File

@@ -94,7 +94,7 @@ expect "Administrator username" {
send "community-scripts\r" send "community-scripts\r"
} }
expect "Administrator email address" { expect "Administrator email address" {
send "admin@community-scripts.com\r" send "admin@community-scripts.org\r"
} }
expect "Password" { expect "Password" {
send "community-scripts\r" send "community-scripts\r"

View File

@@ -60,7 +60,7 @@ read -r -p "${TAB3}Enter your ACME Email: " ACME_EMAIL_INPUT
yq -i " yq -i "
.services.npmplus.environment |= .services.npmplus.environment |=
(map(select(. != \"TZ=*\" and . != \"ACME_EMAIL=*\" and . != \"INITIAL_ADMIN_EMAIL=*\" and . != \"INITIAL_ADMIN_PASSWORD=*\")) + (map(select(. != \"TZ=*\" and . != \"ACME_EMAIL=*\" and . != \"INITIAL_ADMIN_EMAIL=*\" and . != \"INITIAL_ADMIN_PASSWORD=*\")) +
[\"TZ=$TZ_INPUT\", \"ACME_EMAIL=$ACME_EMAIL_INPUT\", \"INITIAL_ADMIN_EMAIL=admin@local.com\", \"INITIAL_ADMIN_PASSWORD=community-scripts.com\"]) [\"TZ=$TZ_INPUT\", \"ACME_EMAIL=$ACME_EMAIL_INPUT\", \"INITIAL_ADMIN_EMAIL=admin@local.com\", \"INITIAL_ADMIN_PASSWORD=community-scripts.org\"])
" /opt/compose.yaml " /opt/compose.yaml
msg_info "Building and Starting NPMplus (Patience)" msg_info "Building and Starting NPMplus (Patience)"

View File

@@ -99,7 +99,7 @@ PHOTOPRISM_DEBUG='false'
PHOTOPRISM_LOG_LEVEL='info' PHOTOPRISM_LOG_LEVEL='info'
# Site Info # Site Info
PHOTOPRISM_SITE_CAPTION='https://community-scripts.com' PHOTOPRISM_SITE_CAPTION='https://community-scripts.org'
PHOTOPRISM_SITE_DESCRIPTION='' PHOTOPRISM_SITE_DESCRIPTION=''
PHOTOPRISM_SITE_AUTHOR='' PHOTOPRISM_SITE_AUTHOR=''
EOF EOF

View File

@@ -40,7 +40,7 @@ cat <<EOF >/opt/semaphore/config.json
"access_key_encryption": "${SEM_KEY}" "access_key_encryption": "${SEM_KEY}"
} }
EOF EOF
$STD semaphore user add --admin --login admin --email admin@community-scripts.com --name Administrator --password "${SEM_PW}" --config /opt/semaphore/config.json $STD semaphore user add --admin --login admin --email admin@community-scripts.org --name Administrator --password "${SEM_PW}" --config /opt/semaphore/config.json
echo "${SEM_PW}" >~/semaphore.creds echo "${SEM_PW}" >~/semaphore.creds
msg_ok "Setup Semaphore" msg_ok "Setup Semaphore"

View File

@@ -40,6 +40,7 @@ sed \
-e "s|^SPARKY_FITNESS_SERVER_HOST=.*|SPARKY_FITNESS_SERVER_HOST=localhost|" \ -e "s|^SPARKY_FITNESS_SERVER_HOST=.*|SPARKY_FITNESS_SERVER_HOST=localhost|" \
-e "s|^SPARKY_FITNESS_SERVER_PORT=.*|SPARKY_FITNESS_SERVER_PORT=3010|" \ -e "s|^SPARKY_FITNESS_SERVER_PORT=.*|SPARKY_FITNESS_SERVER_PORT=3010|" \
-e "s|^SPARKY_FITNESS_FRONTEND_URL=.*|SPARKY_FITNESS_FRONTEND_URL=http://${LOCAL_IP}:80|" \ -e "s|^SPARKY_FITNESS_FRONTEND_URL=.*|SPARKY_FITNESS_FRONTEND_URL=http://${LOCAL_IP}:80|" \
-e "s|^GARMIN_MICROSERVICE_URL=.*|GARMIN_MICROSERVICE_URL=http://${LOCAL_IP}:8000|" \
-e "s|^SPARKY_FITNESS_API_ENCRYPTION_KEY=.*|SPARKY_FITNESS_API_ENCRYPTION_KEY=$(openssl rand -hex 32)|" \ -e "s|^SPARKY_FITNESS_API_ENCRYPTION_KEY=.*|SPARKY_FITNESS_API_ENCRYPTION_KEY=$(openssl rand -hex 32)|" \
-e "s|^BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$(openssl rand -hex 32)|" \ -e "s|^BETTER_AUTH_SECRET=.*|BETTER_AUTH_SECRET=$(openssl rand -hex 32)|" \
"/etc/sparkyfitness/.env" "/etc/sparkyfitness/.env"

View File

@@ -20,7 +20,7 @@ need_tool() {
msg_info "Installing tools: $*" msg_info "Installing tools: $*"
apk add --no-cache "$@" >/dev/null 2>&1 || { apk add --no-cache "$@" >/dev/null 2>&1 || {
msg_error "apk add failed for: $*" msg_error "apk add failed for: $*"
return 1 return 100
} }
msg_ok "Tools ready: $*" msg_ok "Tools ready: $*"
fi fi
@@ -52,17 +52,17 @@ ensure_usr_local_bin_persist() {
download_with_progress() { download_with_progress() {
# $1 url, $2 dest # $1 url, $2 dest
local url="$1" out="$2" cl local url="$1" out="$2" cl
need_tool curl pv || return 1 need_tool curl pv || return 127
cl=$(curl -fsSLI "$url" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\r') cl=$(curl -fsSLI "$url" 2>/dev/null | awk 'tolower($0) ~ /^content-length:/ {print $2}' | tr -d '\r')
if [ -n "$cl" ]; then if [ -n "$cl" ]; then
curl -fsSL "$url" | pv -s "$cl" >"$out" || { curl -fsSL "$url" | pv -s "$cl" >"$out" || {
msg_error "Download failed: $url" msg_error "Download failed: $url"
return 1 return 250
} }
else else
curl -fL# -o "$out" "$url" || { curl -fL# -o "$out" "$url" || {
msg_error "Download failed: $url" msg_error "Download failed: $url"
return 1 return 250
} }
fi fi
} }
@@ -82,14 +82,14 @@ check_for_gh_release() {
net_resolves api.github.com || { net_resolves api.github.com || {
msg_error "DNS/network error: api.github.com" msg_error "DNS/network error: api.github.com"
return 1 return 6
} }
need_tool curl jq || return 1 need_tool curl jq || return 127
tag=$(curl -fsSL "https://api.github.com/repos/${source}/releases/latest" | jq -r '.tag_name // empty') tag=$(curl -fsSL "https://api.github.com/repos/${source}/releases/latest" | jq -r '.tag_name // empty')
[ -z "$tag" ] && { [ -z "$tag" ] && {
msg_error "Unable to fetch latest tag for $app" msg_error "Unable to fetch latest tag for $app"
return 1 return 22
} }
release="${tag#v}" release="${tag#v}"
@@ -133,12 +133,12 @@ fetch_and_deploy_gh() {
net_resolves api.github.com || { net_resolves api.github.com || {
msg_error "DNS/network error" msg_error "DNS/network error"
return 1 return 6
} }
need_tool curl jq tar || return 1 need_tool curl jq tar || return 127
[ "$mode" = "prebuild" ] || [ "$mode" = "singlefile" ] && need_tool unzip >/dev/null 2>&1 || true [ "$mode" = "prebuild" ] || [ "$mode" = "singlefile" ] && need_tool unzip >/dev/null 2>&1 || true
tmpd="$(mktemp -d)" || return 1 tmpd="$(mktemp -d)" || return 252
mkdir -p "$target" mkdir -p "$target"
# Release JSON # Release JSON
@@ -146,13 +146,13 @@ fetch_and_deploy_gh() {
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest")" || { json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/latest")" || {
msg_error "GitHub API failed" msg_error "GitHub API failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 22
} }
else else
json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/tags/$version")" || { json="$(curl -fsSL "https://api.github.com/repos/$repo/releases/tags/$version")" || {
msg_error "GitHub API failed" msg_error "GitHub API failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 22
} }
fi fi
@@ -163,7 +163,7 @@ fetch_and_deploy_gh() {
[ -z "$version" ] && { [ -z "$version" ] && {
msg_error "No tag in release json" msg_error "No tag in release json"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 65
} }
case "$mode" in case "$mode" in
@@ -173,26 +173,26 @@ fetch_and_deploy_gh() {
filename="${app_lc}-${version}.tar.gz" filename="${app_lc}-${version}.tar.gz"
download_with_progress "$url" "$tmpd/$filename" || { download_with_progress "$url" "$tmpd/$filename" || {
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
tar -xzf "$tmpd/$filename" -C "$tmpd" || { tar -xzf "$tmpd/$filename" -C "$tmpd" || {
msg_error "tar extract failed" msg_error "tar extract failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 251
} }
unpack="$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)" unpack="$(find "$tmpd" -mindepth 1 -maxdepth 1 -type d | head -n1)"
# copy content of unpack to target # copy content of unpack to target
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || { (cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
msg_error "copy failed" msg_error "copy failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 252
} }
;; ;;
prebuild) prebuild)
[ -n "$pattern" ] || { [ -n "$pattern" ] || {
msg_error "prebuild requires asset pattern" msg_error "prebuild requires asset pattern"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 65
} }
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" ' url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
BEGIN{IGNORECASE=1} BEGIN{IGNORECASE=1}
@@ -201,19 +201,19 @@ fetch_and_deploy_gh() {
[ -z "$url" ] && { [ -z "$url" ] && {
msg_error "asset not found for pattern: $pattern" msg_error "asset not found for pattern: $pattern"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
filename="${url##*/}" filename="${url##*/}"
download_with_progress "$url" "$tmpd/$filename" || { download_with_progress "$url" "$tmpd/$filename" || {
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
# unpack archive (Zip or tarball) # unpack archive (Zip or tarball)
case "$filename" in case "$filename" in
*.zip) *.zip)
need_tool unzip || { need_tool unzip || {
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 127
} }
mkdir -p "$tmpd/unp" mkdir -p "$tmpd/unp"
unzip -q "$tmpd/$filename" -d "$tmpd/unp" unzip -q "$tmpd/$filename" -d "$tmpd/unp"
@@ -225,7 +225,7 @@ fetch_and_deploy_gh() {
*) *)
msg_error "unsupported archive: $filename" msg_error "unsupported archive: $filename"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 251
;; ;;
esac esac
# top-level folder strippen # top-level folder strippen
@@ -234,13 +234,13 @@ fetch_and_deploy_gh() {
(cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || { (cd "$unpack" && tar -cf - .) | (cd "$target" && tar -xf -) || {
msg_error "copy failed" msg_error "copy failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 252
} }
else else
(cd "$tmpd/unp" && tar -cf - .) | (cd "$target" && tar -xf -) || { (cd "$tmpd/unp" && tar -cf - .) | (cd "$target" && tar -xf -) || {
msg_error "copy failed" msg_error "copy failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 252
} }
fi fi
;; ;;
@@ -248,7 +248,7 @@ fetch_and_deploy_gh() {
[ -n "$pattern" ] || { [ -n "$pattern" ] || {
msg_error "singlefile requires asset pattern" msg_error "singlefile requires asset pattern"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 65
} }
url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" ' url="$(printf '%s' "$json" | jq -r '.assets[].browser_download_url' | awk -v p="$pattern" '
BEGIN{IGNORECASE=1} BEGIN{IGNORECASE=1}
@@ -257,19 +257,19 @@ fetch_and_deploy_gh() {
[ -z "$url" ] && { [ -z "$url" ] && {
msg_error "asset not found for pattern: $pattern" msg_error "asset not found for pattern: $pattern"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
filename="${url##*/}" filename="${url##*/}"
download_with_progress "$url" "$target/$app" || { download_with_progress "$url" "$target/$app" || {
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
chmod +x "$target/$app" chmod +x "$target/$app"
;; ;;
*) *)
msg_error "Unknown mode: $mode" msg_error "Unknown mode: $mode"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 65
;; ;;
esac esac
@@ -291,20 +291,20 @@ setup_yq() {
return 0 return 0
fi fi
need_tool curl || return 1 need_tool curl || return 127
local arch bin url tmp local arch bin url tmp
case "$(uname -m)" in case "$(uname -m)" in
x86_64) arch="amd64" ;; x86_64) arch="amd64" ;;
aarch64) arch="arm64" ;; aarch64) arch="arm64" ;;
*) *)
msg_error "Unsupported arch for yq: $(uname -m)" msg_error "Unsupported arch for yq: $(uname -m)"
return 1 return 238
;; ;;
esac esac
url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${arch}" url="https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${arch}"
tmp="$(mktemp)" tmp="$(mktemp)"
download_with_progress "$url" "$tmp" || return 1 download_with_progress "$url" "$tmp" || return 250
install -m 0755 "$tmp" /usr/local/bin/yq /usr/bin/install -m 0755 "$tmp" /usr/local/bin/yq
rm -f "$tmp" rm -f "$tmp"
msg_ok "Setup yq ($(yq --version 2>/dev/null))" msg_ok "Setup yq ($(yq --version 2>/dev/null))"
} }
@@ -313,13 +313,13 @@ setup_yq() {
# Adminer Alpine # Adminer Alpine
# ------------------------------ # ------------------------------
setup_adminer() { setup_adminer() {
need_tool curl || return 1 need_tool curl || return 127
msg_info "Setup Adminer (Alpine)" msg_info "Setup Adminer (Alpine)"
mkdir -p /var/www/localhost/htdocs/adminer mkdir -p /var/www/localhost/htdocs/adminer
curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \ curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \
-o /var/www/localhost/htdocs/adminer/index.php || { -o /var/www/localhost/htdocs/adminer/index.php || {
msg_error "Adminer download failed" msg_error "Adminer download failed"
return 1 return 250
} }
msg_ok "Adminer at /adminer (served by your webserver)" msg_ok "Adminer at /adminer (served by your webserver)"
} }
@@ -329,7 +329,7 @@ setup_adminer() {
# optional: PYTHON_VERSION="3.12" # optional: PYTHON_VERSION="3.12"
# ------------------------------ # ------------------------------
setup_uv() { setup_uv() {
need_tool curl tar || return 1 need_tool curl tar || return 127
local UV_BIN="/usr/local/bin/uv" local UV_BIN="/usr/local/bin/uv"
local arch tarball url tmpd ver installed local arch tarball url tmpd ver installed
@@ -338,7 +338,7 @@ setup_uv() {
aarch64) arch="aarch64-unknown-linux-musl" ;; aarch64) arch="aarch64-unknown-linux-musl" ;;
*) *)
msg_error "Unsupported arch for uv: $(uname -m)" msg_error "Unsupported arch for uv: $(uname -m)"
return 1 return 238
;; ;;
esac esac
@@ -346,7 +346,7 @@ setup_uv() {
ver="${ver#v}" ver="${ver#v}"
[ -z "$ver" ] && { [ -z "$ver" ] && {
msg_error "uv: cannot determine latest version" msg_error "uv: cannot determine latest version"
return 1 return 250
} }
if has "$UV_BIN"; then if has "$UV_BIN"; then
@@ -360,29 +360,29 @@ setup_uv() {
msg_info "Setup uv $ver" msg_info "Setup uv $ver"
fi fi
tmpd="$(mktemp -d)" || return 1 tmpd="$(mktemp -d)" || return 252
tarball="uv-${arch}.tar.gz" tarball="uv-${arch}.tar.gz"
url="https://github.com/astral-sh/uv/releases/download/v${ver}/${tarball}" url="https://github.com/astral-sh/uv/releases/download/v${ver}/${tarball}"
download_with_progress "$url" "$tmpd/uv.tar.gz" || { download_with_progress "$url" "$tmpd/uv.tar.gz" || {
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 250
} }
tar -xzf "$tmpd/uv.tar.gz" -C "$tmpd" || { tar -xzf "$tmpd/uv.tar.gz" -C "$tmpd" || {
msg_error "uv: extract failed" msg_error "uv: extract failed"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 251
} }
# tar contains ./uv # tar contains ./uv
if [ -x "$tmpd/uv" ]; then if [ -x "$tmpd/uv" ]; then
install -m 0755 "$tmpd/uv" "$UV_BIN" /usr/bin/install -m 0755 "$tmpd/uv" "$UV_BIN"
else else
# fallback: in subfolder # fallback: in subfolder
install -m 0755 "$tmpd"/*/uv "$UV_BIN" 2>/dev/null || { /usr/bin/install -m 0755 "$tmpd"/*/uv "$UV_BIN" 2>/dev/null || {
msg_error "uv binary not found in tar" msg_error "uv binary not found in tar"
rm -rf "$tmpd" rm -rf "$tmpd"
return 1 return 252
} }
fi fi
rm -rf "$tmpd" rm -rf "$tmpd"
@@ -395,13 +395,13 @@ setup_uv() {
$0 ~ "^cpython-"maj"\\." { print $0 }' | awk -F- '{print $2}' | sort -V | tail -n1)" $0 ~ "^cpython-"maj"\\." { print $0 }' | awk -F- '{print $2}' | sort -V | tail -n1)"
[ -z "$match" ] && { [ -z "$match" ] && {
msg_error "No matching Python for $PYTHON_VERSION" msg_error "No matching Python for $PYTHON_VERSION"
return 1 return 250
} }
if ! uv python list | grep -q "cpython-${match}-linux"; then if ! uv python list | grep -q "cpython-${match}-linux"; then
msg_info "Installing Python $match via uv" msg_info "Installing Python $match via uv"
uv python install "$match" || { uv python install "$match" || {
msg_error "uv python install failed" msg_error "uv python install failed"
return 1 return 150
} }
msg_ok "Python $match installed (uv)" msg_ok "Python $match installed (uv)"
fi fi
@@ -421,7 +421,7 @@ setup_java() {
msg_info "Setup Java (OpenJDK $JAVA_VERSION)" msg_info "Setup Java (OpenJDK $JAVA_VERSION)"
apk add --no-cache "$pkg" >/dev/null 2>&1 || { apk add --no-cache "$pkg" >/dev/null 2>&1 || {
msg_error "apk add $pkg failed" msg_error "apk add $pkg failed"
return 1 return 100
} }
# set JAVA_HOME # set JAVA_HOME
local prof="/etc/profile.d/20-java.sh" local prof="/etc/profile.d/20-java.sh"
@@ -441,32 +441,32 @@ setup_go() {
msg_info "Setup Go (apk)" msg_info "Setup Go (apk)"
apk add --no-cache go >/dev/null 2>&1 || { apk add --no-cache go >/dev/null 2>&1 || {
msg_error "apk add go failed" msg_error "apk add go failed"
return 1 return 100
} }
msg_ok "Go ready: $(go version 2>/dev/null)" msg_ok "Go ready: $(go version 2>/dev/null)"
return 0 return 0
fi fi
need_tool curl tar || return 1 need_tool curl tar || return 127
local ARCH TARBALL URL TMP local ARCH TARBALL URL TMP
case "$(uname -m)" in case "$(uname -m)" in
x86_64) ARCH="amd64" ;; x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;; aarch64) ARCH="arm64" ;;
*) *)
msg_error "Unsupported arch for Go: $(uname -m)" msg_error "Unsupported arch for Go: $(uname -m)"
return 1 return 238
;; ;;
esac esac
TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz" TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz"
URL="https://go.dev/dl/${TARBALL}" URL="https://go.dev/dl/${TARBALL}"
msg_info "Setup Go $GO_VERSION (tarball)" msg_info "Setup Go $GO_VERSION (tarball)"
TMP="$(mktemp)" TMP="$(mktemp)"
download_with_progress "$URL" "$TMP" || return 1 download_with_progress "$URL" "$TMP" || return 250
rm -rf /usr/local/go rm -rf /usr/local/go
tar -C /usr/local -xzf "$TMP" || { tar -C /usr/local -xzf "$TMP" || {
msg_error "extract go failed" msg_error "extract go failed"
rm -f "$TMP" rm -f "$TMP"
return 1 return 251
} }
rm -f "$TMP" rm -f "$TMP"
ln -sf /usr/local/go/bin/go /usr/local/bin/go ln -sf /usr/local/go/bin/go /usr/local/bin/go
@@ -488,7 +488,7 @@ setup_composer() {
# Fallback to generic php if 83 not available # Fallback to generic php if 83 not available
apk add --no-cache php-cli php-openssl php-phar php-iconv >/dev/null 2>&1 || { apk add --no-cache php-cli php-openssl php-phar php-iconv >/dev/null 2>&1 || {
msg_error "Failed to install php-cli for composer" msg_error "Failed to install php-cli for composer"
return 1 return 100
} }
} }
msg_ok "PHP CLI ready: $(php -v | head -n1)" msg_ok "PHP CLI ready: $(php -v | head -n1)"
@@ -500,14 +500,14 @@ setup_composer() {
msg_info "Setup Composer" msg_info "Setup Composer"
fi fi
need_tool curl || return 1 need_tool curl || return 127
curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php || { curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php || {
msg_error "composer installer download failed" msg_error "composer installer download failed"
return 1 return 250
} }
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1 || { php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1 || {
msg_error "composer install failed" msg_error "composer install failed"
return 1 return 150
} }
rm -f /tmp/composer-setup.php rm -f /tmp/composer-setup.php
ensure_usr_local_bin_persist ensure_usr_local_bin_persist

View File

@@ -504,7 +504,7 @@ detect_gpu() {
GPU_PASSTHROUGH="unknown" GPU_PASSTHROUGH="unknown"
local gpu_line local gpu_line
gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1) gpu_line=$(lspci 2>/dev/null | grep -iE "VGA|3D|Display" | head -1 || true)
if [[ -n "$gpu_line" ]]; then if [[ -n "$gpu_line" ]]; then
# Extract model: everything after the colon, clean up # Extract model: everything after the colon, clean up
@@ -543,7 +543,7 @@ detect_cpu() {
if [[ -f /proc/cpuinfo ]]; then if [[ -f /proc/cpuinfo ]]; then
local vendor_id local vendor_id
vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ') vendor_id=$(grep -m1 "vendor_id" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | tr -d ' ' || true)
case "$vendor_id" in case "$vendor_id" in
GenuineIntel) CPU_VENDOR="intel" ;; GenuineIntel) CPU_VENDOR="intel" ;;
@@ -557,7 +557,7 @@ detect_cpu() {
esac esac
# Extract model name and clean it up # Extract model name and clean it up
CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64) CPU_MODEL=$(grep -m1 "model name" /proc/cpuinfo 2>/dev/null | cut -d: -f2 | sed 's/^ *//' | sed 's/(R)//g' | sed 's/(TM)//g' | sed 's/ */ /g' | cut -c1-64 || true)
fi fi
export CPU_VENDOR CPU_MODEL export CPU_VENDOR CPU_MODEL
@@ -1347,8 +1347,8 @@ post_addon_to_api() {
# Detect OS info # Detect OS info
local os_type="" os_version="" local os_type="" os_version=""
if [[ -f /etc/os-release ]]; then if [[ -f /etc/os-release ]]; then
os_type=$(grep "^ID=" /etc/os-release | cut -d= -f2 | tr -d '"') os_type=$(grep "^ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true)
os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d= -f2 | tr -d '"') os_version=$(grep "^VERSION_ID=" /etc/os-release | cut -d= -f2 | tr -d '"' || true)
fi fi
local JSON_PAYLOAD local JSON_PAYLOAD

View File

@@ -173,10 +173,10 @@ get_current_ip() {
# Check for Debian/Ubuntu (uses hostname -I) # Check for Debian/Ubuntu (uses hostname -I)
if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
# Try IPv4 first # Try IPv4 first
CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1) CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || true)
# Fallback to IPv6 if no IPv4 # Fallback to IPv6 if no IPv4
if [[ -z "$CURRENT_IP" ]]; then if [[ -z "$CURRENT_IP" ]]; then
CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E ':' | head -n1) CURRENT_IP=$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E ':' | head -n1 || true)
fi fi
# Check for Alpine (uses ip command) # Check for Alpine (uses ip command)
elif grep -q 'ID=alpine' /etc/os-release; then elif grep -q 'ID=alpine' /etc/os-release; then
@@ -267,12 +267,12 @@ install_ssh_keys_into_ct() {
msg_info "Installing selected SSH keys into CT ${CTID}" msg_info "Installing selected SSH keys into CT ${CTID}"
pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || { pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || {
msg_error "prepare /root/.ssh failed" msg_error "prepare /root/.ssh failed"
return 1 return 252
} }
pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 || pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 ||
pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || { pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || {
msg_error "write authorized_keys failed" msg_error "write authorized_keys failed"
return 1 return 252
} }
pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true
msg_ok "Installed SSH keys into CT ${CTID}" msg_ok "Installed SSH keys into CT ${CTID}"
@@ -839,7 +839,7 @@ choose_and_set_storage_for_file() {
template) key="var_template_storage" ;; template) key="var_template_storage" ;;
*) *)
msg_error "Unknown storage class: $class" msg_error "Unknown storage class: $class"
return 1 return 65
;; ;;
esac esac
@@ -862,7 +862,7 @@ choose_and_set_storage_for_file() {
fi fi
else else
# If the current value is preselectable, we could show it, but per your requirement we always offer selection # If the current value is preselectable, we could show it, but per your requirement we always offer selection
select_storage "$class" || return 1 select_storage "$class" || return 150
fi fi
_write_storage_to_vars "$vf" "$key" "$STORAGE_RESULT" _write_storage_to_vars "$vf" "$key" "$STORAGE_RESULT"
@@ -1264,7 +1264,7 @@ default_var_settings() {
return 0 return 0
} }
done done
return 1 return 252
} }
# Allow override of storages via env (for non-interactive use cases) # Allow override of storages via env (for non-interactive use cases)
[ -n "${var_template_storage:-}" ] && TEMPLATE_STORAGE="$var_template_storage" [ -n "${var_template_storage:-}" ] && TEMPLATE_STORAGE="$var_template_storage"
@@ -1357,7 +1357,7 @@ EOF
local dv local dv
dv="$(_find_default_vars)" || { dv="$(_find_default_vars)" || {
msg_error "default.vars not found after ensure step" msg_error "default.vars not found after ensure step"
return 1 return 252
} }
load_vars_file "$dv" load_vars_file "$dv"
@@ -1642,7 +1642,7 @@ maybe_offer_save_app_defaults() {
if whiptail --backtitle "Proxmox VE Helper Scripts" \ if whiptail --backtitle "Proxmox VE Helper Scripts" \
--yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then --yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then
mkdir -p "$(dirname "$app_vars_path")" mkdir -p "$(dirname "$app_vars_path")"
install -m 0644 "$new_tmp" "$app_vars_path" /usr/bin/install -m 0644 "$new_tmp" "$app_vars_path"
msg_ok "Saved app defaults: ${app_vars_path}" msg_ok "Saved app defaults: ${app_vars_path}"
fi fi
rm -f "$new_tmp" "$diff_tmp" rm -f "$new_tmp" "$diff_tmp"
@@ -1676,7 +1676,7 @@ maybe_offer_save_app_defaults() {
case "$sel" in case "$sel" in
"Update Defaults") "Update Defaults")
install -m 0644 "$new_tmp" "$app_vars_path" /usr/bin/install -m 0644 "$new_tmp" "$app_vars_path"
msg_ok "Updated app defaults: ${app_vars_path}" msg_ok "Updated app defaults: ${app_vars_path}"
break break
;; ;;
@@ -1704,8 +1704,8 @@ ensure_storage_selection_for_vars_file() {
# Read stored values (if any) # Read stored values (if any)
local tpl ct local tpl ct
tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2-) tpl=$(grep -E '^var_template_storage=' "$vf" | cut -d= -f2- || true)
ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2-) ct=$(grep -E '^var_container_storage=' "$vf" | cut -d= -f2- || true)
if [[ -n "$tpl" && -n "$ct" ]]; then if [[ -n "$tpl" && -n "$ct" ]]; then
TEMPLATE_STORAGE="$tpl" TEMPLATE_STORAGE="$tpl"
@@ -1840,7 +1840,7 @@ advanced_settings() {
if [[ -n "$BRIDGES" ]]; then if [[ -n "$BRIDGES" ]]; then
while IFS= read -r bridge; do while IFS= read -r bridge; do
if [[ -n "$bridge" ]]; then if [[ -n "$bridge" ]]; then
local description=$(grep -A 10 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\s*//;s/^[- ]*//') local description=$(grep -A 10 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\s*//;s/^[- ]*//' || true)
BRIDGE_MENU_OPTIONS+=("$bridge" "${description:- }") BRIDGE_MENU_OPTIONS+=("$bridge" "${description:- }")
fi fi
done <<<"$BRIDGES" done <<<"$BRIDGES"
@@ -3322,7 +3322,7 @@ configure_ssh_settings() {
tag="${tag%\"}" tag="${tag%\"}"
tag="${tag#\"}" tag="${tag#\"}"
local line local line
line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2- || true)
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
done done
;; ;;
@@ -3349,7 +3349,7 @@ configure_ssh_settings() {
tag="${tag%\"}" tag="${tag%\"}"
tag="${tag#\"}" tag="${tag#\"}"
local line local line
line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-) line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2- || true)
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE" [[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
done done
else else
@@ -4050,7 +4050,7 @@ EOF
# Fix Debian 13 LXC template bug where / is owned by nobody:nogroup # Fix Debian 13 LXC template bug where / is owned by nobody:nogroup
# This must be done from the host as unprivileged containers cannot chown / # This must be done from the host as unprivileged containers cannot chown /
local rootfs local rootfs
rootfs=$(pct config "$CTID" | grep -E '^rootfs:' | sed 's/rootfs: //' | cut -d',' -f1) rootfs=$(pct config "$CTID" | grep -E '^rootfs:' | sed 's/rootfs: //' | cut -d',' -f1 || true)
if [[ -n "$rootfs" ]]; then if [[ -n "$rootfs" ]]; then
local mount_point="/var/lib/lxc/${CTID}/rootfs" local mount_point="/var/lib/lxc/${CTID}/rootfs"
if [[ -d "$mount_point" ]] && [[ "$(stat -c '%U' "$mount_point")" != "root" ]]; then if [[ -d "$mount_point" ]] && [[ "$(stat -c '%U' "$mount_point")" != "root" ]]; then
@@ -4690,7 +4690,7 @@ EOF'
destroy_lxc() { destroy_lxc() {
if [[ -z "$CT_ID" ]]; then if [[ -z "$CT_ID" ]]; then
msg_error "No CT_ID found. Nothing to remove." msg_error "No CT_ID found. Nothing to remove."
return 1 return 65
fi fi
# Abort on Ctrl-C / Ctrl-D / ESC # Abort on Ctrl-C / Ctrl-D / ESC
@@ -4729,12 +4729,12 @@ resolve_storage_preselect() {
case "$class" in case "$class" in
template) required_content="vztmpl" ;; template) required_content="vztmpl" ;;
container) required_content="rootdir" ;; container) required_content="rootdir" ;;
*) return 1 ;; *) return 65 ;;
esac esac
[[ -z "$preselect" ]] && return 1 [[ -z "$preselect" ]] && return 1
if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then
msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)" msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)"
return 1 return 238
fi fi
local line total used free local line total used free
@@ -4858,7 +4858,7 @@ select_storage() {
;; ;;
*) *)
msg_error "Invalid storage class '$CLASS'" msg_error "Invalid storage class '$CLASS'"
return 1 return 65
;; ;;
esac esac
@@ -4940,7 +4940,7 @@ validate_storage_space() {
# Check if storage exists and is active # Check if storage exists and is active
if [[ -z "$storage_line" ]]; then if [[ -z "$storage_line" ]]; then
[[ "$show_dialog" == "yes" ]] && whiptail --msgbox "⚠️ Warning: Storage '$storage' not found!\n\nThe storage may be unavailable or disabled." 10 60 [[ "$show_dialog" == "yes" ]] && whiptail --msgbox "⚠️ Warning: Storage '$storage' not found!\n\nThe storage may be unavailable or disabled." 10 60
return 2 return 236
fi fi
# Check storage status (column 3) # Check storage status (column 3)
@@ -4948,7 +4948,7 @@ validate_storage_space() {
status=$(awk '{print $3}' <<<"$storage_line") status=$(awk '{print $3}' <<<"$storage_line")
if [[ "$status" == "disabled" ]]; then if [[ "$status" == "disabled" ]]; then
[[ "$show_dialog" == "yes" ]] && whiptail --msgbox "⚠️ Warning: Storage '$storage' is disabled!\n\nPlease enable the storage first." 10 60 [[ "$show_dialog" == "yes" ]] && whiptail --msgbox "⚠️ Warning: Storage '$storage' is disabled!\n\nPlease enable the storage first." 10 60
return 2 return 236
fi fi
# Get storage type and free space (column 6) # Get storage type and free space (column 6)
@@ -4971,7 +4971,7 @@ validate_storage_space() {
if [[ "$show_dialog" == "yes" ]]; then if [[ "$show_dialog" == "yes" ]]; then
whiptail --msgbox "⚠️ Warning: Storage '$storage' may not have enough space!\n\nStorage Type: ${storage_type}\nRequired: ${required_gb}GB\nAvailable: ${free_gb_fmt}\n\nYou can continue, but creation might fail." 14 70 whiptail --msgbox "⚠️ Warning: Storage '$storage' may not have enough space!\n\nStorage Type: ${storage_type}\nRequired: ${required_gb}GB\nAvailable: ${free_gb_fmt}\n\nYou can continue, but creation might fail." 14 70
fi fi
return 1 return 236
fi fi
return 0 return 0
@@ -5142,7 +5142,7 @@ create_lxc_container() {
fi fi
msg_info "Validating storage '$CONTAINER_STORAGE'" msg_info "Validating storage '$CONTAINER_STORAGE'"
STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1) STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1 || true)
if [[ -z "$STORAGE_TYPE" ]]; then if [[ -z "$STORAGE_TYPE" ]]; then
msg_error "Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg" msg_error "Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg"
@@ -5181,7 +5181,7 @@ create_lxc_container() {
msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated" msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated"
msg_info "Validating template storage '$TEMPLATE_STORAGE'" msg_info "Validating template storage '$TEMPLATE_STORAGE'"
TEMPLATE_TYPE=$(grep -E "^[^:]+: $TEMPLATE_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1) TEMPLATE_TYPE=$(grep -E "^[^:]+: $TEMPLATE_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 || true)
if ! pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$TEMPLATE_STORAGE"; then if ! pvesm status -content vztmpl 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$TEMPLATE_STORAGE"; then
msg_warn "Template storage '$TEMPLATE_STORAGE' may not support 'vztmpl'" msg_warn "Template storage '$TEMPLATE_STORAGE' may not support 'vztmpl'"
@@ -5704,7 +5704,7 @@ description() {
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -319,11 +319,11 @@ function setup_cloud_init() {
if [ "$network_mode" = "static" ]; then if [ "$network_mode" = "static" ]; then
if [ -n "$static_ip" ] && ! validate_ip_cidr "$static_ip"; then if [ -n "$static_ip" ] && ! validate_ip_cidr "$static_ip"; then
_ci_msg_error "Invalid static IP format: $static_ip (expected: x.x.x.x/xx)" _ci_msg_error "Invalid static IP format: $static_ip (expected: x.x.x.x/xx)"
return 1 return 65
fi fi
if [ -n "$gateway" ] && ! validate_ip "$gateway"; then if [ -n "$gateway" ] && ! validate_ip "$gateway"; then
_ci_msg_error "Invalid gateway IP format: $gateway" _ci_msg_error "Invalid gateway IP format: $gateway"
return 1 return 65
fi fi
fi fi
@@ -433,7 +433,7 @@ function configure_cloud_init_interactive() {
if ! command -v whiptail >/dev/null 2>&1; then if ! command -v whiptail >/dev/null 2>&1; then
echo "Warning: whiptail not available, skipping interactive configuration" echo "Warning: whiptail not available, skipping interactive configuration"
export CLOUDINIT_ENABLE="no" export CLOUDINIT_ENABLE="no"
return 1 return 127
fi fi
# Ask if user wants to enable Cloud-Init # Ask if user wants to enable Cloud-Init
@@ -603,7 +603,7 @@ function get_vm_ip() {
elapsed=$((elapsed + 2)) elapsed=$((elapsed + 2))
done done
return 1 return 7
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -621,7 +621,7 @@ function wait_for_cloud_init() {
if [ -z "$vm_ip" ]; then if [ -z "$vm_ip" ]; then
_ci_msg_warn "Unable to determine VM IP address" _ci_msg_warn "Unable to determine VM IP address"
return 1 return 7
fi fi
_ci_msg_info "Waiting for Cloud-Init to complete on ${vm_ip}" _ci_msg_info "Waiting for Cloud-Init to complete on ${vm_ip}"
@@ -638,7 +638,7 @@ function wait_for_cloud_init() {
done done
_ci_msg_warn "Cloud-Init did not complete within ${timeout}s" _ci_msg_warn "Cloud-Init did not complete within ${timeout}s"
return 1 return 150
} }
# ============================================================================== # ==============================================================================

View File

@@ -858,7 +858,7 @@ get_header() {
if [ ! -s "$local_header_path" ]; then if [ ! -s "$local_header_path" ]; then
if ! curl -fsSL "$header_url" -o "$local_header_path"; then if ! curl -fsSL "$header_url" -o "$local_header_path"; then
msg_warn "Failed to download header: $header_url" msg_warn "Failed to download header: $header_url"
return 1 return 250
fi fi
fi fi
@@ -1358,7 +1358,7 @@ prompt_select() {
if [[ $num_options -eq 0 ]]; then if [[ $num_options -eq 0 ]]; then
msg_warn "prompt_select called with no options" msg_warn "prompt_select called with no options"
echo "" >&2 echo "" >&2
return 1 return 65
fi fi
# Validate default # Validate default
@@ -1600,7 +1600,7 @@ check_or_create_swap() {
swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60) swap_size_mb=$(prompt_input "Enter swap size in MB (e.g., 2048 for 2GB):" "2048" 60)
if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
msg_error "Invalid swap size: '${swap_size_mb}' (must be a number in MB)" msg_error "Invalid swap size: '${swap_size_mb}' (must be a number in MB)"
return 1 return 65
fi fi
local swap_file="/swapfile" local swap_file="/swapfile"
@@ -1608,19 +1608,19 @@ check_or_create_swap() {
msg_info "Creating ${swap_size_mb}MB swap file at $swap_file" msg_info "Creating ${swap_size_mb}MB swap file at $swap_file"
if ! dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress; then if ! dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress; then
msg_error "Failed to allocate swap file (dd failed)" msg_error "Failed to allocate swap file (dd failed)"
return 1 return 150
fi fi
if ! chmod 600 "$swap_file"; then if ! chmod 600 "$swap_file"; then
msg_error "Failed to set permissions on $swap_file" msg_error "Failed to set permissions on $swap_file"
return 1 return 150
fi fi
if ! mkswap "$swap_file"; then if ! mkswap "$swap_file"; then
msg_error "Failed to format swap file (mkswap failed)" msg_error "Failed to format swap file (mkswap failed)"
return 1 return 150
fi fi
if ! swapon "$swap_file"; then if ! swapon "$swap_file"; then
msg_error "Failed to activate swap (swapon failed)" msg_error "Failed to activate swap (swapon failed)"
return 1 return 150
fi fi
msg_ok "Swap file created and activated successfully" msg_ok "Swap file created and activated successfully"
} }
@@ -1699,13 +1699,13 @@ function get_lxc_ip() {
fi fi
done done
return 1 return 6
} }
LOCAL_IP="$(get_current_ip || true)" LOCAL_IP="$(get_current_ip || true)"
if [[ -z "$LOCAL_IP" ]]; then if [[ -z "$LOCAL_IP" ]]; then
msg_error "Could not determine LOCAL_IP (checked: eth0, hostname -I, ip route, IPv6 targets)" msg_error "Could not determine LOCAL_IP (checked: eth0, hostname -I, ip route, IPv6 targets)"
return 1 return 6
fi fi
fi fi

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@ get_header() {
if [ ! -s "$local_header_path" ]; then if [ ! -s "$local_header_path" ]; then
if ! curl -fsSL "$header_url" -o "$local_header_path"; then if ! curl -fsSL "$header_url" -o "$local_header_path"; then
return 1 return 250
fi fi
fi fi
@@ -594,7 +594,7 @@ set_description() {
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -165,9 +165,9 @@ function install() {
else else
read -rp "${TAB}Set admin username [admin]: " admin_user read -rp "${TAB}Set admin username [admin]: " admin_user
admin_user=${admin_user:-admin} admin_user=${admin_user:-admin}
read -rsp "${TAB}Set admin password [community-scripts.com]: " admin_pass read -rsp "${TAB}Set admin password [community-scripts.org]: " admin_pass
echo "" echo ""
admin_pass=${admin_pass:-community-scripts.com} admin_pass=${admin_pass:-community-scripts.org}
msg_ok "Configured with admin user: ${admin_user}" msg_ok "Configured with admin user: ${admin_user}"
fi fi

View File

@@ -201,9 +201,9 @@ server:
- neverWatchPath: "/lost+found" - neverWatchPath: "/lost+found"
auth: auth:
adminUsername: admin adminUsername: admin
adminPassword: community-scripts.com adminPassword: community-scripts.org
EOF EOF
msg_ok "Configured with default admin (admin / community-scripts.com)" msg_ok "Configured with default admin (admin / community-scripts.org)"
fi fi
msg_info "Creating service" msg_info "Creating service"

View File

@@ -140,8 +140,8 @@ if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
cd /usr/local/community-scripts cd /usr/local/community-scripts
filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null filebrowser config init -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null filebrowser config set -a '0.0.0.0' -p "$PORT" -d "$DB_PATH" &>/dev/null
filebrowser users add admin community-scripts.com --perm.admin --database "$DB_PATH" &>/dev/null filebrowser users add admin community-scripts.org --perm.admin --database "$DB_PATH" &>/dev/null
msg_ok "Default authentication configured (admin:community-scripts.com)" msg_ok "Default authentication configured (admin:community-scripts.org)"
fi fi
msg_info "Creating service" msg_info "Creating service"

View File

@@ -0,0 +1,173 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: Tom Frenzel (tomfrenzel)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/CodeWithCJ/SparkyFitness
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)
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
declare -f init_tool_telemetry &>/dev/null && init_tool_telemetry "sparkyfitness-garmin" "addon"
# Enable error handling
set -Eeuo pipefail
trap 'error_handler' ERR
load_functions
# ==============================================================================
# CONFIGURATION
# ==============================================================================
APP="SparkyFitness Garmin Microservice"
APP_TYPE="addon"
INSTALL_PATH="/opt/sparkyfitness-garmin"
CONFIG_PATH="/etc/sparkyfitness-garmin/.env"
SERVICE_PATH="/etc/systemd/system/sparkyfitness-garmin.service"
DEFAULT_PORT=8000
# ==============================================================================
# OS DETECTION
# ==============================================================================
if ! grep -qE 'ID=debian|ID=ubuntu' /etc/os-release 2>/dev/null; then
echo -e "${CROSS} Unsupported OS detected. This script only supports Debian and Ubuntu."
exit 238
fi
# ==============================================================================
# SparkyFitness LXC DETECTION
# ==============================================================================
if [[ ! -d /opt/sparkyfitness ]]; then
echo -e "${CROSS} No SparkyFitness installation detected. This addon must be installed within a container that already has SparkyFitness installed."
exit 238
fi
# ==============================================================================
# UNINSTALL
# ==============================================================================
function uninstall() {
msg_info "Uninstalling ${APP}"
systemctl disable --now sparkyfitness-garmin.service &>/dev/null || true
rm -rf "$SERVICE_PATH" "$CONFIG_PATH" "$INSTALL_PATH" ~/.sparkyfitness-garmin
msg_ok "${APP} has been uninstalled"
}
# ==============================================================================
# UPDATE
# ==============================================================================
function update() {
if check_for_gh_release "sparkyfitness-garmin" "CodeWithCJ/SparkyFitness"; then
PYTHON_VERSION="3.13" setup_uv
msg_info "Stopping service"
systemctl stop sparkyfitness-garmin.service &>/dev/null || true
msg_ok "Stopped service"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "sparkyfitness-garmin" "CodeWithCJ/SparkyFitness" "tarball" "latest" $INSTALL_PATH
msg_info "Starting service"
systemctl start sparkyfitness-garmin
msg_ok "Started service"
msg_ok "Updated successfully"
exit
fi
}
# ==============================================================================
# INSTALL
# ==============================================================================
function install() {
PYTHON_VERSION="3.13" setup_uv
fetch_and_deploy_gh_release "sparkyfitness-garmin" "CodeWithCJ/SparkyFitness" "tarball" "latest" $INSTALL_PATH
msg_info "Setting up ${APP}"
mkdir -p "/etc/sparkyfitness-garmin"
cp "/opt/sparkyfitness-garmin/docker/.env.example" $CONFIG_PATH
cd $INSTALL_PATH/SparkyFitnessGarmin
$STD uv venv --clear .venv
$STD uv pip install -r requirements.txt
sed -i -e "s|^#\?GARMIN_MICROSERVICE_URL=.*|GARMIN_MICROSERVICE_URL=http://${LOCAL_IP}:${DEFAULT_PORT}|" $CONFIG_PATH
cat <<EOF >/etc/systemd/system/sparkyfitness-garmin.service
[Unit]
Description=${APP}
After=network.target sparkyfitness-server.service
Requires=sparkyfitness-server.service
[Service]
Type=simple
WorkingDirectory=$INSTALL_PATH/SparkyFitnessGarmin
EnvironmentFile=$CONFIG_PATH
ExecStart=$INSTALL_PATH/SparkyFitnessGarmin/.venv/bin/python3 -m uvicorn main:app --host 0.0.0.0 --port ${DEFAULT_PORT}
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now sparkyfitness-garmin
msg_ok "Set up ${APP} - reachable at http://${LOCAL_IP}:${DEFAULT_PORT}"
msg_ok "You might need to update the GARMIN_MICROSERVICE_URL in your SparkyFitness .env file to http://${LOCAL_IP}:${DEFAULT_PORT}"
}
# ==============================================================================
# MAIN
# ==============================================================================
header_info
ensure_usr_local_bin_persist
get_lxc_ip
# Handle type=update (called from update script)
if [[ "${type:-}" == "update" ]]; then
if [[ -d "$INSTALL_PATH" ]]; then
update
else
msg_error "${APP} is not installed. Nothing to update."
exit 233
fi
exit 0
fi
# 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} - UV (Python Version Manager)"
echo -e "${TAB} - SparkyFitness Garmin Microservice"
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

View File

@@ -0,0 +1,6 @@
_____ __ _______ __ ______ _ __ ____ _
/ ___/____ ____ ______/ /____ __/ ____(_) /_____ ___ __________ / ____/___ __________ ___ (_)___ / |/ (_)_____________ ________ ______ __(_)_______
\__ \/ __ \/ __ `/ ___/ //_/ / / / /_ / / __/ __ \/ _ \/ ___/ ___/ / / __/ __ `/ ___/ __ `__ \/ / __ \ / /|_/ / / ___/ ___/ __ \/ ___/ _ \/ ___/ | / / / ___/ _ \
___/ / /_/ / /_/ / / / ,< / /_/ / __/ / / /_/ / / / __(__ |__ ) / /_/ / /_/ / / / / / / / / / / / / / / / / / /__/ / / /_/ (__ ) __/ / | |/ / / /__/ __/
/____/ .___/\__,_/_/ /_/|_|\__, /_/ /_/\__/_/ /_/\___/____/____/ \____/\__,_/_/ /_/ /_/ /_/_/_/ /_/ /_/ /_/_/\___/_/ \____/____/\___/_/ |___/_/\___/\___/
/_/ /____/

View File

@@ -551,7 +551,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -631,7 +631,7 @@ rm -f "$WORK_FILE"
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -568,7 +568,7 @@ fi
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -639,7 +639,7 @@ msg_ok "Resized disk"
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -622,7 +622,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -546,7 +546,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -605,7 +605,7 @@ msg_ok "Resized disk to ${DISK_SIZE}"
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -750,7 +750,7 @@ qm resize $VMID scsi0 20G >/dev/null
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/michelroegl-brunner/ProxmoxVE/refs/heads/develop/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/michelroegl-brunner/ProxmoxVE/refs/heads/develop/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -560,7 +560,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -462,7 +462,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -610,7 +610,7 @@ fi
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -542,7 +542,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -544,7 +544,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -543,7 +543,7 @@ qm set $VMID \
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>

View File

@@ -590,7 +590,7 @@ qm resize $VMID scsi0 ${DISK_SIZE} >/dev/null
DESCRIPTION=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
<a href='https://community-scripts.com' target='_blank' rel='noopener noreferrer'> <a href='https://community-scripts.org' target='_blank' rel='noopener noreferrer'>
<img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/> <img src='https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
</a> </a>