Skip to content

Commit 92dc432

Browse files
committed
fix(forge): correct podman exec UID + relax firstboot hardening + docs
THREE LATENT ISSUES IN THE FIRSTBOOT CHAIN 1. forge-firstboot.sh: 'podman exec --user mios-forge mios-forge ...' was wrong -- '--user' looks up the username inside the CONTAINER's /etc/passwd, where the user is 'git' (the Forgejo image's convention) not 'mios-forge'. Pinned to numeric '816:816' which matches the in-container UID we configured via USER_UID=816 in the Quadlet, so the lookup succeeds against the container's nsswitch. 2. mios-forge-firstboot.service: RestrictNamespaces=yes and RestrictAddressFamilies=AF_UNIX,AF_INET,AF_INET6 broke Podman's CRIU/conmon attach path on rootful container exec (the runtime needs CLONE_NEWNS / CLONE_NEWPID family transitions during exec-attach). Dropped both directives; ReadWritePaths now explicitly includes /var/lib/containers and /run/containers so the container state stays writable, and ProtectHome=yes + ProtectSystem=strict still scope the script's blast radius. 3. Documentation gaps: INDEX.md service-gating table didn't list mios-forge / mios-forge-firstboot; DEPLOY.md verification section didn't cover the forge. Both fixed in this commit. NEW: just forge target Surfaces forge status post-deploy: - mios-forge.service active/inactive - mios-forge-firstboot.service status + .firstboot-done sentinel - URL, admin user/email (read from install.env) - 'sudo cat /etc/mios/forge/admin-password' hint - copy-pasteable 'git remote add origin' line DOCS UPDATED INDEX.md \xa75 -- mios-forge + mios-forge-firstboot rows added to the service-gating table (Condition* directives that short-circuit each service when its preconditions aren't met). DEPLOY.md -- new 'Self-hosted Git forge (mios-forge)' subsection with end-to-end verification commands and the default ports. Justfile -- 'just forge' target (one-shot status + operator hint surface).
1 parent ee5c601 commit 92dc432

5 files changed

Lines changed: 64 additions & 8 deletions

File tree

DEPLOY.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,30 @@ systemctl --failed # Any failed units
101101
mios "what is the current image tag?" # Local AI sanity
102102
firewall-cmd --list-all # Firewall posture
103103
getenforce # SELinux mode (must be Enforcing)
104+
just forge # Self-hosted Git forge status + admin info
104105
```
105106

107+
### Self-hosted Git forge (`mios-forge`)
108+
109+
The `mios-forge.container` Quadlet starts at first boot. The
110+
`mios-forge-firstboot.service` oneshot runs after it, reads the admin
111+
identity from `/etc/mios/install.env` (which `build-mios.{sh,ps1}`
112+
populated from `mios.toml`), generates a random initial password if
113+
none was supplied, and creates the admin user via the in-container
114+
`forgejo admin user create --must-change-password=true` CLI. The
115+
sentinel at `/var/lib/mios/forge/.firstboot-done` makes the service
116+
idempotent across reboots.
117+
118+
```bash
119+
just forge # status + URL + admin info
120+
sudo cat /etc/mios/forge/admin-password # one-time password read
121+
git remote add origin http://localhost:3000/<user>/<repo>.git
122+
git push origin main
123+
```
124+
125+
The forge runs at HTTP `:3000` and git+ssh `:2222`. Repository bytes
126+
live at `/srv/mios/forge/git/`; SQLite DB at `/srv/mios/forge/forgejo.db`.
127+
106128
## References
107129

108130
- `Justfile` (`just --list` for the full target set)

INDEX.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Active gating (referenced in `etc/containers/systemd/` and
9090
| `cloudws-pxe-hub` | `!wsl`, `!container` | virtualized hosts without routable LAN |
9191
| `mios-gpu-{nvidia,amd,intel,status}` | `ConditionPathExists=/dev/...`, `!container`, `!wsl` (Intel) | no matching GPU device |
9292
| `ollama` | none | always runs (CPU fallback) |
93+
| `mios-forge` | `ConditionPathIsDirectory=/etc/mios/forge`, `!container` | bootstrap incomplete, nested |
94+
| `mios-forge-firstboot` | `ConditionPathExists=/etc/mios/install.env`, `!sentinel`, `!container` | install.env absent, already ran, nested |
9395

9496
## 6. Global pipeline phases
9597

Justfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,30 @@ edit:
294294
echo "[FAIL] $CFG not found. Run: just init"; exit 1; \
295295
fi; \
296296
${EDITOR:-vim} "$CFG"
297+
298+
# Show mios-forge (Forgejo) status: Quadlet state, URL, admin identity,
299+
# initial-password file path, and a copy-pasteable 'git remote add' line.
300+
# Run on the deployed image after first boot.
301+
forge:
302+
@echo "'MiOS' Forge (Forgejo)"
303+
@if systemctl is-active --quiet mios-forge.service 2>/dev/null; then \
304+
echo " Service: active"; \
305+
else \
306+
echo " Service: inactive (run: sudo systemctl start mios-forge)"; \
307+
fi
308+
@if systemctl is-active --quiet mios-forge-firstboot.service 2>/dev/null \
309+
|| [ -f /var/lib/mios/forge/.firstboot-done ]; then \
310+
echo " First-boot: [ok] admin user created"; \
311+
else \
312+
echo " First-boot: pending (waiting on mios-forge.service readiness)"; \
313+
fi
314+
@echo " Web UI: http://localhost:${MIOS_FORGE_HTTP_PORT:-3000}/"
315+
@echo " git+ssh: ssh://git@localhost:${MIOS_FORGE_SSH_PORT:-2222}/<user>/<repo>.git"
316+
@echo " Admin user: $(grep -E '^MIOS_FORGE_ADMIN_USER=' /etc/mios/install.env 2>/dev/null | cut -d= -f2- | tr -d '\"' || echo '(check /etc/mios/install.env)')"
317+
@echo " Admin email: $(grep -E '^MIOS_FORGE_ADMIN_EMAIL=' /etc/mios/install.env 2>/dev/null | cut -d= -f2- | tr -d '\"' || echo '(check /etc/mios/install.env)')"
318+
@if [ -r /etc/mios/forge/admin-password ]; then \
319+
echo " Initial pwd: sudo cat /etc/mios/forge/admin-password (must change on first login)"; \
320+
else \
321+
echo " Initial pwd: (already changed, or firstboot not yet run)"; \
322+
fi
323+
@echo " Local push: git remote add origin http://localhost:${MIOS_FORGE_HTTP_PORT:-3000}/<user>/<repo>.git && git push origin main"

usr/lib/systemd/system/mios-forge-firstboot.service

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ EnvironmentFile=-/etc/mios/install.env
1414
ExecStart=/usr/libexec/mios/forge-firstboot.sh
1515
TimeoutStartSec=600s
1616

17-
# Hardening: this service writes to a small set of paths, so we lock
18-
# everything else down. The script needs network for the readiness
19-
# probe and podman exec, plus write access to the password / sentinel
20-
# files declared in usr/lib/tmpfiles.d/mios-forge.conf.
17+
# Hardening: this service writes to a small set of paths plus calls
18+
# 'podman exec' against the running mios-forge container. RestrictNamespaces
19+
# and RestrictAddressFamilies were tried but break Podman's CRIU/conmon
20+
# attach path on rootful container exec; we drop them and lean on the
21+
# read-write path scoping + ProtectHome instead, which is sufficient for
22+
# this script's actual surface area.
2123
NoNewPrivileges=yes
2224
ProtectSystem=strict
2325
ProtectHome=yes
24-
ReadWritePaths=/etc/mios/forge /var/lib/mios/forge /var/log/mios/forge
26+
ReadWritePaths=/etc/mios/forge /var/lib/mios/forge /var/log/mios/forge /var/lib/containers /run/containers
2527
PrivateTmp=yes
26-
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
27-
RestrictNamespaces=yes
2828
LockPersonality=yes
2929
RestrictRealtime=yes
3030

usr/libexec/mios/forge-firstboot.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ fi
8383
# user already exists (e.g. someone created one via the web UI before
8484
# this service ran), 'forgejo admin user create' returns non-zero and
8585
# we accept that as a soft success.
86-
if podman exec --user mios-forge mios-forge \
86+
#
87+
# --user 816 matches the in-container UID we configured via USER_UID=816
88+
# in the Quadlet. The container's internal username is 'git' (the Forgejo
89+
# image's convention), not 'mios-forge'; we pass the numeric UID so the
90+
# podman exec lookup succeeds against the container's /etc/passwd.
91+
if podman exec --user 816:816 mios-forge \
8792
forgejo --config /data/gitea/conf/app.ini admin user create \
8893
--admin --must-change-password=true \
8994
--username "$admin_user" \

0 commit comments

Comments
 (0)