home · contact · privacy
Fix install_server.sh putting repo where user can't find or link into it. master
authorPlom Heller <plom@plomlompom.com>
Wed, 23 Sep 2026 06:58:28 +0000 (08:58 +0200)
committerPlom Heller <plom@plomlompom.com>
Wed, 23 Sep 2026 06:58:28 +0000 (08:58 +0200)
CLAUDE.md
scripts/install_server.sh

index 6c186320e30ba06492e05aef27cdc30fcd4f3a0e..be6a695dcc82404641700c19cbb2417a33584d95 100644 (file)
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -89,7 +89,7 @@ root, so all of it is present on every target.
 - First login on a new remote server: triggered by `install_server.sh`.
 - Both first-login scripts can be rerun by hand later (e.g.
   `/data/confplom/scripts/start_root_t490s.sh`, or
 - First login on a new remote server: triggered by `install_server.sh`.
 - Both first-login scripts can be rerun by hand later (e.g.
   `/data/confplom/scripts/start_root_t490s.sh`, or
-  `~/confplom/scripts/start_root_server.sh`); so can `start_user.sh`.
+  `/opt/confplom/scripts/start_root_server.sh`); so can `start_user.sh`.
 
 There is no test runner. For logic changes prefer a dry read-through plus
 `sh -n`/`shellcheck -s dash`; never execute the scripts against real block
 
 There is no test runner. For logic changes prefer a dry read-through plus
 `sh -n`/`shellcheck -s dash`; never execute the scripts against real block
@@ -427,11 +427,22 @@ Run from the admin's workstation; one argument, `server`
 (`LOGIN="root@${SERVER}"`). Targets a server assumed reachable over SSH as
 root (e.g. a provider-handed fresh VM); together with `start_root_server.sh`
 it exists to lock that root access back down. `check_tools scp ssh ssh-keygen
 (`LOGIN="root@${SERVER}"`). Targets a server assumed reachable over SSH as
 root (e.g. a provider-handed fresh VM); together with `start_root_server.sh`
 it exists to lock that root access back down. `check_tools scp ssh ssh-keygen
-tar`. Constants: `FNAME_REPO_TAR=repo.tar`;
+tar`. Constants: `FNAME_REPO_TAR=repo.tar`; `PATH_OPT=/opt`;
 `OPTS_SSH_NEW_HOST="-o StrictHostKeyChecking=accept-new"` on every
 `scp`/`ssh` (a first-ever connection would otherwise block on a prompt);
 `OPTS_SSH_NEW_HOST="-o StrictHostKeyChecking=accept-new"` on every
 `scp`/`ssh` (a first-ever connection would otherwise block on a prompt);
-`PATH_REPO_PARENT`; `RELPATH_SETUP_SCRIPT` (remote path to
-`start_root_server.sh`, built from `DIRNAME_REPO`/`DIRNAME_SCRIPTS`).
+`PATH_REPO_PARENT`; `PATH_REMOTE_REPO` (`/opt/<repo-dirname>`) and
+`PATH_SETUP_SCRIPT` (remote path to `start_root_server.sh`, built from it and
+`DIRNAME_SCRIPTS`).
+
+**Why `/opt`:** the landed repo must be readable by the unprivileged user
+too, since `start_user.sh` runs as that account and `link_home` symlinks its
+home into the repo — root's `~` would be unreachable for both. `/opt` is
+FHS's place for a self-contained add-on tree keeping its own internal layout
+(as opposed to `/usr/local`'s `bin`/`share` split); `/srv` is for data the
+host *serves*, which this checkout isn't (`PATH_GIT_BASE` is); a server-side
+`/data` would suggest a shared LV that doesn't exist there. The local track
+keeps `/data/<repo-dirname>` because there it genuinely is shared across
+installs.
 
 1. `mktemp --directory` locally; `tar cf` the whole repo checkout (same
    "whole repo, not just `scripts/`" principle as `setup_luksvg.sh`).
 
 1. `mktemp --directory` locally; `tar cf` the whole repo checkout (same
    "whole repo, not just `scripts/`" principle as `setup_luksvg.sh`).
@@ -441,13 +452,19 @@ tar`. Constants: `FNAME_REPO_TAR=repo.tar`;
    `ssh-keygen -R` on a nonexistent file exits non-zero, which would abort
    under errexit on a from-scratch admin account.
 3. `scp` the tar to `${LOGIN}:~`; remove the local temp dir.
    `ssh-keygen -R` on a nonexistent file exits non-zero, which would abort
    under errexit on a from-scratch admin account.
 3. `scp` the tar to `${LOGIN}:~`; remove the local temp dir.
-4. `ssh` in to `tar xf --no-same-owner` (lands at `~/<repo-dirname>`). The
-   flag matters: GNU `tar` as root defaults to `--same-owner`, which would
-   restore the *workstation's* numeric UID onto files under `/root`; that
-   silent mismatch would otherwise make any later root-run `git` operation
-   against that tree refuse it as "dubious ownership". Then `ssh -t`
-   (pseudo-tty, since the script ends prompting for a password) to run
-   `./${RELPATH_SETUP_SCRIPT}`.
+4. `ssh` in to `tar xf -C /opt --no-same-owner` (lands at
+   `${PATH_REMOTE_REPO}`), then remove the uploaded tar. The flag matters:
+   GNU `tar` as root defaults to `--same-owner`, which would restore the
+   *workstation's* numeric UID onto the files; that silent mismatch would
+   otherwise make any later root-run `git` operation against that tree
+   refuse it as "dubious ownership". (Root-owned, the tree is still readable
+   by the user as long as the checkout's modes are the usual 644/755, which
+   GNU `tar` as root preserves.) Then `ssh -t` (pseudo-tty, since the script
+   ends prompting for a password) to run `${PATH_SETUP_SCRIPT}`.
+
+Not rerunnable once `start_root_server.sh` got as far as disabling root SSH;
+before that (e.g. an `apt-get` failure), a rerun just unpacks over the
+existing tree.
 
 No `YES!` gate: nothing here is destructive; the point of no easy return
 (locking root SSH) happens on the remote end inside `start_root_server.sh`.
 
 No `YES!` gate: nothing here is destructive; the point of no easy return
 (locking root SSH) happens on the remote end inside `start_root_server.sh`.
@@ -456,8 +473,8 @@ No `YES!` gate: nothing here is destructive; the point of no easy return
 
 `start_root_t490s.sh`, `start_root_server.sh`, `start_user.sh` live in
 `scripts/` only so they ride along on the `data` LV / the pushed tar. They
 
 `start_root_t490s.sh`, `start_root_server.sh`, `start_user.sh` live in
 `scripts/` only so they ride along on the `data` LV / the pushed tar. They
-run *inside* a target, invoked by absolute or `~`-relative path (e.g.
-`/data/confplom/scripts/...`, `~/confplom/scripts/...`), never against the
+run *inside* a target, invoked by absolute path (e.g.
+`/data/confplom/scripts/...`, `/opt/confplom/scripts/...`), never against the
 rescue environment's LUKS/LVM state; that's why `_lib.sh`'s `cd` and
 `${PATH_REPO}` matter more for them than for the LUKS-facing scripts. They
 use only narration, basics, `PATH_REPO`, `link_home` and `start_root`.
 rescue environment's LUKS/LVM state; that's why `_lib.sh`'s `cd` and
 `${PATH_REPO}` matter more for them than for the LUKS-facing scripts. They
 use only narration, basics, `PATH_REPO`, `link_home` and `start_root`.
@@ -723,7 +740,7 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
   resolves the real path before computing `sys.path[0]`, so
   `from lib.argparsing import ArgParser` loads *this repo's own*
   `home/user/.local/bin/lib/` (`/data/confplom/...` locally,
   resolves the real path before computing `sys.path[0]`, so
   `from lib.argparsing import ArgParser` loads *this repo's own*
   `home/user/.local/bin/lib/` (`/data/confplom/...` locally,
-  `~/confplom/...` remotely), never the symlinked copy `link_home` places
+  `/opt/confplom/...` remotely), never the symlinked copy `link_home` places
   under `~/.local/bin/lib/`. Harmless (identical content) but worth knowing.
   `status.py` reaches the same directory explicitly by appending
   `Path(__file__).resolve().parents[2] / '.local' / 'bin' / 'lib'` to
   under `~/.local/bin/lib/`. Harmless (identical content) but worth knowing.
   `status.py` reaches the same directory explicitly by appending
   `Path(__file__).resolve().parents[2] / '.local' / 'bin' / 'lib'` to
@@ -752,6 +769,6 @@ in `.profile.d/` with bare names, sourced by the loop line in `~/.profile`.
 - Line length 80 columns; wrap with backslash-newline (inside double-quoted
   strings it's elided rather than becoming a literal newline) or, where it
   reads as clearly, factor the long expression into a named constant first
 - Line length 80 columns; wrap with backslash-newline (inside double-quoted
   strings it's elided rather than becoming a literal newline) or, where it
   reads as clearly, factor the long expression into a named constant first
-  (as `install_server.sh` does with `RELPATH_SETUP_SCRIPT`).
+  (as `install_server.sh` does with `PATH_SETUP_SCRIPT`).
 - New `mount` calls in the LUKS/VG-facing scripts must go through
   `mount_privately` (see "Shared library").
 - New `mount` calls in the LUKS/VG-facing scripts must go through
   `mount_privately` (see "Shared library").
index ac52aa757ef7dde7b65d67b69a4da7bf5410d158..3f2b902b6cb94f791675faeb4aefd8ba700a802f 100755 (executable)
@@ -11,10 +11,12 @@ include usage
 
 # constants we might want to change at some point
 FNAME_REPO_TAR=repo.tar
 
 # constants we might want to change at some point
 FNAME_REPO_TAR=repo.tar
+PATH_OPT=/opt
 
 # constants derived from changeables
 PATH_REPO_PARENT=$(dirname "${PATH_REPO}")
 
 # constants derived from changeables
 PATH_REPO_PARENT=$(dirname "${PATH_REPO}")
-RELPATH_SETUP_SCRIPT="${DIRNAME_REPO}/${DIRNAME_SCRIPTS}/start_root_server.sh"
+PATH_REMOTE_REPO="${PATH_OPT}/${DIRNAME_REPO}"
+PATH_SETUP_SCRIPT="${PATH_REMOTE_REPO}/${DIRNAME_SCRIPTS}/start_root_server.sh"
 
 # inputs to confirm
 usage $# "server"
 
 # inputs to confirm
 usage $# "server"
@@ -36,8 +38,10 @@ msg 'Uploading repo tar to server …'
 scp ${OPTS_SSH_NEW_HOST} "${PATH_REPO_TAR}" "${LOGIN}:~"
 rm -rf "${PATH_TEMP}"
 
 scp ${OPTS_SSH_NEW_HOST} "${PATH_REPO_TAR}" "${LOGIN}:~"
 rm -rf "${PATH_TEMP}"
 
-msg 'Unpacking repo and running setup script …'
+msg 'Unpacking repo to %s and running setup script …' "${PATH_REMOTE_REPO}"
 # --no-same-owner owns the files to root, preventing "dubious ownership" for
 # any later git operation against this tree (by hand, or a future script)!
 # --no-same-owner owns the files to root, preventing "dubious ownership" for
 # any later git operation against this tree (by hand, or a future script)!
-ssh ${OPTS_SSH_NEW_HOST} "${LOGIN}" tar xf "${FNAME_REPO_TAR}" --no-same-owner
-ssh -t ${OPTS_SSH_NEW_HOST} "${LOGIN}" "./${RELPATH_SETUP_SCRIPT}"
+ssh ${OPTS_SSH_NEW_HOST} "${LOGIN}" \
+        "tar xf ${FNAME_REPO_TAR} -C ${PATH_OPT} --no-same-owner" \
+        "&& rm ${FNAME_REPO_TAR}"
+ssh -t ${OPTS_SSH_NEW_HOST} "${LOGIN}" "${PATH_SETUP_SCRIPT}"