Compare commits

..

1 Commits

Author SHA1 Message Date
MickLesk
7fa1025e65 feat(autocaliweb): migrate from GitHub to Codeberg
- 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 20:43:42 +01:00
48 changed files with 2819 additions and 2425 deletions

View File

@@ -103,7 +103,7 @@ jobs:
}
`;
let discussionQLId;
//
try {
const discussionResponse = await graphqlWithAuth(discussionQuery, {
owner,
@@ -111,7 +111,7 @@ jobs:
number: parseInt(discussionNumber, 10),
});
discussionQLId = discussionResponse.repository.discussion.id;
const discussionQLId = discussionResponse.repository.discussion.id;
if (!discussionQLId) {
console.log("Failed to fetch discussion GraphQL ID.");
return;

View File

@@ -28,12 +28,16 @@ jobs:
const matched = patterns.some((regex) => regex.test(content));
if (matched) {
const message = "👋 Hello!\n\n" +
"It looks like you are referencing a **container creation issue with a Debian 13 template** (e.g. `debian-13-standard_13.x-x_amd64.tar.zst`).\n\n" +
"We receive many similar reports about this, and it's not related to the scripts themselves but to **a Proxmox base template bug**.\n\n" +
"Please refer to [discussion #8126](https://github.com/community-scripts/ProxmoxVE/discussions/8126) for details.\n" +
"If your issue persists after following the guidance there, feel free to reopen this issue.\n\n" +
"_This issue was automatically closed by a bot._";
const message = `👋 Hello!
It looks like you are referencing a **container creation issue with a Debian 13 template** (e.g. \`debian-13-standard_13.x-x_amd64.tar.zst\`).
We receive many similar reports about this, and its not related to the scripts themselves but to **a Proxmox base template bug**.
Please refer to [discussion #8126](https://github.com/community-scripts/ProxmoxVE/discussions/8126) for details.
If your issue persists after following the guidance there, feel free to reopen this issue.
_This issue was automatically closed by a bot._`;
await github.rest.issues.createComment({
...context.repo,
@@ -52,4 +56,4 @@ jobs:
issue_number: issueNumber,
state: "closed"
});
}
}

View File

@@ -1,230 +0,0 @@
name: Update GitHub Versions (New)
on:
workflow_dispatch:
schedule:
# Runs 4x daily: 00:00, 06:00, 12:00, 18:00 UTC
- cron: "0 0,6,12,18 * * *"
permissions:
contents: write
pull-requests: write
env:
VERSIONS_FILE: frontend/public/json/github-versions.json
BRANCH_NAME: automated/update-github-versions
AUTOMATED_PR_LABEL: "automated pr"
jobs:
update-github-versions:
if: github.repository == 'community-scripts/ProxmoxVE'
runs-on: ubuntu-latest
steps:
- name: Generate a token
id: generate-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Generate a token for PR approval and merge
id: generate-token-merge
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }}
private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }}
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: main
- name: Extract GitHub versions from install scripts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
echo "========================================="
echo " Extracting GitHub versions from scripts"
echo "========================================="
# Initialize versions array
versions_json="[]"
# Function to add a version entry
add_version() {
local slug="$1"
local repo="$2"
local version="$3"
local pinned="$4"
local date="$5"
versions_json=$(echo "$versions_json" | jq \
--arg slug "$slug" \
--arg repo "$repo" \
--arg version "$version" \
--argjson pinned "$pinned" \
--arg date "$date" \
'. += [{"slug": $slug, "repo": $repo, "version": $version, "pinned": $pinned, "date": $date}]')
}
# Get list of slugs from JSON files
echo ""
echo "=== Scanning JSON files for slugs ==="
for json_file in frontend/public/json/*.json; do
[[ ! -f "$json_file" ]] && continue
# Skip non-app JSON files
basename_file=$(basename "$json_file")
case "$basename_file" in
metadata.json|versions.json|github-versions.json|dependency-check.json|update-apps.json)
continue
;;
esac
# Extract slug from JSON
slug=$(jq -r '.slug // empty' "$json_file" 2>/dev/null)
[[ -z "$slug" ]] && continue
# Find corresponding install script
install_script="install/${slug}-install.sh"
[[ ! -f "$install_script" ]] && continue
# Look for fetch_and_deploy_gh_release calls
# Pattern: fetch_and_deploy_gh_release "app" "owner/repo" ["mode"] ["version"]
while IFS= read -r line; do
# Skip commented lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
# Extract repo and version from fetch_and_deploy_gh_release
if [[ "$line" =~ fetch_and_deploy_gh_release[[:space:]]+\"[^\"]*\"[[:space:]]+\"([^\"]+)\"([[:space:]]+\"([^\"]+)\")?([[:space:]]+\"([^\"]+)\")? ]]; then
repo="${BASH_REMATCH[1]}"
mode="${BASH_REMATCH[3]:-tarball}"
pinned_version="${BASH_REMATCH[5]:-latest}"
# Check if version is pinned (not "latest" and not empty)
is_pinned=false
target_version=""
if [[ -n "$pinned_version" && "$pinned_version" != "latest" ]]; then
is_pinned=true
target_version="$pinned_version"
fi
# Fetch version from GitHub
if [[ "$is_pinned" == "true" ]]; then
# For pinned versions, verify it exists and get date
response=$(gh api "repos/${repo}/releases/tags/${target_version}" 2>/dev/null || echo '{}')
if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then
version=$(echo "$response" | jq -r '.tag_name')
date=$(echo "$response" | jq -r '.published_at // empty')
add_version "$slug" "$repo" "$version" "true" "$date"
echo "[$slug] ✓ $version (pinned)"
else
echo "[$slug] ⚠ pinned version $target_version not found"
fi
else
# Fetch latest release
response=$(gh api "repos/${repo}/releases/latest" 2>/dev/null || echo '{}')
if echo "$response" | jq -e '.tag_name' > /dev/null 2>&1; then
version=$(echo "$response" | jq -r '.tag_name')
date=$(echo "$response" | jq -r '.published_at // empty')
add_version "$slug" "$repo" "$version" "false" "$date"
echo "[$slug] ✓ $version"
else
# Try tags as fallback
version=$(gh api "repos/${repo}/tags" --jq '.[0].name // empty' 2>/dev/null || echo "")
if [[ -n "$version" ]]; then
add_version "$slug" "$repo" "$version" "false" ""
echo "[$slug] ✓ $version (from tags)"
else
echo "[$slug] ⚠ no version found"
fi
fi
fi
break # Only first match per script
fi
done < <(grep 'fetch_and_deploy_gh_release' "$install_script" 2>/dev/null || true)
done
# Save versions file
echo "$versions_json" | jq --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{generated: $date, versions: (. | sort_by(.slug))}' > "$VERSIONS_FILE"
total=$(echo "$versions_json" | jq 'length')
echo ""
echo "========================================="
echo " Total versions extracted: $total"
echo "========================================="
- name: Check for changes
id: check-changes
run: |
# Check if file is new (untracked) or has changes
if [[ ! -f "$VERSIONS_FILE" ]]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Versions file was not created"
elif ! git ls-files --error-unmatch "$VERSIONS_FILE" &>/dev/null; then
# File exists but is not tracked - it's new
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "New file created: $VERSIONS_FILE"
elif git diff --quiet "$VERSIONS_FILE" 2>/dev/null; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No changes detected"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Changes detected:"
git diff --stat "$VERSIONS_FILE" 2>/dev/null || true
fi
- name: Commit and push changes
if: steps.check-changes.outputs.changed == 'true'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add "$VERSIONS_FILE"
git commit -m "chore: update github-versions.json"
git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME
git push origin $BRANCH_NAME --force
- name: Create pull request if not exists
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -z "$PR_EXISTS" ]; then
gh pr create --title "[Github Action] Update github-versions.json" \
--body "This PR is auto-generated by a Github Action to update the github-versions.json file." \
--head $BRANCH_NAME \
--base main \
--label "$AUTOMATED_PR_LABEL"
fi
- name: Approve pull request
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
fi
- name: Approve pull request and merge
if: steps.check-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }}
run: |
git config --global user.name "github-actions-automege[bot]"
git config --global user.email "github-actions-automege[bot]@users.noreply.github.com"
PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number')
if [ -n "$PR_NUMBER" ]; then
gh pr review $PR_NUMBER --approve
gh pr merge $PR_NUMBER --squash --admin
fi

View File

@@ -391,63 +391,6 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
</details>
## 2026-01-29
### 🚀 Updated Scripts
- #### 💥 Breaking Changes
- fix: vikunja v1 [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11308](https://github.com/community-scripts/ProxmoxVE/pull/11308))
## 2026-01-28
### 🆕 New Scripts
- nodecast-tv ([#11287](https://github.com/community-scripts/ProxmoxVE/pull/11287))
### 🚀 Updated Scripts
- #### 🐞 Bug Fixes
- Ubuntu 25.04 VM - Change default start from yes to no [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#11292](https://github.com/community-scripts/ProxmoxVE/pull/11292))
- #### ✨ New Features
- various scripts: use setup_meilisearch function [@MickLesk](https://github.com/MickLesk) ([#11259](https://github.com/community-scripts/ProxmoxVE/pull/11259))
- #### 🔧 Refactor
- Refactor: NPMPlus / Default Login [@MickLesk](https://github.com/MickLesk) ([#11262](https://github.com/community-scripts/ProxmoxVE/pull/11262))
### 💾 Core
- #### 🐞 Bug Fixes
- core: sed patch for ram [@lavacano](https://github.com/lavacano) ([#11285](https://github.com/community-scripts/ProxmoxVE/pull/11285))
- Fix installer loop caused by invalid whiptail menu separator [@Mesteriis](https://github.com/Mesteriis) ([#11237](https://github.com/community-scripts/ProxmoxVE/pull/11237))
- core: fix Debian 13 LXC template root ownership bug [@MickLesk](https://github.com/MickLesk) ([#11277](https://github.com/community-scripts/ProxmoxVE/pull/11277))
- tools.func: prevent systemd-tmpfiles failure in unprivileged LXC during deb install [@MickLesk](https://github.com/MickLesk) ([#11271](https://github.com/community-scripts/ProxmoxVE/pull/11271))
- tools.func: fix php "wait_for" hint [@MickLesk](https://github.com/MickLesk) ([#11254](https://github.com/community-scripts/ProxmoxVE/pull/11254))
- #### ✨ New Features
- core: update dynamic values in LXC profile on update_motd_ip [@MickLesk](https://github.com/MickLesk) ([#11268](https://github.com/community-scripts/ProxmoxVE/pull/11268))
- tools.func: add new function - setup_meilisearch [@MickLesk](https://github.com/MickLesk) ([#11258](https://github.com/community-scripts/ProxmoxVE/pull/11258))
### 📂 Github
- github: add GitHub-based versions.json updater [@MickLesk](https://github.com/MickLesk) ([#10021](https://github.com/community-scripts/ProxmoxVE/pull/10021))
### 🌐 Website
- #### ✨ New Features
- Frontend: use github-versions.json for version display [@MickLesk](https://github.com/MickLesk) ([#11281](https://github.com/community-scripts/ProxmoxVE/pull/11281))
- #### 📝 Script Information
- fix: homarr: conf location [@CrazyWolf13](https://github.com/CrazyWolf13) ([#11253](https://github.com/community-scripts/ProxmoxVE/pull/11253))
## 2026-01-27
### 🚀 Updated Scripts
@@ -455,21 +398,12 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🐞 Bug Fixes
- Immich: update libraw [@vhsdream](https://github.com/vhsdream) ([#11233](https://github.com/community-scripts/ProxmoxVE/pull/11233))
- #### ✨ New Features
- grist: enable optional enterprise features toggle [@MickLesk](https://github.com/MickLesk) ([#11239](https://github.com/community-scripts/ProxmoxVE/pull/11239))
- [FIX] Jotty: backup and restore custom config [@vhsdream](https://github.com/vhsdream) ([#11212](https://github.com/community-scripts/ProxmoxVE/pull/11212))
- #### 🔧 Refactor
- Termix: use nginx.conf from upstream repo [@MickLesk](https://github.com/MickLesk) ([#11228](https://github.com/community-scripts/ProxmoxVE/pull/11228))
### 💾 Core
- #### ✨ New Features
- feat: add NVIDIA driver install prompt for GPU-enabled containers [@devdecrux](https://github.com/devdecrux) ([#11184](https://github.com/community-scripts/ProxmoxVE/pull/11184))
### 📚 Documentation
- doc setup_deb822_repo arg order [@chrnie](https://github.com/chrnie) ([#11215](https://github.com/community-scripts/ProxmoxVE/pull/11215))

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"

View File

@@ -34,7 +34,7 @@ function update_script() {
systemctl stop nginx
msg_ok "Stopped nginx"
PHP_VERSION="8.4" PHP_FPM=YES PHP_MODULE="ffi,redis,pdo-sqlite" setup_php
PHP_VERSION="8.4" PHP_FPM=YES PHP_MODULE="ffi,opcache,redis,zip,pdo-sqlite,bcmath,pdo,curl,dom,fpm" setup_php
msg_info "Backing up Bar Assistant"
mv /opt/bar-assistant /opt/bar-assistant-backup
@@ -88,8 +88,18 @@ function update_script() {
msg_ok "Started nginx"
fi
setup_meilisearch
if check_for_gh_release "meilisearch" "meilisearch/meilisearch"; then
msg_info "Stopping Meilisearch"
systemctl stop meilisearch
msg_ok "Stopped Meilisearch"
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
msg_info "Starting Meilisearch"
systemctl start meilisearch
msg_ok "Started Meilisearch"
msg_ok "Updated successfully!"
fi
exit
}

View File

@@ -49,7 +49,6 @@ function update_script() {
cp /opt/grist_bak/landing.db /opt/grist/landing.db
cd /opt/grist
$STD yarn install
$STD yarn run install:ee
$STD yarn run build:prod
$STD yarn run install:python
msg_ok "Updated Grist"

View File

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

View File

@@ -61,7 +61,6 @@ function update_script() {
fi
MODULE_VERSION="$(jq -r '.packageManager | split("@")[1]' /opt/karakeep/package.json)"
NODE_VERSION="22" NODE_MODULE="pnpm@${MODULE_VERSION}" setup_nodejs
setup_meilisearch
msg_info "Updating Karakeep"
corepack enable
@@ -91,7 +90,6 @@ function update_script() {
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
}

View File

@@ -31,9 +31,7 @@ function update_script() {
msg_info "Stopping Service"
systemctl stop apache2
msg_ok "Stopped Service"
PHP_VERSION="8.5" PHP_APACHE="YES" PHP_MODULE="apcu,ctype,dom,fileinfo,iconv,pgsql" setup_php
msg_info "Creating a backup"
mv /opt/koillection/ /opt/koillection-backup
msg_ok "Backup created"

View File

@@ -30,7 +30,18 @@ function update_script() {
3>&1 1>&2 2>&3)
if [ "$UPD" == "1" ]; then
setup_meilisearch
if check_for_gh_release "meilisearch" "meilisearch/meilisearch"; then
msg_info "Stopping Meilisearch"
systemctl stop meilisearch
msg_ok "Stopped Meilisearch"
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
msg_info "Starting Meilisearch"
systemctl start meilisearch
msg_ok "Started Meilisearch"
msg_ok "Updated successfully!"
fi
exit
fi

View File

@@ -1,60 +0,0 @@
#!/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: luismco
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/technomancer702/nodecast-tv
APP="nodecast-tv"
var_tags="${var_tags:-media}"
var_cpu="${var_cpu:-2}"
var_ram="${var_ram:-2048}"
var_disk="${var_disk:-4}"
var_os="${var_os:-debian}"
var_version="${var_version:-13}"
var_unprivileged="${var_unprivileged:-1}"
var_gpu="${var_gpu:-yes}"
header_info "$APP"
variables
color
catch_errors
function update_script() {
header_info
check_container_storage
check_container_resources
if [[ ! -d /opt/nodecast-tv ]]; then
msg_error "No ${APP} Installation Found!"
exit
fi
if check_for_gh_release "nodecast-tv" "technomancer702/nodecast-tv"; then
msg_info "Stopping Service"
systemctl stop nodecast-tv
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "nodecast-tv" "technomancer702/nodecast-tv"
msg_info "Updating Modules"
cd /opt/nodecast-tv
$STD npm install
msg_ok "Updated Modules"
msg_info "Starting Service"
systemctl start nodecast-tv
msg_ok "Started Service"
msg_ok "Updated successfully!"
fi
exit
}
start
build_container
description
msg_ok "Completed successfully!\n"
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:3000${CL}"

View File

@@ -28,8 +28,6 @@ function update_script() {
exit
fi
setup_meilisearch
if check_for_gh_release "openarchiver" "LogicLabs-OU/OpenArchiver"; then
msg_info "Stopping Services"
systemctl stop openarchiver
@@ -56,7 +54,6 @@ function update_script() {
msg_ok "Started Services"
msg_ok "Updated successfully!"
fi
exit
}

View File

@@ -31,7 +31,7 @@ function update_script() {
CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)
if [[ "$CURRENT_PHP" != "8.3" ]]; then
PHP_VERSION="8.3" PHP_FPM="YES" PHP_MODULE="mysql,redis" setup_php
PHP_VERSION="8.3" PHP_FPM="YES" PHP_MODULE="common,mysql,fpm,redis" setup_php
setup_composer
sed -i 's|php8\.2-fpm\.sock|php8.3-fpm.sock|g' /etc/nginx/sites-available/paymenter.conf
$STD systemctl reload nginx

View File

@@ -1,7 +1,7 @@
#!/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: MickLesk (Canbiz) | Co-Author: CrazyWolf13
# Author: MickLesk (Canbiz)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://vikunja.io/
@@ -27,37 +27,30 @@ function update_script() {
msg_error "No ${APP} Installation Found!"
exit
fi
RELEASE="$( [[ -f "$HOME/.vikunja" ]] && cat "$HOME/.vikunja" 2>/dev/null || [[ -f /opt/Vikunja_version ]] && cat /opt/Vikunja_version 2>/dev/null || true)"
if [[ -z "$RELEASE" ]] || [[ "$RELEASE" == "unstable" ]] || dpkg --compare-versions "${RELEASE:-0.0.0}" lt "1.0.0"; then
msg_warn "You are upgrading from Vikunja '$RELEASE'."
msg_warn "This requires MANUAL config changes in /etc/vikunja/config.yml."
msg_warn "See: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes"
read -rp "Continue with update? (y to proceed): " -t 30 CONFIRM1 || exit 1
[[ "$CONFIRM1" =~ ^[yY]$ ]] || exit 0
echo
msg_warn "Vikunja may not start after the update until you manually adjust the config."
msg_warn "Details: https://vikunja.io/changelog/whats-new-in-vikunja-1.0.0/#config-changes"
read -rp "Acknowledge and continue? (y): " -t 30 CONFIRM2 || exit 1
[[ "$CONFIRM2" =~ ^[yY]$ ]] || exit 0
fi
if check_for_gh_release "vikunja" "go-vikunja/vikunja"; then
RELEASE=$(curl -fsSL https://dl.vikunja.io/vikunja/ | grep -oP 'href="/vikunja/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1)
if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
msg_info "Stopping Service"
systemctl stop vikunja
msg_ok "Stopped Service"
fetch_and_deploy_gh_release "vikunja" "go-vikunja/vikunja" "binary"
msg_info "Updating ${APP} to ${RELEASE}"
cd /opt
rm -rf /opt/vikunja/vikunja
curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb" -o $(basename "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb")
export DEBIAN_FRONTEND=noninteractive
$STD dpkg -i vikunja-"$RELEASE"-amd64.deb
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
echo "${RELEASE}" >/opt/${APP}_version.txt
msg_ok "Updated ${APP}"
msg_info "Starting Service"
systemctl start vikunja
msg_ok "Started Service"
msg_ok "Updated successfully!"
else
msg_ok "No update required. ${APP} is already at ${RELEASE}"
fi
exit 0
exit
}
start

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": [

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@
"documentation": "https://homarr.dev/docs/getting-started/",
"website": "https://homarr.dev/",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/homarr.webp",
"config_path": "/opt/homarr.env",
"config_path": "/opt/homarr/.env",
"description": "Homarr is a sleek, modern dashboard that puts all of your apps and services at your fingertips.",
"install_methods": [
{

View File

@@ -1,35 +0,0 @@
{
"name": "nodecast-tv",
"slug": "nodecast-tv",
"categories": [
13
],
"date_created": "2026-01-28",
"type": "ct",
"updateable": true,
"privileged": false,
"interface_port": 3000,
"documentation": "https://github.com/technomancer702/nodecast-tv/blob/main/README.md",
"website": "https://github.com/technomancer702/nodecast-tv",
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/nodecast-tv.webp",
"config_path": "",
"description": "nodecast-tv is a modern, web-based IPTV player featuring Live TV, EPG, Movies (VOD), and Series support. Built with performance and user experience in mind.",
"install_methods": [
{
"type": "default",
"script": "ct/nodecast-tv.sh",
"resources": {
"cpu": 2,
"ram": 2048,
"hdd": 4,
"os": "debian",
"version": "13"
}
}
],
"default_credentials": {
"username": null,
"password": null
},
"notes": []
}

View File

@@ -39,8 +39,8 @@
}
],
"default_credentials": {
"username": "admin@local.com",
"password": "helper-scripts.com"
"username": "admin@example.org",
"password": null
},
"notes": [
{
@@ -48,7 +48,11 @@
"type": "info"
},
{
"text": "The initial starting process can take 1-2min. ",
"text": "The initial starting process can be take 1-2min. ",
"type": "info"
},
{
"text": "Application credentials: `cat /opt/.npm_pwd` - if file not exist in LXC check docker logs for password with `docker logs npmplus`",
"type": "info"
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +0,0 @@
import { NextResponse } from "next/server";
import { promises as fs } from "node:fs";
import path from "node:path";
import type { GitHubVersionsResponse } from "@/lib/types";
export const dynamic = "force-static";
const jsonDir = "public/json";
const versionsFileName = "github-versions.json";
const encoding = "utf-8";
async function getVersions(): Promise<GitHubVersionsResponse> {
const filePath = path.resolve(jsonDir, versionsFileName);
const fileContent = await fs.readFile(filePath, encoding);
const data: GitHubVersionsResponse = JSON.parse(fileContent);
return data;
}
export async function GET() {
try {
const versions = await getVersions();
return NextResponse.json(versions);
}
catch (error) {
console.error(error);
const err = error as globalThis.Error;
return NextResponse.json({
generated: "",
versions: [],
error: err.message || "An unexpected error occurred",
}, {
status: 500,
});
}
}

View File

@@ -3,22 +3,18 @@ import { NextResponse } from "next/server";
import { promises as fs } from "node:fs";
import path from "node:path";
import type { AppVersion } from "@/lib/types";
export const dynamic = "force-static";
const jsonDir = "public/json";
const versionsFileName = "versions.json";
const encoding = "utf-8";
interface LegacyVersion {
name: string;
version: string;
date: string;
}
async function getVersions() {
const filePath = path.resolve(jsonDir, versionsFileName);
const fileContent = await fs.readFile(filePath, encoding);
const versions: LegacyVersion[] = JSON.parse(fileContent);
const versions: AppVersion[] = JSON.parse(fileContent);
const modifiedVersions = versions.map((version) => {
let newName = version.name;

View File

@@ -1,13 +1,13 @@
"use client";
import { X, HelpCircle } from "lucide-react";
import { X } from "lucide-react";
import { Suspense } from "react";
import Image from "next/image";
import type { AppVersion, Script } from "@/lib/types";
import { cleanSlug } from "@/lib/utils/resource-utils";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { useVersions } from "@/hooks/use-versions";
import { basePath } from "@/config/site-config";
import { extractDate } from "@/lib/time";
@@ -108,31 +108,18 @@ function VersionInfo({ item }: { item: Script }) {
const { data: versions = [], isLoading } = useVersions();
if (isLoading || versions.length === 0) {
return null;
return <p className="text-sm text-muted-foreground">Loading versions...</p>;
}
const matchedVersion = versions.find((v: AppVersion) => v.slug === item.slug);
const matchedVersion = versions.find((v: AppVersion) => {
const cleanName = v.name.replace(/[^a-z0-9]/gi, "").toLowerCase();
return cleanName === cleanSlug(item.slug) || cleanName.includes(cleanSlug(item.slug));
});
if (!matchedVersion)
return null;
return (
<span className="font-medium text-sm flex items-center gap-1">
{matchedVersion.version}
{matchedVersion.pinned && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="h-3.5 w-3.5 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent className="max-w-xs">
<p>This version is pinned. We test each update for breaking changes before releasing new versions.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</span>
);
return <span className="font-medium text-sm">{matchedVersion.version}</span>;
}
export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {

View File

@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/react-query";
import type { AppVersion, GitHubVersionsResponse } from "@/lib/types";
import type { AppVersion } from "@/lib/types";
import { fetchVersions } from "@/lib/data";
@@ -10,8 +10,14 @@ export function useVersions() {
return useQuery<AppVersion[]>({
queryKey: ["versions"],
queryFn: async () => {
const response: GitHubVersionsResponse = await fetchVersions();
return response.versions ?? [];
const fetchedVersions = await fetchVersions();
if (Array.isArray(fetchedVersions)) {
return fetchedVersions;
}
if (fetchedVersions && typeof fetchedVersions === "object") {
return [fetchedVersions];
}
return [];
},
});
}

View File

@@ -10,7 +10,7 @@ export async function fetchCategories() {
}
export async function fetchVersions() {
const response = await fetch(`/ProxmoxVE/api/github-versions`);
const response = await fetch(`/ProxmoxVE/api/versions`);
if (!response.ok) {
throw new Error(`Failed to fetch versions: ${response.statusText}`);
}

View File

@@ -63,14 +63,7 @@ export type OperatingSystem = {
};
export type AppVersion = {
slug: string;
repo: string;
name: string;
version: string;
pinned: boolean;
date: string;
};
export type GitHubVersionsResponse = {
generated: string;
versions: AppVersion[];
date: Date;
};

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

@@ -23,10 +23,10 @@ $STD apt install -y \
libvips
msg_ok "Installed Dependencies"
PHP_VERSION="8.4" PHP_FPM=YES PHP_MODULE="ffi,redis,pdo-sqlite" setup_php
PHP_VERSION="8.4" PHP_FPM=YES PHP_MODULE="ffi,opcache,redis,zip,pdo-sqlite,bcmath,pdo,curl,dom,fpm" setup_php
setup_composer
NODE_VERSION="22" setup_nodejs
setup_meilisearch
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
fetch_and_deploy_gh_release "bar-assistant" "karlomikus/bar-assistant" "tarball" "latest" "/opt/bar-assistant"
fetch_and_deploy_gh_release "vue-salt-rim" "karlomikus/vue-salt-rim" "tarball" "latest" "/opt/vue-salt-rim"
@@ -36,16 +36,49 @@ sed -i.bak -E 's/^\s*;?\s*ffi\.enable\s*=.*/ffi.enable=true/' /etc/php/${PHPVER}
$STD systemctl reload php${PHPVER}-fpm
msg_info "configured PHP"
msg_info "Configure MeiliSearch"
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml
MASTER_KEY=$(openssl rand -base64 12)
sed -i \
-e 's|^env =.*|env = "production"|' \
-e "s|^# master_key =.*|master_key = \"$MASTER_KEY\"|" \
-e 's|^db_path =.*|db_path = "/var/lib/meilisearch/data"|' \
-e 's|^dump_dir =.*|dump_dir = "/var/lib/meilisearch/dumps"|' \
-e 's|^snapshot_dir =.*|snapshot_dir = "/var/lib/meilisearch/snapshots"|' \
-e 's|^# no_analytics = true|no_analytics = true|' \
-e 's|^http_addr =.*|http_addr = "127.0.0.1:7700"|' \
/etc/meilisearch.toml
msg_ok "Configured MeiliSearch"
msg_info "Creating MeiliSearch service"
cat <<EOF >/etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network.target
[Service]
ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now meilisearch
sleep 5
msg_ok "Created Service MeiliSearch"
msg_info "Installing Bar Assistant"
cd /opt/bar-assistant
cp /opt/bar-assistant/.env.dist /opt/bar-assistant/.env
mkdir -p /opt/bar-assistant/resources/data
curl -fsSL https://github.com/bar-assistant/data/archive/refs/heads/v5.tar.gz | tar -xz --strip-components=1 -C /opt/bar-assistant/resources/data
MeiliSearch_API_KEY=$(curl -s -X GET 'http://127.0.0.1:7700/keys' -H "Authorization: Bearer $MASTER_KEY" | grep -o '"key":"[^"]*"' | head -n 1 | sed 's/"key":"//;s/"//')
MeiliSearch_API_KEY_UID=$(curl -s -X GET 'http://127.0.0.1:7700/keys' -H "Authorization: Bearer $MASTER_KEY" | grep -o '"uid":"[^"]*"' | head -n 1 | sed 's/"uid":"//;s/"//')
sed -i -e "s|^APP_URL=|APP_URL=http://${LOCAL_IP}/bar/|" \
-e "s|^MEILISEARCH_HOST=|MEILISEARCH_HOST=http://127.0.0.1:7700|" \
-e "s|^MEILISEARCH_KEY=|MEILISEARCH_KEY=${MEILISEARCH_MASTER_KEY}|" \
-e "s|^MEILISEARCH_API_KEY=|MEILISEARCH_API_KEY=${MEILISEARCH_API_KEY}|" \
-e "s|^MEILISEARCH_API_KEY_UID=|MEILISEARCH_API_KEY_UID=${MEILISEARCH_API_KEY_UID}|" \
-e "s|^MEILISEARCH_KEY=|MEILISEARCH_KEY=${MASTER_KEY}|" \
-e "s|^MEILISEARCH_API_KEY=|MEILISEARCH_API_KEY=${MeiliSearch_API_KEY}|" \
-e "s|^MEILISEARCH_API_KEY_UID=|MEILISEARCH_API_KEY_UID=${MeiliSearch_API_KEY_UID}|" \
/opt/bar-assistant/.env
$STD composer install --no-interaction
$STD php artisan key:generate

View File

@@ -27,7 +27,6 @@ export CYPRESS_INSTALL_BINARY=0
export NODE_OPTIONS="--max-old-space-size=2048"
cd /opt/grist
$STD yarn install
$STD yarn run install:ee
$STD yarn run build:prod
$STD yarn run install:python
cat <<EOF >/opt/grist/.env

View File

@@ -13,7 +13,6 @@ setting_up_container
network_check
update_os
msg_custom "" "${GN}" "If NVIDIA GPU passthrough is detected, you'll be asked whether to install drivers in the container"
setup_hwaccel
msg_info "Installing Jellyfin"

View File

@@ -25,7 +25,20 @@ msg_ok "Installed Dependencies"
fetch_and_deploy_gh_release "monolith" "Y2Z/monolith" "singlefile" "latest" "/usr/bin" "monolith-gnu-linux-x86_64"
fetch_and_deploy_gh_release "yt-dlp" "yt-dlp/yt-dlp-nightly-builds" "singlefile" "latest" "/usr/bin" "yt-dlp_linux"
setup_meilisearch
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
msg_info "Configuring Meilisearch"
curl -fsSL "https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml" -o "/etc/meilisearch.toml"
MASTER_KEY=$(openssl rand -base64 12)
sed -i \
-e 's|^env =.*|env = "production"|' \
-e "s|^# master_key =.*|master_key = \"$MASTER_KEY\"|" \
-e 's|^db_path =.*|db_path = "/var/lib/meilisearch/data"|' \
-e 's|^dump_dir =.*|dump_dir = "/var/lib/meilisearch/dumps"|' \
-e 's|^snapshot_dir =.*|snapshot_dir = "/var/lib/meilisearch/snapshots"|' \
-e 's|^# no_analytics = true|no_analytics = true|' \
/etc/meilisearch.toml
msg_ok "Configured Meilisearch"
fetch_and_deploy_gh_release "karakeep" "karakeep-app/karakeep" "tarball"
cd /opt/karakeep
@@ -57,7 +70,7 @@ NEXTAUTH_SECRET="$karakeep_SECRET"
NEXTAUTH_URL="http://localhost:3000"
DATA_DIR=${DATA_DIR}
MEILI_ADDR="http://127.0.0.1:7700"
MEILI_MASTER_KEY="$MEILISEARCH_MASTER_KEY"
MEILI_MASTER_KEY="$MASTER_KEY"
BROWSER_WEB_URL="http://127.0.0.1:9222"
DB_WAL_MODE=true
@@ -96,6 +109,19 @@ $STD pnpm migrate
msg_ok "Database Migration Completed"
msg_info "Creating Services"
cat <<EOF >/etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network.target
[Service]
ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=always
[Install]
WantedBy=multi-user.target
EOF
cat <<EOF >/etc/systemd/system/karakeep-web.service
[Unit]
Description=karakeep Web
@@ -143,7 +169,7 @@ TimeoutStopSec=5
WantedBy=multi-user.target
EOF
systemctl enable -q --now karakeep-browser karakeep-workers karakeep-web
systemctl enable -q --now meilisearch karakeep-browser karakeep-workers karakeep-web
msg_ok "Created Services"
motd_ssh

View File

@@ -15,7 +15,7 @@ update_os
NODE_VERSION="22" NODE_MODULE="yarn@latest" setup_nodejs
PG_VERSION="16" setup_postgresql
PHP_VERSION="8.5" PHP_APACHE="YES" PHP_MODULE="apcu,ctype,dom,fileinfo,iconv,pgsql" setup_php
PHP_VERSION="8.4" PHP_APACHE="YES" PHP_MODULE="apcu,ctype,dom,fileinfo,iconv,pgsql" setup_php
setup_composer
msg_info "Setting up PostgreSQL"

View File

@@ -13,7 +13,21 @@ setting_up_container
network_check
update_os
MEILISEARCH_BIND="0.0.0.0:7700" setup_meilisearch
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
msg_info "Configuring ${APPLICATION}"
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml
MASTER_KEY=$(openssl rand -base64 12)
sed -i \
-e 's|^env =.*|env = "production"|' \
-e "s|^# master_key =.*|master_key = \"$MASTER_KEY\"|" \
-e 's|^db_path =.*|db_path = "/var/lib/meilisearch/data"|' \
-e 's|^dump_dir =.*|dump_dir = "/var/lib/meilisearch/dumps"|' \
-e 's|^snapshot_dir =.*|snapshot_dir = "/var/lib/meilisearch/snapshots"|' \
-e 's|^# no_analytics = true|no_analytics = true|' \
-e 's|^http_addr =.*|http_addr = "0.0.0.0:7700"|' \
/etc/meilisearch.toml
msg_ok "Configured ${APPLICATION}"
read -r -p "${TAB3}Do you want add meilisearch-ui? [y/n]: " prompt
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
@@ -27,11 +41,27 @@ if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
cat <<EOF >/opt/meilisearch-ui/.env.local
VITE_SINGLETON_MODE=true
VITE_SINGLETON_HOST=http://${LOCAL_IP}:7700
VITE_SINGLETON_API_KEY=${MEILISEARCH_MASTER_KEY}
VITE_SINGLETON_API_KEY=${MASTER_KEY}
EOF
msg_ok "Configured ${APPLICATION}-ui"
fi
msg_info "Creating Meilisearch-UI service"
msg_info "Creating service"
cat <<EOF >/etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network.target
[Service]
ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now meilisearch
if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
cat <<EOF >/etc/systemd/system/meilisearch-ui.service
[Unit]
Description=Meilisearch UI Service
@@ -52,8 +82,8 @@ SyslogIdentifier=meilisearch-ui
WantedBy=multi-user.target
EOF
systemctl enable -q --now meilisearch-ui
msg_ok "Created Meilisearch-UI service"
fi
msg_ok "Service created"
motd_ssh
customize

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: luismco
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://github.com/technomancer702/nodecast-tv
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
color
verb_ip6
catch_errors
setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "nodecast-tv" "technomancer702/nodecast-tv"
setup_nodejs
msg_info "Installing Dependencies"
$STD apt install -y ffmpeg
msg_ok "Installed Dependencies"
msg_info "Installing Modules"
cd /opt/nodecast-tv
$STD npm install
msg_ok "Installed Modules"
msg_info "Creating Service"
cat <<EOF >/etc/systemd/system/nodecast-tv.service
[Unit]
Description=nodecast-tv
After=network.target
Wants=network.target
[Service]
Type=simple
WorkingDirectory=/opt/nodecast-tv
ExecStart=/bin/npm run dev
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now nodecast-tv
msg_ok "Created Service"
motd_ssh
customize
cleanup_lxc

View File

@@ -59,8 +59,8 @@ read -r -p "${TAB3}Enter your ACME Email: " ACME_EMAIL_INPUT
yq -i "
.services.npmplus.environment |=
(map(select(. != \"TZ=*\" and . != \"ACME_EMAIL=*\" and . != \"INITIAL_ADMIN_EMAIL=*\" and . != \"INITIAL_ADMIN_PASSWORD=*\")) +
[\"TZ=$TZ_INPUT\", \"ACME_EMAIL=$ACME_EMAIL_INPUT\", \"INITIAL_ADMIN_EMAIL=admin@local.com\", \"INITIAL_ADMIN_PASSWORD=helper-scripts.com\"])
(map(select(. != \"TZ=*\" and . != \"ACME_EMAIL=*\")) +
[\"TZ=$TZ_INPUT\", \"ACME_EMAIL=$ACME_EMAIL_INPUT\"])
" /opt/compose.yaml
msg_info "Building and Starting NPMplus (Patience)"
@@ -86,3 +86,39 @@ msg_ok "Builded and started NPMplus"
motd_ssh
customize
msg_info "Retrieving Default Login (Patience)"
PASSWORD_FOUND=0
for i in {1..60}; do
PASSWORD_LINE=$(
{ awk '/Creating a new user:/{print; exit}' < <(docker logs "$CONTAINER_ID" 2>&1); } || true
)
if [[ -n "${PASSWORD_LINE:-}" ]]; then
PASSWORD="${PASSWORD_LINE#*password: }"
printf 'username: admin@example.org\npassword: %s\n' "$PASSWORD" >/opt/.npm_pwd
msg_ok "Saved default login to /opt/.npm_pwd"
PASSWORD_FOUND=1
break
fi
sleep 2
done
if [[ $PASSWORD_FOUND -eq 0 ]]; then
PASSWORD_LINE=$(
timeout 30s bash -c '
docker logs -f --since=0s --tail=0 "$1" 2>&1 | awk "/Creating a new user:/{print; exit}"
' _ "$CONTAINER_ID" || true
)
if [[ -n "${PASSWORD_LINE:-}" ]]; then
PASSWORD="${PASSWORD_LINE#*password: }"
printf 'username: admin@example.org\npassword: %s\n' "$PASSWORD" >/opt/.npm_pwd
msg_ok "Saved default login to /opt/.npm_pwd (live)"
PASSWORD_FOUND=1
fi
fi
if [[ $PASSWORD_FOUND -eq 0 ]]; then
msg_error "Could not retrieve default login after 120s."
echo -e "\nYou can manually check the container logs with:\n docker logs $CONTAINER_ID | grep 'Creating a new user:'\n"
fi

View File

@@ -21,11 +21,40 @@ NODE_VERSION="22" NODE_MODULE="pnpm" setup_nodejs
PG_VERSION="17" setup_postgresql
PG_DB_NAME="openarchiver_db" PG_DB_USER="openarchiver" setup_postgresql_db
setup_meilisearch
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
fetch_and_deploy_gh_release "openarchiver" "LogicLabs-OU/OpenArchiver" "tarball"
JWT_KEY="$(openssl rand -hex 32)"
SECRET_KEY="$(openssl rand -hex 32)"
msg_info "Configuring MeiliSearch"
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml
MASTER_KEY=$(openssl rand -base64 12)
sed -i \
-e 's|^env =.*|env = "production"|' \
-e "s|^# master_key =.*|master_key = \"$MASTER_KEY\"|" \
-e 's|^db_path =.*|db_path = "/var/lib/meilisearch/data"|' \
-e 's|^dump_dir =.*|dump_dir = "/var/lib/meilisearch/dumps"|' \
-e 's|^snapshot_dir =.*|snapshot_dir = "/var/lib/meilisearch/snapshots"|' \
-e 's|^# no_analytics = true|no_analytics = true|' \
-e 's|^http_addr =.*|http_addr = "127.0.0.1:7700"|' \
/etc/meilisearch.toml
cat <<EOF >/etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network.target
[Service]
ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable -q --now meilisearch
sleep 5
msg_ok "Configured MeiliSearch"
msg_info "Setting up Open Archiver"
mkdir -p /opt/openarchiver-data
cd /opt/openarchiver
@@ -36,7 +65,7 @@ sed -i "s|^POSTGRES_USER=.*|POSTGRES_USER=$PG_DB_USER|g" /opt/openarchiver/.env
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$PG_DB_PASS|g" /opt/openarchiver/.env
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"postgresql://$PG_DB_USER:$PG_DB_PASS@localhost:5432/$PG_DB_NAME\"|g" /opt/openarchiver/.env
sed -i "s|^MEILI_HOST=.*|MEILI_HOST=http://localhost:7700|g" /opt/openarchiver/.env
sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$MEILISEARCH_MASTER_KEY|g" /opt/openarchiver/.env
sed -i "s|^MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$MASTER_KEY|g" /opt/openarchiver/.env
sed -i "s|^REDIS_HOST=.*|REDIS_HOST=localhost|g" /opt/openarchiver/.env
sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=|g" /opt/openarchiver/.env
sed -i "s|^STORAGE_LOCAL_ROOT_PATH=.*|STORAGE_LOCAL_ROOT_PATH=/opt/openarchiver-data|g" /opt/openarchiver/.env

View File

@@ -19,7 +19,7 @@ $STD apt install -y \
openssl
msg_ok "Installed Dependencies"
PHP_VERSION="8.2" PHP_FPM="YES" setup_php
PHP_VERSION="8.2" PHP_MODULE="common,fpm" setup_php
create_self_signed_cert
fetch_and_deploy_gh_release "privatebin" "PrivateBin/PrivateBin" "tarball"

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Copyright (c) 2021-2026 community-scripts ORG
# Author: MickLesk (Canbiz) | Co-Author: CrazyWolf13
# Author: MickLesk (Canbiz)
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
# Source: https://vikunja.io/
@@ -13,14 +13,22 @@ setting_up_container
network_check
update_os
fetch_and_deploy_gh_release "vikunja" "go-vikunja/vikunja" "binary"
msg_info "Installing Dependencies"
$STD apt install -y make
msg_ok "Installed Dependencies"
msg_info "Setting up Vikunja"
sed -i 's|^# \(service:\)|\1|' /etc/vikunja/config.yml
sed -i "s|^ # \(publicurl: \).*| \1\"http://$LOCAL_IP\"|" /etc/vikunja/config.yml
sed -i "0,/^ # \(timezone: \).*/s|| \1${tz}|" /etc/vikunja/config.yml
systemctl enable -q --now vikunja
msg_ok "Set up Vikunja"
msg_info "Setup Vikunja (Patience)"
cd /opt
RELEASE=$(curl -fsSL https://dl.vikunja.io/vikunja/ | grep -oP 'href="/vikunja/\K[0-9]+\.[0-9]+\.[0-9]+' | sort -V | tail -n 1)
curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb" -o vikunja-"$RELEASE"-amd64.deb
$STD dpkg -i vikunja-"$RELEASE"-amd64.deb
sed -i 's|^ timezone: .*| timezone: UTC|' /etc/vikunja/config.yml
sed -i 's|"./vikunja.db"|"/etc/vikunja/vikunja.db"|' /etc/vikunja/config.yml
sed -i 's|./files|/etc/vikunja/files|' /etc/vikunja/config.yml
systemctl start vikunja.service
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
echo "${RELEASE}" >/opt/"${APPLICATION}"_version.txt
msg_ok "Installed Vikunja"
motd_ssh
customize

View File

@@ -195,11 +195,9 @@ get_current_ip() {
#
# - Updates /etc/motd with current container IP
# - Removes old IP entries to avoid duplicates
# - Regenerates /etc/profile.d/00_lxc-details.sh with dynamic OS/IP info
# ------------------------------------------------------------------------------
update_motd_ip() {
MOTD_FILE="/etc/motd"
PROFILE_FILE="/etc/profile.d/00_lxc-details.sh"
if [ -f "$MOTD_FILE" ]; then
# Remove existing IP Address lines to prevent duplication
@@ -209,26 +207,6 @@ update_motd_ip() {
# Add the new IP address
echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE"
fi
# Update dynamic LXC details profile if values changed (e.g., after OS upgrade)
# Only update if file exists and is from community-scripts
if [ -f "$PROFILE_FILE" ] && grep -q "community-scripts" "$PROFILE_FILE" 2>/dev/null; then
# Get current values
local current_os="$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')"
local current_hostname="$(hostname)"
local current_ip="$(hostname -I | awk '{print $1}')"
# Update only if values actually changed
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
fi
if ! grep -q "Hostname:.*$current_hostname" "$PROFILE_FILE" 2>/dev/null; then
sed -i "s|Hostname:.*|Hostname: \${GN}$current_hostname\${CL}\\\"|" "$PROFILE_FILE"
fi
if ! grep -q "IP Address:.*$current_ip" "$PROFILE_FILE" 2>/dev/null; then
sed -i "s|IP Address:.*|IP Address: \${GN}$current_ip\${CL}\\\"|" "$PROFILE_FILE"
fi
fi
}
# ------------------------------------------------------------------------------
@@ -1817,7 +1795,7 @@ advanced_settings() {
if [[ -n "$BRIDGES" ]]; then
while IFS= read -r bridge; do
if [[ -n "$bridge" ]]; then
local description=$(grep -A 10 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\s*//;s/^[- ]*//')
local description=$(grep -A 10 "iface $bridge" /etc/network/interfaces 2>/dev/null | grep '^#' | head -n1 | sed 's/^#\s*//')
BRIDGE_MENU_OPTIONS+=("$bridge" "${description:- }")
fi
done <<<"$BRIDGES"
@@ -2049,10 +2027,6 @@ advanced_settings() {
"${BRIDGE_MENU_OPTIONS[@]}" \
3>&1 1>&2 2>&3); then
local bridge_test="${result:-vmbr0}"
# Skip separator entries (e.g., __other__) - re-display menu
if [[ "$bridge_test" == "__other__" || "$bridge_test" == -* ]]; then
continue
fi
if validate_bridge "$bridge_test"; then
_bridge="$bridge_test"
((STEP++))
@@ -3336,7 +3310,6 @@ start() {
set_std_mode
ensure_profile_loaded
update_script
update_motd_ip
cleanup_lxc
else
CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \
@@ -3363,7 +3336,6 @@ start() {
esac
ensure_profile_loaded
update_script
update_motd_ip
cleanup_lxc
fi
}
@@ -3882,17 +3854,6 @@ EOF
fix_gpu_gids
# Fix Debian 13 LXC template bug where / is owned by nobody:nogroup
# This must be done from the host as unprivileged containers cannot chown /
local rootfs
rootfs=$(pct config "$CTID" | grep -E '^rootfs:' | sed 's/rootfs: //' | cut -d',' -f1)
if [[ -n "$rootfs" ]]; then
local mount_point="/var/lib/lxc/${CTID}/rootfs"
if [[ -d "$mount_point" ]] && [[ "$(stat -c '%U' "$mount_point")" != "root" ]]; then
chown root:root "$mount_point" 2>/dev/null || true
fi
fi
# Continue with standard container setup
msg_info "Customizing LXC Container"

View File

@@ -79,13 +79,6 @@ EOF
# ------------------------------------------------------------------------------
setting_up_container() {
msg_info "Setting up Container OS"
# Fix Debian 13 LXC template bug where / is owned by nobody
# Only attempt in privileged containers (unprivileged cannot chown /)
if [[ "$(stat -c '%U' /)" != "root" ]]; then
(chown root:root / 2>/dev/null) || true
fi
for ((i = RETRY_NUM; i > 0; i--)); do
if [ "$(hostname -I)" != "" ]; then
break

View File

@@ -574,8 +574,7 @@ EOF
msg_error "Failed to download PHP keyring"
return 1
}
# Don't use /dev/null redirection for dpkg as it may use background processes
dpkg -i /tmp/debsuryorg-archive-keyring.deb >>"$(get_active_logfile)" 2>&1 || {
dpkg -i /tmp/debsuryorg-archive-keyring.deb >/dev/null 2>&1 || {
msg_error "Failed to install PHP keyring"
rm -f /tmp/debsuryorg-archive-keyring.deb
return 1
@@ -821,6 +820,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 +1432,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 +1637,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 +1839,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).
#
@@ -1845,9 +2470,8 @@ function fetch_and_deploy_gh_release() {
}
chmod 644 "$tmpdir/$filename"
# SYSTEMD_OFFLINE=1 prevents systemd-tmpfiles failures in unprivileged LXC (Debian 13+/systemd 257+)
SYSTEMD_OFFLINE=1 $STD apt install -y "$tmpdir/$filename" || {
SYSTEMD_OFFLINE=1 $STD dpkg -i "$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
@@ -2633,7 +3257,6 @@ function setup_hwaccel() {
# GPU Selection - Let user choose which GPU(s) to configure
# ═══════════════════════════════════════════════════════════════════════════
local -a SELECTED_INDICES=()
local install_nvidia_drivers="yes"
if [[ $gpu_count -eq 1 ]]; then
# Single GPU - auto-select
@@ -2695,30 +3318,6 @@ function setup_hwaccel() {
fi
fi
# Ask whether to install NVIDIA drivers in the container
local nvidia_selected="no"
for idx in "${SELECTED_INDICES[@]}"; do
if [[ "${GPU_TYPES[$idx]}" == "NVIDIA" ]]; then
nvidia_selected="yes"
break
fi
done
if [[ "$nvidia_selected" == "yes" ]]; then
if [[ -n "${INSTALL_NVIDIA_DRIVERS:-}" ]]; then
install_nvidia_drivers="${INSTALL_NVIDIA_DRIVERS}"
else
echo ""
msg_custom "🎮" "${GN}" "NVIDIA GPU passthrough detected"
local nvidia_reply=""
read -r -t 60 -p "${TAB3}⚙️ Install NVIDIA driver libraries in the container? [Y/n] (auto-yes in 60s): " nvidia_reply || nvidia_reply=""
case "${nvidia_reply,,}" in
n | no) install_nvidia_drivers="no" ;;
*) install_nvidia_drivers="yes" ;;
esac
fi
fi
# ═══════════════════════════════════════════════════════════════════════════
# OS Detection
# ═══════════════════════════════════════════════════════════════════════════
@@ -2779,11 +3378,7 @@ function setup_hwaccel() {
# NVIDIA GPUs
# ─────────────────────────────────────────────────────────────────────────
NVIDIA)
if [[ "$install_nvidia_drivers" == "yes" ]]; then
_setup_nvidia_gpu "$os_id" "$os_codename" "$os_version"
else
msg_warn "Skipping NVIDIA driver installation (user opted to install manually)"
fi
_setup_nvidia_gpu "$os_id" "$os_codename" "$os_version"
;;
esac
done
@@ -4530,8 +5125,7 @@ EOF
# Ubuntu: Use ondrej/php PPA
msg_info "Adding ondrej/php PPA for Ubuntu"
$STD apt install -y software-properties-common
# Don't use $STD for add-apt-repository as it uses background processes
add-apt-repository -y ppa:ondrej/php >>"$(get_active_logfile)" 2>&1
$STD add-apt-repository -y ppa:ondrej/php
else
# Debian: Use Sury repository
manage_tool_repository "php" "$PHP_VERSION" "" "https://packages.sury.org/debsuryorg-archive-keyring.deb" || {
@@ -4561,14 +5155,6 @@ EOF
if [[ "$PHP_FPM" == "YES" ]]; then
MODULE_LIST+=" php${PHP_VERSION}-fpm"
# Create systemd override for PHP-FPM to fix runtime directory issues in LXC containers
mkdir -p /etc/systemd/system/php${PHP_VERSION}-fpm.service.d/
cat <<EOF >/etc/systemd/system/php${PHP_VERSION}-fpm.service.d/override.conf
[Service]
RuntimeDirectory=php
RuntimeDirectoryMode=0755
EOF
$STD systemctl daemon-reload
fi
# install apache2 with PHP support if requested
@@ -5136,146 +5722,6 @@ function setup_ruby() {
msg_ok "Setup Ruby $RUBY_VERSION"
}
# ------------------------------------------------------------------------------
# Installs or updates MeiliSearch search engine.
#
# Description:
# - Fresh install: Downloads binary, creates config/service, starts
# - Update: Checks for new release, updates binary if available
# - Waits for service to be ready before returning
# - Exports API keys for use by caller
#
# Variables:
# MEILISEARCH_BIND - Bind address (default: 127.0.0.1:7700)
# MEILISEARCH_ENV - Environment: production/development (default: production)
# MEILISEARCH_DB_PATH - Database path (default: /var/lib/meilisearch/data)
#
# Exports:
# MEILISEARCH_MASTER_KEY - The master key for admin access
# MEILISEARCH_API_KEY - The default search API key
# MEILISEARCH_API_KEY_UID - The UID of the default API key
#
# Example (install script):
# setup_meilisearch
#
# Example (CT update_script):
# setup_meilisearch
# ------------------------------------------------------------------------------
function setup_meilisearch() {
local MEILISEARCH_BIND="${MEILISEARCH_BIND:-127.0.0.1:7700}"
local MEILISEARCH_ENV="${MEILISEARCH_ENV:-production}"
local MEILISEARCH_DB_PATH="${MEILISEARCH_DB_PATH:-/var/lib/meilisearch/data}"
local MEILISEARCH_DUMP_DIR="${MEILISEARCH_DUMP_DIR:-/var/lib/meilisearch/dumps}"
local MEILISEARCH_SNAPSHOT_DIR="${MEILISEARCH_SNAPSHOT_DIR:-/var/lib/meilisearch/snapshots}"
# Get bind address for health checks
local MEILISEARCH_HOST="${MEILISEARCH_BIND%%:*}"
local MEILISEARCH_PORT="${MEILISEARCH_BIND##*:}"
[[ "$MEILISEARCH_HOST" == "0.0.0.0" ]] && MEILISEARCH_HOST="127.0.0.1"
# Update mode: MeiliSearch already installed
if [[ -f /usr/bin/meilisearch ]]; then
if check_for_gh_release "meilisearch" "meilisearch/meilisearch"; then
msg_info "Updating MeiliSearch"
systemctl stop meilisearch
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary"
systemctl start meilisearch
msg_ok "Updated MeiliSearch"
fi
return 0
fi
# Fresh install
msg_info "Setup MeiliSearch"
# Install binary
fetch_and_deploy_gh_release "meilisearch" "meilisearch/meilisearch" "binary" || {
msg_error "Failed to install MeiliSearch binary"
return 1
}
# Download default config
curl -fsSL https://raw.githubusercontent.com/meilisearch/meilisearch/latest/config.toml -o /etc/meilisearch.toml || {
msg_error "Failed to download MeiliSearch config"
return 1
}
# Generate master key
MEILISEARCH_MASTER_KEY=$(openssl rand -base64 12)
export MEILISEARCH_MASTER_KEY
# Configure
sed -i \
-e "s|^env =.*|env = \"${MEILISEARCH_ENV}\"|" \
-e "s|^# master_key =.*|master_key = \"${MEILISEARCH_MASTER_KEY}\"|" \
-e "s|^db_path =.*|db_path = \"${MEILISEARCH_DB_PATH}\"|" \
-e "s|^dump_dir =.*|dump_dir = \"${MEILISEARCH_DUMP_DIR}\"|" \
-e "s|^snapshot_dir =.*|snapshot_dir = \"${MEILISEARCH_SNAPSHOT_DIR}\"|" \
-e 's|^# no_analytics = true|no_analytics = true|' \
-e "s|^http_addr =.*|http_addr = \"${MEILISEARCH_BIND}\"|" \
/etc/meilisearch.toml
# Create data directories
mkdir -p "${MEILISEARCH_DB_PATH}" "${MEILISEARCH_DUMP_DIR}" "${MEILISEARCH_SNAPSHOT_DIR}"
# Create systemd service
cat <<EOF >/etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network.target
[Service]
ExecStart=/usr/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# Enable and start service
systemctl daemon-reload
systemctl enable -q --now meilisearch
# Wait for MeiliSearch to be ready (up to 30 seconds)
for i in {1..30}; do
if curl -s -o /dev/null -w "%{http_code}" "http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/health" 2>/dev/null | grep -q "200"; then
break
fi
sleep 1
done
# Verify service is running
if ! systemctl is-active --quiet meilisearch; then
msg_error "MeiliSearch service failed to start"
return 1
fi
# Get API keys with retry logic
MEILISEARCH_API_KEY=""
for i in {1..10}; do
MEILISEARCH_API_KEY=$(curl -s -X GET "http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/keys" \
-H "Authorization: Bearer ${MEILISEARCH_MASTER_KEY}" 2>/dev/null | \
grep -o '"key":"[^"]*"' | head -n 1 | sed 's/"key":"//;s/"//') || true
[[ -n "$MEILISEARCH_API_KEY" ]] && break
sleep 2
done
MEILISEARCH_API_KEY_UID=$(curl -s -X GET "http://${MEILISEARCH_HOST}:${MEILISEARCH_PORT}/keys" \
-H "Authorization: Bearer ${MEILISEARCH_MASTER_KEY}" 2>/dev/null | \
grep -o '"uid":"[^"]*"' | head -n 1 | sed 's/"uid":"//;s/"//') || true
export MEILISEARCH_API_KEY
export MEILISEARCH_API_KEY_UID
# Cache version
local MEILISEARCH_VERSION
MEILISEARCH_VERSION=$(/usr/bin/meilisearch --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) || true
cache_installed_version "meilisearch" "${MEILISEARCH_VERSION:-unknown}"
msg_ok "Setup MeiliSearch ${MEILISEARCH_VERSION:-}"
}
# ------------------------------------------------------------------------------
# Installs or upgrades ClickHouse database server.
#

View File

@@ -213,7 +213,7 @@ function default_settings() {
MAC="$GEN_MAC"
VLAN=""
MTU=""
START_VM="no"
START_VM="yes"
METHOD="default"
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}"
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}"