mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-02-24 14:05:59 +01:00
Compare commits
14 Commits
bump/node-
...
automated/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ad102e57d | ||
|
|
efcdd65c67 | ||
|
|
8b15af7499 | ||
|
|
13daffdeed | ||
|
|
fd82f5b496 | ||
|
|
83f03d617e | ||
|
|
4c5d5b2030 | ||
|
|
4e1ade4c28 | ||
|
|
e5073d1a4f | ||
|
|
01e956bdf8 | ||
|
|
0cc049edb6 | ||
|
|
ffa1a26b5e | ||
|
|
59ca9c26c9 | ||
|
|
56de2d1e39 |
341
.github/workflows/check-node-versions.yml
generated
vendored
Normal file
341
.github/workflows/check-node-versions.yml
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
name: Check Node.js Version Drift
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Runs weekly on Monday at 06:00 UTC
|
||||
- cron: "0 6 * * 1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-node-versions:
|
||||
if: github.repository == 'community-scripts/ProxmoxVE'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq jq curl > /dev/null 2>&1
|
||||
|
||||
- name: Check upstream Node.js versions
|
||||
id: check
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "================================================"
|
||||
echo " Checking Node.js version drift in install scripts"
|
||||
echo "================================================"
|
||||
|
||||
# Alpine version -> Node major cache (populated on demand)
|
||||
declare -A ALPINE_NODE_CACHE
|
||||
|
||||
# Resolve Node.js major version from Alpine package registry
|
||||
# Usage: resolve_alpine_node "3.21" => sets REPLY to major version (e.g. "22")
|
||||
resolve_alpine_node() {
|
||||
local alpine_ver="$1"
|
||||
if [[ -n "${ALPINE_NODE_CACHE[$alpine_ver]+x}" ]]; then
|
||||
REPLY="${ALPINE_NODE_CACHE[$alpine_ver]}"
|
||||
return
|
||||
fi
|
||||
|
||||
local url="https://pkgs.alpinelinux.org/package/v${alpine_ver}/main/x86_64/nodejs"
|
||||
local page
|
||||
page=$(curl -sf "$url" 2>/dev/null || echo "")
|
||||
local full_ver=""
|
||||
if [[ -n "$page" ]]; then
|
||||
# Parse: "Version | 24.13.0-r1" or similar table row
|
||||
full_ver=$(echo "$page" | grep -oP 'Version\s*\|\s*\K[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
|
||||
if [[ -z "$full_ver" ]]; then
|
||||
# Fallback: look for version pattern after "Version"
|
||||
full_ver=$(echo "$page" | grep -oP '(?<=Version</td><td>)[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
local major=""
|
||||
if [[ -n "$full_ver" ]]; then
|
||||
major="${full_ver%%.*}"
|
||||
fi
|
||||
|
||||
ALPINE_NODE_CACHE[$alpine_ver]="$major"
|
||||
REPLY="$major"
|
||||
}
|
||||
|
||||
# Extract Node major from a Dockerfile content
|
||||
# Sets: DF_NODE_MAJOR, DF_SOURCE (description of where we found it)
|
||||
extract_dockerfile_node() {
|
||||
local content="$1"
|
||||
DF_NODE_MAJOR=""
|
||||
DF_SOURCE=""
|
||||
|
||||
# 1) FROM node:XX (e.g. node:24-alpine, node:22.9.0-bookworm-slim, node:20)
|
||||
local node_from
|
||||
node_from=$(echo "$content" | grep -oP '(?i)FROM\s+(--platform=[^\s]+\s+)?node:\K[0-9]+' | head -1 || echo "")
|
||||
if [[ -n "$node_from" ]]; then
|
||||
DF_NODE_MAJOR="$node_from"
|
||||
DF_SOURCE="FROM node:${node_from}"
|
||||
return
|
||||
fi
|
||||
|
||||
# 2) nodesource/setup_XX.x
|
||||
local nodesource
|
||||
nodesource=$(echo "$content" | grep -oP 'nodesource/setup_\K[0-9]+' | head -1 || echo "")
|
||||
if [[ -n "$nodesource" ]]; then
|
||||
DF_NODE_MAJOR="$nodesource"
|
||||
DF_SOURCE="nodesource/setup_${nodesource}.x"
|
||||
return
|
||||
fi
|
||||
|
||||
# 3) FROM alpine:X.Y — resolve via Alpine packages
|
||||
local alpine_ver
|
||||
alpine_ver=$(echo "$content" | grep -oP '(?i)FROM\s+(--platform=[^\s]+\s+)?alpine:\K[0-9]+\.[0-9]+' | head -1 || echo "")
|
||||
if [[ -n "$alpine_ver" ]]; then
|
||||
resolve_alpine_node "$alpine_ver"
|
||||
if [[ -n "$REPLY" ]]; then
|
||||
DF_NODE_MAJOR="$REPLY"
|
||||
DF_SOURCE="alpine:${alpine_ver} (pkg: nodejs ${DF_NODE_MAJOR})"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Extract Node major from engines.node in package.json
|
||||
# Sets: ENGINES_NODE_RAW (raw string), ENGINES_MIN_MAJOR
|
||||
extract_engines_node() {
|
||||
local content="$1"
|
||||
ENGINES_NODE_RAW=""
|
||||
ENGINES_MIN_MAJOR=""
|
||||
|
||||
ENGINES_NODE_RAW=$(echo "$content" | jq -r '.engines.node // empty' 2>/dev/null || echo "")
|
||||
if [[ -z "$ENGINES_NODE_RAW" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract the first number (major) from the constraint
|
||||
# Handles: ">=24.13.1", "^22", ">=18.0.0", ">=18.15.0 <19 || ^20", etc.
|
||||
ENGINES_MIN_MAJOR=$(echo "$ENGINES_NODE_RAW" | grep -oP '\d+' | head -1 || echo "")
|
||||
}
|
||||
|
||||
# Collect results
|
||||
declare -a issue_scripts=()
|
||||
declare -a report_lines=()
|
||||
total=0
|
||||
checked=0
|
||||
drift_count=0
|
||||
|
||||
for script in install/*-install.sh; do
|
||||
[[ ! -f "$script" ]] && continue
|
||||
if ! grep -q 'setup_nodejs' "$script"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
total=$((total + 1))
|
||||
slug=$(basename "$script" | sed 's/-install\.sh$//')
|
||||
|
||||
# Extract Source URL (GitHub only)
|
||||
source_url=$(head -20 "$script" | grep -oP '(?<=# Source: )https://github\.com/[^\s]+' | head -1 || echo "")
|
||||
if [[ -z "$source_url" ]]; then
|
||||
report_lines+=("| \`$slug\` | — | — | — | — | ⏭️ No GitHub source |")
|
||||
continue
|
||||
fi
|
||||
|
||||
repo=$(echo "$source_url" | sed -E 's|https://github\.com/||; s|/$||; s|\.git$||')
|
||||
if [[ -z "$repo" || "$repo" != */* ]]; then
|
||||
report_lines+=("| \`$slug\` | — | — | — | — | ⏭️ Invalid repo |")
|
||||
continue
|
||||
fi
|
||||
|
||||
checked=$((checked + 1))
|
||||
|
||||
# Extract our NODE_VERSION
|
||||
our_version=$(grep -oP 'NODE_VERSION="(\d+)"' "$script" | head -1 | grep -oP '\d+' || echo "")
|
||||
if [[ -z "$our_version" ]]; then
|
||||
if grep -q 'NODE_VERSION=\$(' "$script"; then
|
||||
our_version="dynamic"
|
||||
else
|
||||
our_version="unset"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fetch upstream Dockerfile
|
||||
df_content=""
|
||||
for branch in main master dev; do
|
||||
df_content=$(curl -sf "https://raw.githubusercontent.com/${repo}/${branch}/Dockerfile" 2>/dev/null || echo "")
|
||||
[[ -n "$df_content" ]] && break
|
||||
done
|
||||
|
||||
DF_NODE_MAJOR=""
|
||||
DF_SOURCE=""
|
||||
if [[ -n "$df_content" ]]; then
|
||||
extract_dockerfile_node "$df_content"
|
||||
fi
|
||||
|
||||
# Fetch upstream package.json
|
||||
pkg_content=""
|
||||
for branch in main master dev; do
|
||||
pkg_content=$(curl -sf "https://raw.githubusercontent.com/${repo}/${branch}/package.json" 2>/dev/null || echo "")
|
||||
[[ -n "$pkg_content" ]] && break
|
||||
done
|
||||
|
||||
ENGINES_NODE_RAW=""
|
||||
ENGINES_MIN_MAJOR=""
|
||||
if [[ -n "$pkg_content" ]]; then
|
||||
extract_engines_node "$pkg_content"
|
||||
fi
|
||||
|
||||
# Determine upstream recommended major version
|
||||
upstream_major=""
|
||||
upstream_hint=""
|
||||
|
||||
if [[ -n "$DF_NODE_MAJOR" ]]; then
|
||||
upstream_major="$DF_NODE_MAJOR"
|
||||
upstream_hint="$DF_SOURCE"
|
||||
elif [[ -n "$ENGINES_MIN_MAJOR" ]]; then
|
||||
upstream_major="$ENGINES_MIN_MAJOR"
|
||||
upstream_hint="engines: $ENGINES_NODE_RAW"
|
||||
fi
|
||||
|
||||
# Build display values
|
||||
engines_display="${ENGINES_NODE_RAW:-—}"
|
||||
dockerfile_display="${DF_SOURCE:-—}"
|
||||
|
||||
# Compare
|
||||
status="✅"
|
||||
if [[ "$our_version" == "dynamic" ]]; then
|
||||
status="🔄 Dynamic"
|
||||
elif [[ "$our_version" == "unset" ]]; then
|
||||
status="⚠️ NODE_VERSION not set"
|
||||
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
|
||||
drift_count=$((drift_count + 1))
|
||||
elif [[ -n "$upstream_major" && "$our_version" != "$upstream_major" ]]; then
|
||||
status="🔸 Drift → upstream=$upstream_major ($upstream_hint)"
|
||||
issue_scripts+=("$slug|$our_version|$upstream_major|$upstream_hint|$repo")
|
||||
drift_count=$((drift_count + 1))
|
||||
fi
|
||||
|
||||
report_lines+=("| \`$slug\` | $our_version | $engines_display | $dockerfile_display | [$repo](https://github.com/$repo) | $status |")
|
||||
|
||||
# Rate-limit to avoid GitHub secondary rate limits
|
||||
sleep 0.3
|
||||
|
||||
done
|
||||
|
||||
# Print summary
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Total scripts with setup_nodejs: $total"
|
||||
echo " Checked (with GitHub source): $checked"
|
||||
echo " Version drift detected: $drift_count"
|
||||
echo "========================================="
|
||||
|
||||
# Export
|
||||
{
|
||||
echo "drift_count=$drift_count"
|
||||
echo "total=$total"
|
||||
echo "checked=$checked"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Save issue details for next step
|
||||
printf '%s\n' "${issue_scripts[@]}" > /tmp/drift_scripts.txt 2>/dev/null || touch /tmp/drift_scripts.txt
|
||||
|
||||
# Save full report
|
||||
{
|
||||
echo "## Node.js Version Drift Report"
|
||||
echo ""
|
||||
echo "**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
echo "**Scripts checked:** $total | **With GitHub source:** $checked | **Drift detected:** $drift_count"
|
||||
echo ""
|
||||
echo "| Script | Our Version | engines.node | Dockerfile | Upstream Repo | Status |"
|
||||
echo "|--------|-------------|-------------|------------|---------------|--------|"
|
||||
printf '%s\n' "${report_lines[@]}" | sort
|
||||
} > /tmp/drift_report.md
|
||||
|
||||
cat /tmp/drift_report.md
|
||||
|
||||
- name: Create or update summary issue
|
||||
if: steps.check.outputs.drift_count != '0'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
TITLE="[Automated] Node.js Version Drift Report"
|
||||
DATE=$(date -u +%Y-%m-%d)
|
||||
DRIFT_COUNT="${{ steps.check.outputs.drift_count }}"
|
||||
TOTAL="${{ steps.check.outputs.total }}"
|
||||
CHECKED="${{ steps.check.outputs.checked }}"
|
||||
|
||||
# Build checklist from drift data
|
||||
CHECKLIST=""
|
||||
while IFS='|' read -r slug our_version upstream_major upstream_hint repo; do
|
||||
[[ -z "$slug" ]] && continue
|
||||
CHECKLIST+="- [ ] **\`${slug}\`** — ours: \`${our_version}\` → upstream: \`${upstream_major}\` (${upstream_hint}) — [repo](https://github.com/${repo})"$'\n'
|
||||
done < /tmp/drift_scripts.txt
|
||||
|
||||
# Build full report table
|
||||
REPORT=$(cat /tmp/drift_report.md)
|
||||
|
||||
BODY=$(cat <<ISSUE_EOF
|
||||
## Node.js Version Drift Report — ${DATE}
|
||||
|
||||
**${DRIFT_COUNT}** script(s) with version drift detected (out of ${CHECKED} checked / ${TOTAL} total).
|
||||
|
||||
### Scripts requiring investigation
|
||||
|
||||
${CHECKLIST}
|
||||
|
||||
### How to resolve
|
||||
|
||||
1. Check upstream Dockerfile / package.json to confirm the required Node.js version
|
||||
2. Test the script with the new Node version
|
||||
3. Update \`NODE_VERSION\` in \`install/<slug>-install.sh\`
|
||||
4. Update \`NODE_VERSION\` in \`ct/<slug>.sh\` (update section) if applicable
|
||||
5. Check off the item above once done
|
||||
|
||||
<details>
|
||||
<summary>Full report</summary>
|
||||
|
||||
${REPORT}
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
*This issue is automatically created/updated weekly by the Node.js version drift check workflow.*
|
||||
*Last updated: ${DATE}*
|
||||
ISSUE_EOF
|
||||
)
|
||||
|
||||
# Check if a matching open issue already exists
|
||||
EXISTING=$(gh issue list --state open --label "automated,dependencies" --search "\"[Automated] Node.js Version Drift Report\"" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$EXISTING" ]]; then
|
||||
gh issue edit "$EXISTING" --body "$BODY"
|
||||
echo "Updated existing issue #$EXISTING"
|
||||
else
|
||||
gh issue create \
|
||||
--title "$TITLE" \
|
||||
--body "$BODY" \
|
||||
--label "automated,dependencies"
|
||||
echo "Created new summary issue"
|
||||
fi
|
||||
|
||||
- name: Close issue if no drift
|
||||
if: steps.check.outputs.drift_count == '0'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
EXISTING=$(gh issue list --state open --label "automated,dependencies" --search "\"[Automated] Node.js Version Drift Report\"" --json number --jq '.[0].number // empty' 2>/dev/null || echo "")
|
||||
if [[ -n "$EXISTING" ]]; then
|
||||
gh issue close "$EXISTING" --comment "All Node.js versions are in sync with upstream. Closing automatically."
|
||||
echo "Closed issue #$EXISTING"
|
||||
fi
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
@@ -415,15 +415,31 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- Refactor n8n [@MickLesk](https://github.com/MickLesk) ([#12264](https://github.com/community-scripts/ProxmoxVE/pull/12264))
|
||||
- Firefly: PHP bump [@tremor021](https://github.com/tremor021) ([#12247](https://github.com/community-scripts/ProxmoxVE/pull/12247))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- make searxng updateable [@shtefko](https://github.com/shtefko) ([#12207](https://github.com/community-scripts/ProxmoxVE/pull/12207))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- bump various scripts from Node 22 to 24 [@MickLesk](https://github.com/MickLesk) ([#12265](https://github.com/community-scripts/ProxmoxVE/pull/12265))
|
||||
|
||||
### 💾 Core
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- tools.func: add get_latest_gh_tag helper function [@MickLesk](https://github.com/MickLesk) ([#12261](https://github.com/community-scripts/ProxmoxVE/pull/12261))
|
||||
|
||||
### 🧰 Tools
|
||||
|
||||
- Arcane ([#12263](https://github.com/community-scripts/ProxmoxVE/pull/12263))
|
||||
|
||||
### 📂 Github
|
||||
|
||||
- add: workflow to close stale PRs [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12243](https://github.com/community-scripts/ProxmoxVE/pull/12243))
|
||||
- github: add weekly Node.js version drift check workflow [@MickLesk](https://github.com/MickLesk) ([#12267](https://github.com/community-scripts/ProxmoxVE/pull/12267))
|
||||
- add: workflow to close stale PRs [@CrazyWolf13](https://github.com/CrazyWolf13) ([#12243](https://github.com/community-scripts/ProxmoxVE/pull/12243))
|
||||
|
||||
## 2026-02-23
|
||||
|
||||
|
||||
14
ct/n8n.sh
14
ct/n8n.sh
@@ -27,7 +27,11 @@ function update_script() {
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
ensure_dependencies graphicsmagick
|
||||
|
||||
ensure_dependencies graphicsmagick
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
msg_info "Updating n8n"
|
||||
if [ ! -f /opt/n8n.env ]; then
|
||||
sed -i 's|^Environment="N8N_SECURE_COOKIE=false"$|EnvironmentFile=/opt/n8n.env|' /etc/systemd/system/n8n.service
|
||||
mkdir -p /opt
|
||||
@@ -37,14 +41,12 @@ N8N_PORT=5678
|
||||
N8N_PROTOCOL=http
|
||||
N8N_HOST=$LOCAL_IP
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
|
||||
msg_info "Updating ${APP} LXC"
|
||||
rm -rf /usr/lib/node_modules/.n8n-* /usr/lib/node_modules/n8n
|
||||
$STD npm install -g n8n --force
|
||||
$STD npm update -g n8n
|
||||
systemctl restart n8n
|
||||
msg_ok "Updated n8n"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
40
frontend/public/json/arcane.json
Normal file
40
frontend/public/json/arcane.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "Arcane",
|
||||
"slug": "arcane",
|
||||
"categories": [
|
||||
3
|
||||
],
|
||||
"date_created": "2026-02-24",
|
||||
"type": "addon",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 3552,
|
||||
"documentation": "https://getarcane.app/docs",
|
||||
"website": "https://getarcane.app/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/arcane.webp",
|
||||
"config_path": "/opt/arcane/.env",
|
||||
"description": "Arcane is designed to be an easy and modern Docker management platform, built with everybody in mind. The goal of Arcane is to be built for and by the community to make sure nobody feels left out or behind with their specific features or processes. ",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "tools/addon/arcane.sh",
|
||||
"resources": {
|
||||
"cpu": null,
|
||||
"ram": null,
|
||||
"hdd": null,
|
||||
"os": null,
|
||||
"version": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": "arcane",
|
||||
"password": "arcane-admin"
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "This is an addon script intended to be used on top of an existing Docker container.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated": "2026-02-24T06:23:39Z",
|
||||
"generated": "2026-02-24T12:15:44Z",
|
||||
"versions": [
|
||||
{
|
||||
"slug": "2fauth",
|
||||
@@ -606,9 +606,9 @@
|
||||
{
|
||||
"slug": "invoiceninja",
|
||||
"repo": "invoiceninja/invoiceninja",
|
||||
"version": "v5.12.65",
|
||||
"version": "v5.12.66",
|
||||
"pinned": false,
|
||||
"date": "2026-02-21T01:03:52Z"
|
||||
"date": "2026-02-24T09:12:50Z"
|
||||
},
|
||||
{
|
||||
"slug": "jackett",
|
||||
@@ -1679,7 +1679,7 @@
|
||||
"repo": "meilisearch/meilisearch",
|
||||
"version": "v1.36.0",
|
||||
"pinned": false,
|
||||
"date": "2026-02-23T08:13:32Z"
|
||||
"date": ""
|
||||
},
|
||||
{
|
||||
"slug": "warracker",
|
||||
|
||||
@@ -15,22 +15,19 @@ update_os
|
||||
|
||||
msg_info "Installing Dependencies"
|
||||
$STD apt install -y \
|
||||
ca-certificates \
|
||||
build-essential \
|
||||
python3 \
|
||||
python3-setuptools \
|
||||
graphicsmagick
|
||||
msg_ok "Installed Dependencies"
|
||||
|
||||
NODE_VERSION="22" setup_nodejs
|
||||
NODE_VERSION="24" setup_nodejs
|
||||
|
||||
msg_info "Installing n8n (Patience)"
|
||||
$STD npm install --global patch-package
|
||||
$STD npm install --global n8n
|
||||
$STD npm install -g n8n
|
||||
msg_ok "Installed n8n"
|
||||
|
||||
msg_info "Creating Service"
|
||||
mkdir -p /opt
|
||||
cat <<EOF >/opt/n8n.env
|
||||
N8N_SECURE_COOKIE=false
|
||||
N8N_PORT=5678
|
||||
|
||||
@@ -1525,6 +1525,82 @@ verify_gpg_fingerprint() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Get latest GitHub tag for a repository.
|
||||
#
|
||||
# Description:
|
||||
# - Queries the GitHub API for tags (not releases)
|
||||
# - Useful for repos that only create tags, not full releases
|
||||
# - Supports optional prefix filter and version-only extraction
|
||||
# - Returns the latest tag name (printed to stdout)
|
||||
#
|
||||
# Usage:
|
||||
# MONGO_VERSION=$(get_latest_gh_tag "mongodb/mongo-tools")
|
||||
# LATEST=$(get_latest_gh_tag "owner/repo" "v") # only tags starting with "v"
|
||||
# LATEST=$(get_latest_gh_tag "owner/repo" "" "true") # strip leading "v"
|
||||
#
|
||||
# Arguments:
|
||||
# $1 - GitHub repo (owner/repo)
|
||||
# $2 - Tag prefix filter (optional, e.g. "v" or "100.")
|
||||
# $3 - Strip prefix from result (optional, "true" to strip $2 prefix)
|
||||
#
|
||||
# Returns:
|
||||
# 0 on success (tag printed to stdout), 1 on failure
|
||||
#
|
||||
# Notes:
|
||||
# - Skips tags containing "rc", "alpha", "beta", "dev", "test"
|
||||
# - Sorts by version number (sort -V) to find the latest
|
||||
# - Respects GITHUB_TOKEN for rate limiting
|
||||
# ------------------------------------------------------------------------------
|
||||
get_latest_gh_tag() {
|
||||
local repo="$1"
|
||||
local prefix="${2:-}"
|
||||
local strip_prefix="${3:-false}"
|
||||
|
||||
local header_args=()
|
||||
[[ -n "${GITHUB_TOKEN:-}" ]] && header_args=(-H "Authorization: Bearer $GITHUB_TOKEN")
|
||||
|
||||
local http_code=""
|
||||
http_code=$(curl -sSL --max-time 20 -w "%{http_code}" -o /tmp/gh_tags.json \
|
||||
-H 'Accept: application/vnd.github+json' \
|
||||
-H 'X-GitHub-Api-Version: 2022-11-28' \
|
||||
"${header_args[@]}" \
|
||||
"https://api.github.com/repos/${repo}/tags?per_page=100" 2>/dev/null) || true
|
||||
|
||||
if [[ "$http_code" == "403" ]]; then
|
||||
msg_warn "GitHub API rate limit exceeded while fetching tags for ${repo}"
|
||||
rm -f /tmp/gh_tags.json
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$http_code" != "200" ]] || [[ ! -s /tmp/gh_tags.json ]]; then
|
||||
rm -f /tmp/gh_tags.json
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tags_json
|
||||
tags_json=$(</tmp/gh_tags.json)
|
||||
rm -f /tmp/gh_tags.json
|
||||
|
||||
# Extract tag names, filter by prefix, exclude pre-release patterns, sort by version
|
||||
local latest=""
|
||||
latest=$(echo "$tags_json" | grep -oP '"name":\s*"\K[^"]+' |
|
||||
{ [[ -n "$prefix" ]] && grep "^${prefix}" || cat; } |
|
||||
grep -viE '(rc|alpha|beta|dev|test|preview|snapshot)' |
|
||||
sort -V | tail -n1)
|
||||
|
||||
if [[ -z "$latest" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$strip_prefix" == "true" && -n "$prefix" ]]; then
|
||||
latest="${latest#"$prefix"}"
|
||||
fi
|
||||
|
||||
echo "$latest"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL FUNCTIONS
|
||||
# ==============================================================================
|
||||
|
||||
219
tools/addon/arcane.sh
Normal file
219
tools/addon/arcane.sh
Normal file
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2021-2026 community-scripts ORG
|
||||
# Author: summoningpixels
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/getarcaneapp/arcane
|
||||
if ! command -v curl &>/dev/null; then
|
||||
printf "\r\e[2K%b" '\033[93m Setup Source \033[m' >&2
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y curl >/dev/null 2>&1
|
||||
fi
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func)
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) 2>/dev/null || true
|
||||
|
||||
# Enable error handling
|
||||
set -Eeuo pipefail
|
||||
trap 'error_handler' ERR
|
||||
|
||||
# ==============================================================================
|
||||
# CONFIGURATION
|
||||
# ==============================================================================
|
||||
APP="Arcane"
|
||||
APP_TYPE="addon"
|
||||
INSTALL_PATH="/opt/arcane"
|
||||
COMPOSE_FILE="${INSTALL_PATH}/compose.yaml"
|
||||
ENV_FILE="${INSTALL_PATH}/.env"
|
||||
DEFAULT_PORT=3552
|
||||
|
||||
# Initialize all core functions (colors, formatting, icons, STD mode)
|
||||
load_functions
|
||||
|
||||
# ==============================================================================
|
||||
# HEADER
|
||||
# ==============================================================================
|
||||
function header_info {
|
||||
clear
|
||||
cat <<"EOF"
|
||||
___ ____ _________ _ ________
|
||||
/ | / __ \/ ____/ | / | / / ____/
|
||||
/ /| | / /_/ / / / /| | / |/ / __/
|
||||
/ ___ |/ _, _/ /___/ ___ |/ /| / /___
|
||||
/_/ |_/_/ |_|\____/_/ |_/_/ |_/_____/
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UNINSTALL
|
||||
# ==============================================================================
|
||||
function uninstall() {
|
||||
msg_info "Uninstalling ${APP}"
|
||||
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_info "Stopping and removing Docker containers"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose down --volumes --remove-orphans
|
||||
msg_ok "Stopped and removed Docker containers"
|
||||
fi
|
||||
|
||||
rm -rf "$INSTALL_PATH"
|
||||
rm -f "/usr/local/bin/update_arcane"
|
||||
msg_ok "${APP} has been uninstalled"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# UPDATE
|
||||
# ==============================================================================
|
||||
function update() {
|
||||
msg_info "Pulling latest ${APP} image"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose pull
|
||||
msg_ok "Pulled latest image"
|
||||
|
||||
msg_info "Restarting ${APP}"
|
||||
$STD docker compose up -d --remove-orphans
|
||||
msg_ok "Restarted ${APP}"
|
||||
|
||||
msg_ok "Updated successfully"
|
||||
exit
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# CHECK DOCKER
|
||||
# ==============================================================================
|
||||
function check_docker() {
|
||||
if ! command -v docker &>/dev/null; then
|
||||
msg_error "Docker is not installed. This script requires an existing Docker LXC. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
if ! docker compose version &>/dev/null; then
|
||||
msg_error "Docker Compose plugin is not available. Please install it before running this script. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
msg_ok "Docker $(docker --version | cut -d' ' -f3 | tr -d ',') and Docker Compose are available"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# INSTALL
|
||||
# ==============================================================================
|
||||
function install() {
|
||||
check_docker
|
||||
|
||||
msg_info "Creating install directory"
|
||||
mkdir -p "$INSTALL_PATH"
|
||||
msg_ok "Created ${INSTALL_PATH}"
|
||||
|
||||
# Generate secrets and config values
|
||||
local ENCRYPTION_KEY JWT_SECRET PROJ_DIR
|
||||
ENCRYPTION_KEY=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||
JWT_SECRET=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c32)
|
||||
PROJ_DIR="/etc/arcane/projects"
|
||||
|
||||
msg_info "Creating stacks directory"
|
||||
mkdir -p "$PROJ_DIR"
|
||||
msg_ok "Created ${PROJ_DIR}"
|
||||
|
||||
msg_info "Downloading Docker Compose file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/docker/examples/compose.basic.yaml" -o "$COMPOSE_FILE"
|
||||
msg_ok "Downloaded Docker Compose file"
|
||||
|
||||
msg_info "Downloading .env file"
|
||||
curl -fsSL "https://raw.githubusercontent.com/getarcaneapp/arcane/refs/heads/main/.env.example" -o "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
msg_ok "Downloaded .env file"
|
||||
|
||||
msg_info "Configuring compose and env files"
|
||||
sed -i '/^[[:space:]]*#/!s|/host/path/to/projects|'"$PROJ_DIR"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|ENCRYPTION_KEY='"$ENCRYPTION_KEY"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|JWT_SECRET='"$JWT_SECRET"'|g' "$COMPOSE_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|APP_URL=.*|APP_URL=http://localhost:'"$DEFAULT_PORT"'|g' "$ENV_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|ENCRYPTION_KEY=.*|#&|g' "$ENV_FILE"
|
||||
sed -i '/^[[:space:]]*#/!s|JWT_SECRET=.*|#&|g' "$ENV_FILE"
|
||||
msg_ok "Configured compose and env files"
|
||||
|
||||
msg_info "Starting ${APP}"
|
||||
cd "$INSTALL_PATH"
|
||||
$STD docker compose up -d
|
||||
msg_ok "Started ${APP}"
|
||||
|
||||
# Create update script
|
||||
msg_info "Creating update script"
|
||||
cat <<'UPDATEEOF' >/usr/local/bin/update_arcane
|
||||
#!/usr/bin/env bash
|
||||
# Arcane Update Script
|
||||
type=update bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/tools/addon/arcane.sh)"
|
||||
UPDATEEOF
|
||||
chmod +x /usr/local/bin/update_arcane
|
||||
msg_ok "Created update script (/usr/local/bin/update_arcane)"
|
||||
|
||||
echo ""
|
||||
msg_ok "${APP} is reachable at: ${BL}http://${LOCAL_IP}:${DEFAULT_PORT}${CL}"
|
||||
echo ""
|
||||
echo -e "Arcane Credentials"
|
||||
echo -e "=================="
|
||||
echo -e "User: arcane"
|
||||
echo -e "Password: arcane-admin"
|
||||
echo ""
|
||||
msg_warn "On first access, you'll be prompted to change your password."
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# MAIN
|
||||
# ==============================================================================
|
||||
|
||||
# Handle type=update (called from update script)
|
||||
if [[ "${type:-}" == "update" ]]; then
|
||||
header_info
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
update
|
||||
else
|
||||
msg_error "${APP} is not installed. Nothing to update."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
header_info
|
||||
get_lxc_ip
|
||||
|
||||
# Check if already installed
|
||||
if [[ -f "$COMPOSE_FILE" ]]; then
|
||||
msg_warn "${APP} is already installed."
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Uninstall ${APP}? (y/N): "
|
||||
read -r uninstall_prompt
|
||||
if [[ "${uninstall_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
uninstall
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -n "${TAB}Update ${APP}? (y/N): "
|
||||
read -r update_prompt
|
||||
if [[ "${update_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
update
|
||||
exit 0
|
||||
fi
|
||||
|
||||
msg_warn "No action selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fresh installation
|
||||
msg_warn "${APP} is not installed."
|
||||
echo ""
|
||||
echo -e "${TAB}${INFO} This will install:"
|
||||
echo -e "${TAB} - Arcane (via Docker Compose)"
|
||||
echo ""
|
||||
|
||||
echo -n "${TAB}Install ${APP}? (y/N): "
|
||||
read -r install_prompt
|
||||
if [[ "${install_prompt,,}" =~ ^(y|yes)$ ]]; then
|
||||
install
|
||||
else
|
||||
msg_warn "Installation cancelled. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
6
tools/headers/arcane
Normal file
6
tools/headers/arcane
Normal file
@@ -0,0 +1,6 @@
|
||||
___
|
||||
/ | ______________ _____ ___
|
||||
/ /| | / ___/ ___/ __ `/ __ \/ _ \
|
||||
/ ___ |/ / / /__/ /_/ / / / / __/
|
||||
/_/ |_/_/ \___/\__,_/_/ /_/\___/
|
||||
|
||||
Reference in New Issue
Block a user