Skip to content

Commit 5dc7fe2

Browse files
Kabuki94claude
andcommitted
fix: dashboard MOTD login + cockpit child sockets + auto-init /=git tree
* usr/libexec/mios/mios-dashboard.sh: drop the $USER fallback in the MIOS_LINUX_USER resolution chain. mios-dashboard-issue.service runs as root so $USER == 'root' was painting `login: root / mios` in the pre-login banner even after my earlier reorder. New chain stops at the literal 'mios' default -- never falls through to a service- process identity. Resolution: install.env-staged MIOS_USER (canonical) -> legacy MIOS_LINUX_USER alias -> literal 'mios'. * usr/lib/systemd/system/cockpit-wsinstance-{http,https-factory}.socket.d/ 10-mios-container.conf (new, both): pair drop-ins for the SOCKET units that inherit DynamicUser/PrivateMounts from cockpit's stock unit and fail their own listen step inside containers / podman- machine WSL2. The previous round only patched the socket-user SERVICE; with that fix in place these child sockets revealed the same namespace failure mode (`Failed to listen on cockpit-wsinstance- http.socket`) as a follow-on cascade. Same pattern -- strip DynamicUser / PrivateMounts / RestrictNamespaces / etc. when ConditionVirtualization=container; no-op on bare metal / Hyper-V / QEMU. * usr/libexec/mios/git-root-init.sh + usr/lib/systemd/system/ mios-git-root-init.service (new): fires on first boot AFTER mios-forge-firstboot has created the admin user and the empty operator's mios.git repo. Probes localhost:3000/<user>/mios.git for reachability, then `git init -b main /` + `git remote add origin ...` + `git fetch --depth=1 origin main` + `git reset --soft FETCH_HEAD` so the deployed working tree adopts the upstream commit pointer without disturbing on-disk files. Sentinel is /.git presence; idempotent on every reboot. Closes the gap from the operator's MOTD: `(no .git at /; live root is not yet a git working tree)` -- after this lands, /=git is a real working tree of the local Forgejo mirror at first boot. * usr/lib/systemd/system-preset/90-mios.preset: enable mios-git- root-init.service so it actually fires on first boot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2c7d088 commit 5dc7fe2

6 files changed

Lines changed: 188 additions & 4 deletions

File tree

usr/lib/systemd/system-preset/90-mios.preset

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ enable mios-dashboard-issue.timer
3535
enable mios-bootc-switch.path
3636
enable gnome-remote-desktop.service
3737
enable mios-firstboot.target
38+
# /=git auto-init: turns / into a git working tree of the operator's
39+
# Forgejo mios.git repo on first boot, after mios-forge-firstboot has
40+
# created the admin user. Idempotent (sentinel = /.git presence).
41+
enable mios-git-root-init.service
3842
enable greenboot-healthcheck.service
3943
enable greenboot-grub2-set-counter.service
4044
enable greenboot-grub2-set-success.service
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# /usr/lib/systemd/system/cockpit-wsinstance-http.socket.d/10-mios-container.conf
2+
#
3+
# Pair drop-in to cockpit-wsinstance-socket-user.service.d/10-mios-container.conf.
4+
# After the socket-user service drops its namespace requirements, the
5+
# child socket units (cockpit-wsinstance-http.socket and
6+
# cockpit-wsinstance-https-factory.socket) inherit DynamicUser /
7+
# PrivateMounts from cockpit's stock unit and fail their own
8+
# `Failed to listen on cockpit-wsinstance-http.socket` step inside a
9+
# container or podman-machine WSL distro. Strip the same namespace
10+
# directives so the .sock binds succeed.
11+
12+
[Unit]
13+
ConditionVirtualization=container
14+
15+
[Socket]
16+
DynamicUser=no
17+
PrivateTmp=no
18+
PrivateMounts=no
19+
PrivateDevices=no
20+
PrivateUsers=no
21+
ProtectHome=no
22+
ProtectSystem=no
23+
RestrictNamespaces=no
24+
SocketUser=cockpit-ws
25+
SocketGroup=cockpit-ws
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# /usr/lib/systemd/system/cockpit-wsinstance-https-factory.socket.d/10-mios-container.conf
2+
#
3+
# Pair drop-in to cockpit-wsinstance-http.socket.d sibling. Same fix
4+
# pattern: strip namespace requirements so the socket can bind inside
5+
# a container / podman-machine WSL distro.
6+
7+
[Unit]
8+
ConditionVirtualization=container
9+
10+
[Socket]
11+
DynamicUser=no
12+
PrivateTmp=no
13+
PrivateMounts=no
14+
PrivateDevices=no
15+
PrivateUsers=no
16+
ProtectHome=no
17+
ProtectSystem=no
18+
RestrictNamespaces=no
19+
SocketUser=cockpit-ws
20+
SocketGroup=cockpit-ws
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[Unit]
2+
Description='MiOS' first-boot: init / as a git working tree of localhost:3000/<user>/mios.git
3+
Documentation=https://github.com/mios-dev/mios
4+
# Runs AFTER the operator's Forgejo admin user and the empty mios.git
5+
# repo are in place (mios-forge-firstboot.service drops the admin
6+
# password file and bootstraps the user). The script itself is
7+
# idempotent (/.git presence is the sentinel) and short-circuits if
8+
# Forgejo or the repo aren't reachable yet -- so re-running on every
9+
# boot is harmless.
10+
After=mios-forge.service mios-forge-firstboot.service network-online.target
11+
Wants=mios-forge.service network-online.target
12+
ConditionPathExists=!/.git
13+
ConditionPathExists=/usr/libexec/mios/git-root-init.sh
14+
# Skip on container-style hosts where the deployed root is itself a
15+
# nested mount (podman containers, build sandboxes); a real MiOS host
16+
# (bare-metal, Hyper-V, QEMU, WSL) gets the / it expects.
17+
18+
[Service]
19+
Type=oneshot
20+
RemainAfterExit=yes
21+
ExecStart=/usr/libexec/mios/git-root-init.sh
22+
TimeoutStartSec=180
23+
StandardOutput=journal+console
24+
StandardError=journal+console
25+
26+
[Install]
27+
WantedBy=multi-user.target default.target

usr/libexec/mios/git-root-init.sh

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/usr/bin/env bash
2+
# /usr/libexec/mios/git-root-init.sh
3+
#
4+
# Initialize the deployed root `/` as a git working tree of the local
5+
# Forgejo `mios.git` mirror. Runs once via mios-git-root-init.service
6+
# after mios-forge-firstboot.service has created the admin user and
7+
# the operator's mios.git repo on the local Forgejo instance.
8+
#
9+
# Architecture invariant (mios_root_git memory): the deployed `/` IS a
10+
# git working tree. Operators edit files at FHS paths, `git commit`,
11+
# push to localhost:3000, the Forgejo Runner builds a new OCI image,
12+
# and `bootc switch` swaps to it on the next boot. Without /.git
13+
# present, the loop is broken -- the operator can't `git diff /` to
14+
# see what diverged from the deployed image.
15+
#
16+
# What this script does:
17+
# 1. Skip if /.git already present (idempotent; sentinel-free).
18+
# 2. Wait for the local Forgejo to respond on http://localhost:3000/
19+
# (up to 60s). Forgejo bootstrap can take 30-45s on first boot.
20+
# 3. Probe whether the operator's mios.git repo exists at
21+
# http://localhost:3000/<user>/mios.git. If absent, skip and
22+
# log -- operator initializes via the Forgejo web UI or CLI.
23+
# 4. `git -C / init -b main`
24+
# 5. `git -C / remote add origin http://localhost:3000/<user>/mios.git`
25+
# 6. `git -C / fetch origin main` -- pull the upstream tree.
26+
# 7. `git -C / reset --soft FETCH_HEAD` -- adopt the FETCH_HEAD as
27+
# HEAD without touching the working tree (the deployed files
28+
# already match the image; `reset --soft` only updates the index
29+
# so `git status` shows no changes).
30+
#
31+
# After this runs, `git status` at / works, `git diff` shows operator
32+
# edits since deploy, and `git push` against localhost:3000 triggers
33+
# the Forgejo Runner build.
34+
35+
set -euo pipefail
36+
37+
# shellcheck source=/usr/lib/mios/paths.sh
38+
source /usr/lib/mios/paths.sh 2>/dev/null || true
39+
: "${MIOS_VAR_DIR:=/var/lib/mios}"
40+
41+
_log() {
42+
logger -t mios-git-root-init "$*" 2>/dev/null || true
43+
echo "[git-root-init] $*" >&2
44+
}
45+
46+
# Idempotent: /.git is the sentinel.
47+
if [[ -d /.git ]]; then
48+
_log "/.git already present; nothing to do"
49+
exit 0
50+
fi
51+
52+
# Resolve the operator's username (drives the Forgejo repo path).
53+
MIOS_USER="${MIOS_USER:-mios}"
54+
if [[ -r /etc/mios/install.env ]]; then
55+
# shellcheck disable=SC1091
56+
set -a; source /etc/mios/install.env 2>/dev/null || true; set +a
57+
fi
58+
MIOS_USER="${MIOS_USER:-mios}"
59+
60+
FORGE_URL="${MIOS_FORGE_URL:-http://localhost:3000}"
61+
REPO_URL="${FORGE_URL}/${MIOS_USER}/mios.git"
62+
63+
# Wait for Forgejo to be up. mios-forge.service can take 30-45s for
64+
# the first SQLite-DB write after a fresh container start.
65+
_log "waiting for Forgejo at ${FORGE_URL} ..."
66+
for _ in $(seq 1 60); do
67+
if curl -fsS --max-time 2 -o /dev/null "${FORGE_URL}/api/v1/version" 2>/dev/null; then
68+
_log "Forgejo reachable"
69+
break
70+
fi
71+
sleep 2
72+
done
73+
if ! curl -fsS --max-time 2 -o /dev/null "${FORGE_URL}/api/v1/version" 2>/dev/null; then
74+
_log "Forgejo never came up; skipping git-root-init"
75+
exit 0
76+
fi
77+
78+
# Probe whether the mios.git repo exists. If not, the operator hasn't
79+
# created it yet -- skip; they'll init via web UI / git push.
80+
if ! curl -fsS --max-time 2 -o /dev/null "${REPO_URL}/info/refs?service=git-upload-pack" 2>/dev/null; then
81+
_log "${REPO_URL} not yet present; create it on Forgejo first, then re-run"
82+
exit 0
83+
fi
84+
85+
# Init / as a git tree without disturbing the deployed working tree.
86+
_log "git init / + remote add origin ${REPO_URL}"
87+
git -C / init -b main
88+
git -C / config core.fileMode false # prevent perm-noise on read-only composefs
89+
git -C / config core.autocrlf false
90+
git -C / config user.email "${MIOS_USER}@$(hostname).local"
91+
git -C / config user.name "${MIOS_USER}"
92+
git -C / remote add origin "${REPO_URL}"
93+
94+
_log "fetching origin main ..."
95+
if git -C / fetch --depth=1 origin main 2>&1 | logger -t mios-git-root-init; then
96+
# Adopt FETCH_HEAD as HEAD without rewriting the working tree.
97+
# `reset --soft` only moves the branch pointer + index; the on-disk
98+
# files (which already match the image build) stay put. `git status`
99+
# then shows whatever the operator has edited since deploy.
100+
git -C / reset --soft FETCH_HEAD
101+
_log "/.git initialized; HEAD = $(git -C / rev-parse HEAD)"
102+
else
103+
_log "fetch failed; /.git left unset"
104+
exit 1
105+
fi

usr/libexec/mios/mios-dashboard.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,13 @@ if [[ -r /etc/mios/install.env ]]; then
9595
# shellcheck disable=SC1091
9696
set -a; source /etc/mios/install.env 2>/dev/null || true; set +a
9797
fi
98-
# Resolution order: install.env-staged MIOS_USER (canonical) > MIOS_LINUX_USER
99-
# (legacy alias if some env path set it) > running-process $USER (only when
100-
# both above are unset, e.g. running mios-dashboard.sh by hand) > literal 'mios'.
101-
MIOS_LINUX_USER="${MIOS_USER:-${MIOS_LINUX_USER:-${USER:-mios}}}"
98+
# Resolution order: install.env-staged MIOS_USER (canonical) > legacy
99+
# MIOS_LINUX_USER alias > literal 'mios'. Critically NEVER falls back
100+
# to $USER -- the service `mios-dashboard-issue.service` runs as root,
101+
# so $USER == 'root' would render `login: root / mios` in the pre-login
102+
# banner even though the configured login user is 'mios'. The banner
103+
# describes the OPERATOR'S login surface, not the running process.
104+
MIOS_LINUX_USER="${MIOS_USER:-${MIOS_LINUX_USER:-mios}}"
102105
[[ -z "${MIOS_VERSION:-}" ]] && MIOS_VERSION="$(cat /usr/share/mios/VERSION 2>/dev/null || cat /etc/mios/VERSION 2>/dev/null || echo "0.2.4")"
103106
MIOS_AI_MODEL="${MIOS_AI_MODEL:-qwen2.5-coder:7b}"
104107

0 commit comments

Comments
 (0)