Compare commits

..

11 Commits

Author SHA1 Message Date
CanbiZ (MickLesk)
c40db61464 Disable NPM install and update due to OpenResty SHA-1 signature issue (#11406) 2026-02-02 13:05:33 +01:00
community-scripts-pr-app[bot]
8226360700 Update .app files (#11456)
Co-authored-by: GitHub Actions <github-actions[bot]@users.noreply.github.com>
2026-02-02 09:04:01 +01:00
community-scripts-pr-app[bot]
b706775a4b Update CHANGELOG.md (#11455)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-02 08:02:34 +00:00
push-app-to-main[bot]
2f907cc4e0 KitchenOwl (#11453)
* Add kitchenowl (ct)

* Remove commented line and update wsgi.py config

* Remove existing web directory before deployment

Remove the web directory before deploying the kitchenowl-web release.

* Update kitchenowl.sh

* Update kitchenowl.json

* Update kitchenowl.sh

---------

Co-authored-by: push-app-to-main[bot] <203845782+push-app-to-main[bot]@users.noreply.github.com>
Co-authored-by: CanbiZ (MickLesk) <47820557+MickLesk@users.noreply.github.com>
Co-authored-by: Tobias <96661824+CrazyWolf13@users.noreply.github.com>
2026-02-02 09:02:11 +01:00
community-scripts-pr-app[bot]
b1927d2678 chore: update github-versions.json (#11452)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-02 06:25:21 +00:00
community-scripts-pr-app[bot]
a35b0fbd79 Update CHANGELOG.md (#11451)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-02 00:21:49 +00:00
community-scripts-pr-app[bot]
4418ed4615 chore: update github-versions.json (#11450)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-02 00:21:27 +00:00
community-scripts-pr-app[bot]
19d8592104 Update CHANGELOG.md (#11447)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-01 21:27:52 +00:00
CanbiZ (MickLesk)
627587c54b feat(autocaliweb): migrate from GitHub to Codeberg (#11440)
- Add Codeberg API functions: codeberg_api_call, get_latest_codeberg_release, check_for_codeberg_release
- Add fetch_and_deploy_codeberg_release function for Codeberg releases
- Update autocaliweb install and update scripts to use Codeberg
- Update autocaliweb.json documentation and website URLs
2026-02-01 22:27:31 +01:00
community-scripts-pr-app[bot]
b36609dfd5 Update CHANGELOG.md (#11446)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-01 21:26:40 +00:00
CanbiZ (MickLesk)
98dc00a1a0 fix(2fauth): export PHP_VERSION for nginx config (#11441)
The PHP_VERSION variable was only available within the setup_php
function call scope. By setting it separately before the function
call, it remains available for the nginx configuration heredoc.

Fixes #11439
2026-02-01 22:26:18 +01:00
15 changed files with 998 additions and 90 deletions

View File

@@ -772,6 +772,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-02-02
### 🆕 New Scripts
- KitchenOwl ([#11453](https://github.com/community-scripts/ProxmoxVE/pull/11453))
## 2026-02-01
### 🚀 Updated Scripts
@@ -780,6 +786,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🐞 Bug Fixes
- 2fauth: export PHP_VERSION for nginx config [@MickLesk](https://github.com/MickLesk) ([#11441](https://github.com/community-scripts/ProxmoxVE/pull/11441))
- Prometheus Paperless NGX Exporter: Set correct binary path in systemd unit file [@andygrunwald](https://github.com/andygrunwald) ([#11438](https://github.com/community-scripts/ProxmoxVE/pull/11438))
- tracearr: install/update new prestart script from upstream [@durzo](https://github.com/durzo) ([#11433](https://github.com/community-scripts/ProxmoxVE/pull/11433))
- n8n: Fix dependencies [@tremor021](https://github.com/tremor021) ([#11429](https://github.com/community-scripts/ProxmoxVE/pull/11429))
@@ -788,6 +795,7 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### ✨ New Features
- tools.func: add codeberg functions & autocaliweb: migrate from GitHub to Codeberg [@MickLesk](https://github.com/MickLesk) ([#11440](https://github.com/community-scripts/ProxmoxVE/pull/11440))
- Immich Refactor #2 [@vhsdream](https://github.com/vhsdream) ([#11375](https://github.com/community-scripts/ProxmoxVE/pull/11375))
- #### 🔧 Refactor

View File

@@ -3,7 +3,7 @@ source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxV
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/gelbphoenix/autocaliweb
# Source: https://codeberg.org/gelbphoenix/autocaliweb
APP="Autocaliweb"
var_tags="${var_tags:-ebooks}"
@@ -30,8 +30,8 @@ function update_script() {
setup_uv
RELEASE=$(get_latest_github_release "gelbphoenix/autocaliweb")
if check_for_gh_release "autocaliweb" "gelbphoenix/autocaliweb"; then
RELEASE=$(get_latest_codeberg_release "gelbphoenix/autocaliweb")
if check_for_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb"; then
msg_info "Stopping Services"
systemctl stop autocaliweb metadata-change-detector acw-ingest-service acw-auto-zipper
msg_ok "Stopped Services"
@@ -39,7 +39,7 @@ function update_script() {
INSTALL_DIR="/opt/autocaliweb"
export VIRTUAL_ENV="${INSTALL_DIR}/venv"
$STD tar -cf ~/autocaliweb_bkp.tar "$INSTALL_DIR"/{metadata_change_logs,dirs.json,.env,scripts/ingest_watcher.sh,scripts/auto_zipper_wrapper.sh,scripts/metadata_change_detector_wrapper.sh}
fetch_and_deploy_gh_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
msg_info "Updating Autocaliweb"
cd "$INSTALL_DIR"

6
ct/headers/kitchenowl Normal file
View File

@@ -0,0 +1,6 @@
__ __ _ __ __ ____ __
/ //_/(_) /______/ /_ ___ ____ / __ \_ __/ /
/ ,< / / __/ ___/ __ \/ _ \/ __ \/ / / / | /| / / /
/ /| |/ / /_/ /__/ / / / __/ / / / /_/ /| |/ |/ / /
/_/ |_/_/\__/\___/_/ /_/\___/_/ /_/\____/ |__/|__/_/

79
ct/kitchenowl.sh Normal file
View File

@@ -0,0 +1,79 @@
#!/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: snazzybean
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/TomBursch/kitchenowl
APP="KitchenOwl"
var_tags="${var_tags:-food;recipes}"
var_cpu="${var_cpu:-1}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-6}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/kitchenowl ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "kitchenowl" "TomBursch/kitchenowl"; then
msg_info "Stopping Service"
systemctl stop kitchenowl
msg_ok "Stopped Service"
msg_info "Creating Backup"
mkdir -p /opt/kitchenowl_backup
cp -r /opt/kitchenowl/data /opt/kitchenowl_backup/
cp -f /opt/kitchenowl/kitchenowl.env /opt/kitchenowl_backup/
msg_ok "Created Backup"
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kitchenowl" "TomBursch/kitchenowl" "tarball" "latest" "/opt/kitchenowl"
rm -rf /opt/kitchenowl/web
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "kitchenowl-web" "TomBursch/kitchenowl" "prebuild" "latest" "/opt/kitchenowl/web" "kitchenowl_Web.tar.gz"
msg_info "Restoring data"
sed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py
cp -r /opt/kitchenowl_backup/data /opt/kitchenowl/
cp -f /opt/kitchenowl_backup/kitchenowl.env /opt/kitchenowl/
rm -rf /opt/kitchenowl_backup
msg_ok "Restored data"
msg_info "Updating KitchenOwl"
cd /opt/kitchenowl/backend
$STD uv sync --frozen
cd /opt/kitchenowl/backend
set -a
source /opt/kitchenowl/kitchenowl.env
set +a
$STD uv run flask db upgrade
msg_ok "Updated KitchenOwl"
msg_info "Starting Service"
systemctl start kitchenowl
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:80${CL}"

View File

@@ -28,6 +28,12 @@ function update_script() {
exit
fi
msg_error "This script is currently disabled due to an external issue with the OpenResty APT repository."
msg_error "The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026."
msg_error "The issue is tracked in openresty/openresty#1097"
msg_error "For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406"
exit 1
if [[ $(grep -E '^VERSION_ID=' /etc/os-release) == *"12"* ]]; then
msg_error "Wrong Debian version detected!"
msg_error "Please create a snapshot first. You must upgrade your LXC to Debian Trixie before updating. Visit: https://github.com/community-scripts/ProxmoxVE/discussions/7489"

View File

@@ -28,8 +28,12 @@ function update_script() {
exit
fi
VAULT=$(get_latest_github_release "dani-garcia/vaultwarden")
WVRELEASE=$(get_latest_github_release "dani-garcia/bw_web_builds")
VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest |
grep "tag_name" |
awk '{print substr($2, 2, length($2)-3) }')
WVRELEASE=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest |
grep "tag_name" |
awk '{print substr($2, 2, length($2)-3) }')
UPD=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SUPPORT" --radiolist --cancel-button Exit-Script "Spacebar = Select" 11 58 3 \
"1" "VaultWarden $VAULT" ON \
@@ -38,68 +42,57 @@ function update_script() {
3>&1 1>&2 2>&3)
if [ "$UPD" == "1" ]; then
if check_for_gh_release "vaultwarden" "dani-garcia/vaultwarden"; then
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src"
msg_info "Updating VaultWarden to $VAULT (Patience)"
cd /tmp/vaultwarden-src
$STD cargo build --features "sqlite,mysql,postgresql" --release
if [[ -f /usr/bin/vaultwarden ]]; then
cp target/release/vaultwarden /usr/bin/
else
cp target/release/vaultwarden /opt/vaultwarden/bin/
fi
cd ~ && rm -rf /tmp/vaultwarden-src
msg_ok "Updated VaultWarden to ${VAULT}"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
msg_info "Updating VaultWarden to $VAULT (Patience)"
cd ~ && rm -rf vaultwarden
$STD git clone https://github.com/dani-garcia/vaultwarden
cd vaultwarden
$STD cargo build --features "sqlite,mysql,postgresql" --release
DIR=/usr/bin/vaultwarden
if [ -d "$DIR" ]; then
cp target/release/vaultwarden /usr/bin/
else
msg_ok "VaultWarden is already up-to-date"
cp target/release/vaultwarden /opt/vaultwarden/bin/
fi
cd ~ && rm -rf vaultwarden
msg_ok "Updated VaultWarden"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
exit
fi
if [ "$UPD" == "2" ]; then
if check_for_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds"; then
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
msg_info "Stopping Service"
systemctl stop vaultwarden
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden" "bw_web_*.tar.gz"
msg_info "Updating Web-Vault to $WVRELEASE"
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WVRELEASE"/bw_web_"$WVRELEASE".tar.gz
$STD tar -zxf bw_web_"$WVRELEASE".tar.gz -C /opt/vaultwarden/
rm bw_web_"$WVRELEASE".tar.gz
msg_ok "Updated Web-Vault"
msg_info "Updating Web-Vault to $WVRELEASE"
rm -rf /opt/vaultwarden/web-vault
chown -R root:root /opt/vaultwarden/web-vault/
msg_ok "Updated Web-Vault to ${WVRELEASE}"
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "Web-Vault is already up-to-date"
fi
msg_info "Starting Service"
systemctl start vaultwarden
msg_ok "Started Service"
msg_ok "Updated successfully!"
exit
fi
if [ "$UPD" == "3" ]; then
if NEWTOKEN=$(whiptail --backtitle "Proxmox VE Helper Scripts" --passwordbox "Set the ADMIN_TOKEN" 10 58 3>&1 1>&2 2>&3); then
if [[ -z "$NEWTOKEN" ]]; then exit; fi
ensure_dependencies argon2
if ! command -v argon2 >/dev/null 2>&1; then $STD apt-get install -y argon2; fi
TOKEN=$(echo -n "${NEWTOKEN}" | argon2 "$(openssl rand -base64 32)" -t 2 -m 16 -p 4 -l 64 -e)
sed -i "s|ADMIN_TOKEN=.*|ADMIN_TOKEN='${TOKEN}'|" /opt/vaultwarden/.env
if [[ -f /opt/vaultwarden/data/config.json ]]; then
sed -i "s|\"admin_token\":.*|\"admin_token\": \"${TOKEN}\"|" /opt/vaultwarden/data/config.json
fi
systemctl restart vaultwarden
msg_ok "Admin token updated"
fi
exit
fi

View File

@@ -9,9 +9,9 @@
"updateable": true,
"privileged": false,
"interface_port": 8083,
"documentation": "https://github.com/gelbphoenix/autocaliweb/wiki",
"documentation": "https://codeberg.org/gelbphoenix/autocaliweb/wiki",
"config_path": "/etc/autocaliweb",
"website": "https://github.com/gelbphoenix/autocaliweb",
"website": "https://codeberg.org/gelbphoenix/autocaliweb",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/autocaliweb.webp",
"description": "A modern web management system for eBooks, eComics and PDFs",
"install_methods": [

View File

@@ -1,5 +1,5 @@
{
"generated": "2026-02-01T18:07:47Z",
"generated": "2026-02-02T06:25:10Z",
"versions": [
{
"slug": "2fauth",
@@ -116,9 +116,9 @@
{
"slug": "beszel",
"repo": "henrygd/beszel",
"version": "v0.18.2",
"version": "v0.18.3",
"pinned": false,
"date": "2026-01-12T23:58:00Z"
"date": "2026-02-01T19:02:42Z"
},
{
"slug": "bitmagnet",
@@ -242,9 +242,9 @@
{
"slug": "discopanel",
"repo": "nickheyer/discopanel",
"version": "v1.0.32",
"version": "v1.0.35",
"pinned": false,
"date": "2026-01-31T22:24:53Z"
"date": "2026-02-02T05:20:12Z"
},
{
"slug": "dispatcharr",
@@ -487,9 +487,9 @@
{
"slug": "homebox",
"repo": "sysadminsmedia/homebox",
"version": "v0.23.0",
"version": "v0.23.1",
"pinned": false,
"date": "2026-01-30T17:41:01Z"
"date": "2026-02-01T22:53:32Z"
},
{
"slug": "homepage",
@@ -515,9 +515,9 @@
{
"slug": "huntarr",
"repo": "plexguide/Huntarr.io",
"version": "9.1.5",
"version": "9.1.8",
"pinned": false,
"date": "2026-01-31T22:55:29Z"
"date": "2026-02-02T01:29:45Z"
},
{
"slug": "inspircd",
@@ -543,9 +543,9 @@
{
"slug": "jackett",
"repo": "Jackett/Jackett",
"version": "v0.24.1003",
"version": "v0.24.1008",
"pinned": false,
"date": "2026-02-01T05:55:30Z"
"date": "2026-02-02T05:55:21Z"
},
{
"slug": "joplin-server",
@@ -1558,9 +1558,9 @@
{
"slug": "zigbee2mqtt",
"repo": "Koenkk/zigbee2mqtt",
"version": "2.7.2",
"version": "2.8.0",
"pinned": false,
"date": "2026-01-01T13:43:47Z"
"date": "2026-02-01T19:27:25Z"
},
{
"slug": "zipline",

View File

@@ -0,0 +1,35 @@
{
"name": "KitchenOwl",
"slug": "kitchenowl",
"categories": [
13
],
"date_created": "2026-02-02",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 80,
"documentation": "https://docs.kitchenowl.org/",
"website": "https://kitchenowl.org/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/kitchenowl.webp",
"config_path": "/opt/kitchenowl/kitchenowl.env",
"description": "KitchenOwl is a smart self-hosted grocery list and recipe manager with real-time synchronization, recipe management, meal planning, and expense tracking.",
"install_methods": [
{
"type": "default",
"script": "ct/kitchenowl.sh",
"resources": {
"cpu": 1,
"ram": 2048,
"hdd": 6,
"os": "Debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -13,6 +13,8 @@
"website": "https://nginxproxymanager.com/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nginx-proxy-manager.webp",
"config_path": "",
"disable": true,
"disable_description": "This script is temporarily disabled due to an external issue with the OpenResty APT repository. The repository's GPG key uses SHA-1 signatures, which are no longer accepted by Debian as of February 1, 2026. This causes installation to fail with APT errors. The issue is tracked in openresty/openresty#1097. A workaround exists but requires manual configuration. The script will be re-enabled once OpenResty updates their repository signing key. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/11406",
"description": "Nginx Proxy Manager is a tool that provides a web-based interface to manage Nginx reverse proxies. It enables users to easily and securely expose their services to the internet by providing features such as HTTPS encryption, domain mapping, and access control. It eliminates the need for manual configuration of Nginx reverse proxies, making it easy for users to quickly and securely expose their services to the public.",
"install_methods": [
{

View File

@@ -17,7 +17,8 @@ msg_info "Installing Dependencies"
$STD apt install -y nginx
msg_ok "Installed Dependencies"
PHP_VERSION="8.4" PHP_FPM="YES" setup_php
export PHP_VERSION="8.4"
PHP_FPM="YES" setup_php
setup_composer
setup_mariadb
MARIADB_DB_NAME="2fauth_db" MARIADB_DB_USER="2fauth" setup_mariadb_db

View File

@@ -3,7 +3,7 @@
# Copyright (c) 2021-2026 community-scripts ORG
# Author: vhsdream
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/gelbphoenix/autocaliweb
# Source: https://codeberg.org/gelbphoenix/autocaliweb
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
@@ -56,7 +56,7 @@ msg_ok "Installed Calibre"
setup_uv
fetch_and_deploy_gh_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tarball" "latest" "/opt/autocaliweb"
msg_info "Configuring Autocaliweb"
INSTALL_DIR="/opt/autocaliweb"
@@ -111,8 +111,8 @@ msg_info "Initializing databases"
KEPUBIFY_PATH=$(command -v kepubify 2>/dev/null || echo "/usr/bin/kepubify")
EBOOK_CONVERT_PATH=$(command -v ebook-convert 2>/dev/null || echo "/usr/bin/ebook-convert")
CALIBRE_BIN_DIR=$(dirname "$EBOOK_CONVERT_PATH")
curl -fsSL https://github.com/gelbphoenix/autocaliweb/raw/refs/heads/main/library/metadata.db -o "$CALIBRE_LIB_DIR"/metadata.db
curl -fsSL https://github.com/gelbphoenix/autocaliweb/raw/refs/heads/main/library/app.db -o "$CONFIG_DIR"/app.db
curl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/metadata.db -o "$CALIBRE_LIB_DIR"/metadata.db
curl -fsSL https://codeberg.org/gelbphoenix/autocaliweb/raw/branch/main/library/app.db -o "$CONFIG_DIR"/app.db
sqlite3 "$CONFIG_DIR/app.db" <<EOS
UPDATE settings SET
config_kepubifypath='$KEPUBIFY_PATH',

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2025 community-scripts ORG
# Author: snazzybean
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/TomBursch/kitchenowl
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
nginx \
build-essential \
gfortran \
pkg-config \
ninja-build \
autoconf \
automake \
libpq-dev \
libffi-dev \
libssl-dev \
libpcre2-dev \
libre2-dev \
libxml2-dev \
libxslt-dev \
libopenblas-dev \
liblapack-dev \
zlib1g-dev \
libjpeg62-turbo-dev \
libsqlite3-dev \
libexpat1-dev \
libicu-dev
msg_ok "Installed Dependencies"
PYTHON_VERSION="3.14" setup_uv
fetch_and_deploy_gh_release "kitchenowl" "TomBursch/kitchenowl" "tarball" "latest" "/opt/kitchenowl"
rm -rf /opt/kitchenowl/web
fetch_and_deploy_gh_release "kitchenowl-web" "TomBursch/kitchenowl" "prebuild" "latest" "/opt/kitchenowl/web" "kitchenowl_Web.tar.gz"
msg_info "Setting up KitchenOwl"
cd /opt/kitchenowl/backend
$STD uv sync --no-dev
sed -i 's/default=True/default=False/' /opt/kitchenowl/backend/wsgi.py
mkdir -p /nltk_data
$STD uv run python -m nltk.downloader -d /nltk_data averaged_perceptron_tagger_eng
JWT_SECRET=$(openssl rand -hex 32)
mkdir -p /opt/kitchenowl/data
cat <<EOF >/opt/kitchenowl/kitchenowl.env
STORAGE_PATH=/opt/kitchenowl/data
JWT_SECRET_KEY=${JWT_SECRET}
NLTK_DATA=/nltk_data
FRONT_URL=http://${LOCAL_IP}
FLASK_APP=wsgi.py
FLASK_ENV=production
EOF
set -a
source /opt/kitchenowl/kitchenowl.env
set +a
$STD uv run flask db upgrade
msg_ok "Set up KitchenOwl"
msg_info "Creating Systemd Service"
cat <<EOF >/etc/systemd/system/kitchenowl.service
[Unit]
Description=KitchenOwl Backend
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kitchenowl/backend
EnvironmentFile=/opt/kitchenowl/kitchenowl.env
ExecStart=/usr/local/bin/uv run wsgi.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now kitchenowl
msg_ok "Created and Started Service"
msg_info "Configuring Nginx"
rm -f /etc/nginx/sites-enabled/default
cat <<'EOF' >/etc/nginx/sites-available/kitchenowl.conf
server {
listen 80;
server_name _;
root /opt/kitchenowl/web;
index index.html;
client_max_body_size 100M;
# Security Headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /socket.io {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# WebSocket Timeouts - allow long-lived connections
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
EOF
ln -sf /etc/nginx/sites-available/kitchenowl.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
$STD systemctl reload nginx
msg_ok "Configured Nginx"
motd_ssh
customize
cleanup_lxc

View File

@@ -14,7 +14,7 @@ network_check
update_os
msg_info "Installing Dependencies"
$STD apt install -y \
$STD apt install -y git \
build-essential \
pkgconf \
libssl-dev \
@@ -24,25 +24,34 @@ $STD apt install -y \
ssl-cert
msg_ok "Installed Dependencies"
setup_rust
fetch_and_deploy_gh_release "vaultwarden" "dani-garcia/vaultwarden" "tarball" "latest" "/tmp/vaultwarden-src"
WEBVAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
VAULT=$(curl -fsSL https://api.github.com/repos/dani-garcia/vaultwarden/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
msg_info "Building Vaultwarden (Patience)"
cd /tmp/vaultwarden-src
msg_info "Installing Rust"
curl -fsSL https://sh.rustup.rs -o rustup-init.sh
$STD bash rustup-init.sh -y --profile minimal
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>~/.bashrc
export PATH="$HOME/.cargo/bin:$PATH"
rm rustup-init.sh
msg_ok "Installed Rust"
msg_info "Building Vaultwarden ${VAULT} (Patience)"
$STD git clone https://github.com/dani-garcia/vaultwarden
cd vaultwarden
$STD cargo build --features "sqlite,mysql,postgresql" --release
msg_ok "Built Vaultwarden"
msg_ok "Built Vaultwarden ${VAULT}"
msg_info "Setting up Vaultwarden"
$STD addgroup --system vaultwarden
$STD adduser --system --home /opt/vaultwarden --shell /usr/sbin/nologin --no-create-home --gecos 'vaultwarden' --ingroup vaultwarden --disabled-login --disabled-password vaultwarden
mkdir -p /opt/vaultwarden/{bin,data,web-vault}
mkdir -p /opt/vaultwarden/bin
mkdir -p /opt/vaultwarden/data
cp target/release/vaultwarden /opt/vaultwarden/bin/
cd ~ && rm -rf /tmp/vaultwarden-src
msg_ok "Set up Vaultwarden"
fetch_and_deploy_gh_release "vaultwarden_webvault" "dani-garcia/bw_web_builds" "prebuild" "latest" "/opt/vaultwarden/web-vault" "bw_web_*.tar.gz"
msg_info "Downloading Web-Vault ${WEBVAULT}"
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WEBVAULT"/bw_web_"$WEBVAULT".tar.gz
$STD tar -xzf bw_web_"$WEBVAULT".tar.gz -C /opt/vaultwarden/
msg_ok "Downloaded Web-Vault ${WEBVAULT}"
msg_info "Configuring Vaultwarden"
cat <<EOF >/opt/vaultwarden/.env
ADMIN_TOKEN=''
ROCKET_ADDRESS=0.0.0.0
@@ -52,23 +61,22 @@ DATABASE_MAX_CONNS=10
WEB_VAULT_FOLDER=/opt/vaultwarden/web-vault
WEB_VAULT_ENABLED=true
EOF
mv /etc/ssl/certs/ssl-cert-snakeoil.pem /opt/vaultwarden/
mv /etc/ssl/private/ssl-cert-snakeoil.key /opt/vaultwarden/
msg_info "Creating Service"
chown -R vaultwarden:vaultwarden /opt/vaultwarden/
chown root:root /opt/vaultwarden/bin/vaultwarden
chmod +x /opt/vaultwarden/bin/vaultwarden
chown -R root:root /opt/vaultwarden/web-vault/
chmod +r /opt/vaultwarden/.env
msg_ok "Configured Vaultwarden"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/vaultwarden.service
[Unit]
service_path="/etc/systemd/system/vaultwarden.service"
echo "[Unit]
Description=Bitwarden Server (Powered by Vaultwarden)
Documentation=https://github.com/dani-garcia/vaultwarden
After=network.target
[Service]
User=vaultwarden
Group=vaultwarden
@@ -91,11 +99,10 @@ LockPersonality=yes
WorkingDirectory=/opt/vaultwarden
ReadWriteDirectories=/opt/vaultwarden/data
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now vaultwarden
WantedBy=multi-user.target" >$service_path
systemctl daemon-reload
$STD systemctl enable --now vaultwarden
msg_ok "Created Service"
motd_ssh

View File

@@ -821,6 +821,54 @@ github_api_call() {
return 1
}
# ------------------------------------------------------------------------------
# Codeberg API call with retry logic
# ------------------------------------------------------------------------------
codeberg_api_call() {
local url="$1"
local output_file="${2:-/dev/stdout}"
local max_retries=3
local retry_delay=2
for attempt in $(seq 1 $max_retries); do
local http_code
http_code=$(curl -fsSL -w "%{http_code}" -o "$output_file" \
-H "Accept: application/json" \
"$url" 2>/dev/null || echo "000")
case "$http_code" in
200)
return 0
;;
403)
# Rate limit - retry
if [[ $attempt -lt $max_retries ]]; then
msg_warn "Codeberg API rate limit, waiting ${retry_delay}s... (attempt $attempt/$max_retries)"
sleep "$retry_delay"
retry_delay=$((retry_delay * 2))
continue
fi
msg_error "Codeberg API rate limit exceeded."
return 1
;;
404)
msg_error "Codeberg API endpoint not found: $url"
return 1
;;
*)
if [[ $attempt -lt $max_retries ]]; then
sleep "$retry_delay"
continue
fi
msg_error "Codeberg API call failed with HTTP $http_code"
return 1
;;
esac
done
return 1
}
should_upgrade() {
local current="$1"
local target="$2"
@@ -1385,6 +1433,37 @@ get_latest_github_release() {
echo "$version"
}
# ------------------------------------------------------------------------------
# Get latest Codeberg release version
# ------------------------------------------------------------------------------
get_latest_codeberg_release() {
local repo="$1"
local strip_v="${2:-true}"
local temp_file=$(mktemp)
# Codeberg API: get all releases and pick the first non-draft/non-prerelease
if ! codeberg_api_call "https://codeberg.org/api/v1/repos/${repo}/releases" "$temp_file"; then
rm -f "$temp_file"
return 1
fi
local version
# Codeberg uses same JSON structure but releases endpoint returns array
version=$(jq -r '[.[] | select(.draft==false and .prerelease==false)][0].tag_name // empty' "$temp_file")
if [[ "$strip_v" == "true" ]]; then
version="${version#v}"
fi
rm -f "$temp_file"
if [[ -z "$version" ]]; then
return 1
fi
echo "$version"
}
# ------------------------------------------------------------------------------
# Debug logging (only if DEBUG=1)
# ------------------------------------------------------------------------------
@@ -1559,6 +1638,119 @@ check_for_gh_release() {
return 1
}
# ------------------------------------------------------------------------------
# Checks for new Codeberg release (latest tag).
#
# Description:
# - Queries the Codeberg API for the latest release tag
# - Compares it to a local cached version (~/.<app>)
# - If newer, sets global CHECK_UPDATE_RELEASE and returns 0
#
# Usage:
# if check_for_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" [optional] "v0.11.3"; then
# # trigger update...
# fi
# exit 0
# } (end of update_script not from the function)
#
# Notes:
# - Requires `jq` (auto-installed if missing)
# - Does not modify anything, only checks version state
# - Does not support pre-releases
# ------------------------------------------------------------------------------
check_for_codeberg_release() {
local app="$1"
local source="$2"
local pinned_version_in="${3:-}" # optional
local app_lc="${app,,}"
local current_file="$HOME/.${app_lc}"
msg_info "Checking for update: ${app}"
# DNS check
if ! getent hosts codeberg.org >/dev/null 2>&1; then
msg_error "Network error: cannot resolve codeberg.org"
return 1
fi
ensure_dependencies jq
# Fetch releases from Codeberg API
local releases_json=""
releases_json=$(curl -fsSL --max-time 20 \
-H 'Accept: application/json' \
"https://codeberg.org/api/v1/repos/${source}/releases" 2>/dev/null) || {
msg_error "Unable to fetch releases for ${app}"
return 1
}
mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json")
if ((${#raw_tags[@]} == 0)); then
msg_error "No stable releases found for ${app}"
return 1
fi
local clean_tags=()
for t in "${raw_tags[@]}"; do
clean_tags+=("${t#v}")
done
local latest_raw="${raw_tags[0]}"
local latest_clean="${clean_tags[0]}"
# current installed (stored without v)
local current=""
if [[ -f "$current_file" ]]; then
current="$(<"$current_file")"
else
# Migration: search for any /opt/*_version.txt
local legacy_files
mapfile -t legacy_files < <(find /opt -maxdepth 1 -type f -name "*_version.txt" 2>/dev/null)
if ((${#legacy_files[@]} == 1)); then
current="$(<"${legacy_files[0]}")"
echo "${current#v}" >"$current_file"
rm -f "${legacy_files[0]}"
fi
fi
current="${current#v}"
# Pinned version handling
if [[ -n "$pinned_version_in" ]]; then
local pin_clean="${pinned_version_in#v}"
local match_raw=""
for i in "${!clean_tags[@]}"; do
if [[ "${clean_tags[$i]}" == "$pin_clean" ]]; then
match_raw="${raw_tags[$i]}"
break
fi
done
if [[ -z "$match_raw" ]]; then
msg_error "Pinned version ${pinned_version_in} not found upstream"
return 1
fi
if [[ "$current" != "$pin_clean" ]]; then
CHECK_UPDATE_RELEASE="$match_raw"
msg_ok "Update available: ${app} ${current:-not installed}${pin_clean}"
return 0
fi
msg_ok "No update available: ${app} is already on pinned version (${current})"
return 1
fi
# No pinning → use latest
if [[ -z "$current" || "$current" != "$latest_clean" ]]; then
CHECK_UPDATE_RELEASE="$latest_raw"
msg_ok "Update available: ${app} ${current:-not installed}${latest_clean}"
return 0
fi
msg_ok "No update available: ${app} (${latest_clean})"
return 1
}
# ------------------------------------------------------------------------------
# Creates and installs self-signed certificates.
#
@@ -1648,6 +1840,440 @@ function ensure_usr_local_bin_persist() {
fi
}
# ------------------------------------------------------------------------------
# Downloads and deploys latest Codeberg release (source, binary, tarball, asset).
#
# Description:
# - Fetches latest release metadata from Codeberg API
# - Supports the following modes:
# - tarball: Source code tarball (default if omitted)
# - source: Alias for tarball (same behavior)
# - binary: .deb package install (arch-dependent)
# - prebuild: Prebuilt .tar.gz archive (e.g. Go binaries)
# - singlefile: Standalone binary (no archive, direct chmod +x install)
# - tag: Direct tag download (bypasses Release API)
# - Handles download, extraction/installation and version tracking in ~/.<app>
#
# Parameters:
# $1 APP - Application name (used for install path and version file)
# $2 REPO - Codeberg repository in form user/repo
# $3 MODE - Release type:
# tarball → source tarball (.tar.gz)
# binary → .deb file (auto-arch matched)
# prebuild → prebuilt archive (e.g. tar.gz)
# singlefile→ standalone binary (chmod +x)
# tag → direct tag (bypasses Release API)
# $4 VERSION - Optional release tag (default: latest)
# $5 TARGET_DIR - Optional install path (default: /opt/<app>)
# $6 ASSET_FILENAME - Required for:
# - prebuild → archive filename or pattern
# - singlefile→ binary filename or pattern
#
# Examples:
# # 1. Minimal: Fetch and deploy source tarball
# fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb"
#
# # 2. Binary install via .deb asset (architecture auto-detected)
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "binary"
#
# # 3. Prebuilt archive (.tar.gz) with asset filename match
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "prebuild" "latest" "/opt/myapp" "myapp_Linux_x86_64.tar.gz"
#
# # 4. Single binary (chmod +x)
# fetch_and_deploy_codeberg_release "myapp" "myuser/myapp" "singlefile" "v1.0.0" "/opt/myapp" "myapp-linux-amd64"
#
# # 5. Explicit tag version
# fetch_and_deploy_codeberg_release "autocaliweb" "gelbphoenix/autocaliweb" "tag" "v0.11.3" "/opt/autocaliweb"
# ------------------------------------------------------------------------------
function fetch_and_deploy_codeberg_release() {
local app="$1"
local repo="$2"
local mode="${3:-tarball}" # tarball | binary | prebuild | singlefile | tag
local version="${4:-latest}"
local target="${5:-/opt/$app}"
local asset_pattern="${6:-}"
local app_lc=$(echo "${app,,}" | tr -d ' ')
local version_file="$HOME/.${app_lc}"
local api_timeout="--connect-timeout 10 --max-time 60"
local download_timeout="--connect-timeout 15 --max-time 900"
local current_version=""
[[ -f "$version_file" ]] && current_version=$(<"$version_file")
ensure_dependencies jq
### Tag Mode (bypass Release API) ###
if [[ "$mode" == "tag" ]]; then
if [[ "$version" == "latest" ]]; then
msg_error "Mode 'tag' requires explicit version (not 'latest')"
return 1
fi
local tag_name="$version"
[[ "$tag_name" =~ ^v ]] && version="${tag_name:1}" || version="$tag_name"
if [[ "$current_version" == "$version" ]]; then
$STD msg_ok "$app is already up-to-date (v$version)"
return 0
fi
# DNS check
if ! getent hosts "codeberg.org" &>/dev/null; then
msg_error "DNS resolution failed for codeberg.org check /etc/resolv.conf or networking"
return 1
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
msg_info "Fetching Codeberg tag: $app ($tag_name)"
local safe_version="${version//@/_}"
safe_version="${safe_version//\//_}"
local filename="${app_lc}-${safe_version}.tar.gz"
local download_success=false
# Codeberg archive URL format: https://codeberg.org/{owner}/{repo}/archive/{tag}.tar.gz
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 1
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 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
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
return 0
fi
# Codeberg API: https://codeberg.org/api/v1/repos/{owner}/{repo}/releases
local api_url="https://codeberg.org/api/v1/repos/$repo/releases"
if [[ "$version" != "latest" ]]; then
# Get release by tag: /repos/{owner}/{repo}/releases/tags/{tag}
api_url="https://codeberg.org/api/v1/repos/$repo/releases/tags/$version"
fi
# dns pre check
if ! getent hosts "codeberg.org" &>/dev/null; then
msg_error "DNS resolution failed for codeberg.org check /etc/resolv.conf or networking"
return 1
fi
local max_retries=3 retry_delay=2 attempt=1 success=false resp http_code
while ((attempt <= max_retries)); do
resp=$(curl $api_timeout -fsSL -w "%{http_code}" -o /tmp/codeberg_rel.json "$api_url") && success=true && break
sleep "$retry_delay"
((attempt++))
done
if ! $success; then
msg_error "Failed to fetch release metadata from $api_url after $max_retries attempts"
return 1
fi
http_code="${resp:(-3)}"
[[ "$http_code" != "200" ]] && {
msg_error "Codeberg API returned HTTP $http_code"
return 1
}
local json tag_name
json=$(</tmp/codeberg_rel.json)
# For "latest", the API returns an array - take the first (most recent) release
if [[ "$version" == "latest" ]]; then
json=$(echo "$json" | jq '.[0]')
fi
tag_name=$(echo "$json" | jq -r '.tag_name // .name // empty')
[[ "$tag_name" =~ ^v ]] && version="${tag_name:1}" || version="$tag_name"
if [[ "$current_version" == "$version" ]]; then
$STD msg_ok "$app is already up-to-date (v$version)"
return 0
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
local filename="" url=""
msg_info "Fetching Codeberg release: $app ($version)"
### Tarball Mode ###
if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then
local safe_version="${version//@/_}"
safe_version="${safe_version//\//_}"
filename="${app_lc}-${safe_version}.tar.gz"
local download_success=false
# Codeberg archive URL format
local archive_url="https://codeberg.org/$repo/archive/${tag_name}.tar.gz"
if curl $download_timeout -fsSL -o "$tmpdir/$filename" "$archive_url"; then
download_success=true
fi
if [[ "$download_success" != "true" ]]; then
msg_error "Download failed for $app ($tag_name)"
rm -rf "$tmpdir"
return 1
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 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
### Binary Mode ###
elif [[ "$mode" == "binary" ]]; then
local arch
arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
[[ "$arch" == "x86_64" ]] && arch="amd64"
[[ "$arch" == "aarch64" ]] && arch="arm64"
local assets url_match=""
# Codeberg assets are in .assets[].browser_download_url
assets=$(echo "$json" | jq -r '.assets[].browser_download_url')
# If explicit filename pattern is provided, match that first
if [[ -n "$asset_pattern" ]]; then
for u in $assets; do
case "${u##*/}" in
$asset_pattern)
url_match="$u"
break
;;
esac
done
fi
# Fall back to architecture heuristic
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
fi
# Fallback: any .deb file
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ \.deb$ ]] && url_match="$u" && break
done
fi
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 -o "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 1
}
chmod 644 "$tmpdir/$filename"
$STD apt install -y "$tmpdir/$filename" || {
$STD dpkg -i "$tmpdir/$filename" || {
msg_error "Both apt and dpkg installation failed"
rm -rf "$tmpdir"
return 1
}
}
### Prebuild Mode ###
elif [[ "$mode" == "prebuild" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'prebuild' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
curl $download_timeout -fsSL -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 ]]; 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_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 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
### Singlefile Mode ###
elif [[ "$mode" == "singlefile" ]]; then
local pattern="${6%\"}"
pattern="${pattern#\"}"
[[ -z "$pattern" ]] && {
msg_error "Mode 'singlefile' requires 6th parameter (asset filename pattern)"
rm -rf "$tmpdir"
return 1
}
local asset_url=""
for u in $(echo "$json" | jq -r '.assets[].browser_download_url'); do
filename_candidate="${u##*/}"
case "$filename_candidate" in
$pattern)
asset_url="$u"
break
;;
esac
done
[[ -z "$asset_url" ]] && {
msg_error "No asset matching '$pattern' found"
rm -rf "$tmpdir"
return 1
}
filename="${asset_url##*/}"
mkdir -p "$target"
local use_filename="${USE_ORIGINAL_FILENAME:-false}"
local target_file="$app"
[[ "$use_filename" == "true" ]] && target_file="$filename"
curl $download_timeout -fsSL -o "$target/$target_file" "$asset_url" || {
msg_error "Download failed: $asset_url"
rm -rf "$tmpdir"
return 1
}
if [[ "$target_file" != *.jar && -f "$target/$target_file" ]]; then
chmod +x "$target/$target_file"
fi
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 1
fi
echo "$version" >"$version_file"
msg_ok "Deployed: $app ($version)"
rm -rf "$tmpdir"
}
# ------------------------------------------------------------------------------
# Downloads and deploys latest GitHub release (source, binary, tarball, asset).
#