Commit graph

79 commits

Author SHA1 Message Date
Claude
7b37f88fb5
fix: use internal Forgejo URL for act_runner — avoids routing through public IP
The runner is on hiy-net and can reach Forgejo directly at http://forgejo:3000
rather than going out through the public IP and Caddy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 15:35:13 +00:00
Claude
84c36c464d
fix: use forgejo-runner register --token instead of create-runner-file --secret
The UI registration token is not a hex string — create-runner-file --secret
expects a hex secret. Use the register subcommand with --token instead,
which accepts the token from the Forgejo UI directly.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 15:28:59 +00:00
Shautvast
868bfbc365 container name removed 2026-03-27 15:58:06 +01:00
Claude
4ac5700ac5
fix: add runner-entrypoint.sh to register Forgejo runner on first start
The data.forgejo.org/forgejo/runner image doesn't auto-register from
env vars — it needs create-runner-file called explicitly before the
daemon starts. The entrypoint handles registration on first run (no
/data/.runner file) then execs the daemon on all subsequent starts.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 14:41:45 +00:00
Claude
3afdc66ec2
fix: correct Forgejo runner image — data.forgejo.org/forgejo/runner:6
code.forgejo.org is the source repo, not the container registry.
The OCI registry is data.forgejo.org and the image is 'runner', not 'act_runner'.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 14:37:02 +00:00
Claude
f9eacd03be
fix: add container_name to act_runner to bypass podman-compose naming bug
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 14:33:54 +00:00
Claude
99ab28d3be
fix: rename act-runner to act_runner — podman-compose chokes on hyphens in service names
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 14:30:30 +00:00
Claude
e80c3dc9a4
feat: add install.sh for fresh Raspberry Pi setup
Covers everything that was done manually on the Pi:
- apt packages: podman, aardvark-dns, sqlite3, git, uidmap, python3-pip
- podman-compose via pip (to ~/.local/bin)
- rclone (optional, prompted)
- .env creation from template with prompted values and generated passwords
- git upstream tracking for auto-update
- hands off to start.sh at the end

Safe to re-run — all steps are idempotent.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 13:52:28 +00:00
Claude
8561ee3e74
feat: add systemd timer for automatic git pull + service restart
auto-update.sh fetches origin every 5 minutes. If new commits are
found it pulls and selectively restarts only what changed:
- server/ or Cargo.*  → rebuild + restart server container
- docker-compose.yml  → full stack up -d
- proxy/Caddyfile     → caddy reload
- anything else       → no restart needed

start.sh now installs hiy-update.service + hiy-update.timer alongside
the existing hiy.service boot unit.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 13:47:12 +00:00
Claude
4ef77bf255
feat: add Forgejo Actions runner (act_runner) to docker-compose
Adds the act-runner service alongside Forgejo. It connects to the
Podman socket proxy so CI jobs can build and run containers on the Pi.

Also enables FORGEJO__actions__ENABLED on the Forgejo service.

FORGEJO_RUNNER_TOKEN must be set in .env — obtain it from:
  Forgejo → Site Administration → Actions → Runners → Create new runner

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 13:33:00 +00:00
Claude
0fb3a6bfe1
fix: add PATH to systemd service so podman-compose is found at boot
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-27 10:23:38 +00:00
Claude
b7430cbb65
fix: add --transfers 1 --retries 5 to rclone — workaround for Proton Drive parallel upload bug
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 15:44:46 +00:00
Claude
84ac8f3b9f
fix: copy hiy.db out of container before dumping — server image has no sqlite3
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 15:32:26 +00:00
Claude
e8d303f184
feat: extend backup script and add restore script
backup.sh now covers all data:
- SQLite via podman exec into server container (fallback to host path)
- Postgres via pg_dumpall inside postgres container
- Forgejo data volume via podman volume export
- Caddy TLS certificates via podman volume export
- .env file (plaintext secrets — store archive securely)

restore.sh reverses each step: imports volumes, restores Postgres,
restores SQLite, optionally restores .env (--force to overwrite).

Both scripts find containers dynamically via compose service labels
so they work regardless of the container name podman-compose assigns.

.env.example documents HIY_BACKUP_DIR, HIY_BACKUP_REMOTE,
HIY_BACKUP_RETAIN_DAYS.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 15:06:16 +00:00
Claude
d3ef4d2030
fix: use /bin/sh in postgres init script — Alpine has no bash
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:47:58 +00:00
Claude
de4b5c49ab
fix: drop service_healthy depends_on — podman-compose doesn't support it
Forgejo restart: unless-stopped handles the retry loop until Postgres is ready.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:41:27 +00:00
Claude
bd863cdf33
fix: hardcode pg_isready args to avoid podman-compose $$ escaping issue
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:33:57 +00:00
Claude
22a6ab103c
fix: wait for Postgres to be ready before starting Forgejo
Adds a pg_isready healthcheck to the postgres service and upgrades the
Forgejo depends_on to condition: service_healthy, preventing the
"connection refused" crash on startup.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:27:10 +00:00
Claude
ea172ae336
feat: lock Forgejo install wizard via env var
Sets FORGEJO__security__INSTALL_LOCK=true so Forgejo skips the first-run
wizard and uses the env var configuration directly.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:21:09 +00:00
Claude
36b89d7620
fix: use FORGEJO_DB_PASSWORD env var in postgres init script
Replaced hardcoded 'CHANGE_ME' in the SQL init file with a shell script
that reads FORGEJO_DB_PASSWORD from the environment. Also pass the variable
into the postgres service in docker-compose.yml so it is available at init time.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 11:11:53 +00:00
Claude
9ba81bd809
fix: drop Caddy --resume, restore app routes from DB on startup
--resume caused Caddyfile changes (e.g. new Forgejo block) to be silently
ignored on restart because Caddy preferred its saved in-memory config.

Instead, Caddy now always starts clean from the Caddyfile, and the HIY
server re-registers every app's Caddy route from the DB on startup
(restore_caddy_routes). This gives us the best of both worlds:
- Caddyfile changes (static services, TLS config) are always picked up
- App routes are restored automatically without needing a redeploy

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 10:56:04 +00:00
Claude
06a8cc189a
fix: remove docker.io/ prefix from Forgejo image (Codeberg registry)
https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 10:46:09 +00:00
Claude
b6e223291a
feat: add Forgejo service + Postgres database provisioning
- docker-compose.yml: Forgejo service on hiy-net, configured via env vars
- postgres-init/01-forgejo.sql: creates forgejo user + database on first Postgres init
- .env.example: document FORGEJO_DB_PASSWORD and FORGEJO_DOMAIN

Routing: add FORGEJO_DOMAIN as an app in HIY pointing to forgejo:3000,
or add a Caddyfile block manually.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-26 10:44:19 +00:00
Claude
73ea7320fd
fix: use Caddy internal CA when ACME_EMAIL is not set
DOMAIN_SUFFIX=local (or any non-localhost LAN name) caused a TLS handshake
failure because Caddy attempted an ACME challenge that can never succeed for
private domains.

- Caddyfile: tls {$ACME_EMAIL:internal} — falls back to Caddy's built-in CA
  when ACME_EMAIL is absent, uses Let's Encrypt when it is set.
- start.sh: ACME_EMAIL is now optional; missing it prints a warning instead
  of aborting, so local/LAN setups work without an email address.

To trust the self-signed cert in a browser run: caddy trust

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-25 22:09:00 +00:00
Claude
60f5df52f7
fix: copy server/templates into build image for include_str! macros
include_str!("../../templates/...") is resolved at compile time, so the
template files must be present in the Docker build context. The previous
Dockerfile only copied server/src, not server/templates.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 16:29:15 +00:00
Claude
0bd7b44b81
fix: drop cross-compilation, build natively in Dockerfile
podman-compose does not populate BUILDPLATFORM/TARGETARCH build args, so
the platform-detection logic always fell back to x86_64 — even on arm64.
This caused cc-rs to look for 'x86_64-linux-gnu-gcc' instead of 'gcc'.

Replace the entire cross-compile scaffolding with a plain native build:
  cargo build --release (no --target)

Cargo targets the host platform automatically. If cross-compilation is
ever needed it can be reintroduced with a properly-tested setup.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 16:25:48 +00:00
Claude
a873049e96
fix: install gcc and configure native x86_64 linker in build image
rust:slim-bookworm doesn't include gcc, and aes-gcm's build deps (via
cc-rs) need a C compiler. With --target x86_64-unknown-linux-gnu set
explicitly, cc-rs looks for the cross-compiler 'x86_64-linux-gnu-gcc'
instead of native 'gcc'.

Fix: install gcc in the build stage and add a [target.x86_64-*] linker
entry pointing to 'gcc' so cc-rs finds it on native x86_64 builds.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 16:23:02 +00:00
Claude
f50492f132
fix: fully-qualify all image names for Podman without search registries
Podman without unqualified-search registries configured in
/etc/containers/registries.conf refuses to resolve short image names.
Prefix every image with docker.io/library/ (official images) or
docker.io/<org>/ (third-party) so pulls succeed unconditionally.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 16:20:22 +00:00
Claude
b23e02f2d2
fix: declare default network for podman-compose compatibility
podman-compose requires all networks referenced in service configs to be
explicitly declared in the top-level networks block. Docker Compose
creates the default network implicitly, but podman-compose errors with
'missing networks: default'.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 16:18:46 +00:00
Claude
48b9ccf152
feat: M4 Hardening — encryption, resource limits, monitoring, backups
## Env var encryption at rest (AES-256-GCM)
- server/src/crypto.rs: new module — encrypt/decrypt with AES-256-GCM
  Key = SHA-256(HIY_SECRET_KEY); non-prefixed values pass through
  transparently for zero-downtime migration
- Cargo.toml: aes-gcm = "0.10"
- routes/envvars.rs: encrypt on SET; list returns masked values (••••)
- routes/databases.rs: pg_password and DATABASE_URL stored encrypted
- routes/ui.rs: decrypt pg_password when rendering DB card
- builder.rs: decrypt env vars when writing the .env file for containers
- .env.example: add HIY_SECRET_KEY entry

## Per-app resource limits
- apps table: memory_limit (default 512m) + cpu_limit (default 0.5)
  added via idempotent ALTER TABLE in db.rs migration
- models.rs: App, CreateApp, UpdateApp gain memory_limit + cpu_limit
- routes/apps.rs: persist limits on create, update via PUT
- builder.rs: pass MEMORY_LIMIT + CPU_LIMIT to build script
- builder/build.sh: use $MEMORY_LIMIT / $CPU_LIMIT in podman run
  (replaces hardcoded --cpus="0.5"; --memory now also set)

## Monitoring (opt-in compose profile)
- infra/docker-compose.yml: gatus + netdata under `monitoring` profile
  Enable: podman compose --profile monitoring up -d
  Gatus on :8080, Netdata on :19999
- infra/gatus.yml: Gatus config checking HIY /api/status every minute

## Backup cron job
- infra/backup.sh: dumps SQLite, copies env files + git repos into a
  dated .tar.gz; optional rclone upload; 30-day local retention
  Suggested cron: 0 3 * * * /path/to/infra/backup.sh

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 15:06:42 +00:00
Shautvast
2654a26b06 rust 1.94 2026-03-24 14:27:12 +01:00
Claude
f4aa6972e1
feat: shared Postgres with per-app schemas
One Postgres 16 instance runs in the infra stack (docker-compose).
Each app can be given its own isolated schema with a dedicated,
scoped Postgres user via the new Database card on the app detail page.

What was added:

infra/
  docker-compose.yml  — postgres:16-alpine service + hiy-pg-data
                        volume; POSTGRES_URL injected into server
  .env.example        — POSTGRES_PASSWORD entry

server/
  Cargo.toml          — sqlx postgres feature
  src/db.rs           — databases table (SQLite) migration
  src/models.rs       — Database model
  src/main.rs         — PgPool (lazy) added to AppState;
                        /api/apps/:id/database routes registered
  src/routes/mod.rs   — databases module
  src/routes/databases.rs — GET / POST / DELETE handlers:
      provision  — creates schema + scoped PG user, sets search_path,
                   injects DATABASE_URL env var
      deprovision — DROP OWNED BY + DROP ROLE + DROP SCHEMA CASCADE,
                   removes SQLite record
  src/routes/ui.rs    — app_detail queries databases table, renders
                        db_card based on provisioning state
  templates/app_detail.html — {{db_card}} placeholder +
                              provisionDb / deprovisionDb JS

Apps connect via:
  postgres://hiy-<app>:<pw>@postgres:5432/hiy
search_path is set on the role so no URL option is needed.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 13:16:39 +00:00
Claude
8267b30b15
fix: restore app reachability after platform restart
Two root causes:

1. Caddy was started without --resume, so every restart wiped all
   dynamically-registered app routes (only the base Caddyfile survived).
   Adding --resume makes Caddy reload its auto-saved config (stored in
   the caddy-config volume) which includes all app routes.

2. App routes used the container IP address, which changes whenever
   hiy-net is torn down and recreated by compose. Switch to the
   container name as the upstream dial address; Podman's aardvark-dns
   resolves it by name within hiy-net, so it stays valid across
   network recreations.

Together with the existing reconnect loop in start.sh these two
changes mean deployed apps survive a platform restart without needing
a redeploy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:47:32 +00:00
Claude
a8e73df2c3
fix: reconnect app containers to hiy-net after platform restart
compose down destroys hiy-net and evicts running hiy-* containers
from it. compose up recreates the network but leaves those containers
disconnected, making them unreachable until a redeploy.

After compose up, reconnect all running hiy-* containers to hiy-net.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:42:26 +00:00
Claude
31944d128b
remove: unnecessary app restart loop from start.sh
Without podman system migrate, compose down/up only touches infra
containers. Deployed hiy-* containers are never stopped during a
platform restart so they need no special handling there.

The restart loop stays in boot.sh where it is needed (system reboot
stops all containers).

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:36:03 +00:00
Claude
9fbbdf62ee
remove: podman system migrate (wrong tool for the wrong problem)
It was added to "pick up subuid/subgid mappings" but that's not what it
does — it migrates container storage after a Podman version upgrade.
Subuid/subgid changes are picked up by restarting the Podman socket,
which the script already does. The only effect of running it was stopping
all containers on every platform start.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:34:16 +00:00
Claude
852e3f6ccb
fix: restart deployed app containers after platform start
podman system migrate explicitly stops all containers, which overrides
the --restart unless-stopped policy set on deployed apps. After compose
up-d brings the infra stack back, any exited hiy-* container is now
restarted automatically.

Same logic added to boot.sh for the on-boot path.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:32:45 +00:00
Claude
88f6e02d4e
feat: auto-restart stack on boot via systemd user service
- Add infra/boot.sh: lightweight startup (no build) that brings up the
  Podman stack — used by the systemd unit on every system boot
- start.sh now installs/refreshes hiy.service (a systemd --user unit)
  and enables loginctl linger so it runs without an active login session

After the next `infra/start.sh` run the Pi will automatically restart
the stack after a reboot or power cut.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 12:22:34 +00:00
Claude
031c3bdd41
fix: defer podman system migrate to after the build to eliminate early downtime
podman system migrate was stopping all containers immediately (visible in
the terminal output as "stopped <id>" lines), before the build even began.

Moving it to just before compose down/up means running containers stay
alive for the entire duration of the image build.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 10:48:45 +00:00
Claude
a16ccdcef4
fix: build images before tearing down compose to reduce downtime
Old behaviour: compose down → long build → compose up
New behaviour: long build (service stays live) → compose down → compose up

Downtime is now limited to the few seconds of the swap instead of the
entire duration of the Rust/image build.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-24 10:43:36 +00:00
Claude
e7fd2a4365
fix: auto-enable cgroup swap accounting on Pi before starting containers
runc (used by Podman) always writes memory.swap.max when initializing the
cgroup v2 memory controller, even without explicit --memory flags. On
Raspberry Pi OS this file is absent because swap accounting is disabled
by default in the kernel, causing every container start to fail with:

  openat2 …/memory.swap.max: no such file or directory

start.sh now detects this condition early, patches the kernel cmdline
(cgroup_enable=memory cgroup_memory=1 swapaccount=1) in either
/boot/firmware/cmdline.txt (Pi OS Bookworm) or /boot/cmdline.txt
(older releases), and tells the user to reboot once before continuing.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 18:05:11 +00:00
Claude
2fdffc0acb
Fix builds delegating to host Podman via CONTAINER_HOST
build.sh calls `podman build` inside the server container.
DOCKER_HOST is a Docker CLI variable; Podman does not use it to
automatically switch to remote mode.  Without CONTAINER_HOST set,
Podman runs locally inside the (unprivileged) container, has no
user-namespace support, and lchown fails for any layer file owned
by a non-zero GID (e.g. gid=42 for /etc/shadow).

Setting CONTAINER_HOST=tcp://podman-proxy:2375 makes Podman
automatically operate in remote mode and delegate all operations
to the host Podman service, which has the correct subuid/subgid
mappings and full user-namespace support.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 10:50:41 +00:00
Claude
b5e6c8fcd3
Fix rootless Podman lchown EINVAL by ensuring uidmap and fresh service
Two root causes for "invalid argument" when chowning non-root UIDs/GIDs
in image layers:

1. Missing uidmap package: without setuid newuidmap/newgidmap binaries,
   Podman can only map a single UID (0 → current user) in the user
   namespace.  Any layer file owned by gid=42 (shadow) or similar then
   has no mapping and lchown returns EINVAL.  Now install uidmap if absent.

2. Stale Podman service: a service started before subuid/subgid entries
   existed silently keeps the single-UID mapping for its lifetime even
   after the entries are added and podman system migrate is run.  Now
   always kill and restart the service on each start.sh run so it always
   reads the current subuid/subgid configuration.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 10:32:13 +00:00
Claude
b64195c58a
Always run podman system migrate, not only when subuid/subgid entries are added
If entries already existed before this script first ran, _HIY_SUBID_CHANGED
stayed 0 and migrate was skipped, leaving Podman storage out of sync with
the namespace mappings and causing lchown errors on layer extraction.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 10:25:25 +00:00
Claude
4f5c2e8432
Add subuid/subgid entries for rootless Podman user namespace mapping
Without entries in /etc/subuid and /etc/subgid, Podman cannot map the
UIDs/GIDs present in image layers (e.g. gid 42 for /etc/shadow) into
the user namespace, causing 'lchown: invalid argument' on layer extraction.

Add a 65536-ID range starting at 100000 for the current user if missing,
then run podman system migrate so existing storage is updated.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 10:19:21 +00:00
Claude
dae5fd3b53
Allow rootless Podman to bind ports 80 and 443
Rootless processes cannot bind privileged ports (<1024) by default.
Lower net.ipv4.ip_unprivileged_port_start to 80 at startup, and persist
it to /etc/sysctl.conf so the setting survives reboots.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 10:11:21 +00:00
Claude
d2cba788ab
Fix rootless Podman by owning /run/user/<uid> instead of redirecting to /tmp
Podman rootless unconditionally resets XDG_RUNTIME_DIR to /run/user/<uid>
if that directory exists, overriding any env var we set. Redirecting to
/tmp is therefore ineffective.

Instead, ensure /run/user/<uid> exists and is owned by the current user
(using sudo if needed), mirroring what PAM/logind does for login sessions.
All Podman runtime state (socket, events, netavark) then works correctly.

Remove the now-unnecessary storage.conf/containers.conf writes and the
inline env override on podman system service.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 08:02:10 +00:00
Claude
0932308ed6
Fix make and podman compose to use correct paths when run from repo root
make build was looking for Makefile in cwd (repo root) instead of infra/.
Use -C "$SCRIPT_DIR" so it always finds infra/Makefile regardless of where
the script is invoked from.

Add -f flag to podman compose up so it finds infra/docker-compose.yml
from any working directory.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 07:55:58 +00:00
Claude
ea5b6e5594
Write containers.conf tmp_dir and force env var inline on podman call
Podman's events engine reads tmp_dir from containers.conf, not from
XDG_RUNTIME_DIR directly. Write both storage.conf and containers.conf
to /tmp/podman-<uid> so no path under /run/user/<uid> is ever used.
Also use `env XDG_RUNTIME_DIR=...` prefix on podman invocation to
override any stale value in the calling shell environment.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 07:49:00 +00:00
Claude
0690e3c48a
Unconditionally redirect Podman runtime to /tmp; override storage.conf
Stop relying on conditional checks. Always point XDG_RUNTIME_DIR and
storage.conf runroot to /tmp/podman-<uid> so Podman never touches
/run/user/<uid>, which requires PAM/logind to create.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-22 07:42:54 +00:00