diff --git a/docs/features/containers.md b/docs/features/containers.md index aa5174d..077b6c9 100644 --- a/docs/features/containers.md +++ b/docs/features/containers.md @@ -29,6 +29,11 @@ A container runtime with a sandboxed-by-default configuration. Installs `podman`, `podman-compose`, and the `crun` runtime from distro packages - no scripts, no extra repos. +Rootless prerequisites come along: `passt` (the `pasta` network backend) and `uidmap` on Debian/Ubuntu (`shadow-utils` on RHEL) for `newuidmap`/`newgidmap`. + +`podman-auto-update.timer` is enabled system-wide. +When `users.manage` is on, the [managed user](users.md) also gets lingering enabled (`loginctl enable-linger`) and the same timer enabled in its user session, so rootless containers labelled `io.containers.autoupdate` are refreshed daily. + ## Configuration (`features.containers`) | Field | Default | Description | diff --git a/nullforge/runes/containers.py b/nullforge/runes/containers.py index a2d583a..656f0a2 100644 --- a/nullforge/runes/containers.py +++ b/nullforge/runes/containers.py @@ -1,8 +1,10 @@ """Containers deployment module.""" +import re + from pyinfra.context import host -from pyinfra.facts.files import File -from pyinfra.facts.server import Arch, Which +from pyinfra.facts.files import File, FileContents +from pyinfra.facts.server import Arch, Users, Which from pyinfra.operations import apt, files, server, systemd from nullforge.models.containers import ContainersBackendType @@ -13,6 +15,13 @@ from nullforge.smithy.versions import GPG_KEYS, STATIC_URLS +SUBID_MIN = 100000 +"""First host id handed out to rootless users, below it live real accounts.""" + +SUBID_COUNT = 65536 +"""Ids per rootless user, matches shadow's SUB_UID_COUNT default.""" + + def deploy_containers() -> None: """Deploy containers runtime and related tools.""" @@ -29,6 +38,9 @@ def deploy_containers() -> None: case ContainersBackendType.PODMAN: _install_crun() _install_podman() + if user_opts.manage: + _ensure_subid_ranges(user_opts.name) + _enable_podman_autoupdate(user_opts) case ContainersBackendType.CRIO: raise ValueError("CRIO is not supported yet") @@ -197,6 +209,65 @@ def _install_podman() -> None: packages=[ "podman", "podman-compose", + "passt", + "uidmap", + ], + _sudo=True, + ) + + +def _ensure_subid_ranges(user: str) -> None: + """Allocate subuid/subgid ranges for user when absent.""" + + user_exists = user in host.get_fact(Users) + + for path in host.loop(("/etc/subuid", "/etc/subgid")): + entries = host.get_fact(FileContents, path) + + if entries is not None and not user_exists: + host.noop(f"useradd allocates {path} range when creating {user}") + continue + + entries = entries or [] + if any(entry.startswith(f"{user}:") for entry in entries): + host.noop(f"{user} already has {path} range") + continue + + files.line( + name=f"Allocate {path} range for {user}", + path=path, + line=f"^{re.escape(user)}:", + replace=f"{user}:{_next_subid_start(entries)}:{SUBID_COUNT}", + _sudo=True, + ) + + +def _next_subid_start(entries: list[str]) -> int: + ends = [int(start) + int(count) for _, start, count in (e.split(":") for e in entries if e.count(":") == 2)] + return max([SUBID_MIN, *ends]) + + +def _enable_podman_autoupdate(user_opts: UserMold) -> None: + """Enable podman auto-update timer, rootful and rootless.""" + + systemd.service( + name="Enable podman auto-update timer", + service="podman-auto-update.timer", + running=True, + enabled=True, + _sudo=True, + ) + + if not user_opts.manage: + return + + user = user_opts.name + server.shell( + name=f"Enable rootless podman auto-update timer for {user}", + commands=[ + f"loginctl enable-linger {user}", + f"runuser -u {user} -- env XDG_RUNTIME_DIR=/run/user/$(id -u {user})" + " systemctl --user enable --now podman-auto-update.timer", ], _sudo=True, ) diff --git a/nullforge/smithy/packages.py b/nullforge/smithy/packages.py index fffb564..30aee18 100644 --- a/nullforge/smithy/packages.py +++ b/nullforge/smithy/packages.py @@ -36,6 +36,7 @@ "pkg-config": "pkgconfig", "python3-dev": "python3-devel", "software-properties-common": None, + "uidmap": "shadow-utils", "ufw": "firewalld", } """Package overrides for RHEL/CentOS/Fedora families