diff --git a/configs/general/general_install b/configs/general/general_install index a587914..33795ed 100755 --- a/configs/general/general_install +++ b/configs/general/general_install @@ -1,9 +1,11 @@ # ================================================================================ # Installation Setup - Local or Git Repository configuration and version control # ================================================================================ -CFG_INSTALL_MODE=local # Installation Mode - Method used for installation of LibrePortal +CFG_INSTALL_MODE=release # Installation Mode - How LibrePortal is fetched + updated [release:Release tarball (recommended)|git:Git clone (dev)|local:Local folder (dev)] +CFG_RELEASE_BASE_URL=https://get.libreportal.org # Release Host - Base URL serving the release channels (override for self-hosting) **ADVANCED** +CFG_RELEASE_CHANNEL=stable # Release Channel - Which channel to install/update from [stable:Stable|edge:Edge] CFG_GIT_URL=changeme # Git Repository URL - Git repository URL for LibrePortal configuration CFG_GIT_USER=changeme # Git Username - Git username for repository authentication CFG_GIT_KEY=changeme # Git Access Key - SSH key or API key for Git repository access -CFG_GIT_UPDATES=true # Auto Check Updates - Check for Git repository updates automatically -CFG_GIT_AUTO_UPDATES=true # Auto Apply Updates - Automatically apply Git updates when available +CFG_GIT_UPDATES=true # Auto Check Updates - Check for updates automatically +CFG_GIT_AUTO_UPDATES=true # Auto Apply Updates - Automatically apply updates when available diff --git a/init.sh b/init.sh index 3995ef7..1bfcfc9 100755 --- a/init.sh +++ b/init.sh @@ -1059,7 +1059,28 @@ initGIT() copyFilesFromLocal return fi - + + # Handle release installation — a verified tarball, not a clone. The bootstrap + # (install.sh) usually staged + verified the code already (LP_ALREADY_FETCHED); + # otherwise fetch it via the sourced helper (only possible when run from a + # populated install tree — a bare /root reinstall must go through install.sh). + if [[ "$param7" == "release" ]]; then + if [[ "${LP_ALREADY_FETCHED:-0}" == "1" ]]; then + isNotice "Using the release staged + verified by the bootstrap installer." + elif [[ -f "$script_dir/scripts/source/fetch.sh" ]]; then + source "$script_dir/scripts/docker/command/run_privileged.sh" + source "$script_dir/scripts/source/fetch.sh" + lpFetchRelease || { isError "Release fetch failed."; exit 1; } + else + isError "Release-mode (re)install must be run via install.sh: curl -fsSL /install.sh | sudo bash" + exit 1 + fi + sudo cp -f "$script_dir/init.sh" /root/ 2>/dev/null || true + setupConfigsFromRepo + sudo chown -R "$sudo_user_name":"$sudo_user_name" "$script_dir" + return + fi + GIT_USER="$param3" GIT_TOKEN="$param4" GIT_URL="$param5" diff --git a/install.sh b/install.sh index 6001fa4..774a7ae 100644 --- a/install.sh +++ b/install.sh @@ -171,4 +171,7 @@ esac [[ "${GEN_PW:-0}" == 1 ]] && echo "Generated manager/WebUI password: $PASSWORD (also recorded in the install log)" echo "Running installer ..." cd "$INSTALL_DIR" +# Tell init.sh the code is already staged + verified, so initGIT skips re-fetching. +export LP_ALREADY_FETCHED=1 +export LP_RELEASE_BASE_URL # carry the (possibly overridden) host through exec bash "$INSTALL_DIR/init.sh" "${flags[@]}" "$@" diff --git a/scripts/source/fetch.sh b/scripts/source/fetch.sh new file mode 100644 index 0000000..7f6bd29 --- /dev/null +++ b/scripts/source/fetch.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# +# LibrePortal source-fetch helpers for the RUNTIME paths (update / reinstall / +# reset). They place a fresh copy of the code at $script_dir (the install tree, +# which holds ONLY code — configs/logs live in the separate system tree, so the +# install tree can be replaced wholesale). +# +# Three modes, keyed off CFG_INSTALL_MODE: +# release download a versioned, checksum-verified tarball over HTTPS (default) +# git clone (handled by the existing git_* helpers / initGIT) +# local copy from a local folder (existing copyFilesFromLocal) +# +# init.sh's initGIT does the FRESH-install fetch inline (it is self-contained for +# the bare /root reinstall copy); these functions are the sourced runtime twins. + +# Release host + channel: env wins (testing), then config, then the public default. +lpReleaseBaseUrl() { echo "${LP_RELEASE_BASE_URL:-${CFG_RELEASE_BASE_URL:-https://get.libreportal.org}}"; } +lpReleaseChannel() { echo "${LP_RELEASE_CHANNEL:-${CFG_RELEASE_CHANNEL:-stable}}"; } + +# Downloader + checksum tool detection (curl|wget, sha256sum|shasum). +_lpFetchTool() { command -v curl >/dev/null 2>&1 && { echo curl; return; }; command -v wget >/dev/null 2>&1 && { echo wget; return; }; echo ""; } +_lpDownload() { # _lpDownload + local tool; tool="$(_lpFetchTool)" + case "$tool" in + curl) [[ "$2" == "-" ]] && curl -fsSL "$1" || curl -fsSL "$1" -o "$2" ;; + wget) [[ "$2" == "-" ]] && wget -qO- "$1" || wget -qO "$2" "$1" ;; + *) return 1 ;; + esac +} +_lpSha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; elif command -v shasum >/dev/null 2>&1; then shasum -a 256 "$1" | cut -d' ' -f1; fi; } +_lpJsonStr() { printf '%s' "$1" | grep -oE "\"$2\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed -E 's/.*"([^"]*)"$/\1/'; } + +# Resolve the latest published version of the configured channel (empty on failure). +lpReleaseLatestVersion() { + local m; m="$(_lpDownload "$(lpReleaseBaseUrl)/$(lpReleaseChannel)/latest.json" - 2>/dev/null)" || return 1 + _lpJsonStr "$m" version +} + +# Download + verify + extract a release into $script_dir. Arg: optional version +# (else the channel's latest). Returns non-zero (and leaves $script_dir untouched) +# on any download/checksum failure — callers must not proceed on failure. +lpFetchRelease() { + local want_ver="${1:-}" base channel manifest tarname want_sha tmp tar got + base="$(lpReleaseBaseUrl)"; channel="$(lpReleaseChannel)" + [[ -n "$(_lpFetchTool)" ]] || { isError "lpFetchRelease: need curl or wget"; return 1; } + + if [[ -z "$want_ver" ]]; then + manifest="$(_lpDownload "$base/$channel/latest.json" - 2>/dev/null)" || { isError "lpFetchRelease: cannot fetch channel manifest"; return 1; } + want_ver="$(_lpJsonStr "$manifest" version)" + tarname="$(_lpJsonStr "$manifest" url)" + want_sha="$(_lpJsonStr "$manifest" sha256)" + fi + [[ -n "$want_ver" ]] || { isError "lpFetchRelease: could not determine version"; return 1; } + [[ -n "$tarname" ]] || tarname="libreportal-${want_ver}.tar.gz" + + tmp="$(mktemp -d)"; tar="$tmp/$tarname" + if ! _lpDownload "$base/$channel/$tarname" "$tar"; then isError "lpFetchRelease: download failed ($tarname)"; rm -rf "$tmp"; return 1; fi + if [[ -z "$want_sha" ]]; then + _lpDownload "$base/$channel/$tarname.sha256" "$tar.sha256" 2>/dev/null && want_sha="$(cut -d' ' -f1 < "$tar.sha256")" + fi + [[ -n "$want_sha" ]] || { isError "lpFetchRelease: no checksum available — refusing"; rm -rf "$tmp"; return 1; } + got="$(_lpSha256 "$tar")" + if [[ "$got" != "$want_sha" ]]; then isError "lpFetchRelease: CHECKSUM MISMATCH ($tarname) — refusing"; rm -rf "$tmp"; return 1; fi + + # Replace the install tree (code only; configs/logs are in the system tree). + runInstallOp rm -rf "$script_dir" + runInstallOp mkdir -p "$script_dir" + if ! runInstallOp tar xzf "$tar" -C "$script_dir" --strip-components=1; then + isError "lpFetchRelease: extract failed"; rm -rf "$tmp"; return 1 + fi + rm -rf "$tmp" + isSuccessful "Fetched LibrePortal $want_ver ($channel) and verified its checksum." +} + +# Mode-aware runtime fetch. git/local stay with their existing helpers; this adds +# the release path. Callers (updater/reinstall/reset) decide pre/post (config +# backup, redeploy) around it. +lpFetchSource() { + case "${CFG_INSTALL_MODE:-release}" in + release) lpFetchRelease "$@" ;; + local) declare -f copyFilesFromLocal >/dev/null 2>&1 && copyFilesFromLocal ;; + git) declare -f gitFolderResetAndBackup >/dev/null 2>&1 && gitFolderResetAndBackup ;; + *) isError "lpFetchSource: unknown CFG_INSTALL_MODE '${CFG_INSTALL_MODE}'"; return 1 ;; + esac +} + +# Semver-ish compare: lpVersionGt A B → true if A > B (numeric dotted compare, +# trailing non-numeric ignored). Used by the updater + the badge generator. +lpVersionGt() { + local a="${1%%-*}" b="${2%%-*}" i x y + local -a A B; IFS='.' read -r -a A <<< "$a"; IFS='.' read -r -a B <<< "$b" + for ((i=0; i<${#A[@]} || i<${#B[@]}; i++)); do + x=$((10#${A[i]:-0} + 0)) 2>/dev/null; y=$((10#${B[i]:-0} + 0)) 2>/dev/null + (( x > y )) && return 0 + (( x < y )) && return 1 + done + return 1 +} diff --git a/scripts/source/files/arrays/files_release.sh b/scripts/source/files/arrays/files_release.sh new file mode 100644 index 0000000..fab1575 --- /dev/null +++ b/scripts/source/files/arrays/files_release.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# This file is auto-generated by generate_arrays.sh +# Do not edit manually - run './scripts/source/files/generate_arrays.sh run' to regenerate + +release_scripts=( + "release/make_release.sh" + +) diff --git a/scripts/source/files/arrays/files_source.sh b/scripts/source/files/arrays/files_source.sh index 14277fd..d47f033 100755 --- a/scripts/source/files/arrays/files_source.sh +++ b/scripts/source/files/arrays/files_source.sh @@ -4,6 +4,7 @@ # Do not edit manually - run './scripts/source/files/generate_arrays.sh run' to regenerate source_scripts=( + "source/fetch.sh" "source/files/arrays/files_app.sh" "source/files/arrays/files_backup.sh" "source/files/arrays/files_checks.sh" @@ -21,6 +22,7 @@ source_scripts=( "source/files/arrays/files_migrate.sh" "source/files/arrays/files_network.sh" "source/files/arrays/files_os.sh" + "source/files/arrays/files_release.sh" "source/files/arrays/files_restore.sh" "source/files/arrays/files_setup.sh" "source/files/arrays/files_source.sh"