Merge claude/1

This commit is contained in:
librelad 2026-05-25 15:09:39 +01:00
commit fc2c6a6197
27 changed files with 170 additions and 58 deletions

View File

@ -2,7 +2,7 @@
# Backup Engine - **ADVANCED** Engine-level knobs most users won't need to touch # 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_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 (<path>/<id>) CFG_BACKUP_DEFAULT_PATH= # Default Backup Location - Base directory for locations set to Automatic path mode; each location lives in its own numbered subfolder (<path>/<id>). 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_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_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 CFG_BACKUP_VERIFY_DATA_PERCENT=5 # Verify Data Sample % - Percentage of repo data to checksum-verify weekly

View File

@ -32,7 +32,11 @@ const router = express.Router();
const TASKS_DIR = path.join(__dirname, '..', '..', 'frontend', 'data', 'tasks'); const TASKS_DIR = path.join(__dirname, '..', '..', 'frontend', 'data', 'tasks');
const FIFO_PATH = path.join(TASKS_DIR, '.queue.fifo'); 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'); const APPS_SERVICES_JSON = path.join(__dirname, '..', '..', 'frontend', 'data', 'apps', 'generated', 'apps-services.json');
// ===================================================================== // =====================================================================

View File

@ -31,6 +31,7 @@ services:
environment: environment:
FRONTEND_PATH: /data/frontend FRONTEND_PATH: /data/frontend
LIBREPORTAL_CONFIG_PATH: /app/libreportal.config 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 TZ: TIMEZONE_DATA #LIBREPORTAL|TIMEZONE_TAG|TIMEZONE_DATA
labels: labels:
libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA libreportal.category: "CATEGORY_DATA" #LIBREPORTAL|CATEGORY_TAG|CATEGORY_DATA

57
init.sh
View File

@ -110,22 +110,47 @@ lp_lib_dir="/usr/local/lib/libreportal"
command_script="$lp_lib_dir/libreportal" command_script="$lp_lib_dir/libreportal"
command_symlink="/usr/local/bin/libreportal" command_symlink="/usr/local/bin/libreportal"
# Directories # Directories — three independently-relocatable roots (see scripts/source/paths.sh
docker_dir="/docker" # for the canonical description). Defaults below; overridden by --system-dir /
containers_dir="$docker_dir/containers/" # --containers-dir / --backups-dir (parsed in the flag loop) via the LP_*_DIR
ssl_dir="$docker_dir/ssl/" # vars. init.sh derives inline (kept in sync with paths.sh) because the bare
ssh_dir="$docker_dir/ssh/" # /root/init.sh reinstall copy has no scripts/ alongside to source.
wireguard_dir="$docker_dir/wireguard/" # LP_SYSTEM_DIR — manager-owned control plane (configs/logs/install/db/…)
logs_dir="$docker_dir/logs/" # LP_CONTAINERS_DIR — container-user-owned live app data
configs_dir="$docker_dir/configs/" # LP_BACKUPS_DIR — container-user-owned backup repos (own mount-able)
backup_dir="$docker_dir/backups" libreportalDerivePaths() {
restore_dir="$docker_dir/restore" # Transitional compat: an existing install (legacy single /docker tree,
migrate_dir="$docker_dir/migrate" # identified by its config marker) keeps using /docker until a deliberate
# Install Scripts # reinstall — so deploying new code never strands a running box.
script_dir="$docker_dir/install" if [[ -z "${LP_SYSTEM_DIR:-}" ]]; then
install_configs_dir="$script_dir/configs/" if [[ ! -e /libreportal-system && -f /docker/configs/general/general_docker_install ]]; then
install_containers_dir="$script_dir/containers/" LP_SYSTEM_DIR=/docker
install_scripts_dir="$script_dir/scripts/" : "${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 # Parse flags
init_shift_count=0 init_shift_count=0

View File

@ -52,7 +52,7 @@ backupLocationsMigrate()
runFileOp chown "$owner":"$owner" "$loc_dir" runFileOp chown "$owner":"$owner" "$loc_dir"
runFileOp chmod 0700 "$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="" local pass_value=""
if [[ -f "$old_pass" ]]; then if [[ -f "$old_pass" ]]; then
pass_value=$(runFileOp cat "$old_pass" | tr -d '\n\r') pass_value=$(runFileOp cat "$old_pass" | tr -d '\n\r')
@ -75,7 +75,7 @@ backupLocationsMigrate()
replacePlainPasswords "$cfg_file" replacePlainPasswords "$cfg_file"
fi 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 local new_kopia
new_kopia=$(backupLocationKopiaConfig "$idx") new_kopia=$(backupLocationKopiaConfig "$idx")
if [[ -f "$old_kopia" ]]; then if [[ -f "$old_kopia" ]]; then
@ -85,7 +85,7 @@ backupLocationsMigrate()
isNotice "Moved kopia state for location $idx$new_kopia" isNotice "Moved kopia state for location $idx$new_kopia"
fi 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 local new_ssh
new_ssh=$(backupLocationSshKey "$idx") new_ssh=$(backupLocationSshKey "$idx")
if [[ -f "$old_ssh" ]]; then if [[ -f "$old_ssh" ]]; then

View File

@ -69,7 +69,7 @@ backupLocationResolvedPath()
if [[ "$mode" == "auto" ]]; then if [[ "$mode" == "auto" ]]; then
# Base dir is the configurable Default Backup Location (Backup Engine # Base dir is the configurable Default Backup Location (Backup Engine
# config); each location gets its own numbered subfolder. # 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}" echo "${base%/}/${idx}"
else else
resticLocationField "$idx" PATH resticLocationField "$idx" PATH

View File

@ -51,6 +51,6 @@ tagsManagerUpdateUniversalTag()
# manager-owned configs/ + install templates use runInstallOp. The read # manager-owned configs/ + install templates use runInstallOp. The read
# (awk above) needs no escalation — config/compose files are world-readable. # (awk above) needs no escalation — config/compose files are world-readable.
local op="runInstallOp" 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" $op sed -i "/#LIBREPORTAL|${tag_name}|/s|${esc_placeholder}|${esc_new}|g" "$file_path"
} }

View File

@ -21,6 +21,9 @@ tagsProcessorStandardReplacements()
tagsManagerUpdateUniversalTag "$full_file_path" "DOMAINSUBNAME_TAG" "$host_setup" tagsManagerUpdateUniversalTag "$full_file_path" "DOMAINSUBNAME_TAG" "$host_setup"
tagsManagerUpdateUniversalTag "$full_file_path" "TIMEZONE_TAG" "$CFG_TIMEZONE" tagsManagerUpdateUniversalTag "$full_file_path" "TIMEZONE_TAG" "$CFG_TIMEZONE"
tagsManagerUpdateUniversalTag "$full_file_path" "DOCKER_NETWORK_TAG" "$CFG_NETWORK_NAME" 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" isSuccessful "Standard LibrePortal tag replacements applied using universal tag manager"
} }

View File

@ -4,7 +4,7 @@
crontabClean() crontabClean()
{ {
# Remove old backup log entries # 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 if crontab -l 2>/dev/null | grep -q "$data_to_remove"; then
crontab -l 2>/dev/null | sed "s|$data_to_remove||g" | crontab - crontab -l 2>/dev/null | sed "s|$data_to_remove||g" | crontab -

View File

@ -16,13 +16,18 @@ if [[ "$script_check_processor_flag" == "start_script" ]]; then
# them every privileged op is "command not found". Same bootstrap as # them every privileged op is "command not found". Same bootstrap as
# crontab_task_processor.sh. These files are pure function/var defs, safe to # crontab_task_processor.sh. These files are pure function/var defs, safe to
# source. # source.
LP_SCRIPTS="${install_scripts_dir:-/docker/install/scripts/}" # Self-locate the scripts dir, then load the relocatable path roots (sets
LP_DOCKER_CFG="/docker/configs/general/general_docker_install" # 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" ]] && \ [[ -f "$LP_DOCKER_CFG" ]] && \
eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')" eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')"
: "${sudo_user_name:=libreportal}" : "${sudo_user_name:=libreportal}"
: "${containers_dir:=/docker/containers/}" : "${containers_dir:=/libreportal-containers/}"
: "${docker_dir:=/docker}" : "${docker_dir:=/libreportal-system}"
for _lp_f in docker/command/run_privileged.sh \ for _lp_f in docker/command/run_privileged.sh \
docker/command/docker_run_install.sh \ docker/command/docker_run_install.sh \
checks/requirements/check_install_type.sh; do 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) # Essential configuration (keeping your original structure)
TASK_DIR="" TASK_DIR=""
if [[ "$TASK_DIR" == "" ]]; then if [[ "$TASK_DIR" == "" ]]; then
TASK_DIR="/docker/containers/libreportal/frontend/data/tasks" TASK_DIR="${containers_dir:-/libreportal-containers/}libreportal/frontend/data/tasks"
fi fi
LOCK_FILE="$TASK_DIR/task_processor.lock" LOCK_FILE="$TASK_DIR/task_processor.lock"

View File

@ -32,13 +32,19 @@ script_task_processor_flag="$1"
# docker-install-owned task dir fails ("command not found") and tasks loop # 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 # forever. Load them here — these files are pure function/var defs, safe to
# source, no side effects. # source, no side effects.
LP_SCRIPTS="${install_scripts_dir:-/docker/install/scripts/}" # Self-locate the scripts dir (where systemd launched us) so we can load the
LP_DOCKER_CFG="/docker/configs/general/general_docker_install" # 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" ]] && \ [[ -f "$LP_DOCKER_CFG" ]] && \
eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')" eval "$(grep -E '^CFG_DOCKER_INSTALL_(TYPE|USER)=' "$LP_DOCKER_CFG" | sed 's/[[:space:]]*#.*//')"
: "${sudo_user_name:=libreportal}" : "${sudo_user_name:=libreportal}"
: "${containers_dir:=/docker/containers/}" : "${containers_dir:=/libreportal-containers/}"
: "${docker_dir:=/docker}" : "${docker_dir:=/libreportal-system}"
for _lp_f in docker/command/run_privileged.sh \ for _lp_f in docker/command/run_privileged.sh \
docker/command/docker_run_install.sh \ docker/command/docker_run_install.sh \
checks/requirements/check_install_type.sh; do checks/requirements/check_install_type.sh; do
@ -50,7 +56,7 @@ command -v resolveDockerInstallUser >/dev/null 2>&1 && resolveDockerInstallUser
# PATHS & CONSTANTS # 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" LOCK_FILE="$TASK_DIR/.processor.lock"
FIFO="$TASK_DIR/.queue.fifo" FIFO="$TASK_DIR/.queue.fifo"
LOG_FILE="$TASK_DIR/task_processor.log" LOG_FILE="$TASK_DIR/task_processor.log"

View File

@ -101,7 +101,7 @@ _runRootHelper() {
if [[ -x "$helper" ]]; then if [[ -x "$helper" ]]; then
sudo "$helper" "$@" sudo "$helper" "$@"
elif [[ $EUID -eq 0 ]]; then elif [[ $EUID -eq 0 ]]; then
bash "${script_dir:-/docker/install}/scripts/system/$name" "$@" bash "${script_dir:-/libreportal-system/install}/scripts/system/$name" "$@"
else else
sudo "$helper" "$@" sudo "$helper" "$@"
fi fi

View File

@ -17,7 +17,7 @@ copyFile()
# the manager-owned control plane (configs/logs/etc.) is runInstallOp. # the manager-owned control plane (configs/logs/etc.) is runInstallOp.
# Mirrors createTouch's path-based ownership; $user_name is now advisory. # Mirrors createTouch's path-based ownership; $user_name is now advisory.
local op="runInstallOp" 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 if [ "$silent_flag" == "loud" ]; then
local result=$($op cp $flags_full "$file" "$save_dir") local result=$($op cp $flags_full "$file" "$save_dir")

View File

@ -9,7 +9,7 @@ copyFiles()
# Write as the destination's owner (see copyFile). # Write as the destination's owner (see copyFile).
local op="runInstallOp" 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)) local files=($($op find "$source" -type f))
if [ ${#files[@]} -eq 0 ]; then if [ ${#files[@]} -eq 0 ]; then

View File

@ -21,7 +21,7 @@ createTouch()
local file_dir=$(dirname "$clean_file") local file_dir=$(dirname "$clean_file")
local op="runInstallOp" 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" op="runFileOp"
fi fi

View File

@ -10,7 +10,7 @@ moveFile()
if [ -e "$file" ]; then if [ -e "$file" ]; then
# Move as the destination's owner — no root, no chown (see copyFile). # Move as the destination's owner — no root, no chown (see copyFile).
local op="runInstallOp" 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") local result=$($op mv "$file" "$save_dir")
checkSuccess "Moving $file_name to $save_dir" checkSuccess "Moving $file_name to $save_dir"
else else

View File

@ -8,8 +8,8 @@ copyFolder()
local user_name="$3" # advisory — the destination path determines the owner local user_name="$3" # advisory — the destination path determines the owner
# Write as the destination's owner — no root, no chown (see copyFile). # Write as the destination's owner — no root, no chown (see copyFile).
if [[ "$save_dir" == "$containers_dir"* || "$save_dir" == /docker/containers/* ]]; then if [[ "$save_dir" == "$containers_dir"* || "$save_dir" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then
if [[ "$folder" == "$containers_dir"* || "$folder" == /docker/containers/* ]]; then if [[ "$folder" == "$containers_dir"* || "$folder" == "${LP_CONTAINERS_DIR:-/libreportal-containers}"/* ]]; then
# container -> container: same owner (dockerinstall), a plain cp works. # container -> container: same owner (dockerinstall), a plain cp works.
local result=$(runFileOp cp -rf "$folder" "$save_dir") local result=$(runFileOp cp -rf "$folder" "$save_dir")
else else

View File

@ -8,7 +8,7 @@ copyFolders()
# Write as the destination's owner — no root, no chown (see copyFile). # Write as the destination's owner — no root, no chown (see copyFile).
local op="runInstallOp" 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)) local subdirs=($(find "$source" -mindepth 1 -maxdepth 1 -type d))
if [ ${#subdirs[@]} -eq 0 ]; then if [ ${#subdirs[@]} -eq 0 ]; then

View File

@ -14,7 +14,7 @@ createFolders()
# AS that user via runFileOp — creating it as the right owner avoids a # AS that user via runFileOp — creating it as the right owner avoids a
# chown-to-another-user the unprivileged runtime can't do. Mirrors # chown-to-another-user the unprivileged runtime can't do. Mirrors
# createTouch; the $user_name hint is advisory for these paths. # 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 if [ ! -d "$dir_path" ]; then
local result=$(runFileOp mkdir -p "$dir_path") local result=$(runFileOp mkdir -p "$dir_path")
[ "$silent_flag" == "loud" ] && checkSuccess "Creating $folder_name directory" [ "$silent_flag" == "loud" ] && checkSuccess "Creating $folder_name directory"

View File

@ -63,17 +63,17 @@ runReinstall()
AUTH_HTTP_REPO_URL="http://${CFG_GIT_USER}:${CFG_GIT_KEY}@${CLEAN_GIT_URL}.git" AUTH_HTTP_REPO_URL="http://${CFG_GIT_USER}:${CFG_GIT_KEY}@${CLEAN_GIT_URL}.git"
# Try HTTPS first # Try HTTPS first
if runAsManager git clone -q "$AUTH_HTTPS_REPO_URL" "/docker/install" 2>/dev/null; then if runAsManager git clone -q "$AUTH_HTTPS_REPO_URL" "$script_dir" 2>/dev/null; then
runSystem cp -f /docker/install/init.sh /root/ runSystem cp -f "$script_dir/init.sh" /root/
echo "SUCCESS: Git repository cloned via HTTPS into /docker/install." echo "SUCCESS: Git repository cloned via HTTPS into $script_dir."
echo "" echo ""
echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command." echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command."
echo "" echo ""
else else
# If HTTPS fails, try HTTP # If HTTPS fails, try HTTP
if runAsManager git clone -q "$AUTH_HTTP_REPO_URL" "/docker/install" 2>/dev/null; then if runAsManager git clone -q "$AUTH_HTTP_REPO_URL" "$script_dir" 2>/dev/null; then
runSystem cp -f /docker/install/init.sh /root/ runSystem cp -f "$script_dir/init.sh" /root/
echo "SUCCESS: Git repository cloned via HTTP into /docker/install." echo "SUCCESS: Git repository cloned via HTTP into $script_dir."
echo "" echo ""
echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command." echo "SUCCESS: Reinstallation complete, you can now run the "libreportal run" command."
echo "" echo ""

View File

@ -3,7 +3,7 @@
setupHeadscaleGenerateAuthKey() setupHeadscaleGenerateAuthKey()
{ {
headscale_preauthkey="" 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:]') 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 dockerCommandRun "docker exec headscale headscale preauthkeys create -e 1h -u $CFG_INSTALL_NAME" > "$temp_key_file" 2>&1

View File

@ -5,7 +5,7 @@
# mount set takes effect. # mount set takes effect.
crowdsecToggleLibrePortalLogMounts() { crowdsecToggleLibrePortalLogMounts() {
local mode="$1" local mode="$1"
local compose="/docker/containers/libreportal/docker-compose.yml" local compose="${containers_dir}libreportal/docker-compose.yml"
[[ -f "$compose" ]] || return 0 [[ -f "$compose" ]] || return 0
case "$mode" in case "$mode" in
@ -24,7 +24,7 @@ crowdsecToggleLibrePortalLogMounts() {
if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^libreportal-service$'; then if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^libreportal-service$'; then
isNotice "Recreating libreportal so log mount toggle takes effect..." 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 fi
} }
@ -215,7 +215,7 @@ installCrowdsecHost()
# (use the rotate Tools action for that); this is a visibility # (use the rotate Tools action for that); this is a visibility
# surface, not the auth source of truth. # surface, not the auth source of truth.
local key_file="/etc/crowdsec/traefik_bouncer.key" 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 if ! runSystem cscli bouncers list -o raw 2>/dev/null | grep -q '^traefik-bouncer'; then
local bouncer_key local bouncer_key

View File

@ -22,7 +22,7 @@ installResticHost()
installResticMigrateLegacyPasswords() installResticMigrateLegacyPasswords()
{ {
local pass_dir="/docker/configs/security/restic" local pass_dir="${configs_dir}security/restic"
[[ ! -d "$pass_dir" ]] && return 0 [[ ! -d "$pass_dir" ]] && return 0
declare -A legacy_map=( declare -A legacy_map=(

View File

@ -95,6 +95,7 @@ EOF
"source/files/app_files.sh"|"source/files/cli_files.sh"| \ "source/files/app_files.sh"|"source/files/cli_files.sh"| \
"source/loading/check_files.sh"|"source/loading/initilize_files.sh"| \ "source/loading/check_files.sh"|"source/loading/initilize_files.sh"| \
"source/loading/scan_files.sh"|"source/load_sources.sh"| \ "source/loading/scan_files.sh"|"source/load_sources.sh"| \
"source/paths.sh"| \
"source/files/generate_arrays.sh") "source/files/generate_arrays.sh")
continue continue
;; ;;

62
scripts/source/paths.sh Normal file
View File

@ -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"

View File

@ -13,7 +13,7 @@ atomicWriteWebUI() {
# can write the dockerinstall-owned WebUI/app files. Temp + rename share the # can write the dockerinstall-owned WebUI/app files. Temp + rename share the
# target's directory, so the mv stays atomic (same filesystem, same owner). # target's directory, so the mv stays atomic (same filesystem, same owner).
local op="runInstallOp" wop="runInstallWrite" 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" op="runFileOp"; wop="runFileWrite"
fi fi

View File

@ -34,6 +34,11 @@ showRunHelp()
initLibrePortal() 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 # For the full application loading
if [[ "$initial_command1" == "run" ]]; then if [[ "$initial_command1" == "run" ]]; then
if [[ -z "$initial_command2" ]]; then if [[ -z "$initial_command2" ]]; then
@ -46,8 +51,8 @@ initLibrePortal()
# Capture the install run to a log so credentials/URLs can be recovered # Capture the install run to a log so credentials/URLs can be recovered
# after we clear the screen at the end. # after we clear the screen at the end.
if [[ "$initial_command2" == "install" ]]; then if [[ "$initial_command2" == "install" ]]; then
install_log_path="/docker/logs/install-$(date +%Y%m%d-%H%M%S).log" install_log_path="${logs_dir:-/libreportal-system/logs/}install-$(date +%Y%m%d-%H%M%S).log"
sudo mkdir -p /docker/logs 2>/dev/null sudo mkdir -p "${logs_dir:-/libreportal-system/logs/}" 2>/dev/null
sudo touch "$install_log_path" 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 # 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 # tee below — which runs as that user, not root — can append. A