#!/bin/bash # webuiRunUpdate — non-interactive LibrePortal update for the WebUI. # # Invoked as a task via `libreportal update apply` (see cli_update_commands.sh), # so it runs under the task processor with stdin closed and # LIBREPORTAL_NONINTERACTIVE=1. It must therefore never block on a prompt: # every decision is resolved up front from config, and it bails out cleanly # (non-zero) instead of asking a question. # # Flow: guard -> forced update check -> if behind, run the proven file update # (gitPerformUpdate) -> redeploy the portal so the new WebUI is live -> # regenerate WebUI data and refresh the out-of-date status. webuiRunUpdate() { isHeader "LibrePortal Update" sourceCheckFiles; local install_mode="${CFG_INSTALL_MODE:-git}" local git_updates="${CFG_GIT_UPDATES:-true}" if [[ "$install_mode" == "local" ]]; then isError "This is a local installation — updates are managed manually, nothing to pull." return 1 fi if [[ "$git_updates" != "true" ]]; then isError "Updates are disabled (CFG_GIT_UPDATES). Enable them to update from the WebUI." return 1 fi # Release mode: version-compare the channel's latest against the local VERSION # and, if newer, fetch + verify the new tarball and redeploy. No config backup # dance — lpFetchRelease replaces only the install tree; configs/logs live in # the separate system tree. if [[ "$install_mode" == "release" ]]; then webuiSystemUpdateCheck "force" local cur lat cur=$(tr -d ' \t\n\r' < "$script_dir/VERSION" 2>/dev/null) lat=$(lpReleaseLatestVersion 2>/dev/null) if [[ -z "$lat" ]]; then isError "Could not reach the release server ($(lpReleaseBaseUrl))." return 1 fi if ! lpVersionGt "$lat" "$cur"; then isSuccessful "LibrePortal is already up to date (v${cur})." return 0 fi # If the release bumps the root-owned footprint (helpers/wrapper/unit), a # manager-run apply can't install it — those are deliberately not # manager-writable. Require a root re-install, which fetches + re-bakes # everything atomically. local inst_fp rel_fp inst_fp=$(lpInstalledFootprintVersion 2>/dev/null) rel_fp=$(lpReleaseLatestFootprint 2>/dev/null) if [[ -n "$rel_fp" && "${rel_fp:-0}" -gt "${inst_fp:-0}" ]]; then isError "Update v${lat} changes system components — it must be applied as root, not from the WebUI." isNotice "Run on the host: curl -fsSL $(lpReleaseBaseUrl)/install.sh | sudo bash" isNotice "(or, from the install tree: sudo ${script_dir}/init.sh --unattended init ... )" return 1 fi isNotice "Update found — v${cur} → v${lat}. Fetching the verified release..." lpFetchRelease "$lat" || { isError "Release fetch failed — install unchanged."; return 1; } isNotice "Redeploying LibrePortal with the new version..." dockerInstallApp "libreportal" WEBUI_UPDATER_FORCE=1 webuiLibrePortalUpdate webuiSystemUpdateCheck "force" isSuccessful "LibrePortal has been updated to v${lat}." return 0 fi # Credential guard — gitReset()/gitCheckGitDetails() would otherwise prompt # for missing git details and hang the task forever. Fail fast with a clear # message instead. if [[ "$install_mode" == "git" ]]; then if [[ -z "$CFG_GIT_USER" || "$CFG_GIT_USER" == "changeme" ]]; then isError "Git credentials are not configured. Run the 'libreportal' command once on the host, then retry." return 1 fi if [[ "$CFG_GIT_USER" != "empty" && ( -z "$CFG_GIT_KEY" || "$CFG_GIT_KEY" == "changeme" ) ]]; then isError "Git access token is not configured. Run the 'libreportal' command once on the host, then retry." return 1 fi fi cd "$script_dir" || { isError "Cannot access the install directory ($script_dir)."; return 1; } runAsManager git config core.fileMode false # Force a fresh fetch + status write so the decision below (and the badge) # reflect reality right now, not a stale throttled snapshot. webuiSystemUpdateCheck "force" local branch behind branch=$(runAsManager git -C "$script_dir" rev-parse --abbrev-ref HEAD 2>/dev/null) [[ -z "$branch" || "$branch" == "HEAD" ]] && branch="main" behind=$(runAsManager git -C "$script_dir" rev-list --count "HEAD..refs/remotes/origin/$branch" 2>/dev/null) [[ -z "$behind" ]] && behind=0 if [[ "$behind" -eq 0 ]]; then isSuccessful "LibrePortal is already up to date." return 0 fi isNotice "Update found — $behind commit(s) behind origin/$branch. Updating now..." # Proven file-level update: back up configs/logs, re-clone, restore. gitPerformUpdate; # Redeploy the portal from the freshly pulled source so the new WebUI goes # live. This is exactly what `libreportal app install libreportal` does, so # it's already safe to run non-interactively. It restarts the container; the # task processor runs on the host, so this task survives the restart. isNotice "Redeploying LibrePortal with the new version..." dockerInstallApp "libreportal" # Regenerate WebUI data (new version, configs, etc.) and clear the # out-of-date flag. WEBUI_UPDATER_FORCE=1 webuiLibrePortalUpdate webuiSystemUpdateCheck "force" isSuccessful "LibrePortal has been updated." } checkUpdates() { local param1="$1" sourceCheckFiles; # Skip Git updates if installation mode is local or Git updates are disabled if [[ $CFG_INSTALL_MODE == "local" ]]; then isNotice "Local installation detected - Git updates are disabled." if [[ $init_run_flag == "true" ]]; then startLoad; fi return fi if [[ $CFG_GIT_UPDATES == "true" ]]; then isHeader "Checking for Updates" # Ask user if they want to check for updates while true; do if [[ $CFG_GIT_UPDATES == "false" ]]; then isQuestion "Would you like to check for updates? (y/n): " read -p "" check_updates_choice elif [[ $CFG_GIT_UPDATES == "true" ]]; then check_updates_choice=y fi case $check_updates_choice in [yY]) # DNS Query Test with Quad9 isNotice "Testing internet DNS, please wait..." if command -v dig >/dev/null 2>&1; then dns_check_cmd="dig +short +time=3 +tries=1 @9.9.9.9 quad9.net" elif command -v getent >/dev/null 2>&1; then dns_check_cmd="getent hosts quad9.net" else dns_check_cmd="ping -c 1 -W 3 9.9.9.9" fi if $dns_check_cmd >/dev/null 2>&1; then isSuccessful "Internet DNS is working." else isError "Internet DNS is not working." exit 1 fi cd "$script_dir" || { isError " Cannot navigate to the repository directory"; exit 1; } # Update Git to ignore changes in file permissions runAsManager git config core.fileMode false # Update Git with email address runAsManager git config --global user.name "$CFG_INSTALL_NAME" runAsManager git config --global user.email "noreply@${CFG_INSTALL_NAME,,}.libreportal.local" # Check if there are edited (modified) files if git status --porcelain | grep -q "^ M"; then isNotice "There are uncommitted changes in the repository." while true; do isQuestion "Do you want to discard these changes and update the repository? (y/n): " read -p "" customupdatesfound case $customupdatesfound in [yY]) remove_changes=true gitCheckForUpdate; gitCheckConfigs; fixPermissionsBeforeStart "" "update"; sourceCheckFiles; if [[ $init_run_flag == "true" ]]; then isSuccessful "Starting/Restarting LibrePortal" startLoad; fi ;; [nN]) isNotice "Custom changes will be kept, continuing..." remove_changes=false gitCheckForUpdate; gitCheckConfigs; fixPermissionsBeforeStart "" "update"; sourceCheckFiles; if [[ $init_run_flag == "true" ]]; then startLoad; fi ;; *) isNotice "Please provide a valid input (y or n)." ;; esac done fi # Make sure an update happens after custom code check if [[ $update_done != "true" ]]; then gitCheckForUpdate; gitCheckConfigs; fixPermissionsBeforeStart "" "update"; sourceCheckFiles; if [[ $init_run_flag == "true" ]]; then isSuccessful "Starting/Restarting LibrePortal" startLoad; fi fi ;; [nN]) echo "" isNotice "Skipping update check. Starting application..." if [[ $init_run_flag == "true" ]]; then startLoad; fi ;; *) isNotice "Please enter y or n." ;; esac done else if [[ $init_run_flag == "true" ]]; then startLoad; fi fi }