Commit Graph

96 Commits

Author SHA1 Message Date
53a070a59a fix(guests): keep a networked guest's resolver and order it after its bridge (task 0010)
A guest placed on a VLAN runs its own networkd, which default-enables
systemd-resolved. That conflicts with the nested-container default of
inheriting the host's resolv.conf, so its toplevel failed to build; keep the
guest's own resolver with networking.useHostResolvConf = false.

The same guest's container enslaves its veth to the VLAN bridge at start,
but the container backend orders the unit only after the network is up, not
after that specific bridge exists — a race the veth enslavement could lose.
Order container@<name> after the br-vlan<id> device so the bridge is there
first.

Both were surfaced by a VM integration test that was explored and then
dropped as not worth its regression cost; the task file records the call.
2026-07-26 00:01:10 -04:00
969737b6b5 feat(guests): let a guest nest OCI containers (task 0009)
Add a `nesting` placement field to the Host-side guest interface, a bool
off by default. On, it grants the guest's container the prerequisites its
interior needs to run Podman and other OCI containers: the `CAP_NET_ADMIN`
capability an OCI runtime uses to build its bridges and firewall rules,
and the `/dev/net/tun` and `/dev/fuse` device nodes it reaches for to
network those containers and back their overlay storage. Off, both the
capability and device lists are empty, so a non-nesting guest is untouched.

cgroup delegation, the other nested prerequisite, the NixOS container
backend already grants every container unconditionally, so the Skeleton
records it with an absence pointer rather than re-emitting it.

Add a nesting-sample guest whose interior defines an `oci-containers`
workload on Podman, and enable it on neogaia with `nesting` on, so the
path builds end to end through the Host's `nix flake check` — which pulls
in podman and the generated container unit for the nested system.
2026-07-25 22:10:46 -04:00
0b7d409fbc feat(guests): cap guest resources and gate boot-start (task 0008)
Give the Host-side placement a `limits` field — `memory`, `cpu`, and
`tasksMax` — that caps the guest's `container@<name>.service` unit via
`MemoryMax`, `CPUQuota`, and `TasksMax`, uncapped by default so an unset
limit contributes no key and systemd keeps its default.

Add an `autoStart` placement option, on by default, driving the
container's boot-start so a Host can define a guest without starting it
at boot.

Give neogaia's skeleton guest modest demonstrative caps.
2026-07-25 21:26:53 -04:00
d637d3e7f6 feat(guests): give guests only the host-decrypted secrets they name
Add a `secrets` placement option to the guest builder: a list of secret
names. The host declares each as one of its own sops secrets, so the host
is the sole decryptor from the sops files it already holds, and the
decrypted file is bind-mounted into the guest read-only at the same
`/run/secrets/<name>` path it occupies on a host. A service inside the
guest reads its credentials at a predictable location while the guest
carries no age key and decrypts nothing itself.

Ownership needs no new code: the container already runs in the host's uid
and gid space one to one, so the decrypted file's host owner is its owner
inside the guest.

Assert that no in-guest path is claimed by both a mount and a secret, so
the attribute-set merge fails loudly rather than resolving a collision
silently in the secret's favour.
2026-07-25 20:46:08 -04:00
ab9b9e9f8f feat(guests): bind-mount host pool paths into guests
Add a `mounts` placement option to the guest builder: a Host maps guest
interior paths to host paths, each read-write by default or read-only per
mount, realized as the nested container's bind mounts. A guest sees exactly the
data it should, at any granularity from a single folder to a whole pool.

Pin the container to the host's uid and gid space with `privateUsers = "no"`,
so a guest process writing as the shared storage group lands on a bind-mounted
pool as that same group without permission juggling.
2026-07-25 17:53:14 -04:00
7edc1ce94b feat(zfs): import ZFS pools and add the shared write group
Add `modules.zfs`, the host-level pool import: a host declares its ZFS host id
and the pools to import with their dataset mountpoints, and the module imports
those pools as durable state rather than recreating them, so a service's data
survives any rebuild or reimage.

Add a shared `storage` group with a fixed gid to the base config both the host
base and the guest-base build on, so a host and every guest carry the same
number and an identity-mapped container write lands on the pool as that group
without per-service permission juggling.

No host enables the module: the only host is a laptop with no pools and a
kernel with no ZFS build, so the enabled build was verified by ad-hoc
enablement against a ZFS-supported kernel while the committed tree stays inert.
2026-07-25 17:26:36 -04:00
e6ea8a0060 feat(guests): place networked guests on tagged VLANs (task 0004)
A guest sets `vlan` to attach to its host's `br-vlan<id>` bridge, `mac`
to reuse an existing address (else a stable one is derived from its
namespace path and read back via `nix eval`), and `address` for a static
IP (else DHCP). The MAC and address are pinned inside the guest by its
own networkd, the only stable MAC pin for a nested container. A guest
naming a VLAN its host has not declared fails the build with an
actionable message.

The `br-vlan<id>` naming moves into a shared `bridgeName` in the lib, so
the bridge a guest attaches to and the bridge the host emits have one
source.
2026-07-25 16:45:26 -04:00
e05adef7b7 feat(network): add host VLAN-bridge networking foundation
Introduce `modules.network`, the host-level networking foundation a Host
declares once. A Host states its trunk interface and the tagged VLAN ids to
materialize, and the module emits one systemd-networkd bridge per VLAN, named
by the `br-vlan<id>` convention, plus the host's own management address on a
chosen VLAN's bridge.

The trunk and every bridge set `RequiredForOnline = "no"` so wait-online never
blocks boot on a carrier-less link, and the module owns its own NetworkManager
`unmanaged` guard so enabling it is self-sufficient. Two assertions tie the
management VLAN to the declared VLANs and a management address to a VLAN, so an
address can never be silently dropped.

No host commits an enablement: the only host is a wifi laptop that cannot
present guests as L2 citizens and whose DNS the module's networkd/resolved
would disturb, so the standing enablement waits for the first wired server
host. The build and bridge-name evaluation were verified by enabling the
module ad hoc, and stay reproducible through `extendModules`.
2026-07-25 16:18:10 -04:00
e7d7eb14e1 feat: introduce guests as nested-container definitions
Add a third auto-loaded kind beside the host and the module: the guest, a
reusable definition under guests/ that a host enables like a module and that
realizes its interior as a systemd-nspawn nested container.

Split the shared base config so a guest can stand on it.
base.nix now holds the substrate both bases share — the primary user,
home-manager, and the unstable/stable overlays.
system.nix keeps the host-only machinery, and a new guest.nix is the slim
guest-base: it imports the full modules tree, pins the interior release, and
auto-enables the toolkit bundle and SSH so any guest is workable on sight.

Give modules.ssh a guest flavor.
A host restores its host keys from secrets as before, while a guest sets
hostKeys.restore = false, names no sops files, and self-generates a host key,
so it holds no age key of its own.

The lib grows a guest helper that declares the guests.<path> namespace with an
enable and a backend field.
Only the container backend is built; microvm is a reserved value that trips a
clear build-time assertion rather than silently building nothing.

A sample guest exercises the whole path, and neogaia enables it, so the guest
interior builds through the existing nix flake check seam.
2026-07-25 13:19:07 -04:00
8cd59cb292 feat(toolkit): bundle the baseline interactive toolset
Add modules.toolkit, an aggregator that enables fish, tmux, nvim, git,
and direnv as one unit, and makes fish the login shell, so any Host or
Guest shell feels identical. Each member is set via mkDefault, so a Host
can still override any single piece while the one flag brings up the
whole bundle.

neogaia now enables the bundle as a single line in place of its five
individual enables and its explicit default-shell line, keeping the Host
a flat checklist.

Also lands the domain model, ADR 0006, and the guests spec: the toolkit
is the first piece of the wider guests work, and the guest-base will
auto-enable this bundle.
2026-07-25 12:48:37 -04:00
582800d548 chore: retire consumed specs and tasks, trim CLAUDE.md gotchas
The .claude specs and tasks have been distilled into the notes vault, so
remove the 37 consumed files. Trim the CLAUDE.md gotchas to their
project-specific residue: drop the entries now captured as general notes,
and fix the two references that pointed at the deleted specs.
2026-07-24 21:40:50 -04:00
6484f9466c docs(firefox): record the profiles.ini and chrome-theming fixes in the task (task 0032) 2026-07-24 08:33:55 -04:00
e42101e08b feat(firefox): Nord-theme the chrome via Stylix colorTheme (task 0032)
The base Stylix Firefox target themes only fonts and the reader view, leaving
the toolbar and tabs at Firefox's default look. Enable colorTheme so Stylix
recolours the chrome Nord from the shared scheme through its managed Firefox
Color add-on, with no hand-written chrome CSS and no flake input of ours.

Scope ADR 0005's no-pinned-add-on rule to the three functional extensions and
record the signed, Stylix-managed theming add-on as a deliberate exception.
2026-07-24 08:33:19 -04:00
ee672d2479 feat(firefox): add the desktop Firefox browser module (task 0032)
Add Firefox as a single-purpose desktop Module configured entirely through
home-manager's programs.firefox, and fan it out from the desktop aggregator so
any Host with the desktop enabled brings the browser up.

Ship stock mainline Firefox, hardened and de-monetized through locked
enterprise policies (telemetry, studies, Pocket, offer-to-save-logins, the
default-browser check, Firefox accounts and sync, and the sponsored new-tab
surfaces all off). Force-install three extensions by policy, keyed by their
verified add-on ids: uBlock Origin, Proton Pass, and SponsorBlock.

Default search to DuckDuckGo over a lean list: the general-purpose commercial
engines are hidden with metaData.hidden so Firefox does not reconcile them back
in, leaving DuckDuckGo and Wikipedia. Theme the browser Nord from the shared
Stylix scheme against the one profile, and register it as the default handler
for the web-link schemes and HTML.

Record the stock-Firefox-plus-policy-extensions decision as ADR 0005.
2026-07-23 23:04:19 -04:00
e684ac481e refactor: mirror module namespaces to their directories (task 0031)
Adopt the convention that a Module's option path mirrors its directory
under modules/, with an index file naming the directory's own segment.

- Group agent Modules under modules.agents.*: claude-code (whole
  directory), pi (flattened to a file), skills (renamed from
  agent-skills), and gitea-axi under an agents/tools/ subgroup. The
  agents/ and tools/ folders are pure namespace prefixes with no
  aggregator enable.
- Nest hypridle and hyprlock under modules.desktop.hyprland.*, with
  hyprland.nix as the index, and update the desktop aggregator.
- Remove the obsolete example Module.
- Record the convention in CONTEXT.md and ADR 0004, and update the
  neogaia host, the two live CLAUDE.md gotchas, and the skills Module's
  intentional Enable-convention exception comment.
2026-07-23 19:21:12 -04:00
ac96639c20 feat(desktop): add XDG portals module for in-app screen sharing (task 0030)
Add modules.desktop.portals, enabled by the desktop aggregator, pinning the
XDG desktop portal routing explicitly: the three interfaces the Hyprland
portal implements (screencast, screenshot, global shortcuts) go to Hyprland,
and GTK is the default for file dialogs and appearance.

The backend packages already arrive with the Hyprland compositor integration,
so this module owns only the routing, which was previously empty and rode on
the config file the Hyprland package happens to ship. Making it a first-class
module so in-app screen sharing does not depend on that incidental default.
2026-07-22 23:27:55 -04:00
ab89ba8391 feat(desktop): resolve media output paths through XDG user-dirs
Add a modules.desktop.userdirs module that declares the XDG user directories,
and route the recorder and screenshot tools through xdg-user-dir so their
output folders (Videos/Recordings, Pictures/Screenshots) follow one relocatable
source instead of a hardcoded $HOME path.
2026-07-22 23:11:16 -04:00
0e92de7eea feat(desktop): add screen recording (task 0029)
Add a wf-recorder module, enabled by the desktop aggregator. Super+Shift+R
picks a region with slurp and toggles a video-only capture (no audio), stopped
with SIGINT so the file finalises, saved to ~/Videos/Recordings. Start and
saved notifications fire via notify-send.

A Waybar custom/recording widget samples the wf-recorder process once a second
and shows a video glyph while a capture runs.
2026-07-22 22:56:56 -04:00
9963a0dbe4 feat(desktop): add screenshot capture (task 0028)
Add a screenshot module wiring grimblast (grim + slurp) through the satty
annotation editor, enabled by the desktop aggregator. Region, active-window,
and full-screen captures each open in satty and, on confirm, land in both the
clipboard and a dated file under ~/Pictures/Screenshots.

Bound to Print / Shift+Print / Ctrl+Print rather than the spec's Super+L
family, whose keys task 0021 already holds for hjkl focus and window movement.
2026-07-22 22:04:38 -04:00
210a260735 feat(desktop): add clipboard history (task 0027)
Add a clipboard module to the desktop group, enabled through the
aggregator. Home-manager's services.cliphist runs the text and image
watchers as systemd user services bound to the graphical-session
target, and a rofi-dmenu picker recalls history on Super+Shift+V.
wl-clipboard is on PATH so the shell can pipe to and from the clipboard.
2026-07-22 21:42:53 -04:00
005928ef88 feat(desktop): add hyprlock and hypridle (task 0026)
Add a session-lock screen and idle management to the Hyprland-native
subdirectory, enabled through the desktop aggregator.

hyprlock draws through the compositor session-lock protocol, themed by
Stylix, with a centered field and clock. Super+X locks directly through a
guarded launch so the key works without the idle daemon.

hypridle locks on idle and powers the displays off, with tunable timeouts,
and locks before every suspend. Lid-close routes through logind to suspend,
which the pre-sleep lock covers, so the lid lands at a locked screen.
2026-07-22 18:37:32 -04:00
900cb6b8e8 feat(desktop): add mako notifications (task 0025)
Add a mako notification daemon module to the desktop group, enabled by
the aggregator. Toasts auto-dismiss into history after five seconds, a
do-not-disturb mode ([mode=dnd] invisible=true) suppresses display while
still recording to history, and Super+N recalls the last notification
via makoctl restore. Colors and the popup font come from Stylix's mako
target. The bar-side do-not-disturb toggle and media controls already
live in waybar.nix, so this task adds only the daemon.
2026-07-22 17:15:32 -04:00
7d9a0dae36 feat(desktop): add the rofi launcher (task 0024)
Add a rofi module to the desktop group, enabled by the aggregator.
It runs the drun application launcher, bound on Super+R. A session menu
built on the same themed rofi is bound on Super+Shift+X, and every
rofi -dmenu call inherits the Stylix theme, so later utility menus
reuse it for free.

Also give layer surfaces their own fade-in (layersIn and fadeLayersIn)
a touch quicker than the window animation, so the launcher fades in
briskly rather than snapping into place.
2026-07-22 17:03:54 -04:00
b21b4ff77e feat(desktop): add the Waybar status bar (task 0023)
Add a Waybar module to the desktop group, enabled through the aggregator,
showing workspaces with per-application icons plus a clock, MPRIS media
controls, audio, network, battery, and a do-not-disturb toggle.

The do-not-disturb widget drives mako's dnd mode and degrades gracefully
until the notifications module lands. Module glyphs are Nerd Font codepoints
decoded through JSON, backed by a system symbols font.

Add a PipeWire audio server so the bar's volume widget has a running sink to
read and drive.
2026-07-22 12:03:30 -04:00
6d1afa41ed feat(desktop): let Stylix manage tmux and fish theming (task 0022)
Narrow the terminal tools excluded from Stylix to nvim alone. nvim keeps
its dedicated gbprod/nord.nvim colorscheme, which is richer than the
base16 mapping Stylix would apply.

tmux and fish are now Stylix-managed. Drop the hand-written Nord colours
from the tmux status bar so Stylix themes the status and pane styles,
keeping the minimal layout (session name plus window list) reapplied
after Stylix so it still wins.
2026-07-22 11:38:34 -04:00
27a28b1ae7 feat(desktop): theme the graphical layer Nord with Stylix (task 0022)
Add Stylix as a flake input and a desktop theming module that resolves a
single Nord base16 scheme across the graphical surface: GTK, Qt, cursor,
and the system monospace font, plus a static Nord wallpaper drawn as a
Polar Night gradient.

Scope the theming to the graphical layer alone. The Stylix targets for
fish (at both system and home-manager level), tmux, and nixvim stay off
so the terminal tools keep their hand-written themes.
2026-07-22 10:28:03 -04:00
5ca717ca4a feat(desktop): toggle the tiling layout on Super+Shift+T
Flip between the dwindle and master layouts with one bind. Neither a
dispatcher nor a keyword toggles the layout on its own, so a small pinned
script reads general:layout and sets the other through hyprctl keyword.
2026-07-22 10:08:26 -04:00
c0ec330024 fix(desktop): switch the terminal from Ghostty to Alacritty
Ghostty's GTK4 window construction made every launch sluggish on neogaia's
integrated graphics (~440 ms to map a window), which a head-to-head
comparison against Alacritty confirmed. Alacritty's lightweight OpenGL
renderer opens fast on the iGPU, so it becomes the terminal on Super+Return.

The choice stays reversible per host, so a capable host such as zeus could
adopt Ghostty later.
2026-07-22 09:46:27 -04:00
4b6fbfe732 feat(desktop): add the desktop group and Hyprland session (task 0021)
Introduce the modules/desktop/ group behind a single modules.desktop.enable
aggregator that hand-lists and turns on each piece at default priority, so a
host enables the whole desktop with one flag yet can override any single piece.

The Hyprland compositor lives in a subdirectory of the group, sourced from
nixpkgs with the program integration owning the session, portals, and polkit
and home-manager writing the config against that one package. greetd with the
tuigreet greeter provides a mouse-free text login that launches the session
through uwsm, and Ghostty is the terminal on Super+Return.

Port the operator's KDE/i3 bindings expressed entirely in hjkl and letters,
tune input (US layout, Caps->Escape, snappy repeat, touchpad taps and natural
scroll, flat mouse accel), and set a subtle feel with blur left as a host knob.
Enable the desktop on neogaia.
2026-07-22 08:03:01 -04:00
f552269cb3 docs: plan the hyprland desktop epic (spec, ADR, tasks)
Add the hyprland-desktop spec, ADR 0003 recording the compositor choice
over Sway/KDE/niri, and the task breakdown (0021-0030) that decomposes
the desktop into granular grouped modules. Record the corrected host GPU
facts (zeus is AMD, raichu the sole Nvidia machine) in CLAUDE.md.
2026-07-22 08:03:01 -04:00
fc3380f8ce feat(pi): install the coding agent for a side-by-side trial (task 0020)
Add a `pi` module that turns on the home-manager `programs.pi-coding-agent`
integration for the primary user and freezes a single settings.json: the
default provider set to Anthropic, the default model to Opus (claude-opus-4-8,
Pi's own catalogue id), and analytics disabled. Everything else in
~/.pi/agent — the credential and all self-modification state — is left
unmanaged, so login survives rebuilds and backing Pi out is a one-line enable
flip. Enabled on neogaia alone.
2026-07-21 19:48:24 -04:00
6b5729b98a refactor(ssh): state the admitted keys directly instead of deriving them
The access policy was a separate fleet declaration mapping each machine to a
role and a client key, which the module looked up by hostname to derive what to
authorize. Authorizing a key needs the key text and nothing else, so the
per-machine names, the hostname lookup and the role existed only to reconstruct
a grouping that two lists already are.

The module now exposes those two lists and the set a machine admits, and a host
declares what it draws from in its own file. The fleet declaration is gone, and
with it a single-file directory at the repository root.

`authorizedKeys` defaults to the workstation keys. An option of a list type is
not mandatory the way a scalar one is: undeclared, it yields the empty list
rather than failing, and a machine admitting no key is unreachable. The default
makes the safe case the silent one.

Verified to produce the same system as the design it replaces, and against the
running machine in both directions.
2026-07-20 13:05:07 -04:00
f18b40091c feat(ssh): adopt the client key as a secret and derive access from roles (task 0019)
The operator's SSH client key existed only as a file created by hand on one
laptop, so a reimage would destroy it and lock the operator out of the remote.
It now lives in neogaia's own secrets file, encrypted to the admin identity and
neogaia alone, and the client is pointed at the decrypted path rather than a
copy in the user's home.

Access becomes a policy over roles instead of a per-host list of keys. A new
fleet declaration names each machine's role and client public key, and every
machine derives what it authorizes from that: a workstation admits workstations
alone, a server admits both, so a compromised server reaches no machine of the
operator's own. Registering a machine is an entry in that one file.

Only neogaia exists, so the server half of the policy is built rather than
exercised. Two assertions reject a machine missing from the fleet and any entry
whose role no policy defines.
2026-07-20 13:05:07 -04:00
4ecb86052b docs(git): record the verified deployment (task 0015)
Also note that `git config --global` lists only ~/.gitconfig and is not a
view of what git resolves, which misreads as the module's file being
ignored.
2026-07-20 12:22:36 -04:00
62eb6286b4 feat(git): enable the module per host rather than by default
Defaulting the module on hid it from a host's config, leaving no single
place that lists what a host carries. Each host enables it explicitly
instead, at the cost of a step when adding one.
2026-07-20 12:20:13 -04:00
75c5745cbd feat(git): carry the commit identity on every host
Default the module on rather than restating it per host, so a new host
commits without remembering the line. A host that should not carry a
personal identity sets enable to false.
2026-07-20 11:15:15 -04:00
c9fc17ecf5 feat(git): declare the operator's commit identity (task 0015)
Git identity lived only in one checkout's local configuration on one
machine, so it was invisible to every other checkout and lost on a
reimage. Declare it as a module instead, enabled on neogaia.

It is a module rather than base plumbing so a host that should not carry
a personal commit identity can decline it.
2026-07-20 10:53:47 -04:00
0d685ce277 docs: close out task 0014 after confirming the timers 2026-07-20 10:27:40 -04:00
c63c3079be feat(nix): bound the store, generations and boot menu (task 0014)
Garbage collection and store optimisation each run weekly, on separate
days so they never contend, and retained boot configurations are capped
at 15.
2026-07-20 10:21:24 -04:00
dcc03155a2 docs(install): revise for sops-provisioned identities (task 0012)
The procedure was built around a login password set by hand through
nixos-enter, a step that no longer exists now that the password arrives
as a decrypted secret. Rather than patch around it, revise the document:
the LUKS passphrase is named as the only hand-entered secret, and identity
provisioning moves ahead of first boot, since there is no fallback
credential to get in with if decryption fails.

Cover the three procedures the install alone did not: provisioning a
running host live, the editing workflow and what it needs the admin
identity for, and recovery from the live ISO.
2026-07-20 10:04:12 -04:00
11d7cb053c docs(tasks): add task 0019, user SSH keys and an access policy
Per-machine client identities, so a compromised machine surrenders only
its own key, plus a role-based rule for which machines may reach which.
Only neogaia exists, so the server half is recorded rather than exercised.
2026-07-20 09:51:34 -04:00
61ce9cc1be feat(neogaia): restore the SSH host keys from sops (task 0011)
The host's SSH identity was generated by the daemon, so reimaging the
laptop would invalidate it and break `known_hosts` for every client that
had ever connected.

Add `secrets/neogaia.yaml`, the first per-host secrets file, encrypted to
the admin identity and `neogaia` alone so a compromised machine cannot
decrypt another's material. It holds both host private keys; the public
halves are committed in plaintext.

Clear `services.openssh.hostKeys` to stop generation and point `HostKey`
at the decrypted paths. These are ordinary secrets rather than the
pre-user ones the password uses, exercising the second decryption path.
2026-07-19 23:53:01 -04:00
ce103a7353 feat(secrets): decrypt the login password from sops (task 0010)
Wire sops-nix into the shared base config as unconditional plumbing, with
a two-tier age identity model: an admin identity held outside the repo, and
a per-host identity generated on the machine and kept on its encrypted root.
Both `sshKeyPaths` defaults are cleared so the SSH host keys stay out of the
decryption path and remain free to become secrets in their own right.

The primary user's password hash moves into a shared secrets file encrypted
to admin plus neogaia, consumed through `hashedPasswordFile` and decrypted
before accounts are created.

This needs `users.mutableUsers = false`: NixOS applies a declared hash to an
already-existing account only when that flag is false, so at the default the
hand-set password would have been kept and the change would have been inert.
Root consequently has no password and is locked; sudo from wheel is the way
in, and generation rollback remains the recovery path.
2026-07-19 23:43:28 -04:00
7711b841dd docs(neogaia): record the boot verification for task 0017 2026-07-19 23:12:14 -04:00
66582b498c fix(neogaia): replace guessed hardware detection with a real scan (task 0017)
The detection file was written before the machine ran NixOS, as an educated
guess at what a Dell XPS 13 9380 needs, and said so. Its contents now come
from nixos-generate-config on the running machine.

The guess was wider than the measurement rather than narrower: it named
thunderbolt, usb_storage and sd_mod, none of which the scan reports, and
omitted rtsx_pci_sdmmc. Nothing needed to reach the root device was missing.

Filesystem, swap, LUKS and microcode declarations from the scan are dropped.
The disk layout derives the first three and the hardware profile supplies the
last, so carrying them here would duplicate definitions that are produced on
every evaluation.
2026-07-19 22:28:02 -04:00
54c191f801 feat(claude-code): share sudo's credential cache across sessions (task 0018)
Key sudo's credential cache per user rather than per terminal, holding it
for 60 minutes. An authentication made in the operator's own terminal then
covers commands issued by processes holding no terminal of their own, which
previously failed with a bare non-zero exit and no output.

No command is made passwordless. The password remains required; only the
cache holding it is shared, and any process running as the primary user can
spend that credential until it lapses.

A PreToolUse hook refuses a privileged command while the cache is cold,
naming the command that warms it, so the condition announces itself rather
than presenting as a stall. Both states were exercised against the running
system.
2026-07-19 22:18:58 -04:00
42c602b814 docs: close out task 0016 after reimaging neogaia
The reimage is done and verified on hardware: the boot partition is 2 GiB
and reports it, the hardware profile's kernel parameter and module blacklist
are live, and the encrypted root unlocks to a console login.

The install procedure ran without divergence, which is the reproducibility
evidence the task was after.

The reimage also wiped the hand-written git identity, so the gotcha
describing it is updated to record the failure it now provokes.
2026-07-19 18:53:17 -04:00
431f75aad7 feat(neogaia): declare a 2 GiB EFI system partition (task 0016)
Each generation stores a kernel and an initrd on the EFI system partition,
and this host runs a large third-party kernel, so 512 MiB holds only about
seven. An exhausted partition fails at bootloader installation, after the
build has already succeeded.

The partition cannot grow in place: it sits first on the disk with the
encrypted container immediately behind it, and an encrypted volume's start
offset cannot move without rewriting its whole payload. Only an install
applies this layout, so the change takes effect at the next reimage and
does nothing to the running machine until then.

Record that a flake only sees git-tracked files, which cost an evaluation
earlier, and correct the git identity gotcha that a hand-written gitconfig
had made false.
2026-07-19 17:14:18 -04:00
8c85c02a7d docs: close out task 0013 after confirming it on hardware
The rebooted laptop selects deep sleep, runs thermal and power management,
and no longer loads the PS/2 mouse driver. Booted, running and freshly
built toplevels are the same store path, so the readings come from this
configuration rather than a surviving generation.
2026-07-19 16:51:03 -04:00
25049c8aef feat(neogaia): adopt the upstream hardware profile (task 0013)
The laptop's hardware facts were guessed before it ran NixOS. Hand them to
the upstream nixos-hardware profile for this exact model, which fixes four
things that are wrong on the running machine: the laptop suspends into
s2idle rather than deep S3, the PS/2 mouse driver loads over an i2c
touchpad, no thermal management runs, and firmware updates are impossible.

The profile is taken wholesale, including the Intel graphics support it
carries. Those packages are inert without a display server, and trimming
them would mean diverging from upstream for no present benefit.

Drop the host's own Intel microcode setting, which the profile now defaults
from the redistributable firmware already enabled here.

The input follows the base nixpkgs: only its NixOS modules are consumed, so
its own pin would be evaluated by nothing while drifting silently.

The two acceptance criteria needing a reboot are left open; the sleep mode
and module blacklist only take effect on a fresh boot.
2026-07-19 16:41:34 -04:00