From 3034eaf4c7e69c32d040a014a6160cf46fd3f2ea Mon Sep 17 00:00:00 2001 From: librelad Date: Wed, 26 Aug 2026 03:48:55 +0100 Subject: [PATCH] feat(install): choose system and app-data disks independently; theme the dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes. The wizard's "new apps store their data on" dropdown was a bare native + `; diff --git a/docs/guide/install-and-use.md b/docs/guide/install-and-use.md index 4330d39..e1292f6 100644 --- a/docs/guide/install-and-use.md +++ b/docs/guide/install-and-use.md @@ -26,23 +26,29 @@ login), installs LibrePortal, and prints the WebUI address + a generated passwor ### Put data where you want it (separate disks, external drives) -**The installer just asks.** If it finds a second drive, it offers it for app -data before installing anything: +**The installer just asks.** If it finds another drive, it offers it before +installing anything — one list, two independent questions: ``` -Where should app data live? +Where should LibrePortal keep things? 1) This disk (default) 911.9G 808.4G free 2) /mnt/bigdisk 3.6T 3.6T free - LibrePortal itself stays on this disk either way — only app data moves. +LibrePortal itself — settings, database, logs. Around 20 MB, and it stays small. +Choose [1]: +App data — everything your apps store. This is the one that grows. Choose [1]: ``` +They're separate on purpose. On a desktop you usually only move the second. On +a small board with an 8 GB eMMC and a USB SSD you move both. + It picks a subdirectory on the drive you choose, never the mount point itself, -and skips the question entirely when there is nothing else to choose, when you -passed `--containers-dir`, or when running unattended. +and skips a question when the matching flag was already passed — or the whole +thing when there is nothing else to choose, no terminal, or you're running +unattended. The three roots below are still there for scripted installs: diff --git a/init.sh b/init.sh index 6c2cea2..4689a47 100755 --- a/init.sh +++ b/init.sh @@ -188,32 +188,38 @@ libreportalDerivePaths() { } libreportalDerivePaths -# Interactive disk picker for app data. +# Interactive disk picker, in the shape an OS installer asks it. # -# The --containers-dir flag has existed for a while, but someone running the -# curl|bash installer never learns it exists — so a box with a 4 TB disk sitting -# next to a small system SSD quietly put everything on the SSD. This asks, once, -# in the shape an OS installer asks it. +# --system-dir/--containers-dir have existed for a while and nobody running the +# curl|bash installer ever learned they existed, so a box with a 4 TB disk beside +# a small system SSD quietly put everything on the SSD. +# +# Two roots, asked independently, because they are genuinely different choices: +# the control plane is ~20 MB and never grows, while app data is what becomes +# terabytes. On a normal desktop only the second matters; on an SBC with a small +# eMMC and a USB SSD you want both moved. Backups stay on --backups-dir, which +# is a scripted-install concern rather than a first-run one. # # Deliberately narrow: -# * app data only. The control plane is ~20 MB and never grows; the thing -# worth placing is the data. Offering three roots would be three questions -# to answer badly. -# * skipped entirely when unattended, when --containers-dir was passed, when -# there is no TTY, or when there is nothing else to choose. A prompt with -# one answer is not a question. +# * one disk list, two short questions. Three questions nobody can answer at +# that moment is worse than a flag they never see. # * a SUBDIRECTORY on the chosen disk, never its mount point — that keeps the # "root only ever takes an empty directory" rule intact and leaves anything # already on the disk alone. +# * candidates exclude anything on the same filesystem as /, since placing +# data there gains nothing. # -# Self-contained (findmnt only), like the rest of init.sh: scripts/ is not +# Skipped when unattended, with no TTY, when the matching flag was already +# passed, or when the scan finds nothing else — a prompt with one possible +# answer is not a question. Self-contained (findmnt only): scripts/ is not # necessarily loadable this early. -initPickContainersDir() +initPickRoots() { [[ "$init_unattended_mode" == true ]] && return 0 - [[ -n "${LP_CONTAINERS_DIR_EXPLICIT:-}" ]] && return 0 [[ -t 0 && -t 1 ]] || return 0 command -v findmnt >/dev/null 2>&1 || return 0 + # Nothing left to ask? + [[ -n "${LP_SYSTEM_DIR_EXPLICIT:-}" && -n "${LP_CONTAINERS_DIR_EXPLICIT:-}" ]] && return 0 local sys_dev; sys_dev=$(stat -c '%d' -- / 2>/dev/null) local -a paths=() labels=() @@ -239,7 +245,7 @@ initPickContainersDir() squashfs|overlay|overlay2|aufs|tmpfs|devtmpfs|ramfs|iso9660|udf|vfat|exfat|ntfs|ntfs3|msdos|fuseblk|"") continue ;; esac dev=$(stat -c '%d' -- "$target" 2>/dev/null) - [[ -n "$dev" && "$dev" == "$sys_dev" ]] && continue # same disk as / — no gain + [[ -n "$dev" && "$dev" == "$sys_dev" ]] && continue paths+=("$target") labels+=("$(printf '%-24s %-8s %s free' "$target" "${size:-?}" "${avail:-?}")") done < <(findmnt -Pno TARGET,SOURCE,FSTYPE,SIZE,AVAIL 2>/dev/null) @@ -251,7 +257,7 @@ initPickContainersDir() root_avail=$(findmnt -no AVAIL --target / 2>/dev/null | tail -1) echo "" - isHeader "Where should app data live?" + isHeader "Where should LibrePortal keep things?" echo "" printf ' %s %-24s %-8s %s free\n' "1)" "This disk (default)" "${root_size:-?}" "${root_avail:-?}" local i @@ -259,22 +265,43 @@ initPickContainersDir() printf ' %s %s\n' "$((i + 2)))" "${labels[$i]}" done echo "" - echo " LibrePortal itself stays on this disk either way — only app data moves." - echo "" - local choice="" - isQuestion "Choose [1]:" - read -r choice - echo "" - [[ -z "$choice" || "$choice" == "1" ]] && return 0 - if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 2 || choice > ${#paths[@]} + 1 )); then - isNotice "Not a listed option — using this disk." - return 0 + # Ask for one root. Echoes the chosen mount point on STDOUT, or nothing for + # "this disk". Everything the human reads goes to stderr — stdout is the + # return channel here, and prompting on it makes the prompt part of the + # answer. + _initAskDisk() { + local prompt="$1" choice="" + echo "$prompt" >&2 + isQuestion "Choose [1]:" >&2 + read -r choice + echo "" >&2 + [[ -z "$choice" || "$choice" == "1" ]] && return 0 + if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 2 || choice > ${#paths[@]} + 1 )); then + isNotice "Not a listed option — using this disk." >&2 + return 0 + fi + printf '%s' "${paths[$((choice - 2))]}" + } + + local chosen + if [[ -z "${LP_SYSTEM_DIR_EXPLICIT:-}" ]]; then + chosen=$(_initAskDisk "LibrePortal itself — settings, database, logs. Around 20 MB, and it stays small.") + if [[ -n "$chosen" ]]; then + LP_SYSTEM_DIR="${chosen%/}/libreportal-system" + isSuccessful "LibrePortal will live in $LP_SYSTEM_DIR" + fi fi - local chosen="${paths[$((choice - 2))]}" - LP_CONTAINERS_DIR="${chosen%/}/libreportal-containers" - isSuccessful "App data will live in $LP_CONTAINERS_DIR" + if [[ -z "${LP_CONTAINERS_DIR_EXPLICIT:-}" ]]; then + chosen=$(_initAskDisk "App data — everything your apps store. This is the one that grows.") + if [[ -n "$chosen" ]]; then + LP_CONTAINERS_DIR="${chosen%/}/libreportal-containers" + isSuccessful "App data will live in $LP_CONTAINERS_DIR" + fi + fi + + unset -f _initAskDisk return 0 } @@ -365,7 +392,7 @@ for ((i=1; i<=$#; i++)); do # Relocatable roots (=form keeps the single-token shift logic). Validated # by libreportalValidatePaths before any folder is created. Can also be set # via the LP_*_DIR environment. - --system-dir=*) LP_SYSTEM_DIR="${!i#*=}"; ((init_shift_count++)) ;; + --system-dir=*) LP_SYSTEM_DIR="${!i#*=}"; LP_SYSTEM_DIR_EXPLICIT=1; ((init_shift_count++)) ;; --containers-dir=*) LP_CONTAINERS_DIR="${!i#*=}"; LP_CONTAINERS_DIR_EXPLICIT=1; ((init_shift_count++)) ;; --backups-dir=*) LP_BACKUPS_DIR="${!i#*=}"; ((init_shift_count++)) ;; --manager-user=*) LP_MANAGER_USER="${!i#*=}"; ((init_shift_count++)) ;; @@ -2000,10 +2027,10 @@ if [[ $EUID -ne 0 ]]; then exit 1 else if [[ "$param1" == "init" ]]; then - # Ask where app data goes before anything is validated or created. - # No-op when unattended, when --containers-dir was passed, or when - # there is only one possible answer. - initPickContainersDir + # Ask where things go before anything is validated or created. + # No-op when unattended, when the flags were passed, or when there + # is only one possible answer. + initPickRoots libreportalDerivePaths # Validate the chosen install roots before creating/baking anything.