diff --git a/configs/backup/backup_engine b/configs/backup/backup_engine index d8f0484..ab8457f 100644 --- a/configs/backup/backup_engine +++ b/configs/backup/backup_engine @@ -2,7 +2,7 @@ # Backup Engine - **ADVANCED** Engine-level knobs most users won't need to touch # ================================================================================ CFG_BACKUP_ENGINE=restic # Default Backup Engine - Fallback engine for new locations (each location can override) [restic:Restic|borg:BorgBackup|kopia:Kopia] -CFG_BACKUP_DEFAULT_PATH=/docker/backups # Default Backup Location - Base directory for locations set to Automatic path mode; each location lives in its own numbered subfolder (/) +CFG_BACKUP_DEFAULT_PATH= # Default Backup Location - Base directory for locations set to Automatic path mode; each location lives in its own numbered subfolder (/). Empty = the LibrePortal backups root (own mount-able). CFG_BACKUP_STRATEGY=auto # Backup Strategy - How containers are quiesced before snapshotting [auto:Automatic — live where safe, stop otherwise (recommended)|stop-snapshot-start:Stop → snapshot → start (always safe)|pause-snapshot-unpause:Pause → snapshot → unpause (less downtime)|live:Live — snapshot while running (force)] CFG_BACKUP_VERIFY_AFTER=true # Verify After Backup - Run integrity check after each backup CFG_BACKUP_VERIFY_DATA_PERCENT=5 # Verify Data Sample % - Percentage of repo data to checksum-verify weekly diff --git a/containers/libreportal/backend/routes/service-routes.js b/containers/libreportal/backend/routes/service-routes.js index b51c683..eed0a9a 100644 --- a/containers/libreportal/backend/routes/service-routes.js +++ b/containers/libreportal/backend/routes/service-routes.js @@ -32,7 +32,11 @@ const router = express.Router(); const TASKS_DIR = path.join(__dirname, '..', '..', 'frontend', 'data', 'tasks'); const FIFO_PATH = path.join(TASKS_DIR, '.queue.fifo'); -const CONTAINERS_DIR = '/docker/containers'; +// Host live-app-data root. Provided by the compose env (LP_CONTAINERS_DIR, filled +// from the host's containers root at generation — see scripts/source/paths.sh). +// Falls back to the legacy /docker path so a container that hasn't been recreated +// since the split-layout change keeps working until it is. +const CONTAINERS_DIR = process.env.LP_CONTAINERS_DIR || '/docker/containers'; const APPS_SERVICES_JSON = path.join(__dirname, '..', '..', 'frontend', 'data', 'apps', 'generated', 'apps-services.json'); // ===================================================================== diff --git a/containers/libreportal/docker-compose.yml b/containers/libreportal/docker-compose.yml index c5e4f2a..a16cbd2 100644 --- a/containers/libreportal/docker-compose.yml +++ b/containers/libreportal/docker-compose.yml @@ -31,6 +31,7 @@ services: environment: FRONTEND_PATH: /data/frontend LIBREPORTAL_CONFIG_PATH: /app/libreportal.config + LP_CONTAINERS_DIR: CONTAINERS_DIR_DATA #LIBREPORTAL|CONTAINERS_DIR_TAG|CONTAINERS_DIR_DATA TZ: TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA labels: libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA diff --git a/init.sh b/init.sh index 98b577b..4e3bd0d 100755 --- a/init.sh +++ b/init.sh @@ -110,22 +110,47 @@ lp_lib_dir="/usr/local/lib/libreportal" command_script="$lp_lib_dir/libreportal" command_symlink="/usr/local/bin/libreportal" -# Directories -docker_dir="/docker" -containers_dir="$docker_dir/containers/" -ssl_dir="$docker_dir/ssl/" -ssh_dir="$docker_dir/ssh/" -wireguard_dir="$docker_dir/wireguard/" -logs_dir="$docker_dir/logs/" -configs_dir="$docker_dir/configs/" -backup_dir="$docker_dir/backups" -restore_dir="$docker_dir/restore" -migrate_dir="$docker_dir/migrate" -# Install Scripts -script_dir="$docker_dir/install" -install_configs_dir="$script_dir/configs/" -install_containers_dir="$script_dir/containers/" -install_scripts_dir="$script_dir/scripts/" +# Directories — three independently-relocatable roots (see scripts/source/paths.sh +# for the canonical description). Defaults below; overridden by --system-dir / +# --containers-dir / --backups-dir (parsed in the flag loop) via the LP_*_DIR +# vars. init.sh derives inline (kept in sync with paths.sh) because the bare +# /root/init.sh reinstall copy has no scripts/ alongside to source. +# LP_SYSTEM_DIR — manager-owned control plane (configs/logs/install/db/…) +# LP_CONTAINERS_DIR — container-user-owned live app data +# LP_BACKUPS_DIR — container-user-owned backup repos (own mount-able) +libreportalDerivePaths() { + # Transitional compat: an existing install (legacy single /docker tree, + # identified by its config marker) keeps using /docker until a deliberate + # reinstall — so deploying new code never strands a running box. + if [[ -z "${LP_SYSTEM_DIR:-}" ]]; then + if [[ ! -e /libreportal-system && -f /docker/configs/general/general_docker_install ]]; then + LP_SYSTEM_DIR=/docker + : "${LP_CONTAINERS_DIR:=/docker/containers}" + : "${LP_BACKUPS_DIR:=/docker/backups}" + else + LP_SYSTEM_DIR=/libreportal-system + fi + fi + : "${LP_CONTAINERS_DIR:=/libreportal-containers}" + : "${LP_BACKUPS_DIR:=/libreportal-backups}" + + docker_dir="$LP_SYSTEM_DIR" + system_dir="$LP_SYSTEM_DIR" + configs_dir="$LP_SYSTEM_DIR/configs/" + logs_dir="$LP_SYSTEM_DIR/logs/" + ssl_dir="$LP_SYSTEM_DIR/ssl/" + ssh_dir="$LP_SYSTEM_DIR/ssh/" + wireguard_dir="$LP_SYSTEM_DIR/wireguard/" + migrate_dir="$LP_SYSTEM_DIR/migrate" + restore_dir="$LP_SYSTEM_DIR/restore" + script_dir="$LP_SYSTEM_DIR/install" + install_configs_dir="$script_dir/configs/" + install_containers_dir="$script_dir/containers/" + install_scripts_dir="$script_dir/scripts/" + containers_dir="$LP_CONTAINERS_DIR/" + backup_dir="$LP_BACKUPS_DIR" +} +libreportalDerivePaths # Parse flags init_shift_count=0 diff --git a/scripts/backup/locations/location_migrate.sh b/scripts/backup/locations/location_migrate.sh index 1895217..d9f355b 100644 --- a/scripts/backup/locations/location_migrate.sh +++ b/scripts/backup/locations/location_migrate.sh @@ -52,7 +52,7 @@ backupLocationsMigrate() runFileOp chown "$owner":"$owner" "$loc_dir" runFileOp chmod 0700 "$loc_dir" - local old_pass="/docker/configs/security/restic/loc_${idx}.pass" + local old_pass="${configs_dir}security/restic/loc_${idx}.pass" local pass_value="" if [[ -f "$old_pass" ]]; then pass_value=$(runFileOp cat "$old_pass" | tr -d '\n\r') @@ -75,7 +75,7 @@ backupLocationsMigrate() replacePlainPasswords "$cfg_file" fi - local old_kopia="/docker/configs/security/restic/kopia/loc_${idx}.config" + local old_kopia="${configs_dir}security/restic/kopia/loc_${idx}.config" local new_kopia new_kopia=$(backupLocationKopiaConfig "$idx") if [[ -f "$old_kopia" ]]; then @@ -85,7 +85,7 @@ backupLocationsMigrate() isNotice "Moved kopia state for location $idx → $new_kopia" fi - local old_ssh="/docker/configs/security/restic/ssh/loc_${idx}.key" + local old_ssh="${configs_dir}security/restic/ssh/loc_${idx}.key" local new_ssh new_ssh=$(backupLocationSshKey "$idx") if [[ -f "$old_ssh" ]]; then diff --git a/scripts/backup/locations/location_paths.sh b/scripts/backup/locations/location_paths.sh index 948c31b..422e882 100644 --- a/scripts/backup/locations/location_paths.sh +++ b/scripts/backup/locations/location_paths.sh @@ -69,7 +69,7 @@ backupLocationResolvedPath() if [[ "$mode" == "auto" ]]; then # Base dir is the configurable Default Backup Location (Backup Engine # config); each location gets its own numbered subfolder. - local base="${CFG_BACKUP_DEFAULT_PATH:-$docker_dir/backups}" + local base="${CFG_BACKUP_DEFAULT_PATH:-${backup_dir:-$docker_dir/backups}}" echo "${base%/}/${idx}" else resticLocationField "$idx" PATH diff --git a/scripts/config/tags/manager/tags_manager_update.sh b/scripts/config/tags/manager/tags_manager_update.sh index aa047ab..5c72ab8 100755 --- a/scripts/config/tags/manager/tags_manager_update.sh +++ b/scripts/config/tags/manager/tags_manager_update.sh @@ -51,6 +51,6 @@ tagsManagerUpdateUniversalTag() # manager-owned configs/ + install templates use runInstallOp. The read # (awk above) needs no escalation — config/compose files are world-readable. local op="runInstallOp" - [[ "$file_path" == "$containers_dir"* || "$file_path" == /docker/containers/* ]] && op="runFileOp" + [[ "$file_path" == "$containers_dir"* || "$file_path" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]] && op="runFileOp" $op sed -i "/#LIBREPORTAL|${tag_name}|/s|${esc_placeholder}|${esc_new}|g" "$file_path" } diff --git a/scripts/config/tags/processors/tags_processor_standard_replacements.sh b/scripts/config/tags/processors/tags_processor_standard_replacements.sh index 3a9977f..dfa3be4 100755 --- a/scripts/config/tags/processors/tags_processor_standard_replacements.sh +++ b/scripts/config/tags/processors/tags_processor_standard_replacements.sh @@ -21,6 +21,9 @@ tagsProcessorStandardReplacements() tagsManagerUpdateUniversalTag "$full_file_path" "DOMAINSUBNAME_TAG" "$host_setup" tagsManagerUpdateUniversalTag "$full_file_path" "TIMEZONE_TAG" "$CFG_TIMEZONE" tagsManagerUpdateUniversalTag "$full_file_path" "DOCKER_NETWORK_TAG" "$CFG_NETWORK_NAME" - + # Host live-app-data root, passed into the WebUI container (only the + # libreportal compose carries this tag; "only update tags that exist"). + tagsManagerUpdateUniversalTag "$full_file_path" "CONTAINERS_DIR_TAG" "${containers_dir%/}" + isSuccessful "Standard LibrePortal tag replacements applied using universal tag manager" } diff --git a/scripts/crontab/crontab_clean.sh b/scripts/crontab/crontab_clean.sh index 2e47fac..bb1e726 100755 --- a/scripts/crontab/crontab_clean.sh +++ b/scripts/crontab/crontab_clean.sh @@ -4,7 +4,7 @@ crontabClean() { # Remove old backup log entries - data_to_remove=" >> /docker/logs/backup.log 2>&1" + data_to_remove=" >> ${logs_dir}${backup_log_file} 2>&1" if crontab -l 2>/dev/null | grep -q "$data_to_remove"; then crontab -l 2>/dev/null | sed "s|$data_to_remove||g" | crontab - diff --git a/scripts/crontab/task/crontab_check_processor.sh b/scripts/crontab/task/crontab_check_processor.sh index ea0c082..48a310b 100755 --- a/scripts/crontab/task/crontab_check_processor.sh +++ b/scripts/crontab/task/crontab_check_processor.sh @@ -16,13 +16,18 @@ if [[ "$script_check_processor_flag" == "start_script" ]]; then # them every privileged op is "command not found". Same bootstrap as # crontab_task_processor.sh. These files are pure function/var defs, safe to # source. -LP_SCRIPTS="${install_scripts_dir:-/docker/install/scripts/}" -LP_DOCKER_CFG="/docker/configs/general/general_docker_install" +# Self-locate the scripts dir, then load the relocatable path roots (sets +# docker_dir/containers_dir/configs_dir/…). Same bootstrap as the task processor. +LP_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" +LP_SCRIPTS="${install_scripts_dir:-$(cd "$LP_SELF_DIR/../.." 2>/dev/null && pwd)/}" +[[ -f "${LP_SCRIPTS}source/paths.sh" ]] && source "${LP_SCRIPTS}source/paths.sh" +LP_SCRIPTS="${install_scripts_dir:-$LP_SCRIPTS}" +LP_DOCKER_CFG="${configs_dir:-/libreportal-system/configs/}general/general_docker_install" [[ -f "$LP_DOCKER_CFG" ]] && \ eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')" : "${sudo_user_name:=libreportal}" -: "${containers_dir:=/docker/containers/}" -: "${docker_dir:=/docker}" +: "${containers_dir:=/libreportal-containers/}" +: "${docker_dir:=/libreportal-system}" for _lp_f in docker/command/run_privileged.sh \ docker/command/docker_run_install.sh \ checks/requirements/check_install_type.sh; do @@ -37,7 +42,7 @@ command -v resolveDockerInstallUser >/dev/null 2>&1 && resolveDockerInstallUser # Essential configuration (keeping your original structure) TASK_DIR="" if [[ "$TASK_DIR" == "" ]]; then - TASK_DIR="/docker/containers/libreportal/frontend/data/tasks" + TASK_DIR="${containers_dir:-/libreportal-containers/}libreportal/frontend/data/tasks" fi LOCK_FILE="$TASK_DIR/task_processor.lock" diff --git a/scripts/crontab/task/crontab_task_processor.sh b/scripts/crontab/task/crontab_task_processor.sh index b3baeb2..b970308 100755 --- a/scripts/crontab/task/crontab_task_processor.sh +++ b/scripts/crontab/task/crontab_task_processor.sh @@ -32,13 +32,19 @@ script_task_processor_flag="$1" # docker-install-owned task dir fails ("command not found") and tasks loop # forever. Load them here — these files are pure function/var defs, safe to # source, no side effects. -LP_SCRIPTS="${install_scripts_dir:-/docker/install/scripts/}" -LP_DOCKER_CFG="/docker/configs/general/general_docker_install" +# Self-locate the scripts dir (where systemd launched us) so we can load the +# relocatable path roots — paths.sh sets docker_dir/containers_dir/configs_dir/… +# (the data + backup roots can't be derived from our own location). +LP_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)" +LP_SCRIPTS="${install_scripts_dir:-$(cd "$LP_SELF_DIR/../.." 2>/dev/null && pwd)/}" +[[ -f "${LP_SCRIPTS}source/paths.sh" ]] && source "${LP_SCRIPTS}source/paths.sh" +LP_SCRIPTS="${install_scripts_dir:-$LP_SCRIPTS}" +LP_DOCKER_CFG="${configs_dir:-/libreportal-system/configs/}general/general_docker_install" [[ -f "$LP_DOCKER_CFG" ]] && \ eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')" : "${sudo_user_name:=libreportal}" -: "${containers_dir:=/docker/containers/}" -: "${docker_dir:=/docker}" +: "${containers_dir:=/libreportal-containers/}" +: "${docker_dir:=/libreportal-system}" for _lp_f in docker/command/run_privileged.sh \ docker/command/docker_run_install.sh \ checks/requirements/check_install_type.sh; do @@ -50,7 +56,7 @@ command -v resolveDockerInstallUser >/dev/null 2>&1 && resolveDockerInstallUser # PATHS & CONSTANTS # ============================================================================ -TASK_DIR="${TASK_DIR:-/docker/containers/libreportal/frontend/data/tasks}" +TASK_DIR="${TASK_DIR:-${containers_dir:-/libreportal-containers/}libreportal/frontend/data/tasks}" LOCK_FILE="$TASK_DIR/.processor.lock" FIFO="$TASK_DIR/.queue.fifo" LOG_FILE="$TASK_DIR/task_processor.log" diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index b8408f7..9f364b2 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -101,7 +101,7 @@ _runRootHelper() { if [[ -x "$helper" ]]; then sudo "$helper" "$@" elif [[ $EUID -eq 0 ]]; then - bash "${script_dir:-/docker/install}/scripts/system/$name" "$@" + bash "${script_dir:-/libreportal-system/install}/scripts/system/$name" "$@" else sudo "$helper" "$@" fi diff --git a/scripts/function/file/copy_file.sh b/scripts/function/file/copy_file.sh index ab2ef33..4443836 100755 --- a/scripts/function/file/copy_file.sh +++ b/scripts/function/file/copy_file.sh @@ -17,7 +17,7 @@ copyFile() # the manager-owned control plane (configs/logs/etc.) is runInstallOp. # Mirrors createTouch's path-based ownership; $user_name is now advisory. local op="runInstallOp" - [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]] && op="runFileOp" + [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]] && op="runFileOp" if [ "$silent_flag" == "loud" ]; then local result=$($op cp $flags_full "$file" "$save_dir") diff --git a/scripts/function/file/copy_files.sh b/scripts/function/file/copy_files.sh index d2be18b..aaeea82 100755 --- a/scripts/function/file/copy_files.sh +++ b/scripts/function/file/copy_files.sh @@ -9,7 +9,7 @@ copyFiles() # Write as the destination's owner (see copyFile). local op="runInstallOp" - [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]] && op="runFileOp" + [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]] && op="runFileOp" local files=($($op find "$source" -type f)) if [ ${#files[@]} -eq 0 ]; then diff --git a/scripts/function/file/create_touch.sh b/scripts/function/file/create_touch.sh index 45f32d0..62cdda0 100755 --- a/scripts/function/file/create_touch.sh +++ b/scripts/function/file/create_touch.sh @@ -21,7 +21,7 @@ createTouch() local file_dir=$(dirname "$clean_file") local op="runInstallOp" - if [[ "$clean_file" == "$containers_dir"* || "$clean_file" == /docker/containers/* ]]; then + if [[ "$clean_file" == "$containers_dir"* || "$clean_file" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then op="runFileOp" fi diff --git a/scripts/function/file/move_file.sh b/scripts/function/file/move_file.sh index 6c54ce0..d55ff48 100755 --- a/scripts/function/file/move_file.sh +++ b/scripts/function/file/move_file.sh @@ -10,7 +10,7 @@ moveFile() if [ -e "$file" ]; then # Move as the destination's owner — no root, no chown (see copyFile). local op="runInstallOp" - [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]] && op="runFileOp" + [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]] && op="runFileOp" local result=$($op mv "$file" "$save_dir") checkSuccess "Moving $file_name to $save_dir" else diff --git a/scripts/function/folder/copy_folder.sh b/scripts/function/folder/copy_folder.sh index 0683bef..ddece96 100755 --- a/scripts/function/folder/copy_folder.sh +++ b/scripts/function/folder/copy_folder.sh @@ -8,8 +8,8 @@ copyFolder() local user_name="$3" # advisory — the destination path determines the owner # Write as the destination's owner — no root, no chown (see copyFile). - if [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]]; then - if [[ "$folder" == "$containers_dir"* || "$folder" == /docker/containers/* ]]; then + if [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then + if [[ "$folder" == "$containers_dir"* || "$folder" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then # container -> container: same owner (dockerinstall), a plain cp works. local result=$(runFileOp cp -rf "$folder" "$save_dir") else diff --git a/scripts/function/folder/copy_folders.sh b/scripts/function/folder/copy_folders.sh index 7e1fb42..c29f304 100755 --- a/scripts/function/folder/copy_folders.sh +++ b/scripts/function/folder/copy_folders.sh @@ -8,7 +8,7 @@ copyFolders() # Write as the destination's owner — no root, no chown (see copyFile). local op="runInstallOp" - [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]] && op="runFileOp" + [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]] && op="runFileOp" local subdirs=($(find "$source" -mindepth 1 -maxdepth 1 -type d)) if [ ${#subdirs[@]} -eq 0 ]; then diff --git a/scripts/function/folder/create_folder.sh b/scripts/function/folder/create_folder.sh index 079fb88..746e2a2 100755 --- a/scripts/function/folder/create_folder.sh +++ b/scripts/function/folder/create_folder.sh @@ -14,7 +14,7 @@ createFolders() # AS that user via runFileOp — creating it as the right owner avoids a # chown-to-another-user the unprivileged runtime can't do. Mirrors # createTouch; the $user_name hint is advisory for these paths. - if [[ "$clean_dir" == "$containers_dir"* || "$clean_dir" == /docker/containers/* ]]; then + if [[ "$clean_dir" == "$containers_dir"* || "$clean_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then if [ ! -d "$dir_path" ]; then local result=$(runFileOp mkdir -p "$dir_path") [ "$silent_flag" == "loud" ] && checkSuccess "Creating $folder_name directory" diff --git a/scripts/function/run/reinstall_libreportal.sh b/scripts/function/run/reinstall_libreportal.sh index f2a15be..5eb0894 100755 --- a/scripts/function/run/reinstall_libreportal.sh +++ b/scripts/function/run/reinstall_libreportal.sh @@ -63,17 +63,17 @@ runReinstall() AUTH_HTTP_REPO_URL="http://${CFG_GIT_USER}:${CFG_GIT_KEY}@${CLEAN_GIT_URL}.git" # Try HTTPS first - if runAsManager git clone -q "$AUTH_HTTPS_REPO_URL" "/docker/install" 2>/dev/null; then - runSystem cp -f /docker/install/init.sh /root/ - echo "SUCCESS: Git repository cloned via HTTPS into /docker/install." + if runAsManager git clone -q "$AUTH_HTTPS_REPO_URL" "$script_dir" 2>/dev/null; then + runSystem cp -f "$script_dir/init.sh" /root/ + echo "SUCCESS: Git repository cloned via HTTPS into $script_dir." echo "" echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command." echo "" else # If HTTPS fails, try HTTP - if runAsManager git clone -q "$AUTH_HTTP_REPO_URL" "/docker/install" 2>/dev/null; then - runSystem cp -f /docker/install/init.sh /root/ - echo "SUCCESS: Git repository cloned via HTTP into /docker/install." + if runAsManager git clone -q "$AUTH_HTTP_REPO_URL" "$script_dir" 2>/dev/null; then + runSystem cp -f "$script_dir/init.sh" /root/ + echo "SUCCESS: Git repository cloned via HTTP into $script_dir." echo "" echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command." echo "" diff --git a/scripts/headscale/local/headscale_generate_auth.sh b/scripts/headscale/local/headscale_generate_auth.sh index d0ce8ef..aca66cb 100755 --- a/scripts/headscale/local/headscale_generate_auth.sh +++ b/scripts/headscale/local/headscale_generate_auth.sh @@ -3,7 +3,7 @@ setupHeadscaleGenerateAuthKey() { headscale_preauthkey="" - local temp_key_file="/docker/key.txt" + local temp_key_file="${docker_dir:-/libreportal-system}/key.txt" local CFG_INSTALL_NAME=$(echo "$CFG_INSTALL_NAME" | tr '[:upper:]' '[:lower:]') dockerCommandRun "docker exec headscale headscale preauthkeys create -e 1h -u $CFG_INSTALL_NAME" > "$temp_key_file" 2>&1 diff --git a/scripts/install/install_crowdsec.sh b/scripts/install/install_crowdsec.sh index 88b72e0..fff6124 100644 --- a/scripts/install/install_crowdsec.sh +++ b/scripts/install/install_crowdsec.sh @@ -5,7 +5,7 @@ # mount set takes effect. crowdsecToggleLibrePortalLogMounts() { local mode="$1" - local compose="/docker/containers/libreportal/docker-compose.yml" + local compose="${containers_dir}libreportal/docker-compose.yml" [[ -f "$compose" ]] || return 0 case "$mode" in @@ -24,7 +24,7 @@ crowdsecToggleLibrePortalLogMounts() { if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^libreportal-service$'; then isNotice "Recreating libreportal so log mount toggle takes effect..." - ( cd /docker/containers/libreportal && runAsManager docker compose up -d >/dev/null 2>&1 ) || true + ( cd "${containers_dir}libreportal" && runAsManager docker compose up -d >/dev/null 2>&1 ) || true fi } @@ -215,7 +215,7 @@ installCrowdsecHost() # (use the rotate Tools action for that); this is a visibility # surface, not the auth source of truth. local key_file="/etc/crowdsec/traefik_bouncer.key" - local cfg_file="/docker/configs/security/security_crowdsec" + local cfg_file="${configs_dir}security/security_crowdsec" if ! runSystem cscli bouncers list -o raw 2>/dev/null | grep -q '^traefik-bouncer'; then local bouncer_key diff --git a/scripts/install/install_restic.sh b/scripts/install/install_restic.sh index 497645b..afed02d 100644 --- a/scripts/install/install_restic.sh +++ b/scripts/install/install_restic.sh @@ -22,7 +22,7 @@ installResticHost() installResticMigrateLegacyPasswords() { - local pass_dir="/docker/configs/security/restic" + local pass_dir="${configs_dir}security/restic" [[ ! -d "$pass_dir" ]] && return 0 declare -A legacy_map=( diff --git a/scripts/source/files/generate_arrays.sh b/scripts/source/files/generate_arrays.sh index 0ed4cee..11136d9 100755 --- a/scripts/source/files/generate_arrays.sh +++ b/scripts/source/files/generate_arrays.sh @@ -95,6 +95,7 @@ EOF "source/files/app_files.sh"|"source/files/cli_files.sh"| \ "source/loading/check_files.sh"|"source/loading/initilize_files.sh"| \ "source/loading/scan_files.sh"|"source/load_sources.sh"| \ + "source/paths.sh"| \ "source/files/generate_arrays.sh") continue ;; diff --git a/scripts/source/paths.sh b/scripts/source/paths.sh new file mode 100644 index 0000000..ae6b2d3 --- /dev/null +++ b/scripts/source/paths.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# +# LibrePortal path roots — single source of truth for the (relocatable) layout. +# +# Three independently-placeable roots, each owned by exactly one principal: +# LP_SYSTEM_DIR control plane — manager (libreportal) owned, 750 +# configs/ logs/ install/ database.db ssl/ ssh/ migrate/ restore/ +# LP_CONTAINERS_DIR live app data — container user (dockerinstall) owned (rootless) +# LP_BACKUPS_DIR restic/kopia repos — container user owned (separable / own mount) +# +# The roots come from the environment when set (the install bakes them into the +# task-processor systemd unit, and the CLI/app inherit them from init.sh), else +# they default to /libreportal-*. A custom location is chosen at INSTALL time and +# baked by root — never read at runtime from a manager-writable config. +# +# SECURITY: the root-owned helpers under /usr/local/lib/libreportal/ do NOT source +# this file. They get the paths baked in at install (sed placeholders), so the +# manager cannot redirect a root `chown`/`chmod` by editing config. This file is +# only for the manager-run code (app, CLI, task processor), which runs without +# extra privilege. +# +# Mirror copy: init.sh derives the same vars inline (it is self-contained for the +# bare /root/init.sh reinstall case, where scripts/ isn't alongside). Keep the two +# derivations in sync. + +# --- Resolve the three roots ------------------------------------------------ +# Transitional compat: an EXISTING install (the legacy single /docker tree, +# identified by its config marker) keeps using /docker until a deliberate +# reinstall to the split layout — so deploying new code never strands a running +# box. Fresh installs (no marker) get the /libreportal-* split. +if [[ -z "${LP_SYSTEM_DIR:-}" ]]; then + if [[ ! -e /libreportal-system && -f /docker/configs/general/general_docker_install ]]; then + LP_SYSTEM_DIR=/docker + : "${LP_CONTAINERS_DIR:=/docker/containers}" + : "${LP_BACKUPS_DIR:=/docker/backups}" + else + LP_SYSTEM_DIR=/libreportal-system + fi +fi +: "${LP_CONTAINERS_DIR:=/libreportal-containers}" +: "${LP_BACKUPS_DIR:=/libreportal-backups}" + +# --- Derived: system tree (manager-owned). docker_dir is the legacy name. --- +docker_dir="$LP_SYSTEM_DIR" +system_dir="$LP_SYSTEM_DIR" +configs_dir="$LP_SYSTEM_DIR/configs/" +logs_dir="$LP_SYSTEM_DIR/logs/" +ssl_dir="$LP_SYSTEM_DIR/ssl/" +ssh_dir="$LP_SYSTEM_DIR/ssh/" +wireguard_dir="$LP_SYSTEM_DIR/wireguard/" +migrate_dir="$LP_SYSTEM_DIR/migrate" +restore_dir="$LP_SYSTEM_DIR/restore" +script_dir="$LP_SYSTEM_DIR/install" +install_configs_dir="$script_dir/configs/" +install_containers_dir="$script_dir/containers/" +install_scripts_dir="$script_dir/scripts/" + +# --- Derived: data tree (container-user-owned) — the root IS the dir --------- +containers_dir="$LP_CONTAINERS_DIR/" + +# --- Derived: backups tree (container-user-owned; own mount-able) ----------- +backup_dir="$LP_BACKUPS_DIR" diff --git a/scripts/webui/data/utils/webui_atomic_write.sh b/scripts/webui/data/utils/webui_atomic_write.sh index a408811..a070a56 100755 --- a/scripts/webui/data/utils/webui_atomic_write.sh +++ b/scripts/webui/data/utils/webui_atomic_write.sh @@ -13,7 +13,7 @@ atomicWriteWebUI() { # can write the dockerinstall-owned WebUI/app files. Temp + rename share the # target's directory, so the mv stays atomic (same filesystem, same owner). local op="runInstallOp" wop="runInstallWrite" - if [[ "$target_file" == "$containers_dir"* || "$target_file" == /docker/containers/* ]]; then + if [[ "$target_file" == "$containers_dir"* || "$target_file" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then op="runFileOp"; wop="runFileWrite" fi diff --git a/start.sh b/start.sh index def88ba..53365e0 100755 --- a/start.sh +++ b/start.sh @@ -34,6 +34,11 @@ showRunHelp() initLibrePortal() { + # Load the relocatable path roots up front (sets logs_dir/docker_dir/…) — the + # install-log below needs logs_dir before load_sources runs. cwd is the install + # dir, so the relative path resolves. + [[ -f "scripts/source/paths.sh" ]] && source "scripts/source/paths.sh" + # For the full application loading if [[ "$initial_command1" == "run" ]]; then if [[ -z "$initial_command2" ]]; then @@ -46,8 +51,8 @@ initLibrePortal() # Capture the install run to a log so credentials/URLs can be recovered # after we clear the screen at the end. if [[ "$initial_command2" == "install" ]]; then - install_log_path="/docker/logs/install-$(date +%Y%m%d-%H%M%S).log" - sudo mkdir -p /docker/logs 2>/dev/null + install_log_path="${logs_dir:-/libreportal-system/logs/}install-$(date +%Y%m%d-%H%M%S).log" + sudo mkdir -p "${logs_dir:-/libreportal-system/logs/}" 2>/dev/null sudo touch "$install_log_path" 2>/dev/null # Own it by whoever runs the install (the manager under Model A) so the # tee below — which runs as that user, not root — can append. A