Compare commits

...

33 Commits

Author SHA1 Message Date
MickLesk 06da31156c Add _download_source_tarball with retry/validation
Introduce `_download_source_tarball` helper that validates gzip integrity after download and retries up to 3 times. This guards against truncated-but-valid-HTTP responses from GitHub/GitLab/Codeberg on-the-fly archive generation. Replace ad-hoc curl/curl_download calls in fetch_and_deploy_* functions with the new helper. Also remove redundant tmpdir cleanup before early returns (tmpdir is cleaned up at function exit).
2026-07-18 11:33:28 +02:00
MickLesk 89f284a003 refactor(tools.func): centralize deploy tail + trap-based tmpdir cleanup
Extract the duplicated post-download logic of the fetch_and_deploy_*
helpers into two shared functions and switch per-branch cleanup to a
single RETURN trap.

- _deploy_source_tarball: shared source-tarball tail (6 call sites)
- _deploy_unpacked_archive: shared prebuild-archive tail (3 call sites)
- RETURN trap per function guarantees tmpdir/unpack_tmp cleanup on every
  return path, replacing ~40 manual `rm -rf "$tmpdir"` lines
- CLEAN_INSTALL now lives in the helpers instead of 12 copies

Behavior-preserving except: codeberg prebuild gains .txz support and
uses helper return codes; from_url resets shopt on error paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 10:45:02 +02:00
MickLesk 4560806137 tools.func: replace rm -rf with find for safer directory cleanup 2026-07-18 09:44:43 +02:00
MickLesk 456af0bf91 tools.func: replace rm -rf with find for safer directory cleanup 2026-07-18 09:40:40 +02:00
community-scripts-pr-app[bot] c24bba00de Update CHANGELOG.md (#15863)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 19:16:42 +00:00
Michel Roegl-Brunner 3f7228f9c6 fix(webtrees): initialize database schema before admin user creation (#15837)
PR #14818 replaced the setup wizard curl with CLI commands but omitted
the schema migration step, causing fresh installs to fail when creating
the admin user. Trigger schema init via HTTP after config-ini.

Fixes #15828
2026-07-17 21:16:15 +02:00
community-scripts-pr-app[bot] c0a327dad8 Update CHANGELOG.md (#15862)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 19:13:41 +00:00
Copilot 78990277f2 Fix DocuSeal missing Leptonica deps on install and update (#15858)
* Initial plan

* Fix DocuSeal leptonica dependencies

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-17 21:13:19 +02:00
community-scripts-pr-app[bot] 0b15e19500 Update CHANGELOG.md (#15861)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 19:05:36 +00:00
CanbiZ (MickLesk) f75b0a8b0e AFFiNE: Bump to 0.27.0 (#15848) 2026-07-17 21:05:10 +02:00
community-scripts-pr-app[bot] b5360882c9 Update CHANGELOG.md (#15860)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 19:04:58 +00:00
TowyTowy b2936ff9ce fix(apache-guacamole): detect installed extensions during update (#15841) 2026-07-17 21:04:31 +02:00
community-scripts-pr-app[bot] 29552c6e2e Update CHANGELOG.md (#15859)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 19:03:14 +00:00
Austin d2ecb9f44c CLIProxyAPI: fix update deleting config.yaml (#15834) 2026-07-17 21:02:47 +02:00
community-scripts-pr-app[bot] ec3c25c5f5 Update CHANGELOG.md (#15853)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 12:31:18 +00:00
community-scripts-pr-app[bot] b1259e3749 Update CHANGELOG.md (#15852)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 12:31:05 +00:00
push-app-to-main[bot] ff192c4530 Invidious (#15824)
* Add invidious (ct)

* Update install/invidious-install.sh

* Update install/invidious-install.sh

* Update install/invidious-install.sh

* Update ct/invidious.sh

* Update ct/invidious.sh

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: Sam Heinz <sam@samheinz.com>
2026-07-17 14:30:55 +02:00
community-scripts-pr-app[bot] e72f335b46 Update CHANGELOG.md (#15851)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 12:30:52 +00:00
community-scripts-pr-app[bot] 13d7bf6c78 Update CHANGELOG.md (#15850)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 12:30:43 +00:00
push-app-to-main[bot] 2ab4a31dd1 OxiCloud (#15823)
* Add oxicloud (ct)

* Update ct/oxicloud.sh

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: Sam Heinz <sam@samheinz.com>
2026-07-17 14:30:27 +02:00
community-scripts-pr-app[bot] e7557c2355 Update CHANGELOG.md (#15849)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 12:30:21 +00:00
CanbiZ (MickLesk) 79cee47df6 SFTPGo: Update APT Repo & Re-Enable Script (#15829)
* SFTPGo: Update APT Repo & Re-Enable Script

* Update sftpgo.sh
2026-07-17 14:29:59 +02:00
MickLesk b4dcdd4472 Merge branch 'main' of https://github.com/community-scripts/ProxmoxVE 2026-07-17 12:54:18 +02:00
community-scripts-pr-app[bot] 8f2b68ed7e Update CHANGELOG.md (#15847)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 10:53:42 +00:00
community-scripts-pr-app[bot] 853f5e868a Update CHANGELOG.md (#15846)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 10:53:20 +00:00
CanbiZ (MickLesk) 7c7d40cfe3 tools.func: enhance rbenv with profile updates / bundle in bashrc (#15822)
* tools.func: enhance rbenv with profile updates / bundle in bashrc

Added checks to update shell profile files for rbenv integration and removed redundant profile setup code.

* Refactor Ruby version installation script

* Fix comment formatting in tools.func
2026-07-17 12:53:13 +02:00
CanbiZ (MickLesk) 373ae7e143 MongoDB: Implement kernel version check and patch (#15821) 2026-07-17 12:53:05 +02:00
CanbiZ (MickLesk) abc31499a2 n8n: unpin / use latest release (#15817)
* n8n: unpin / use latest release

startup issue is fixed, so unpin and set to latest release

* Update n8n installation to latest version
2026-07-17 12:52:57 +02:00
MickLesk 7bf45d5b66 fix stupid branch delete bot 2026-07-17 12:49:24 +02:00
community-scripts-pr-app[bot] f119e8782a Update CHANGELOG.md (#15844)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 10:18:59 +00:00
Michel Roegl-Brunner 271df3c3fb feat(build.func): notify users when already on a pinned script version (#15819)
Query PocketBase pinned_version/pin_reason during LXC updates and show an informational message when the installed version matches the pin, so users know not to open issues for missing newer updates.
2026-07-17 12:18:36 +02:00
community-scripts-pr-app[bot] c991a7eccf Update CHANGELOG.md (#15843)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-17 09:59:30 +00:00
Michel Roegl-Brunner 5d4eff0493 fix(esphome): install libusb-1.0-0 for ESP-IDF native builds (#15838)
ESPHome 2026.7.0 validates openocd-esp32 during native ESP-IDF setup, which requires libusb-1.0.so.0. Add the runtime package to install and update paths.

Fixes #15835
2026-07-17 11:59:05 +02:00
23 changed files with 729 additions and 362 deletions
+24
View File
@@ -91,6 +91,30 @@ jobs:
let skipped = 0;
for (const branch of candidates) {
// A branch name can be reused after an earlier PR was merged. Never delete a
// branch while it is the head of a current open PR, even if it is also a
// candidate from an older merged PR.
try {
const { data: openPrs } = await github.rest.pulls.list({
owner,
repo,
state: "open",
head: `${owner}:${branch}`,
per_page: 1,
});
if (openPrs.length > 0) {
console.log(`Skipped "${branch}" (head of open PR #${openPrs[0].number})`);
skipped++;
continue;
}
} catch (error) {
// Do not risk deleting a branch if GitHub cannot confirm it has no open PR.
console.log(`Failed to check open PRs for "${branch}": ${error.message}`);
skipped++;
continue;
}
// Confirm the branch still exists and isn't protected.
let branchData;
try {
+30
View File
@@ -504,12 +504,42 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
## 2026-07-17
### 🆕 New Scripts
- Invidious ([#15824](https://github.com/community-scripts/ProxmoxVE/pull/15824))
- OxiCloud ([#15823](https://github.com/community-scripts/ProxmoxVE/pull/15823))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- webtrees: initialize database schema before admin user creation [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15837](https://github.com/community-scripts/ProxmoxVE/pull/15837))
- Fix DocuSeal missing Leptonica deps on install and update [@Copilot](https://github.com/Copilot) ([#15858](https://github.com/community-scripts/ProxmoxVE/pull/15858))
- apache-guacamole: detect installed extensions during update [@TowyTowy](https://github.com/TowyTowy) ([#15841](https://github.com/community-scripts/ProxmoxVE/pull/15841))
- CLIProxyAPI: fix update deleting config.yaml [@austinpilz](https://github.com/austinpilz) ([#15834](https://github.com/community-scripts/ProxmoxVE/pull/15834))
- esphome: install libusb-1.0-0 for ESP-IDF native builds [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15838](https://github.com/community-scripts/ProxmoxVE/pull/15838))
- #### ✨ New Features
- AFFiNE: Bump to 0.27.0 [@MickLesk](https://github.com/MickLesk) ([#15848](https://github.com/community-scripts/ProxmoxVE/pull/15848))
- n8n: unpin / use latest release [@MickLesk](https://github.com/MickLesk) ([#15817](https://github.com/community-scripts/ProxmoxVE/pull/15817))
- Pin Opencloud to v7.3.0 [@vhsdream](https://github.com/vhsdream) ([#15826](https://github.com/community-scripts/ProxmoxVE/pull/15826))
- #### 🔧 Refactor
- SFTPGo: Update APT Repo & Re-Enable Script [@MickLesk](https://github.com/MickLesk) ([#15829](https://github.com/community-scripts/ProxmoxVE/pull/15829))
### 💾 Core
- #### ✨ New Features
- tools.func: enhance rbenv with profile updates / bundle in bashrc [@MickLesk](https://github.com/MickLesk) ([#15822](https://github.com/community-scripts/ProxmoxVE/pull/15822))
- feat(build.func): notify users when already on a pinned script version [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#15819](https://github.com/community-scripts/ProxmoxVE/pull/15819))
- #### 💥 Breaking Changes
- MongoDB: Implement kernel version check and patch [@MickLesk](https://github.com/MickLesk) ([#15821](https://github.com/community-scripts/ProxmoxVE/pull/15821))
### 🧰 Tools
- #### ✨ New Features
+7 -4
View File
@@ -30,17 +30,19 @@ function update_script() {
exit
fi
RELEASE="v0.26.3"
RELEASE="v0.27.0"
if check_for_gh_release "affine_app" "toeverything/AFFiNE" "${RELEASE}" "each release is tested individually before the version is updated. Please do not open issues for this"; then
msg_info "Stopping Services"
systemctl stop affine-web affine-worker
msg_ok "Stopped Services"
ensure_dependencies cmake
create_backup /root/.affine/config /root/.affine/storage
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "${RELEASE}" "/opt/affine"
msg_info "Rebuilding Application (Patience)"
msg_info "Rebuilding Application (Patience ~25 mins, don't close the console!)"
cd /opt/affine
source /root/.profile
export PATH="/root/.cargo/bin:/root/.rbenv/shims:$PATH"
@@ -51,11 +53,12 @@ function update_script() {
export VITE_CORE_COMMIT_SHA=$(cat ~/.affine_app)
# Initialize git repo (required for build process)
export HUSKY=0
$STD git init -q
$STD git config user.email "build@local"
$STD git config user.name "Build"
$STD git add -A
$STD git commit -q -m "update"
$STD git commit -q -m "update" --no-verify --allow-empty
# Force Turbo to run sequentially
mkdir -p /opt/affine/.turbo
@@ -66,7 +69,7 @@ function update_script() {
TURBO
$STD corepack enable
$STD corepack prepare yarn@4.12.0 --activate
$STD corepack prepare yarn@4.13.0 --activate
$STD yarn config set enableTelemetry 0
export NODE_OPTIONS="--max-old-space-size=2048"
+5 -5
View File
@@ -146,7 +146,7 @@ function update_script() {
# Check and upgrade optional extensions
# TOTP Extension
if [[ -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar ]]; then
if compgen -G "/etc/guacamole/extensions/guacamole-auth-totp-*.jar" >/dev/null; then
msg_info "Updating TOTP Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-totp-*.jar
curl_download "/tmp/guacamole-auth-totp.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-totp-${LATEST_SERVER}.tar.gz"
@@ -158,7 +158,7 @@ function update_script() {
fi
# DUO Extension
if [[ -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar ]]; then
if compgen -G "/etc/guacamole/extensions/guacamole-auth-duo-*.jar" >/dev/null; then
msg_info "Updating DUO Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-duo-*.jar
curl_download "/tmp/guacamole-auth-duo.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-duo-${LATEST_SERVER}.tar.gz"
@@ -170,7 +170,7 @@ function update_script() {
fi
# LDAP Extension
if [[ -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar ]]; then
if compgen -G "/etc/guacamole/extensions/guacamole-auth-ldap-*.jar" >/dev/null; then
msg_info "Updating LDAP Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-ldap-*.jar
curl_download "/tmp/guacamole-auth-ldap.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-ldap-${LATEST_SERVER}.tar.gz"
@@ -182,7 +182,7 @@ function update_script() {
fi
# Quick Connect Extension
if [[ -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar ]]; then
if compgen -G "/etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar" >/dev/null; then
msg_info "Updating Quick Connect Extension"
rm -f /etc/guacamole/extensions/guacamole-auth-quickconnect-*.jar
curl_download "/tmp/guacamole-auth-quickconnect.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-auth-quickconnect-${LATEST_SERVER}.tar.gz"
@@ -194,7 +194,7 @@ function update_script() {
fi
# History Recording Storage Extension
if [[ -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar ]]; then
if compgen -G "/etc/guacamole/extensions/guacamole-history-recording-storage-*.jar" >/dev/null; then
msg_info "Updating History Recording Storage Extension"
rm -f /etc/guacamole/extensions/guacamole-history-recording-storage-*.jar
curl_download "/tmp/guacamole-history-recording-storage.tar.gz" "https://downloads.apache.org/guacamole/${LATEST_SERVER}/binary/guacamole-history-recording-storage-${LATEST_SERVER}.tar.gz"
+4
View File
@@ -36,8 +36,12 @@ function update_script() {
systemctl stop cliproxyapi
msg_ok "Stopped CLIProxyAPI"
create_backup /opt/cliproxyapi/config.yaml
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "cliproxyapi" "router-for-me/CLIProxyAPI" "prebuild" "latest" "/opt/cliproxyapi" "CLIProxyAPI_*_linux_$(arch_resolve "amd64" "aarch64").tar.gz"
restore_backup
msg_info "Starting CLIProxyAPI"
systemctl start cliproxyapi
msg_ok "Started CLIProxyAPI"
+2
View File
@@ -35,6 +35,8 @@ function update_script() {
systemctl stop docuseal docuseal-sidekiq
msg_ok "Stopped Services"
ensure_dependencies libleptonica-dev libleptonica6
create_backup /opt/docuseal/.env \
/opt/docuseal/data
+1
View File
@@ -28,6 +28,7 @@ function update_script() {
msg_error "No ${APP} Installation Found!"
exit
fi
ensure_dependencies libusb-1.0-0
msg_info "Stopping Service"
systemctl stop esphome-device-builder 2>/dev/null || true
+6
View File
@@ -0,0 +1,6 @@
____ _ ___
/ _/___ _ __(_)___/ (_)___ __ _______
/ // __ \ | / / / __ / / __ \/ / / / ___/
_/ // / / / |/ / / /_/ / / /_/ / /_/ (__ )
/___/_/ /_/|___/_/\__,_/_/\____/\__,_/____/
+6
View File
@@ -0,0 +1,6 @@
____ _ ________ __
/ __ \_ __(_) ____/ /___ __ ______/ /
/ / / / |/_/ / / / / __ \/ / / / __ /
/ /_/ /> </ / /___/ / /_/ / /_/ / /_/ /
\____/_/|_/_/\____/_/\____/\__,_/\__,_/
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/iv-org/invidious
APP="Invidious"
var_tags="${var_tags:-streaming}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-4096}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/invidious ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "Invidious" "iv-org/invidious"; then
msg_info "Stopping services"
$STD systemctl stop invidious-companion invidious
msg_ok "Stopped services"
create_backup /opt/invidious/config/config.yml
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Invidious" "iv-org/invidious" "tarball" "latest" "/opt/invidious"
if check_for_gh_release "Invidious-Companion" "iv-org/invidious-companion"; then
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "Invidious-Companion" "iv-org/invidious-companion" "prebuild" "latest" "/opt/invidious-companion" "invidious_companion-$(arch_resolve x86_64 aarch64)-unknown-linux-gnu.tar.gz"
fi
msg_info "Rebuilding Invidious"
cd /opt/invidious
INVIDIOUS_VERSION="$(cat ~/.invidious 2>/dev/null || echo "unknown")"
INVIDIOUS_VERSION="${INVIDIOUS_VERSION#v}"
sed -i \
-e "s~^\(\s*CURRENT_BRANCH\s*=\).*~\1 \"master\"~" \
-e "s~^\(\s*CURRENT_COMMIT\s*=\).*~\1 \"\"~" \
-e "s~^\(\s*CURRENT_VERSION\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
-e "s~^\(\s*CURRENT_TAG\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
-e "s~^\(\s*ASSET_COMMIT\s*=\).*~\1 \"\"~" \
src/invidious.cr
$STD make
msg_ok "Rebuilt Invidious"
restore_backup
msg_info "Starting services"
$STD systemctl start invidious invidious-companion
msg_ok "Started services"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW}Access it using the following URL:${CL}"
echo -e "${GATEWAY}${BGN}http://${IP}:3000${CL}"
+1 -1
View File
@@ -45,7 +45,7 @@ EOF
systemctl daemon-reload
fi
$STD npm install -g n8n@2.27.5
$STD npm install -g n8n@latest
systemctl restart n8n
msg_ok "Updated n8n"
msg_ok "Updated successfully!"
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/DioCrafts/OxiCloud
APP="OxiCloud"
var_tags="${var_tags:-files;documents}"
var_cpu="${var_cpu:-4}"
var_ram="${var_ram:-6144}"
var_disk="${var_disk:-20}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_arm64="${var_arm64:-yes}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/oxicloud ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "OxiCloud" "DioCrafts/OxiCloud"; then
msg_info "Stopping OxiCloud"
systemctl stop oxicloud
msg_ok "Stopped OxiCloud"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
TOOLCHAIN="$(grep -oP 'FROM\s+rust:\K[0-9]+\.[0-9]+(\.[0-9]+)?' /opt/oxicloud/Dockerfile | head -1)"
RUST_TOOLCHAIN="${TOOLCHAIN:-stable}" setup_rust
msg_info "Building Frontend SPA"
cd /opt/oxicloud/frontend
$STD npm ci
$STD npm run build
msg_ok "Built Frontend SPA"
msg_info "Updating OxiCloud (Patience)"
set -a
source /etc/oxicloud/.env
set +a
cd /opt/oxicloud
export DATABASE_URL
export RUSTFLAGS="-C target-cpu=native"
RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
CARGO_JOBS=$((RAM_MB / 2560))
[[ $CARGO_JOBS -lt 1 ]] && CARGO_JOBS=1
[[ $CARGO_JOBS -gt $(nproc) ]] && CARGO_JOBS=$(nproc)
$STD cargo build --release -j "$CARGO_JOBS" --bin oxicloud --bin migrate-nfc-filenames
mv target/release/oxicloud /usr/local/bin/oxicloud
mv target/release/migrate-nfc-filenames /usr/local/bin/migrate-nfc-filenames
chmod +x /usr/local/bin/oxicloud /usr/local/bin/migrate-nfc-filenames
rm -f /usr/bin/oxicloud
rm -rf /opt/oxicloud/static
mv /opt/oxicloud/static-dist /opt/oxicloud/static
rm -rf /opt/oxicloud/target /opt/oxicloud/frontend/node_modules
msg_ok "Updated OxiCloud"
msg_info "Starting OxiCloud"
systemctl start oxicloud
msg_ok "Started OxiCloud"
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 "${GATEWAY}${BGN}http://${IP}:8086${CL}"
+6
View File
@@ -28,6 +28,12 @@ function update_script() {
msg_error "No ${APP} Installation Found!"
exit
fi
setup_deb822_repo \
"sftpgo" \
"https://oss.sftpgo.com/apt/gpg.key" \
"https://oss.sftpgo.com/apt" \
"trixie"
msg_info "Updating SFTPGo"
$STD apt update
+6 -4
View File
@@ -22,7 +22,8 @@ $STD apt install -y \
libssl-dev \
libjemalloc2 \
redis-server \
nginx
nginx \
cmake
msg_ok "Installed Dependencies"
PG_VERSION="16" PG_MODULES="pgvector" setup_postgresql
@@ -30,7 +31,7 @@ PG_DB_NAME="affine" PG_DB_USER="affine" setup_postgresql_db
NODE_VERSION="22" setup_nodejs
setup_rust
fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "v0.26.3" "/opt/affine"
fetch_and_deploy_gh_release "affine_app" "toeverything/AFFiNE" "tarball" "v0.27.0" "/opt/affine"
msg_info "Setting up Directories"
rm -rf /root/.affine
@@ -59,11 +60,12 @@ export PATH="/root/.cargo/bin:$PATH"
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
export VITE_CORE_COMMIT_SHA=$(cat ~/.affine_app)
# # Initialize git repo (required for build process)
export HUSKY=0
$STD git init -q
$STD git config user.email "build@local"
$STD git config user.name "Build"
$STD git add -A
$STD git commit -q -m "initial"
$STD git commit -q -m "update" --no-verify --allow-empty
mkdir -p /opt/affine/.turbo
cat <<TURBO >/opt/affine/.turbo/config.json
{
@@ -71,7 +73,7 @@ cat <<TURBO >/opt/affine/.turbo/config.json
}
TURBO
$STD corepack enable
$STD corepack prepare yarn@4.12.0 --activate
$STD corepack prepare yarn@4.13.0 --activate
$STD yarn config set enableTelemetry 0
export NODE_OPTIONS="--max-old-space-size=4096"
export TSC_COMPILE_ON_ERROR=true
+2
View File
@@ -23,6 +23,8 @@ $STD apt install -y \
libreadline-dev \
zlib1g-dev \
libffi-dev \
libleptonica-dev \
libleptonica6 \
libvips42 \
libvips-dev \
libheif1 \
+2 -1
View File
@@ -14,7 +14,8 @@ network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y git
$STD apt install -y git \
libusb-1.0-0
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.12" setup_uv
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/iv-org/invidious
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 \
build-essential \
git \
pkg-config \
libssl-dev \
libxml2-dev \
libyaml-dev \
libgmp-dev \
libreadline-dev \
librsvg2-bin \
libsqlite3-dev \
zlib1g-dev \
libpcre2-dev \
libevent-dev \
fonts-open-sans
msg_ok "Installed Dependencies"
if [[ "$(arch_resolve amd64 arm64)" == "amd64" ]]; then
setup_deb822_repo "crystal" "https://download.opensuse.org/repositories/devel:/languages:/crystal/Debian_13/Release.key" "https://download.opensuse.org/repositories/devel:/languages:/crystal/Debian_13/" "./"
$STD apt install -y crystal
else
fetch_and_deploy_gh_release "Crystal" "crystal-lang/crystal" "prebuild" "latest" "/opt/crystal" "crystal-*-linux-aarch64-bundled.tar.gz"
ln -sf /opt/crystal/bin/crystal /usr/local/bin/crystal
ln -sf /opt/crystal/bin/shards /usr/local/bin/shards
fi
PG_VERSION="17" setup_postgresql
PG_DB_NAME="invidious" PG_DB_USER="invidious" setup_postgresql_db
fetch_and_deploy_gh_release "Invidious" "iv-org/invidious" "tarball" "latest" "/opt/invidious"
fetch_and_deploy_gh_release "Invidious Companion" "iv-org/invidious-companion" "prebuild" "latest" "/opt/invidious-companion" "invidious_companion-$(arch_resolve x86_64 aarch64)-unknown-linux-gnu.tar.gz"
msg_info "Building Invidious"
cd /opt/invidious
INVIDIOUS_VERSION="$(cat ~/.invidious 2>/dev/null || echo "unknown")"
INVIDIOUS_VERSION="${INVIDIOUS_VERSION#v}"
sed -i \
-e "s~^\(\s*CURRENT_BRANCH\s*=\).*~\1 \"master\"~" \
-e "s~^\(\s*CURRENT_COMMIT\s*=\).*~\1 \"\"~" \
-e "s~^\(\s*CURRENT_VERSION\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
-e "s~^\(\s*CURRENT_TAG\s*=\).*~\1 \"${INVIDIOUS_VERSION}\"~" \
-e "s~^\(\s*ASSET_COMMIT\s*=\).*~\1 \"\"~" \
src/invidious.cr
$STD make
msg_ok "Built Invidious"
msg_info "Configuring Invidious"
SECRET_KEY="$(openssl rand -hex 8)"
HMAC_KEY="$(openssl rand -hex 32)"
sed -e '\~^db:~,\~dbname:~d' \
-e "s~^#database_.*~database_url: postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost:5432/${PG_DB_NAME}~" \
-e 's~^#check_tables.*~check_tables: true~' \
-e 's~^#invidious_companion:~invidious_companion:~' \
-e 's~^# - private_~ - private_~' \
-e "s~^#invidious_companion_key:.*~invidious_companion_key: \"${SECRET_KEY}\"~" \
-e "s~^hmac_key:.*~hmac_key: \"${HMAC_KEY}\"~" \
/opt/invidious/config/config.example.yml >/opt/invidious/config/config.yml
chmod 600 /opt/invidious/config/config.yml
cat <<EOF >/etc/logrotate.d/invidious.logrotate
/opt/invidious/invidious.log {
rotate 4
weekly
notifempty
missingok
compress
minsize 1048576
}
EOF
chmod 0644 /etc/logrotate.d/invidious.logrotate
msg_ok "Configured Invidious"
msg_info "Migrating database"
$STD ./invidious --migrate
msg_ok "Migrated database"
msg_info "Configuring services"
sed -e 's|^User=invidious|User=root|' \
-e 's|^Group=invidious|Group=root|' \
-e 's|/home/invidious/invidious|/opt/invidious|g' \
/opt/invidious/invidious.service >/etc/systemd/system/invidious.service
mkdir -p /var/tmp/youtubei.js
cat <<EOF >/etc/systemd/system/invidious-companion.service
[Unit]
Description=Invidious Companion
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/invidious-companion
Environment=SERVER_SECRET_KEY=${SECRET_KEY}
Environment=CACHE_DIRECTORY=/var/tmp/youtubei.js
ExecStart=/opt/invidious-companion/invidious_companion
Restart=always
RestartSec=2s
[Install]
WantedBy=multi-user.target
EOF
systemctl -q enable --now invidious invidious-companion
msg_ok "Configured services"
motd_ssh
customize
cleanup_lxc
+1 -2
View File
@@ -16,7 +16,6 @@ update_os
msg_info "Installing Dependencies"
$STD apt install -y \
build-essential \
python3 \
python3-setuptools \
graphicsmagick
msg_ok "Installed Dependencies"
@@ -24,7 +23,7 @@ msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
msg_info "Installing n8n (Patience)"
$STD npm install -g n8n@2.27.5
$STD npm install -g n8n@latest
msg_ok "Installed n8n"
msg_info "Creating Service"
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/DioCrafts/OxiCloud
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 build-essential
msg_ok "Installed Dependencies"
NODE_VERSION="24" setup_nodejs
PG_VERSION="17" setup_postgresql
PG_DB_NAME="oxicloud" PG_DB_USER="oxicloud" setup_postgresql_db
fetch_and_deploy_gh_release "OxiCloud" "DioCrafts/OxiCloud" "tarball" "latest" "/opt/oxicloud"
TOOLCHAIN="$(grep -oP 'FROM\s+rust:\K[0-9]+\.[0-9]+(\.[0-9]+)?' /opt/oxicloud/Dockerfile | head -1)"
RUST_TOOLCHAIN="${TOOLCHAIN:-stable}" setup_rust
msg_info "Building Frontend SPA"
cd /opt/oxicloud/frontend
$STD npm ci
$STD npm run build
msg_ok "Built Frontend SPA"
msg_info "Building OxiCloud (Patience)"
cd /opt/oxicloud
export DATABASE_URL="postgres://${PG_DB_USER}:${PG_DB_PASS}@localhost/${PG_DB_NAME}"
export RUSTFLAGS="-C target-cpu=native"
RAM_MB=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo)
CARGO_JOBS=$((RAM_MB / 2560))
[[ $CARGO_JOBS -lt 1 ]] && CARGO_JOBS=1
[[ $CARGO_JOBS -gt $(nproc) ]] && CARGO_JOBS=$(nproc)
$STD cargo build --release -j "$CARGO_JOBS" --bin oxicloud --bin migrate-nfc-filenames
mv target/release/oxicloud /usr/local/bin/oxicloud
mv target/release/migrate-nfc-filenames /usr/local/bin/migrate-nfc-filenames
rm -rf /opt/oxicloud/static
mv /opt/oxicloud/static-dist /opt/oxicloud/static
rm -rf /opt/oxicloud/target /opt/oxicloud/frontend/node_modules
msg_ok "Built OxiCloud"
msg_info "Configuring OxiCloud"
mkdir -p {/mnt/oxicloud,/etc/oxicloud}
sed -e 's|OXICLOUD_STORAGE_PATH=.*|OXICLOUD_STORAGE_PATH=/mnt/oxicloud|' \
-e 's|OXICLOUD_SERVER_HOST=.*|OXICLOUD_SERVER_HOST=0.0.0.0|' \
-e 's|OXICLOUD_STATIC_PATH=.*|OXICLOUD_STATIC_PATH=/opt/oxicloud/static|' \
-e "s|^#OXICLOUD_BASE_URL=.*|OXICLOUD_BASE_URL=http://${LOCAL_IP}:8086|" \
-e "s|OXICLOUD_DB_CONNECTION_STRING=.*|OXICLOUD_DB_CONNECTION_STRING=${DATABASE_URL}|" \
-e "s|^DATABASE_URL=.*|DATABASE_URL=${DATABASE_URL}|" \
-e "s|^#OXICLOUD_JWT_SECRET=.*|OXICLOUD_JWT_SECRET=$(openssl rand -hex 32)|" \
/opt/oxicloud/example.env >/etc/oxicloud/.env
chmod 600 /etc/oxicloud/.env
msg_ok "Configured OxiCloud"
msg_info "Creating OxiCloud Service"
cat <<EOF >/etc/systemd/system/oxicloud.service
[Unit]
Description=OxiCloud Service
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/oxicloud
EnvironmentFile=/etc/oxicloud/.env
ExecStart=/usr/local/bin/oxicloud
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now oxicloud
msg_ok "Created OxiCloud Service"
motd_ssh
customize
cleanup_lxc
+2 -2
View File
@@ -19,8 +19,8 @@ msg_ok "Installed Dependencies"
setup_deb822_repo \
"sftpgo" \
"https://ftp.osuosl.org/pub/sftpgo/apt/gpg.key" \
"https://ftp.osuosl.org/pub/sftpgo/apt" \
"https://oss.sftpgo.com/apt/gpg.key" \
"https://oss.sftpgo.com/apt" \
"trixie"
msg_info "Installing SFTPGo"
+11
View File
@@ -47,6 +47,8 @@ msg_ok "Configured Caddy"
msg_info "Automating Webtrees Setup"
cd /opt/webtrees
mkdir -p /opt/webtrees/data
chown -R www-data:www-data /opt/webtrees/data
WT_ADMIN_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c15)
$STD sudo -u www-data php /opt/webtrees/index.php config-ini \
--dbhost=127.0.0.1 \
@@ -56,6 +58,15 @@ $STD sudo -u www-data php /opt/webtrees/index.php config-ini \
--dbname=webtrees \
--tblpfx=wt_ \
--base-url="http://${LOCAL_IP}"
msg_info "Initializing Webtrees database schema"
for i in {1..15}; do
if curl -sf "http://127.0.0.1/" >/dev/null 2>&1; then
break
fi
sleep 2
done
$STD mariadb -u webtrees -p"${MARIADB_DB_PASS}" -h 127.0.0.1 webtrees -e "SHOW TABLES LIKE 'wt_user';" | grep -q wt_user
msg_ok "Initialized Webtrees database schema"
$STD sudo -u www-data php /opt/webtrees/index.php user Admin \
--create \
--real-name="Administrator" \
+29 -6
View File
@@ -3753,18 +3753,19 @@ run_addon_updates() {
}
runtime_script_status_guard() {
local mode="${1:-}"
local script_slug="${SCRIPT_SLUG:-${NSAPP:-}}"
script_slug="$(echo "$script_slug" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
[[ -z "$script_slug" ]] && return 0
local api_url="https://db.community-scripts.org/api/collections/script_scripts/records?filter=(slug='${script_slug}')&perPage=1&fields=slug,is_disabled,is_deleted,disable_message,deleted_message"
local api_url="https://db.community-scripts.org/api/collections/script_scripts/records?filter=(slug='${script_slug}')&perPage=1&fields=slug,is_disabled,is_deleted,disable_message,deleted_message,pinned_version,pin_reason"
local response
if ! response=$(curl -fsSL --connect-timeout 2 --max-time 3 "$api_url" 2>/dev/null); then
msg_warn "Script status check is unavailable. Continuing without status verification."
return 0
fi
local is_deleted is_disabled deleted_message disable_message info_url
local is_deleted is_disabled deleted_message disable_message pinned_version pin_reason info_url
if printf '%s' "$response" | grep -qE '"items":[[:space:]]*\[[[:space:]]*\]'; then
return 0
fi
@@ -3774,6 +3775,8 @@ runtime_script_status_guard() {
is_disabled=$(printf '%s' "$response" | sed -n 's/.*"is_disabled"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p' | head -1)
deleted_message=$(printf '%s' "$response" | sed -n 's/.*"deleted_message"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
disable_message=$(printf '%s' "$response" | sed -n 's/.*"disable_message"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
pinned_version=$(printf '%s' "$response" | sed -n 's/.*"pinned_version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
pin_reason=$(printf '%s' "$response" | sed -n 's/.*"pin_reason"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
is_deleted=${is_deleted:-false}
is_disabled=${is_disabled:-false}
info_url="https://community-scripts.org/scripts/${script_slug}"
@@ -3805,6 +3808,26 @@ runtime_script_status_guard() {
return 1
fi
if [[ "$mode" == "update" && -n "$pinned_version" && -n "${NSAPP:-}" ]]; then
local current_file="$HOME/.${NSAPP}"
if [[ -f "$current_file" ]]; then
local installed pinned_clean installed_clean
installed="$(<"$current_file")"
pinned_clean="$pinned_version"
installed_clean="$installed"
[[ "$pinned_clean" =~ ^v[0-9] ]] && pinned_clean="${pinned_clean:1}"
[[ "$installed_clean" =~ ^v[0-9] ]] && installed_clean="${installed_clean:1}"
if [[ "$installed_clean" == "$pinned_clean" ]]; then
if [[ -n "$pin_reason" ]]; then
msg_info "You are already on the pinned version (${pinned_version}). ${pin_reason}"
else
msg_info "You are already on the pinned version (${pinned_version}). No newer update is offered intentionally — please do not open an issue unless you see an actual error."
fi
msg_info "More info: ${info_url}"
fi
fi
fi
return 0
}
@@ -3820,7 +3843,7 @@ runtime_script_status_guard() {
start() {
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
if command -v pveversion >/dev/null 2>&1; then
runtime_script_status_guard || return 0
runtime_script_status_guard install || return 0
install_script || return 0
return 0
elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then
@@ -3828,7 +3851,7 @@ start() {
set_std_mode
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard || return 0
runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3839,7 +3862,7 @@ start() {
set_std_mode
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard || return 0
runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3869,7 +3892,7 @@ start() {
esac
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard || return 0
runtime_script_status_guard update || return 0
update_script
run_addon_updates
update_motd_ip
+213 -337
View File
@@ -2483,6 +2483,150 @@ verify_gpg_fingerprint() {
return 65
}
# ------------------------------------------------------------------------------
# _download_source_tarball <url> <dest> [curl args...]
#
# Downloads a source .tar.gz to <dest> and verifies the gzip stream is complete
# before returning. Source forges (GitHub/GitLab/Codeberg) build these archives
# on the fly; for large repos the response is occasionally a truncated-but-
# cleanly-closed gzip that curl accepts as success (HTTP 200) and which then
# fails at extraction time. We validate with `gzip -t` and re-download on
# failure, and abort stalled transfers (--speed-time) so a hung generation
# retries instead of blocking for minutes. Extra args pass through to curl
# (e.g. auth headers like -H "PRIVATE-TOKEN: ...").
#
# Returns: 0 on success, 250 on persistent failure.
# ------------------------------------------------------------------------------
_download_source_tarball() {
local url="$1" dest="$2"
shift 2
local attempt max=3
for ((attempt = 1; attempt <= max; attempt++)); do
if curl --connect-timeout 15 --max-time 900 --speed-limit 1024 --speed-time 60 \
-fsSL "$@" -o "$dest" "$url" && gzip -t "$dest" 2>/dev/null; then
return 0
fi
rm -f "$dest"
((attempt < max)) && {
msg_warn "Source archive download failed or incomplete (attempt ${attempt}/${max}), retrying..."
sleep $((attempt * 3))
}
done
return 250
}
# ------------------------------------------------------------------------------
# _deploy_source_tarball <tarball_path> <target> <workdir>
#
# Shared tail for the *source tarball* modes of the fetch_and_deploy_* helpers
# (GitHub/GitLab/Codeberg archive tarballs that contain a single top-level
# directory). Extracts <tarball_path> into <workdir>, then copies the contents
# of that top-level directory into <target>.
#
# - Honors CLEAN_INSTALL=1 (wipes <target> first, dotfiles included).
# - Does NOT own <workdir>: the caller creates it and is responsible for its
# cleanup (typically via a RETURN trap on its tmpdir).
# - cp failures are non-fatal here, matching the previous inline behavior.
#
# Returns: 0 on success (or non-fatal cp failure), 251 on extraction failure.
# ------------------------------------------------------------------------------
_deploy_source_tarball() {
local tarball="$1" target="$2" workdir="$3"
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
find "${target:?}" -mindepth 1 -delete
fi
tar --no-same-owner -xzf "$tarball" -C "$workdir" || {
msg_error "Failed to extract tarball"
return 251
}
local unpack_dir
unpack_dir=$(find "$workdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
return 0
}
# ------------------------------------------------------------------------------
# _deploy_unpacked_archive <archive_path> <target> <workdir>
#
# Shared tail for the *prebuild* modes of the fetch_and_deploy_*_release helpers
# (release assets shipped as .zip / .tar.* / .tgz / .txz). Extracts the archive
# into <workdir>, then copies its payload into <target>. If the archive contains
# a single top-level directory, that directory is stripped (its contents land
# directly in <target>); otherwise the archive contents are copied as-is.
#
# - Honors CLEAN_INSTALL=1 (wipes <target> first, dotfiles included).
# - Does NOT own <workdir>: the caller creates it and cleans it up.
#
# Returns: 0 on success, 65 on unsupported format, 251 on extraction failure,
# 252 on copy failure / empty archive.
# ------------------------------------------------------------------------------
_deploy_unpacked_archive() {
local archive="$1" target="$2" workdir="$3"
local filename="${archive##*/}"
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
find "${target:?}" -mindepth 1 -delete
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$archive" -d "$workdir" || {
msg_error "Failed to extract ZIP archive"
return 251
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz || "$filename" == *.txz ]]; then
tar --no-same-owner -xf "$archive" -C "$workdir" || {
msg_error "Failed to extract TAR archive"
return 251
}
else
msg_error "Unsupported archive format: $filename"
return 65
fi
local top_entries inner_dir
top_entries=$(find "$workdir" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
shopt -u dotglob nullglob
return 252
}
else
msg_error "Inner directory is empty: $inner_dir"
shopt -u dotglob nullglob
return 252
fi
shopt -u dotglob nullglob
else
shopt -s dotglob nullglob
if compgen -G "$workdir/*" >/dev/null; then
cp -r "$workdir"/* "$target/" || {
msg_error "Failed to copy contents to $target"
shopt -u dotglob nullglob
return 252
}
else
msg_error "Unpacked archive is empty"
shopt -u dotglob nullglob
return 252
fi
shopt -u dotglob nullglob
fi
return 0
}
# ------------------------------------------------------------------------------
# Fetches and deploys a GitHub tag-based source tarball.
#
@@ -2531,36 +2675,19 @@ fetch_and_deploy_gh_tag() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir"' RETURN
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" || {
_download_source_tarball "$tarball_url" "$tmpdir/$filename" || {
msg_error "Download failed: $tarball_url"
rm -rf "$tmpdir"
return 7
}
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 251
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 251
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
rm -rf "$tmpdir"
echo "$version" >"$version_file"
msg_ok "Deployed ${app} ${version} to ${target}"
return 0
@@ -2725,35 +2852,18 @@ fetch_and_deploy_gl_tag() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir"' RETURN
local filename="${app_lc}-${version_safe}.tar.gz"
msg_info "Fetching GitLab tag: ${app} (${resolved_tag})"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$tarball_url" || {
_download_source_tarball "$tarball_url" "$tmpdir/$filename" "${header[@]}" || {
msg_error "Download failed: $tarball_url"
rm -rf "$tmpdir"
return 7
}
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 251
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 251
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
rm -rf "$tmpdir"
echo "$resolved_tag" >"$version_file"
msg_ok "Deployed ${app} ${resolved_tag} to ${target}"
return 0
@@ -3450,6 +3560,7 @@ fetch_and_deploy_codeberg_release() {
local tmpdir
tmpdir=$(mktemp -d) || return 252
trap 'rm -rf "$tmpdir"' RETURN
msg_info "Fetching Codeberg tag: $app ($tag_name)"
@@ -3460,37 +3571,19 @@ 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 "$tmpdir/$filename" "$archive_url"; then
if _download_source_tarball "$archive_url" "$tmpdir/$filename"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 250
fi
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 251
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 251
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
return 0
fi
@@ -3509,7 +3602,7 @@ fetch_and_deploy_codeberg_release() {
local codeberg_rel_json
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX) || return 73
trap 'rm -f "$codeberg_rel_json"' RETURN
trap 'rm -f "$codeberg_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
local attempt=0 success=false resp http_code
@@ -3563,32 +3656,16 @@ fetch_and_deploy_codeberg_release() {
# Codeberg archive URL format
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
if curl_download "$tmpdir/$filename" "$archive_url"; then
if _download_source_tarball "$archive_url" "$tmpdir/$filename"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 250
fi
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 251
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 251
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
@@ -3636,14 +3713,12 @@ fetch_and_deploy_codeberg_release() {
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 252
fi
filename="${url_match##*/}"
curl_download "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 250
}
@@ -3651,7 +3726,6 @@ fetch_and_deploy_codeberg_release() {
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
}
@@ -3662,7 +3736,6 @@ fetch_and_deploy_codeberg_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 65
}
@@ -3679,77 +3752,18 @@ fetch_and_deploy_codeberg_release() {
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 252
}
filename="${asset_url##*/}"
curl_download "$tmpdir/$filename" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 250
}
local unpack_tmp
unpack_tmp=$(mktemp -d)
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
else
msg_error "Unsupported archive format: $filename"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
fi
local top_dirs
top_dirs=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1 -type d | wc -l)
local top_entries inner_dir
top_entries=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
fi
shopt -u dotglob nullglob
else
shopt -s dotglob nullglob
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$target/" || {
msg_error "Failed to copy contents to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
fi
shopt -u dotglob nullglob
fi
_deploy_unpacked_archive "$tmpdir/$filename" "$target" "$unpack_tmp" || return
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
@@ -3757,7 +3771,6 @@ fetch_and_deploy_codeberg_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 65
}
@@ -3774,7 +3787,6 @@ fetch_and_deploy_codeberg_release() {
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 252
}
@@ -3787,7 +3799,6 @@ fetch_and_deploy_codeberg_release() {
curl_download "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 250
}
@@ -3797,13 +3808,11 @@ fetch_and_deploy_codeberg_release() {
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 65
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}
# ------------------------------------------------------------------------------
@@ -4090,6 +4099,7 @@ fetch_and_deploy_gh_release() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
local filename="" url=""
msg_info "Fetching GitHub release: $app ($version)"
@@ -4104,28 +4114,12 @@ 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 "$tmpdir/$filename" "$direct_tarball_url" || {
_download_source_tarball "$direct_tarball_url" "$tmpdir/$filename" || {
msg_error "Download failed: $direct_tarball_url"
rm -rf "$tmpdir"
return 250
}
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 251
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 251
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
@@ -4210,14 +4204,12 @@ fetch_and_deploy_gh_release() {
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 252
fi
filename="${url_match##*/}"
curl_download "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 250
}
@@ -4230,7 +4222,6 @@ fetch_and_deploy_gh_release() {
DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$tmpdir/$filename" || {
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
}
@@ -4241,7 +4232,6 @@ fetch_and_deploy_gh_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 65
}
@@ -4277,79 +4267,18 @@ fetch_and_deploy_gh_release() {
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 252
}
filename="${asset_url##*/}"
curl_download "$tmpdir/$filename" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 250
}
local unpack_tmp
unpack_tmp=$(mktemp -d)
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz || "$filename" == *.txz ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
else
msg_error "Unsupported archive format: $filename"
rm -rf "$tmpdir" "$unpack_tmp"
return 65
fi
local top_dirs
top_dirs=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1 -type d | wc -l)
local top_entries inner_dir
top_entries=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
# Strip leading folder
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
fi
shopt -u dotglob nullglob
else
# Copy all contents
shopt -s dotglob nullglob
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$target/" || {
msg_error "Failed to copy contents to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
return 252
fi
shopt -u dotglob nullglob
fi
_deploy_unpacked_archive "$tmpdir/$filename" "$target" "$unpack_tmp" || return
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
@@ -4357,7 +4286,6 @@ fetch_and_deploy_gh_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 65
}
@@ -4392,7 +4320,6 @@ fetch_and_deploy_gh_release() {
fi
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 252
}
@@ -4405,7 +4332,6 @@ fetch_and_deploy_gh_release() {
curl_download "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 250
}
@@ -4415,13 +4341,11 @@ fetch_and_deploy_gh_release() {
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 65
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}
# ------------------------------------------------------------------------------
@@ -7157,7 +7081,7 @@ setup_meilisearch() {
MEILI_DB_PATH="${MEILI_DB_PATH:-/var/lib/meilisearch/data}"
msg_info "Removing old MeiliSearch database for migration"
rm -rf "${MEILI_DB_PATH:?}"/*
find "${MEILI_DB_PATH:?}" -mindepth 1 -delete
# Import dump using CLI flag (this is the supported method)
local DUMP_FILE="${MEILI_DUMP_DIR}/${DUMP_UID}.dump"
@@ -7431,6 +7355,18 @@ setup_mongodb() {
mkdir -p /var/lib/mongodb
chown -R mongodb:mongodb /var/lib/mongodb
local KERNEL_VERSION MONGO_MAJOR
KERNEL_VERSION=$(uname -r | cut -d- -f1)
MONGO_MAJOR="${MONGO_VERSION%%.*}"
if ((MONGO_MAJOR >= 8)) && [[ "$(printf '%s\n' "6.19" "$KERNEL_VERSION" | sort -V | head -n1)" == "6.19" ]]; then
mkdir -p /etc/systemd/system/mongod.service.d
cat <<EOF >/etc/systemd/system/mongod.service.d/rseq.conf
[Service]
Environment=GLIBC_TUNABLES=glibc.pthread.rseq=1
EOF
systemctl daemon-reload
fi
$STD systemctl enable mongod || {
msg_warn "Failed to enable mongod service"
}
@@ -8677,6 +8613,7 @@ setup_postgresql_db() {
export PG_DB_USER
export PG_DB_PASS
}
# ------------------------------------------------------------------------------
# Installs rbenv and ruby-build, installs Ruby and optionally Rails.
#
@@ -8696,8 +8633,26 @@ setup_ruby() {
local RBENV_DIR="$HOME/.rbenv"
local RBENV_BIN="$RBENV_DIR/bin/rbenv"
local PROFILE_FILE="$HOME/.profile"
local BASH_PROFILE_FILE="$HOME/.bash_profile"
local BASHRC_FILE="$HOME/.bashrc"
local TMP_DIR=$(mktemp -d)
if ! grep -q 'rbenv init' "$PROFILE_FILE" 2>/dev/null; then
cat <<'EOF' >>"$PROFILE_FILE"
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
EOF
fi
if ! grep -q '.rbenv/shims' "$PROFILE_FILE" 2>/dev/null; then
echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$PROFILE_FILE"
fi
if [[ -f "$BASH_PROFILE_FILE" ]] && ! grep -q '.rbenv/shims' "$BASH_PROFILE_FILE"; then
echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASH_PROFILE_FILE"
fi
if [[ -f "$BASHRC_FILE" ]] && ! grep -q '.rbenv/shims' "$BASHRC_FILE"; then
echo 'export PATH="$HOME/.rbenv/shims:$HOME/.rbenv/bin:$PATH"' >>"$BASHRC_FILE"
fi
# Get currently installed Ruby version
local CURRENT_RUBY_VERSION=""
if [[ -x "$RBENV_BIN" ]]; then
@@ -9100,6 +9055,16 @@ setup_uv() {
msg_ok "uvx wrapper installed"
fi
# Install specific Python version if requested (even when uv is already up to date)
if [[ -n "${PYTHON_VERSION:-}" ]]; then
msg_info "Installing Python $PYTHON_VERSION via uv"
$STD uv python install "$PYTHON_VERSION" || {
msg_error "Failed to install Python $PYTHON_VERSION"
return 150
}
msg_ok "Python $PYTHON_VERSION installed"
fi
return 0
fi
@@ -9302,10 +9267,10 @@ fetch_and_deploy_from_url() {
msg_error "Failed to create temporary directory"
return 252
}
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
curl -fsSL -o "$tmpdir/$filename" "$url" || {
msg_error "Download failed: $url"
rm -rf "$tmpdir"
return 250
}
@@ -9325,7 +9290,6 @@ fetch_and_deploy_from_url() {
archive_type="tar"
else
msg_error "Unsupported or unknown archive type: $file_desc"
rm -rf "$tmpdir"
return 65
fi
@@ -9338,19 +9302,16 @@ fetch_and_deploy_from_url() {
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 100
}
}
rm -rf "$tmpdir"
msg_ok "Successfully installed .deb package"
return 0
fi
if [[ -z "$directory" ]]; then
msg_error "Directory parameter is required for archive extraction"
rm -rf "$tmpdir"
return 65
fi
@@ -9359,7 +9320,7 @@ fetch_and_deploy_from_url() {
mkdir -p "$directory"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${directory:?}/"*
find "${directory:?}" -mindepth 1 -delete
fi
local unpack_tmp
@@ -9369,13 +9330,11 @@ fetch_and_deploy_from_url() {
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
elif [[ "$archive_type" == "tar" ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 251
}
fi
@@ -9389,12 +9348,12 @@ fetch_and_deploy_from_url() {
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$directory/" || {
msg_error "Failed to copy contents from $inner_dir to $directory"
rm -rf "$tmpdir" "$unpack_tmp"
shopt -u dotglob nullglob
return 252
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
shopt -u dotglob nullglob
return 252
fi
shopt -u dotglob nullglob
@@ -9403,18 +9362,17 @@ fetch_and_deploy_from_url() {
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$directory/" || {
msg_error "Failed to copy contents to $directory"
rm -rf "$tmpdir" "$unpack_tmp"
shopt -u dotglob nullglob
return 252
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
shopt -u dotglob nullglob
return 252
fi
shopt -u dotglob nullglob
fi
rm -rf "$tmpdir" "$unpack_tmp"
msg_ok "Successfully deployed archive to $directory"
return 0
}
@@ -9815,7 +9773,7 @@ fetch_and_deploy_gl_release() {
local gl_rel_json
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX) || return 73
trap 'rm -f "$gl_rel_json"' RETURN
trap 'rm -f "$gl_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
local repo_encoded
repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')
@@ -9927,28 +9885,12 @@ fetch_and_deploy_gl_release() {
local direct_tarball_url="https://gitlab.com/$repo/-/archive/$tag_name/${app_lc}-${version_safe}.tar.gz"
filename="${app_lc}-${version_safe}.tar.gz"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$direct_tarball_url" || {
_download_source_tarball "$direct_tarball_url" "$tmpdir/$filename" "${header[@]}" || {
msg_error "Download failed: $direct_tarball_url"
rm -rf "$tmpdir"
return 1
}
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
tar --no-same-owner -xzf "$tmpdir/$filename" -C "$tmpdir" || {
msg_error "Failed to extract tarball"
rm -rf "$tmpdir"
return 1
}
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
_deploy_source_tarball "$tmpdir/$filename" "$target" "$tmpdir" || return 1
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
@@ -10018,14 +9960,12 @@ fetch_and_deploy_gl_release() {
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 1
fi
filename="${url_match##*/}"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 1
}
@@ -10036,7 +9976,6 @@ fetch_and_deploy_gl_release() {
DEBIAN_FRONTEND=noninteractive SYSTEMD_OFFLINE=1 $STD apt install -y $dpkg_opts "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$tmpdir/$filename" || {
_diagnose_deb_failure "$tmpdir/$filename"
rm -rf "$tmpdir"
return 1
}
}
@@ -10047,7 +9986,6 @@ fetch_and_deploy_gl_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
@@ -10082,75 +10020,18 @@ fetch_and_deploy_gl_release() {
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
curl $download_timeout -fsSL "${header[@]}" -o "$tmpdir/$filename" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
local unpack_tmp
unpack_tmp=$(mktemp -d)
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then
rm -rf "${target:?}/"*
fi
if [[ "$filename" == *.zip ]]; then
ensure_dependencies unzip
unzip -q "$tmpdir/$filename" -d "$unpack_tmp" || {
msg_error "Failed to extract ZIP archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
elif [[ "$filename" == *.tar.* || "$filename" == *.tgz || "$filename" == *.txz ]]; then
tar --no-same-owner -xf "$tmpdir/$filename" -C "$unpack_tmp" || {
msg_error "Failed to extract TAR archive"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unsupported archive format: $filename"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
local top_entries inner_dir
top_entries=$(find "$unpack_tmp" -mindepth 1 -maxdepth 1)
if [[ "$(echo "$top_entries" | wc -l)" -eq 1 && -d "$top_entries" ]]; then
inner_dir="$top_entries"
shopt -s dotglob nullglob
if compgen -G "$inner_dir/*" >/dev/null; then
cp -r "$inner_dir"/* "$target/" || {
msg_error "Failed to copy contents from $inner_dir to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Inner directory is empty: $inner_dir"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
else
shopt -s dotglob nullglob
if compgen -G "$unpack_tmp/*" >/dev/null; then
cp -r "$unpack_tmp"/* "$target/" || {
msg_error "Failed to copy contents to $target"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
}
else
msg_error "Unpacked archive is empty"
rm -rf "$tmpdir" "$unpack_tmp"
return 1
fi
shopt -u dotglob nullglob
fi
_deploy_unpacked_archive "$tmpdir/$filename" "$target" "$unpack_tmp" || return
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
@@ -10158,7 +10039,6 @@ fetch_and_deploy_gl_release() {
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
@@ -10193,7 +10073,6 @@ fetch_and_deploy_gl_release() {
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
@@ -10206,7 +10085,6 @@ fetch_and_deploy_gl_release() {
curl $download_timeout -fsSL "${header[@]}" -o "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
@@ -10216,13 +10094,11 @@ fetch_and_deploy_gl_release() {
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 1
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}
# ------------------------------------------------------------------------------