From 6c611fb76b2f4f8e7b07a5e50a945d4a21a91322 Mon Sep 17 00:00:00 2001 From: MickLesk Date: Wed, 1 Apr 2026 21:23:58 +0200 Subject: [PATCH] feat(core): harden runtime sourcing and simplify LXC update flow --- docs/README.md | 61 ++++--- docs/guides/CORE_RUNTIME_SOURCING_GUIDE.md | 202 +++++++++++++++++++++ docs/guides/README.md | 14 ++ misc/alpine-install.func | 109 ++++++++++- misc/build.func | 130 +++++++++++-- misc/install.func | 109 ++++++++++- 6 files changed, 572 insertions(+), 53 deletions(-) create mode 100644 docs/guides/CORE_RUNTIME_SOURCING_GUIDE.md diff --git a/docs/README.md b/docs/README.md index 44f7b9464..a3f96e550 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,6 +27,9 @@ Complete guide to all ProxmoxVE documentation - quickly find what you need. **Deploy containers automatically** → Read: [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md) +**Pin runtime source loading (branch/tag/commit)** +→ Read: [guides/CORE_RUNTIME_SOURCING_GUIDE.md](guides/CORE_RUNTIME_SOURCING_GUIDE.md) + **Develop a function library** → Study: [misc/](misc/) documentation @@ -93,71 +96,84 @@ ProxmoxVE/ ### Core Documentation -| Document | Purpose | Audience | -|----------|---------|----------| -| [contribution/README.md](contribution/README.md) | How to contribute | Contributors | -| [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) | Create ct scripts | Container developers | -| [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) | Create install scripts | Installation developers | -| [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md) | Architecture deep-dive | Architects, advanced users | -| [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md) | Configuration system | Operators, power users | -| [guides/CONFIGURATION_REFERENCE.md](guides/CONFIGURATION_REFERENCE.md) | Configuration options reference | Advanced users | -| [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md) | Automated deployments | DevOps, automation | -| [EXIT_CODES.md](EXIT_CODES.md) | Exit code reference | Troubleshooters | -| [DEV_MODE.md](DEV_MODE.md) | Debugging tools | Developers | +| Document | Purpose | Audience | +| ------------------------------------------------------------------------------ | ------------------------------- | -------------------------- | +| [contribution/README.md](contribution/README.md) | How to contribute | Contributors | +| [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) | Create ct scripts | Container developers | +| [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) | Create install scripts | Installation developers | +| [TECHNICAL_REFERENCE.md](TECHNICAL_REFERENCE.md) | Architecture deep-dive | Architects, advanced users | +| [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md) | Configuration system | Operators, power users | +| [guides/CONFIGURATION_REFERENCE.md](guides/CONFIGURATION_REFERENCE.md) | Configuration options reference | Advanced users | +| [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md) | Automated deployments | DevOps, automation | +| [guides/CORE_RUNTIME_SOURCING_GUIDE.md](guides/CORE_RUNTIME_SOURCING_GUIDE.md) | Runtime local-first + pinning | Operators, maintainers | +| [EXIT_CODES.md](EXIT_CODES.md) | Exit code reference | Troubleshooters | +| [DEV_MODE.md](DEV_MODE.md) | Debugging tools | Developers | --- ## 📂 **Directory Guide** ### [ct/](ct/) - Container Scripts + Documentation for `/ct` - Container creation scripts that run on the Proxmox host. **Includes**: + - Overview of container creation process - Deep dive: [DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) - Complete reference with examples - Reference to [misc/build.func/](misc/build.func/) - Quick start for creating new containers ### [install/](install/) - Installation Scripts + Documentation for `/install` - Scripts that run inside containers to install applications. **Includes**: + - Overview of 10-phase installation pattern - Deep dive: [DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) - Complete reference with examples - Reference to [misc/tools.func/](misc/tools.func/) - Alpine vs Debian differences ### [vm/](vm/) - Virtual Machine Scripts + Documentation for `/vm` - VM creation scripts using cloud-init provisioning. **Includes**: + - Overview of VM provisioning - Link to [misc/cloud-init.func/](misc/cloud-init.func/) - VM vs Container comparison - Cloud-init examples ### [tools/](tools/) - Tools & Utilities + Documentation for `/tools` - Management tools and add-ons. **Includes**: + - Overview of tools structure - Integration points - Contributing new tools - Common operations ### [api/](api/) - API Integration + Documentation for `/api` - Telemetry and API backend. **Includes**: + - API overview - Integration methods - API endpoints - Privacy information ### [misc/](misc/) - Function Libraries + Documentation for `/misc` - 9 core function libraries with complete references. **Contains**: + - **build.func/** - Container orchestration (7 files) - **core.func/** - Utilities and messaging (5 files) - **error_handler.func/** - Error handling (5 files) @@ -212,22 +228,23 @@ Documentation for `/misc` - 9 core function libraries with complete references. ## 📊 **By the Numbers** -| Metric | Count | -|--------|:---:| -| **Documentation Files** | 63 | -| **Total Lines** | 15,000+ | -| **Function Libraries** | 9 | -| **Functions Documented** | 150+ | -| **Code Examples** | 50+ | -| **Flowcharts** | 15+ | -| **Do/Don't Sections** | 20+ | -| **Real-World Examples** | 30+ | +| Metric | Count | +| ------------------------ | :-----: | +| **Documentation Files** | 63 | +| **Total Lines** | 15,000+ | +| **Function Libraries** | 9 | +| **Functions Documented** | 150+ | +| **Code Examples** | 50+ | +| **Flowcharts** | 15+ | +| **Do/Don't Sections** | 20+ | +| **Real-World Examples** | 30+ | --- ## 🔍 **Find It Fast** ### By Feature + - **How do I create a container?** → [ct/DETAILED_GUIDE.md](ct/DETAILED_GUIDE.md) - **How do I create an install script?** → [install/DETAILED_GUIDE.md](install/DETAILED_GUIDE.md) - **How do I create a VM?** → [vm/README.md](vm/README.md) @@ -235,11 +252,13 @@ Documentation for `/misc` - 9 core function libraries with complete references. - **How do I debug?** → [DEV_MODE.md](DEV_MODE.md) ### By Error + - **Exit code 206?** → [EXIT_CODES.md](EXIT_CODES.md) - **Network failed?** → [misc/install.func/](misc/install.func/) - **Package error?** → [misc/tools.func/](misc/tools.func/) ### By Role + - **Contributor** → [contribution/README.md](contribution/README.md) - **Operator** → [guides/DEFAULTS_SYSTEM_GUIDE.md](guides/DEFAULTS_SYSTEM_GUIDE.md) - **Automation** → [guides/UNATTENDED_DEPLOYMENTS.md](guides/UNATTENDED_DEPLOYMENTS.md) diff --git a/docs/guides/CORE_RUNTIME_SOURCING_GUIDE.md b/docs/guides/CORE_RUNTIME_SOURCING_GUIDE.md new file mode 100644 index 000000000..91f79fabd --- /dev/null +++ b/docs/guides/CORE_RUNTIME_SOURCING_GUIDE.md @@ -0,0 +1,202 @@ +# Core Runtime Sourcing Guide + +This guide explains how runtime module loading works after the core hardening changes, and how to operate it safely in production. + +## Why this exists + +The runtime now uses a **local-first** loading strategy for core modules (`core.func`, `error_handler.func`, `tools.func`, `install.func`, `alpine-install.func`). + +That means: + +1. Try local files first (preferred, deterministic) +2. Fall back to remote source only if local files are not available +3. Allow pinning to a specific branch/tag/commit via environment variables + +This reduces failures from transient network/CDN issues and improves deployment reproducibility. + +--- + +## Default behavior (no config needed) + +If you do nothing, scripts will: + +- Use local `misc/*.func` files when available +- Otherwise use GitHub raw URLs under `community-scripts/ProxmoxVE/main` + +This is backward compatible with existing usage. + +--- + +## Host vs LXC: where data is needed + +Short answer: **for normal online operation, no full duplication is required**. + +### If you only care about `update` inside the LXC + +That is now the simplest path: + +- Installer writes `/usr/local/community-scripts/runtime-source.env` inside the container +- `/usr/bin/update` reads that file first +- `update` therefore keeps using the container's pinned source settings by default + +In other words, you can manage update source behavior entirely inside the LXC without requiring host-side duplication. + +### Runtime split + +- **Host side** + - `misc/build.func` orchestrates creation and bootstrapping. + - It provides bootstrap function payload for install scripts. + +- **LXC side** + - install scripts run inside the container. + - They try local core modules first; if not present, they use remote fallback. + +### Practical implications + +1. **Online default mode** + - Host local files + remote fallback inside LXC are enough. + - No manual copy of all `misc/*.func` into the container is strictly required. + +2. **Strict reproducible/offline mode** + - You should provide the same module set on both sides: + - host checkout (for orchestration) + - local module files in LXC (for local-first resolution) + - Otherwise LXC may use remote fallback and pick newer content than host-local branch state. + +3. **Pinned mode (`COMMUNITY_SCRIPTS_REF`)** + - Greatly reduces mismatch risk because all fallback URLs resolve to the same ref/tag/commit. + +--- + +## Configuration knobs + +You can control runtime source resolution with these environment variables. + +### 1) `COMMUNITY_SCRIPTS_REF` + +- Purpose: Select branch/tag/commit reference used for remote fallback +- Default: `main` + +Example values: + +- `main` +- `v2026.04` +- `` + +### 2) `COMMUNITY_SCRIPTS_REMOTE_BASE` + +- Purpose: Override remote base for `misc/*.func` +- Default: `https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${COMMUNITY_SCRIPTS_REF}/misc` + +### 3) `COMMUNITY_SCRIPTS_INSTALL_BASE` + +- Purpose: Override remote base for `install/*.sh` +- Default: `https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${COMMUNITY_SCRIPTS_REF}/install` + +### 4) `COMMUNITY_SCRIPTS_CT_BASE` + +- Purpose: Override remote base for `ct/*.sh` update launcher (`/usr/bin/update` inside CT) +- Default: `https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${COMMUNITY_SCRIPTS_REF}/ct` + +--- + +## Recommended operating modes + +### Mode A: Standard users (recommended default) + +- Do not set any variables +- Local-first will automatically improve resilience + +### Mode B: Release pinning (stable operations) + +- Set `COMMUNITY_SCRIPTS_REF` to a known release tag +- Keeps behavior reproducible across nodes and rebuilds + +### Mode C: Controlled mirror / internal hosting + +- Set `COMMUNITY_SCRIPTS_REMOTE_BASE`, `COMMUNITY_SCRIPTS_INSTALL_BASE`, `COMMUNITY_SCRIPTS_CT_BASE` +- Useful for air-gapped or enterprise mirror setups + +--- + +## What changed in runtime flow + +### `misc/build.func` + +- Core/API/tools/install payload loading now uses local-first helper resolution +- Remote fallback is configurable via `COMMUNITY_SCRIPTS_*` variables +- Upstream drift check warns if local code differs from latest `origin/main` (when using `COMMUNITY_SCRIPTS_REF=main`) + +### `misc/install.func` and `misc/alpine-install.func` + +- `core.func` and `error_handler.func` are loaded local-first +- `tools.func` is loaded local-first with remote fallback and retries +- `/usr/bin/update` now uses configurable `COMMUNITY_SCRIPTS_CT_BASE` + +--- + +## Troubleshooting + +### How upstream changes are detected + +When running from `main` (default), runtime checks for upstream drift: + +1. **Git mode (preferred)** + - If the script runs from a git worktree, it compares: + - local `HEAD` + - `origin/main` (`git ls-remote`) + - If different, a warning is shown. + +2. **API fallback (non-git environments)** + - Reads latest `main` commit SHA from GitHub API + - Compares it with a locally cached SHA (`/var/cache/community-scripts/upstream-main.sha`) + - Warns when it changed since the previous run + +> Note: drift check is advisory (warning only), not blocking. + +### How to avoid stale variants + +- **Best practice for production:** + - Pin a known release/tag/commit via `COMMUNITY_SCRIPTS_REF` +- **If following `main`:** + - Update/sync local checkout regularly (fetch/rebase or merge) + - Watch for drift warnings during installation flow + +### Symptom: "Failed to load core.func" / "Failed to download tools.func" + +Check: + +1. Local files exist in one of the expected locations: + - script directory (`$(dirname "${BASH_SOURCE[0]}")`) + - `/opt/community-scripts/misc` + - `/usr/local/share/community-scripts/misc` + - `/usr/local/community-scripts/misc` +2. Remote base URLs are reachable +3. `COMMUNITY_SCRIPTS_REF` points to a valid branch/tag/commit + +### Symptom: CT `update` script points to unexpected source + +Check: + +- `COMMUNITY_SCRIPTS_CT_BASE` +- `COMMUNITY_SCRIPTS_REF` + +--- + +## Security and reproducibility notes + +- For production-grade reproducibility, prefer **pinning** (`COMMUNITY_SCRIPTS_REF` as tag/commit) +- For highest control, use internal mirrors with explicit base URLs +- Local-first loading reduces runtime dependence on external services + +--- + +## Summary + +You now have a safer runtime model: + +- **Resilient**: local-first +- **Flexible**: configurable remote bases +- **Reproducible**: ref pinning + +Use defaults for simplicity, pin refs for stability, and override bases for enterprise/mirrored deployments. diff --git a/docs/guides/README.md b/docs/guides/README.md index 0623e2626..d00b79762 100644 --- a/docs/guides/README.md +++ b/docs/guides/README.md @@ -39,6 +39,18 @@ Automating container deployments without user interaction. - Scripted installations - Pre-configured templates +### [Core Runtime Sourcing Guide](CORE_RUNTIME_SOURCING_GUIDE.md) + +How local-first loading, remote fallback, and ref/base pinning work for core runtime modules. + +**Topics covered:** + +- Local-first module resolution (`misc/*.func`) +- Branch/tag/commit pinning with `COMMUNITY_SCRIPTS_REF` +- Custom remote base URLs (`COMMUNITY_SCRIPTS_REMOTE_BASE`, `COMMUNITY_SCRIPTS_INSTALL_BASE`, `COMMUNITY_SCRIPTS_CT_BASE`) +- CT update behavior (`/usr/bin/update` source resolution) +- Production hardening and troubleshooting + ## 🔗 Related Documentation - **[CT Scripts Guide](../ct/)** - Container script structure and usage @@ -53,6 +65,8 @@ For most users, start with the **Unattended Deployments** guide to learn how to For advanced configuration options, refer to the **Configuration Reference**. +For runtime source hardening and pinning, read the **Core Runtime Sourcing Guide**. + ## 🤝 Contributing If you'd like to improve these guides or add new ones, please see our [Contribution Guide](../contribution/). diff --git a/misc/alpine-install.func b/misc/alpine-install.func index c54a7d598..7e76511e8 100644 --- a/misc/alpine-install.func +++ b/misc/alpine-install.func @@ -6,8 +6,54 @@ if ! command -v curl >/dev/null 2>&1; then apk update && apk add 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/error_handler.func) + +REMOTE_CORE_REF="${COMMUNITY_SCRIPTS_REF:-main}" +REMOTE_CORE_BASE="${COMMUNITY_SCRIPTS_REMOTE_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/misc}" +REMOTE_CT_BASE="${COMMUNITY_SCRIPTS_CT_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/ct}" + +fetch_remote_core_file() { + local file="$1" + local retries=3 + local delay=2 + local attempt + for attempt in $(seq 1 "$retries"); do + if curl -fsSL --connect-timeout 10 --max-time 45 "${REMOTE_CORE_BASE}/${file}"; then + return 0 + fi + sleep "$delay" + done + return 1 +} + +source_core_module_prefer_local() { + local file="$1" + local local_candidates=( + "$(dirname "${BASH_SOURCE[0]}")/${file}" + "/opt/community-scripts/misc/${file}" + "/usr/local/share/community-scripts/misc/${file}" + "/usr/local/community-scripts/misc/${file}" + ) + local candidate + for candidate in "${local_candidates[@]}"; do + if [[ -r "$candidate" ]]; then + source "$candidate" + return 0 + fi + done + + local content + content="$(fetch_remote_core_file "$file")" || return 1 + source /dev/stdin <<<"$content" +} + +source_core_module_prefer_local "core.func" || { + echo "Failed to load core.func" >&2 + exit 115 +} +source_core_module_prefer_local "error_handler.func" || { + echo "Failed to load error_handler.func" >&2 + exit 115 +} load_functions catch_errors @@ -163,12 +209,30 @@ EOF exit 1 fi fi - local tools_content - tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { - msg_error "Failed to download tools.func" - exit 115 - } - source /dev/stdin <<<"$tools_content" + local tools_content="" + local local_tools_candidates=( + "$(dirname "${BASH_SOURCE[0]}")/tools.func" + "/opt/community-scripts/misc/tools.func" + "/usr/local/share/community-scripts/misc/tools.func" + "/usr/local/community-scripts/misc/tools.func" + ) + local tools_candidate + for tools_candidate in "${local_tools_candidates[@]}"; do + if [[ -r "$tools_candidate" ]]; then + source "$tools_candidate" + tools_content="local" + break + fi + done + + if [[ -z "$tools_content" ]]; then + tools_content=$(fetch_remote_core_file "tools.func") || { + msg_error "Failed to download tools.func" + exit 115 + } + source /dev/stdin <<<"$tools_content" + fi + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then msg_error "tools.func loaded but incomplete — missing expected functions" exit 115 @@ -234,7 +298,34 @@ EOF msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + mkdir -p /usr/local/community-scripts + cat </usr/local/community-scripts/runtime-source.env +COMMUNITY_SCRIPTS_REF=${REMOTE_CORE_REF} +COMMUNITY_SCRIPTS_CT_BASE=${REMOTE_CT_BASE} +APP_SLUG=${app} +EOF + + cat </usr/bin/update +#!/usr/bin/env bash +set -euo pipefail + +DEFAULT_REF="${REMOTE_CORE_REF}" +DEFAULT_CT_BASE="${REMOTE_CT_BASE}" +DEFAULT_APP="${app}" +RUNTIME_SOURCE_FILE="/usr/local/community-scripts/runtime-source.env" + +if [[ -r "\$RUNTIME_SOURCE_FILE" ]]; then + # shellcheck disable=SC1090 + source "\$RUNTIME_SOURCE_FILE" +fi + +REF="\${COMMUNITY_SCRIPTS_REF:-\$DEFAULT_REF}" +CT_BASE="\${COMMUNITY_SCRIPTS_CT_BASE:-\$DEFAULT_CT_BASE}" +APP_NAME="\${APP_SLUG:-\$DEFAULT_APP}" +URL="\${CT_BASE}/\${APP_NAME}.sh" + +exec bash -c "\$(curl -fsSL \"\$URL\")" +EOF chmod +x /usr/bin/update post_progress_to_api } diff --git a/misc/build.func b/misc/build.func index 3db6c7615..1e2911684 100644 --- a/misc/build.func +++ b/misc/build.func @@ -83,16 +83,70 @@ variables() { fi } -source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) +REMOTE_CORE_REF="${COMMUNITY_SCRIPTS_REF:-main}" +REMOTE_CORE_BASE="${COMMUNITY_SCRIPTS_REMOTE_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/misc}" +REMOTE_INSTALL_BASE="${COMMUNITY_SCRIPTS_INSTALL_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/install}" + +_fetch_core_file_content() { + local file="$1" + local local_candidates=( + "$(dirname "${BASH_SOURCE[0]}")/${file}" + "/opt/community-scripts/misc/${file}" + "/usr/local/share/community-scripts/misc/${file}" + "/usr/local/community-scripts/misc/${file}" + ) + local candidate + for candidate in "${local_candidates[@]}"; do + if [[ -r "$candidate" ]]; then + cat "$candidate" + return 0 + fi + done + + local url="${REMOTE_CORE_BASE}/${file}" + if command -v curl >/dev/null 2>&1; then + curl -fsSL --connect-timeout 10 --max-time 45 "$url" + return $? + elif command -v wget >/dev/null 2>&1; then + wget -qO- "$url" + return $? + fi + + return 1 +} + +_source_core_file() { + local file="$1" + local content + content="$(_fetch_core_file_content "$file")" || return 1 + source /dev/stdin <<<"$content" +} + +_source_core_file "api.func" || { + echo "Failed to load api.func" >&2 + exit 115 +} if command -v curl >/dev/null 2>&1; then - 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/error_handler.func) + _source_core_file "core.func" || { + echo "Failed to load core.func" >&2 + exit 115 + } + _source_core_file "error_handler.func" || { + echo "Failed to load error_handler.func" >&2 + exit 115 + } load_functions catch_errors elif command -v wget >/dev/null 2>&1; then - source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/core.func) - source <(wget -qO- https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/error_handler.func) + _source_core_file "core.func" || { + echo "Failed to load core.func" >&2 + exit 115 + } + _source_core_file "error_handler.func" || { + echo "Failed to load error_handler.func" >&2 + exit 115 + } load_functions catch_errors fi @@ -2953,6 +3007,50 @@ echo_default() { # - Builds interactive menu (Default, Verbose, Advanced, My Defaults, App Defaults, Diagnostics, Storage, Exit) # - Applies chosen settings and triggers container build # ------------------------------------------------------------------------------ +check_upstream_drift() { + # Skip check for pinned refs (tags/commits/branches != main) + if [[ "${COMMUNITY_SCRIPTS_REF:-main}" != "main" ]]; then + return 0 + fi + + local repo_root + repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" + [[ -z "$repo_root" ]] && return 0 + + # Preferred: Compare local HEAD with origin/main (git worktree) + if command -v git >/dev/null 2>&1 && git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + local local_head upstream_head branch + branch="$(git -C "$repo_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")" + local_head="$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || true)" + upstream_head="$(git -C "$repo_root" ls-remote --heads origin main 2>/dev/null | awk '{print $1}' | head -n1)" + + if [[ -n "$local_head" && -n "$upstream_head" && "$local_head" != "$upstream_head" ]]; then + msg_warn "Upstream changed: local ${branch} is behind/diverged from origin/main" + msg_custom "â„šī¸" "${YW}" "Local: ${local_head:0:8} Upstream: ${upstream_head:0:8}" + msg_custom "â„šī¸" "${YW}" "Run a sync/rebase to avoid outdated runtime variants." + fi + return 0 + fi + + # Fallback (non-git): check latest upstream main SHA via API and compare with cache + if command -v curl >/dev/null 2>&1; then + local api_url="https://api.github.com/repos/community-scripts/ProxmoxVE/commits/main" + local remote_sha cache_dir cache_file old_sha + remote_sha="$(curl -fsSL --connect-timeout 5 --max-time 10 "$api_url" 2>/dev/null | grep -oE '"sha"\s*:\s*"[a-f0-9]{40}"' | head -n1 | cut -d'"' -f4)" + if [[ -n "$remote_sha" ]]; then + cache_dir="/var/cache/community-scripts" + cache_file="${cache_dir}/upstream-main.sha" + mkdir -p "$cache_dir" 2>/dev/null || true + old_sha="$(cat "$cache_file" 2>/dev/null || true)" + if [[ -n "$old_sha" && "$old_sha" != "$remote_sha" ]]; then + msg_warn "Upstream main changed since last run (${old_sha:0:8} -> ${remote_sha:0:8})" + msg_custom "â„šī¸" "${YW}" "Consider updating local scripts to avoid stale variants." + fi + echo "$remote_sha" >"$cache_file" 2>/dev/null || true + fi + fi +} + install_script() { pve_check shell_check @@ -2960,6 +3058,7 @@ install_script() { arch_check ssh_check maxkeys_check + check_upstream_drift diagnostics_check if systemctl is-active -q ping-instances.service; then @@ -3451,7 +3550,10 @@ msg_menu() { # - Otherwise: shows update/setting menu and runs update_script with cleanup # ------------------------------------------------------------------------------ start() { - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) + _source_core_file "tools.func" || { + msg_error "Failed to load tools.func" + exit 115 + } if command -v pveversion >/dev/null 2>&1; then install_script || return 0 return 0 @@ -3587,15 +3689,15 @@ build_container() { # Build PCT_OPTIONS as string for export TEMP_DIR=$(mktemp -d) pushd "$TEMP_DIR" >/dev/null - local _func_url + local _func_file if [ "$var_os" == "alpine" ]; then - _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/alpine-install.func" + _func_file="alpine-install.func" else - _func_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func" + _func_file="install.func" fi - export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")" + export FUNCTIONS_FILE_PATH="$(_fetch_core_file_content "$_func_file")" if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then - msg_error "Failed to download install functions from: $_func_url" + msg_error "Failed to load install functions: ${_func_file}" exit 115 fi @@ -4301,7 +4403,7 @@ EOF # that sends "configuring" status AFTER the host already reported "failed" export CONTAINER_INSTALLING=true - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${REMOTE_INSTALL_BASE}/${var_install}.sh)" local lxc_exit=$? unset CONTAINER_INSTALLING @@ -4624,7 +4726,7 @@ EOF if [[ "${DEV_MODE_MOTD:-false}" == "true" ]]; then echo -e "${TAB}${HOLD}${DGN}Setting up MOTD and SSH for debugging...${CL}" if pct exec "$CTID" -- bash -c " - source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/install.func) + source <(curl -fsSL ${REMOTE_CORE_BASE}/install.func) declare -f motd_ssh >/dev/null 2>&1 && motd_ssh || true " >/dev/null 2>&1; then local ct_ip=$(pct exec "$CTID" ip a s dev eth0 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1) @@ -4696,7 +4798,7 @@ EOF # Re-run install script in existing container (don't destroy/recreate) set +Eeuo pipefail trap - ERR - lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/install/${var_install}.sh)" + lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL ${REMOTE_INSTALL_BASE}/${var_install}.sh)" local apt_retry_exit=$? set -Eeuo pipefail trap 'error_handler' ERR diff --git a/misc/install.func b/misc/install.func index 30cf93f2c..e088e243b 100644 --- a/misc/install.func +++ b/misc/install.func @@ -32,8 +32,54 @@ if ! command -v curl >/dev/null 2>&1; then apt update >/dev/null 2>&1 apt 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/error_handler.func) + +REMOTE_CORE_REF="${COMMUNITY_SCRIPTS_REF:-main}" +REMOTE_CORE_BASE="${COMMUNITY_SCRIPTS_REMOTE_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/misc}" +REMOTE_CT_BASE="${COMMUNITY_SCRIPTS_CT_BASE:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/${REMOTE_CORE_REF}/ct}" + +fetch_remote_core_file() { + local file="$1" + local retries=3 + local delay=2 + local attempt + for attempt in $(seq 1 "$retries"); do + if curl -fsSL --connect-timeout 10 --max-time 45 "${REMOTE_CORE_BASE}/${file}"; then + return 0 + fi + sleep "$delay" + done + return 1 +} + +source_core_module_prefer_local() { + local file="$1" + local local_candidates=( + "$(dirname "${BASH_SOURCE[0]}")/${file}" + "/opt/community-scripts/misc/${file}" + "/usr/local/share/community-scripts/misc/${file}" + "/usr/local/community-scripts/misc/${file}" + ) + local candidate + for candidate in "${local_candidates[@]}"; do + if [[ -r "$candidate" ]]; then + source "$candidate" + return 0 + fi + done + + local content + content="$(fetch_remote_core_file "$file")" || return 1 + source /dev/stdin <<<"$content" +} + +source_core_module_prefer_local "core.func" || { + echo "Failed to load core.func" >&2 + exit 115 +} +source_core_module_prefer_local "error_handler.func" || { + echo "Failed to load error_handler.func" >&2 + exit 115 +} load_functions catch_errors @@ -406,12 +452,30 @@ EOF msg_ok "Updated Container OS" post_progress_to_api - local tools_content - tools_content=$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func) || { - msg_error "Failed to download tools.func" - exit 115 - } - source /dev/stdin <<<"$tools_content" + local tools_content="" + local local_tools_candidates=( + "$(dirname "${BASH_SOURCE[0]}")/tools.func" + "/opt/community-scripts/misc/tools.func" + "/usr/local/share/community-scripts/misc/tools.func" + "/usr/local/community-scripts/misc/tools.func" + ) + local tools_candidate + for tools_candidate in "${local_tools_candidates[@]}"; do + if [[ -r "$tools_candidate" ]]; then + source "$tools_candidate" + tools_content="local" + break + fi + done + + if [[ -z "$tools_content" ]]; then + tools_content=$(fetch_remote_core_file "tools.func") || { + msg_error "Failed to download tools.func" + exit 115 + } + source /dev/stdin <<<"$tools_content" + fi + if ! declare -f fetch_and_deploy_gh_release >/dev/null 2>&1; then msg_error "tools.func loaded but incomplete — missing expected functions" exit 115 @@ -486,7 +550,34 @@ EOF systemctl restart "$(basename "$(dirname "$GETTY_OVERRIDE")" | sed 's/\.d//')" msg_ok "Customized Container" fi - echo "bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/${app}.sh)\"" >/usr/bin/update + mkdir -p /usr/local/community-scripts + cat </usr/local/community-scripts/runtime-source.env +COMMUNITY_SCRIPTS_REF=${REMOTE_CORE_REF} +COMMUNITY_SCRIPTS_CT_BASE=${REMOTE_CT_BASE} +APP_SLUG=${app} +EOF + + cat </usr/bin/update +#!/usr/bin/env bash +set -euo pipefail + +DEFAULT_REF="${REMOTE_CORE_REF}" +DEFAULT_CT_BASE="${REMOTE_CT_BASE}" +DEFAULT_APP="${app}" +RUNTIME_SOURCE_FILE="/usr/local/community-scripts/runtime-source.env" + +if [[ -r "\$RUNTIME_SOURCE_FILE" ]]; then + # shellcheck disable=SC1090 + source "\$RUNTIME_SOURCE_FILE" +fi + +REF="\${COMMUNITY_SCRIPTS_REF:-\$DEFAULT_REF}" +CT_BASE="\${COMMUNITY_SCRIPTS_CT_BASE:-\$DEFAULT_CT_BASE}" +APP_NAME="\${APP_SLUG:-\$DEFAULT_APP}" +URL="\${CT_BASE}/\${APP_NAME}.sh" + +exec bash -c "\$(curl -fsSL \"\$URL\")" +EOF chmod +x /usr/bin/update if [[ -n "${SSH_AUTHORIZED_KEY}" ]]; then