159 Commits
Author SHA1 Message Date
John Lancaster 1b98c83d3f breaking up mtls with better wrappers 2026-07-05 22:59:05 -05:00
John Lancaster 39504cd856 reorg 2026-07-05 20:40:28 -05:00
John Lancaster ca75da7c37 soteria off the ground 2026-07-05 20:29:04 -05:00
John Lancaster 9f1fed071d updates for soteria host 2026-07-05 20:05:02 -05:00
John Lancaster bfbdbae99b name tweak 2026-07-05 19:35:10 -05:00
John Lancaster 9f29907ec3 janus module cleanup 2026-07-05 19:34:51 -05:00
John Lancaster 87821ed109 pruned old ssh module 2026-07-05 19:23:32 -05:00
John Lancaster b37ee5777d some ssh match blocks 2026-07-05 19:11:53 -05:00
John Lancaster 68f40feffb moved 1password agent config 2026-07-05 19:04:48 -05:00
John Lancaster a60d2bcd28 moving to john-pc 2026-07-05 19:01:38 -05:00
John Lancaster dc89592eb1 started new ssh module with janus 2026-07-05 18:21:52 -05:00
John Lancaster fa29ce93f4 reorg 2026-07-05 11:26:36 -05:00
John Lancaster 73f5df1832 WIP SSH cert wrappers 2026-07-05 11:16:20 -05:00
John Lancaster 225020eb8d check wrappers 2026-07-05 08:56:32 -05:00
John Lancaster c762c24d86 added github copilot cli 2026-07-05 00:04:42 -05:00
John Lancaster a9b831a6b0 ssh cert consolidation 2026-07-05 00:03:28 -05:00
John Lancaster b1f4b6ad41 janus deployment 2026-07-04 23:19:35 -05:00
John Lancaster f2ba5d914c formatting tweaks 2026-07-04 22:25:48 -05:00
John Lancaster deae5d223a ssh fallback keys during bootstrap 2026-07-04 22:25:28 -05:00
John Lancaster fae21e2962 flake.lock update 2026-07-04 11:52:15 -05:00
John Lancaster 69f492c7cc WIP janus secrets 2026-07-04 11:49:58 -05:00
John Lancaster cddc369687 commiting public keys/certs for janus 2026-07-04 10:38:29 -05:00
John Lancaster 3f767dfc43 sops instructions 2026-07-04 00:30:15 -05:00
John Lancaster dccdc15851 setting nvim as editor 2026-07-03 23:42:27 -05:00
John Lancaster a3a1cd7066 WIP janus secrets 2026-07-03 23:29:20 -05:00
John Lancaster 35fec024e0 created config options for step-ca 2026-07-03 23:06:06 -05:00
John Lancaster 6499ad7612 WIP with placeholder secrets 2026-07-03 22:46:49 -05:00
John Lancaster cf418ca7a0 root version of jsl-zsh 2026-07-03 21:46:31 -05:00
John Lancaster 8ae74a9bc9 silencing warning 2026-07-03 21:22:50 -05:00
John Lancaster b74133985c pruning zsh 2026-07-03 21:16:00 -05:00
John Lancaster 2de7650f3d jsl-zsh in ghostty 2026-07-03 20:52:31 -05:00
John Lancaster 41f33653a7 central time zone on LXCs 2026-07-02 20:20:12 -05:00
John Lancaster 460d8908bb jsl-zsh working as a login shell 2026-07-02 20:11:49 -05:00
John Lancaster 621a61fb9e trusting ubuntu key by default 2026-07-02 08:38:42 -05:00
John Lancaster 3f3d847134 extra principals for SSH host certs 2026-07-02 08:38:06 -05:00
John Lancaster 4e5f1230ab host table 2026-07-01 22:40:04 -05:00
John Lancaster c3eeeb16f8 flake updates 2026-07-01 22:39:57 -05:00
John Lancaster 7556b13eb9 sublime fix 2026-07-01 22:35:55 -05:00
John Lancaster a72f34ab51 fix 2026-07-01 14:52:40 -05:00
John Lancaster 4e465b1846 allowing broken sublime 2026-07-01 14:35:58 -05:00
John Lancaster d2737a42e8 fix 2026-07-01 14:35:37 -05:00
John Lancaster a9ae07d5c1 zsh notes 2026-07-01 00:53:17 -05:00
John Lancaster f505100bb4 step ssh-host notes 2026-07-01 00:31:13 -05:00
John Lancaster aff22e3a0f soteria notes updates 2026-07-01 00:25:50 -05:00
John Lancaster b13b774738 consolidated options 2026-07-01 00:19:17 -05:00
John Lancaster 43fe37d4e2 soteria host notes 2026-07-01 00:03:48 -05:00
John Lancaster a98e637ce2 rename 2026-06-30 23:34:27 -05:00
John Lancaster 0e194e5d44 naming conventions 2026-06-30 23:31:11 -05:00
John Lancaster 68737a513f instructions files 2026-06-30 23:30:25 -05:00
John Lancaster e76fc00a98 hosts instructions 2026-06-30 23:08:46 -05:00
John Lancaster f22e8cb1d5 moved programs to desktop feature 2026-06-30 23:00:55 -05:00
John Lancaster bb06b848be audio module 2026-06-30 23:00:37 -05:00
John Lancaster 021ca1ba83 added steam to omen 2026-06-14 20:47:34 -05:00
John Lancaster 4bd11e1047 audio stuff in nixos-base 2026-06-14 20:47:22 -05:00
John Lancaster ff80098418 niri hotkey changes 2026-06-14 18:05:13 -05:00
John Lancaster 3265669503 security stuff in nixos-base 2026-06-14 18:04:27 -05:00
John Lancaster 7294e04ae2 onepassword module 2026-06-14 13:17:05 -05:00
John Lancaster 3e1d438453 desktop import 2026-06-14 12:50:36 -05:00
John Lancaster 2f653b95ce added websockets 2026-06-14 12:43:31 -05:00
John Lancaster 1c1515678a created nixos-base 2026-06-14 12:25:33 -05:00
John Lancaster ee4839877c null fixes 2026-06-14 11:31:33 -05:00
John Lancaster fc2325e70b fix 2026-06-14 11:21:01 -05:00
John Lancaster 2ae5eb9547 niri hotkeys 2026-06-14 11:15:27 -05:00
John Lancaster ba305e29fd WIP noctalia rendering 2026-06-14 10:58:26 -05:00
John Lancaster deda2c9bbc WIP greetd 2026-06-14 10:06:43 -05:00
John Lancaster 10c8c887db WIP greetd 2026-06-14 10:02:11 -05:00
John Lancaster 7b2dffbccf WIP niri greeter 2026-06-14 09:41:14 -05:00
John Lancaster 95f382107d separated wrapped nixGL packages 2026-06-14 08:37:30 -05:00
John Lancaster 3cd3099987 added greetd 2026-06-14 08:33:00 -05:00
John Lancaster 775543224e moved 2026-06-14 08:29:56 -05:00
John Lancaster aff608c1ce login stuff 2026-06-14 08:26:13 -05:00
John Lancaster ecb640fa45 undo 2026-06-14 08:11:55 -05:00
John Lancaster ceeba1c786 added spawn-at-startup command to niri 2026-06-14 08:09:18 -05:00
John Lancaster b8153f0ebc formatting 2026-06-14 07:52:17 -05:00
John Lancaster e9ec34f15c flake.lock update 2026-06-14 07:45:10 -05:00
John Lancaster 6c8ca29751 added john module 2026-06-13 10:46:29 -05:00
John Lancaster bcd07532db flake.lock update 2026-06-13 10:06:46 -05:00
John Lancaster 2861c460c2 ssh updates 2026-06-13 10:05:50 -05:00
John Lancaster 6e27e92f89 flake.lock update 2026-06-13 09:43:07 -05:00
John Lancaster 1002e6e0da wrapped versions of niri and noctalia 2026-06-13 09:27:01 -05:00
John Lancaster 24eeb5967c initial omen config 2026-06-11 08:28:49 -05:00
John Lancaster b7f5474893 commented out my-neovim because it's in jsl-zsh 2026-06-10 08:56:50 -05:00
John Lancaster 1667e362aa gpu stuff for zed on x11 2026-06-10 08:56:04 -05:00
John Lancaster cd5a49c4a6 commented out mtls for john-pc 2026-06-10 08:54:08 -05:00
John Lancaster 244c60d9cd moved yazi to shell-tools 2026-06-10 08:53:21 -05:00
John Lancaster f2254e5dc7 added yazi 2026-06-10 08:53:21 -05:00
John Lancaster a337ce6f2c kde updates 2026-06-09 09:06:21 -05:00
John Lancaster 278796f47c again 2026-06-03 19:43:50 -05:00
John Lancaster 1ab00f286e obsolete 2026-06-03 19:43:39 -05:00
John Lancaster 771813a064 fixed double description 2026-06-03 19:43:21 -05:00
John Lancaster 722cb78737 gnome 50 update 2026-06-03 19:43:07 -05:00
John Lancaster 6bb73959c6 updated zsh with devenv 2026-06-03 08:50:02 -05:00
John Lancaster fcff43adb1 flake.lock update 2026-06-03 08:49:24 -05:00
John Lancaster 65751a14c5 format 2026-05-09 10:55:10 -05:00
John Lancaster 05f92d1f59 again 2026-05-08 17:07:32 -05:00
John Lancaster 7c4ce8a637 lazygit restructure 2026-05-08 17:05:54 -05:00
John Lancaster bb4fa7b82b more neovim submodules 2026-05-06 22:13:32 -05:00
John Lancaster dc4df3af29 flake.lock update 2026-05-06 22:13:02 -05:00
John Lancaster 6b8727b29d started neovim-min 2026-05-06 22:05:02 -05:00
John Lancaster d64ce644c3 added flake dir output 2026-05-06 20:32:30 -05:00
John Lancaster 4746468a94 created gitignore for result/ 2026-05-06 20:32:09 -05:00
John Lancaster 12e7d290a1 ghostty-gpu-check 2026-05-06 20:25:55 -05:00
John Lancaster 24f43872ce hermes comments 2026-05-06 19:30:05 -05:00
John Lancaster 35bd80424d sha256 update 2026-05-02 09:47:23 -05:00
John Lancaster 3447b28af1 syntax fix 2026-05-02 09:45:48 -05:00
John Lancaster 2fea8238d1 systemd 2026-05-01 00:11:55 -05:00
John Lancaster cf1174d36b consolidation 2026-04-30 22:39:27 -05:00
John Lancaster bae2b3027e WIP mtls wrapper 2026-04-30 22:28:35 -05:00
John Lancaster 68483c0231 improvement 2026-04-30 16:36:38 -05:00
John Lancaster f0eba76e49 mtls-check wrapping openssl kinda working 2026-04-30 16:31:26 -05:00
John Lancaster 8357372b39 mtlsCheck wrapper 2026-04-29 21:46:11 -05:00
John Lancaster 3c4aa74b0f WIP mtls wrappers 2026-04-29 21:36:16 -05:00
John Lancaster ed473ddfae started zed-editor wrapper, package, and home-manager module 2026-04-29 17:46:01 -05:00
John Lancaster 133bad5aef started mtls wrappers 2026-04-27 09:01:19 -05:00
John Lancaster 3fc08793fe enabled tailscale ssh block 2026-04-26 19:04:01 -05:00
John Lancaster 7f4fdcf4b9 setting zsh shell 2026-04-26 19:03:44 -05:00
John Lancaster bfc5da791a added tailscale ssh block 2026-04-26 18:58:09 -05:00
John Lancaster 9a09399ca3 broke out starship 2026-04-22 23:40:35 -05:00
John Lancaster cf90d3e876 various reorgs 2026-04-20 23:52:03 -05:00
John Lancaster 443020df4d formatting 2026-04-20 22:40:08 -05:00
John Lancaster 3fc3beb4ed test and dev wrappers 2026-04-20 22:38:31 -05:00
John Lancaster 43ae292f39 rename 2026-04-20 21:17:18 -05:00
John Lancaster 026bf541e1 sops with wrappers 2026-04-20 21:13:31 -05:00
John Lancaster e75951318d better ssh certs wrappers 2026-04-20 21:13:19 -05:00
John Lancaster bd236ed977 temp 2026-04-20 16:49:31 -05:00
John Lancaster b07bf102a4 added jsl-zsh to john-p14s config 2026-04-19 23:38:32 -05:00
John Lancaster 9bfe140367 added name to silence warning 2026-04-19 23:33:57 -05:00
John Lancaster 545a17586e prune 2026-04-19 23:29:44 -05:00
John Lancaster 685c1bc05a root fix 2026-04-19 23:29:44 -05:00
John Lancaster a4f988c223 fix 2026-04-19 23:25:08 -05:00
John Lancaster a05de7df1f Added nerd fonts to nixos config for john-p14s 2026-04-19 23:20:06 -05:00
John Lancaster aace1776d5 sign-ssh-user-cert 2026-04-19 18:54:09 -05:00
John Lancaster 03965917ea reload-or-restart sshd 2026-04-19 17:49:14 -05:00
John Lancaster dac6b70445 sign-ssh-host-cert 2026-04-19 17:31:38 -05:00
John Lancaster 932616177a history fix 2026-04-19 15:59:28 -05:00
John Lancaster c1bfa64cc8 step-bootstrap 2026-04-19 15:45:06 -05:00
John Lancaster 235cd297c5 silencing warning 2026-04-19 15:13:56 -05:00
John Lancaster 0c91b1d493 fixed histfile 2026-04-19 15:04:06 -05:00
John Lancaster 9825270d64 keybind for delete 2026-04-19 15:03:58 -05:00
John Lancaster ee9573fc97 removed direnv hook 2026-04-19 14:56:45 -05:00
John Lancaster d47394d4cc reordered zshrc 2026-04-19 14:47:24 -05:00
John Lancaster 0abbcf0fd2 added some key bindings for compatibility 2026-04-19 14:45:36 -05:00
John Lancaster 718aa466b6 prune 2026-04-19 14:37:24 -05:00
John Lancaster 916fd41555 background 2026-04-19 14:35:02 -05:00
John Lancaster 65608646bb ssh updates 2026-04-19 14:33:49 -05:00
John Lancaster 1278177e4d added hostname to starship format 2026-04-19 14:26:57 -05:00
John Lancaster f36bb22635 extraOptions for docs 2026-04-19 14:04:23 -05:00
John Lancaster fe24eb2dde prune binName 2026-04-19 14:03:48 -05:00
John Lancaster e9d585f8d0 shell-tools update 2026-04-19 14:02:35 -05:00
John Lancaster 93e58c341d added lazydocker stuff to jsl-zsh 2026-04-19 11:45:34 -05:00
John Lancaster b8c73b446c moved env vars 2026-04-19 10:55:51 -05:00
John Lancaster 58816b2356 variables to correct gdu output 2026-04-19 10:48:07 -05:00
John Lancaster ca2f5ac7c8 added glibc to runtimeInputs of gdu 2026-04-19 10:32:18 -05:00
John Lancaster 904dd6e329 started wireguard wrapper 2026-04-19 09:38:06 -05:00
John Lancaster 8073125f3e flake.lock update 2026-04-19 09:37:11 -05:00
John Lancaster f24a269af3 newline in starship prompt 2026-04-19 09:29:46 -05:00
John Lancaster a5a10772d1 reworked jsl-zsh 2026-04-17 00:22:45 -05:00
John Lancaster 510a026de7 user@hostname for nhms 2026-04-16 22:20:19 -05:00
John Lancaster dd47ae94bf wrapped version of eza 2026-04-16 19:05:04 -05:00
75 changed files with 3942 additions and 1897 deletions
@@ -0,0 +1,68 @@
# Host Naming Discrepancy Report
This report compares the current `modules/hosts` tree against the naming rules for new hosts in `hosts.instructions.md`.
## Rules Checked
- The host directory slug should be the canonical host identity.
- A directory-backed host should prefer `default.nix` as its main entrypoint.
- The canonical slug should line up with the primary `hostname` binding, `flake.nixosConfigurations.<slug>`, and `networking.hostName`.
- A host-local Home Manager module should use the same slug.
- A standalone Home Manager configuration should prefer the key `"<username>@<slug>"`.
- Host-local helper module names should be prefixed by the host slug.
## Summary
- Recently normalized: `john-kde`, `omen-nixos`.
- Hosts that fit the new rules closely: `janus`, `john-kde`, `omen-nixos`, `test-nix`.
- Hosts with notable discrepancies: `john-p14s`, `soteria`.
- Approved exception: `john-pc` directory uses immutable deployed slug `john-pc-ubuntu` for exported keys.
## Detailed Discrepancies
### `john-p14s`
- The main host export uses the canonical slug: `flake.nixosConfigurations.john-p14s`.
- The host-local helper modules are named `p14sConfiguration` and `p14sHardware`, which drop the `john-` prefix and therefore do not use the full host slug.
- `networking.hostname` uses the correct host value, but the option name differs from the dominant `networking.hostName` spelling used elsewhere in this tree.
### `john-pc`
- Approved exception for immutable deployed hostname:
- Directory slug is `john-pc`, but exported keys and hostname bindings intentionally use `john-pc-ubuntu`.
- `hostname` binding, `flake.modules.homeManager.<name>`, and `flake.homeConfigurations` are aligned to `john-pc-ubuntu`.
- Shared SSH alias intentionally remains `john-pc-ubuntu` in `modules/services/ssh.nix`.
### `omen-nixos`
- No active discrepancy after normalization:
- The directory slug, `flake.nixosConfigurations` key, host-local module keys, and `networking.hostName` are now aligned to `omen-nixos`.
### `john-kde`
- No active discrepancy after normalization:
- The directory slug, `hostname` binding, host-local Home Manager module key, and standalone Home Manager key are now aligned to `john-kde`.
### `soteria`
- Naming is mostly coherent across directory slug, `hostname`, `flake.nixosConfigurations.soteria`, and `flake.modules.homeManager.soteria`.
- The standalone Home Manager export is `flake.homeConfigurations.soteria`, which does not follow the preferred `"<username>@<slug>"` form.
- The main host entrypoint is `soteria.nix` instead of the preferred `default.nix`.
## No Discrepancy Found
### `janus`
- Directory slug, `hostname`, `flake.nixosConfigurations.janus`, and `networking.hostName` are aligned.
- The host-local helper module `janus-ca` is clearly prefixed by the host slug.
### `test-nix`
- The single-file host uses the same slug for the file stem, `hostname`, and `flake.nixosConfigurations.test-nix`.
- It does not define `networking.hostName`, but it does not contradict the slug anywhere else.
## Follow-up Candidates
- Rename `john-p14s` helper module keys to include the full host slug (`john-p14s-*`) if strict slug consistency is desired.
- Decide whether `soteria` should move to `default.nix` and whether its standalone Home Manager key should include `john@`.
- If `john-pc-ubuntu` ever becomes changeable, decide whether to rename the directory to match or keep this as a permanent exception.
@@ -0,0 +1,78 @@
---
description: "Use when defining or modifying hosts and host-local data under modules/hosts. Explains how this repo exports nixosConfigurations and homeConfigurations, when to create flake.modules.nixos or flake.modules.homeManager helpers, and how secrets, defaults, keys, and hardware files fit into a host definition."
applyTo: 'modules/hosts/**/*.nix, modules/hosts/**/secrets.yaml, modules/hosts/**/defaults.json, modules/hosts/**/fingerprint, modules/hosts/**/*.pub'
---
# Host Definitions
Host files under `modules/hosts` are flake entrypoints, not just loose Nix snippets. Because `modules` is auto-imported through `import-tree` and `flake-file`, files here usually export one or more of these attributes:
- `flake.nixosConfigurations.<name>` for a bootable NixOS machine or container.
- `flake.homeConfigurations.<name>` for a standalone Home Manager target.
- `flake.modules.nixos.<name>` or `flake.modules.homeManager.<name>` when a host is split into reusable host-local modules that are then assembled by a nearby entrypoint.
Follow the existing host patterns in this tree:
- Keep the host entrypoint file focused on composing modules into `nixosSystem` or `homeManagerConfiguration`.
- If a host has substantial machine-specific logic, put that logic in sibling files such as `configuration.nix` or `hardware.nix`, export them as `flake.modules.nixos.<name>`, and have the entrypoint import those modules.
- For Home Manager only hosts, define `flake.modules.homeManager.<name>` and then expose a matching `flake.homeConfigurations` entry.
- Prefer composing from shared modules in `self.modules.nixos` or `inputs.self.modules.homeManager` instead of re-implementing shared behavior inline.
Treat host-local data as part of the host definition:
- Keep hardware-specific settings in the host directory, typically in `hardware.nix`.
- Keep install-time or machine-specific configuration in the same host directory, typically in `configuration.nix`.
- Keep host-local secret references beside the host and wire them through `sops.defaultSopsFile` and `mysops.hostSecretFile` instead of pointing at unrelated locations.
- Preserve nearby auxiliary files such as `defaults.json`, fingerprints, public keys, and `secrets.yaml`; these are part of the host contract.
For host-adjacent data files:
- Keep `secrets.yaml` scoped to the host that consumes it; do not reuse another host's secret file as a shortcut.
- Treat files such as `defaults.json`, `fingerprint`, and `*.pub` as inputs consumed by the host module. Update references together with the data when changing paths or filenames.
- Avoid moving or renaming these files unless the corresponding Nix references are updated in the same change.
Naming in this tree is not perfectly uniform, so preserve existing interfaces unless the task is explicitly a rename:
- The directory name, exported flake key, and `networking.hostName` may differ.
- Some hosts export from `default.nix`, while others keep the main definition in a differently named file such as `soteria.nix`.
- Do not normalize names or move files just for consistency unless the user asks for that structural change.
Naming rules for new hosts:
- Treat the host directory name as the canonical host slug for new work.
- Prefer `modules/hosts/<slug>/default.nix` as the main entrypoint for a host directory.
- Use the same slug for the primary `hostname` binding, `flake.nixosConfigurations.<slug>`, and `networking.hostName` unless the task explicitly requires a different deployed hostname.
- If the host exports a host-local Home Manager module, name it `flake.modules.homeManager.<slug>`.
- For standalone Home Manager configurations, prefer `flake.homeConfigurations."<username>@<slug>"` so the exported key still carries the host slug.
- Name host-local helper modules with the same slug as a prefix, for example `flake.modules.nixos.<slug>-hardware` or `flake.modules.nixos.<slug>-configuration`, to make ownership obvious and avoid collisions with shared modules.
- Keep host-local secrets and auxiliary files under the same host slug directory. Do not point a new host at another host's path just because the contents are similar.
Allowed exception for immutable deployed hostnames:
- If the deployed hostname is externally constrained and cannot change, use that deployed hostname as the canonical slug for exported keys and `hostname` bindings, even if the directory name differs.
- In that case, keep the exception explicit in comments or reports so future cleanup work does not accidentally rename a live hostname contract.
When working in existing hosts that predate these rules:
- Preserve the current public names by default.
- If the user asks for a rename or cleanup, update the directory slug, exported flake keys, host-local module names, and `networking.hostName` together in one change so the host identity stays coherent.
Use nearby hosts as composition examples:
- `john-p14s` splits the reusable machine logic into `configuration.nix` and `hardware.nix`, exports `flake.modules.nixos.p14sConfiguration` and `flake.modules.nixos.p14sHardware`, and assembles them from `default.nix`.
- `janus` defines a host-local reusable module (`flake.modules.nixos.janus-ca`) in the same file that also exports the final `flake.nixosConfigurations.janus` host.
- `john-pc` is a Home Manager target, so it exports a `flake.modules.homeManager` module and a `flake.homeConfigurations` entry rather than a `nixosConfiguration`.
- `soteria` combines a NixOS host, a host-local Home Manager module, and host-local secrets in one host directory.
When adding or changing a host:
- Mirror the local style first. Small hosts may define the full configuration inline; larger hosts should split reusable modules out.
- Keep `let` bindings such as `username`, `hostname`, `flakeDir`, and package aliases near the top when the file already follows that pattern.
- Add shared behavior by importing existing modules, not by copying option blocks between hosts.
- If the change affects both system and Home Manager state for a host, update both sides in the same host area when that host already models both.
For reviews and answers, distinguish between these layers:
- host entrypoint wiring in `modules/hosts/...`
- reusable shared modules in `modules/nixos`, `modules/programs`, `modules/services`, and `modules/users`
- host-local modules exported from a host directory and then consumed by its entrypoint
@@ -0,0 +1,96 @@
---
description: "Use when modifying the Janus host, Janus step-ca deployment, Step SSH CA, mTLS issuance, CA bootstrap artifacts, or Janus SOPS secrets. Covers idiomatic Nix, secret placement, and troubleshooting."
name: "Janus Host Instructions"
applyTo: "modules/hosts/janus/**, modules/services/step-ca/step-ca.nix, modules/features/step-client.nix"
---
# Janus Host Instructions
Use this instruction when changing Janus host wiring, Janus CA material, or the shared Step CA module that Janus consumes.
## Host Intent And Boundaries
- Treat Janus as the homelab certificate authority host. It runs `step-ca` for X.509 issuance, SSH user and host certificates, and mTLS certificates.
- Assume Janus CA endpoints are private/LAN-only unless the task explicitly changes the exposure model.
- Keep Janus as a concrete host under `modules/hosts/janus`; keep reusable behavior in shared modules such as `modules/services/step-ca/step-ca.nix`, `modules/services/step-ca/ssh-host.nix`, `modules/features/step-client.nix`, and `modules/features/mtls.nix`.
- Preserve host identity unless the task explicitly renames the deployed CA: `hostname = "janus"`, `flake.nixosConfigurations."${hostname}"`, and `networking.hostName = hostname`.
- Keep Janus-specific paths, DNS names, IP SANs, and CA artifacts in the Janus host area or in Janus-specific option values. Do not move them into generic shared modules unless they become a deliberate module interface.
## Required Module Composition
When editing `modules/hosts/janus/default.nix`, preserve this composition unless the task explicitly changes Janus architecture:
- `nixos.lxc` for the container host shape.
- `nixos.mysops` with `sops.defaultSopsFile = ./secrets.yaml` for host-local encrypted CA runtime material.
- `nixos.step-ca` to run the CA daemon and render `/etc/smallstep/ca.json` from Nix plus SOPS paths.
- `nixos.step-client` to install Step trust bootstrap defaults and root CA material.
- `nixos.ssh-certs` so Janus itself participates in SSH host certificate flow.
- `nixos.mtls` so Janus can issue and renew its own mTLS certificate bundle.
## Step CA Deployment Pattern
- Prefer structured Nix values rendered with `builtins.toJSON` over hand-written JSON strings for `ca.json`.
- Prefer the NixOS `services.step-ca.settings` interface for normal `ca.json` settings. Use a `sops.templates`-rendered config only when the rendered JSON needs sops-nix placeholders or runtime secret paths.
- Keep secret-bearing `ca.json` content rendered through `sops.templates` when it includes values from `config.sops.placeholder`; do not put decrypted private keys, passwords, or provisioner encrypted private-key values directly in the Nix store.
- Continue using `services.step-ca` for the daemon contract: `enable`, `settings`, `address`, `port`, `openFirewall`, `package`, and `intermediatePasswordFile`.
- Remember that the NixOS module overrides `services.step-ca.settings.address` with `services.step-ca.address` plus `services.step-ca.port`. Change the high-level address/port options when changing the listener.
- Keep the intermediate password as a runtime string path from `config.sops.secrets.<name>.path`; do not use a Nix path literal or the password file can be copied to the globally readable Nix store. Do not pass passwords through command-line arguments or environment variables.
- Account for upstream NixOS service behavior when debugging: the module writes `/etc/smallstep/ca.json`, starts `step-ca.service` as `Type=notify`, sets `DynamicUser = true`, uses `StateDirectory = "step-ca"`, and passes `intermediatePasswordFile` through systemd `LoadCredential`.
- If `step-ca` cannot read key files referenced by `ca.json`, check sops-nix owner/group/mode and systemd credential handling before loosening permissions. The password file path and CA signing key paths are separate contracts.
- Keep Step CA runtime state in `/var/lib/step-ca/db` unless the task explicitly migrates storage. If moving away from embedded Badger, document database backup and HA implications.
- Keep `root`, `crt`, `key`, `ssh.hostKey`, and `ssh.userKey` in `ca.json` pointing at public cert paths or sops-nix materialized secret paths as appropriate.
- Use an ACME provisioner for automated X.509 issuance, an `SSHPOP` provisioner for SSH certificate renewal/rekey behavior, and the `admin` JWK provisioner for explicit administrative or scripted issuance. Ensure JWK claims include `enableSSHCA = true` when it is expected to sign SSH certificates.
- Treat Nix and SOPS as the source of truth. If `step ca provisioner add`, `remove`, or `update` is used for investigation, copy the intended result back into Nix/SOPS and reload or restart `step-ca`; do not leave live `ca.json` drift on the host.
- Use `authority.claims` or provisioner-level claims for certificate lifetimes, renewal behavior, and SSH CA enablement. Avoid `allowRenewalAfterExpiry` unless the task explicitly accepts the added risk for intermittently connected clients.
- Add `authority.policy` when constraining which X.509 SANs or SSH principals Janus may issue. This is mandatory for any move away from private/LAN-only operation.
## CA Material And Secret Placement
Store each kind of material in the narrowest place that matches who needs it:
- `modules/hosts/janus/secrets.yaml`: Janus runtime CA secrets used by `step-ca`, including `janus/ca_password`, `janus/intermediate_ca_key`, `janus/ssh_host_ca_key`, `janus/ssh_user_ca_key`, and `janus/admin_provisioner_encrypted_key`.
- `keys/secrets.yaml`: shared provisioner credential `janus/admin_jwk`, because client and host certificate modules outside Janus consume it to request certs.
- `modules/hosts/janus/root_ca.crt`, `intermediate_ca.crt`, `fingerprint`, `ssh_user_ca_key.pub`, and `ssh_host_ca_key.pub`: public trust artifacts that may be committed and reviewed.
- Offline secret storage only: `root_ca_key` and the root key password. The running CA should not need the root private key for normal operation.
When editing secrets:
- Use SOPS commands (`sops`, `edit-secrets`, or `sops set`) rather than editing `ENC[...]` payloads.
- Use YAML literal blocks for PEM or OpenSSH private keys so newlines are preserved.
- After changing Janus recipient rules, run `sops --config .sops.yaml updatekeys -y modules/hosts/janus/secrets.yaml` so the host-local file is actually rewrapped.
- Keep `.sops.yaml` scoped: Janus host runtime secrets should match `modules/hosts/janus/secrets.yaml`; shared credentials should match `keys/secrets.yaml`.
## Public Trust Artifacts
- If `root_ca.crt` changes, update `fingerprint` from `step certificate fingerprint <root_ca.crt>` and update `modules/features/step-client.nix` defaults in the same change.
- If `intermediate_ca.crt` changes, confirm it still chains to the committed root and that the running CA uses the matching `intermediate_ca_key` from SOPS.
- If SSH CA keys rotate, update the matching public key files and every SSH trust consumer, including `modules/services/ssh.nix` known-host CA values and `TrustedUserCAKeys` behavior.
- Treat `README.md` bootstrap commands as part of the operational contract. Keep them aligned with secret names, public artifact paths, CA URL, DNS names, and IP SANs.
## mTLS And Client Trust
- Janus mTLS values should stay aligned with Step CA identity: update `step-ca.dnsNames`, `mtls.subject`, `mtls.san`, and Step client defaults together when CA names or addresses change.
- `modules/features/step-client.nix` is the shared bootstrap surface. Its default CA URL, root certificate, and fingerprint should describe Janus unless the repo intentionally migrates to another CA.
- mTLS certificate files are runtime outputs under the configured cert directory, not tracked source files. Do not store mTLS private keys in Git or the Nix store.
## Step/Nix Troubleshooting Checklist
Start with the smallest check that matches the change:
1. Host evaluation: `nix eval .#nixosConfigurations.janus.config.system.build.toplevel.drvPath`
2. Build planning: `nix build .#nixosConfigurations.janus.config.system.build.toplevel --no-link --dry-run`
3. Secret contract: confirm every `step-ca.secrets.*` value has a matching `sops.secrets` declaration and a matching key in `modules/hosts/janus/secrets.yaml`.
4. Rendered config: on the host, inspect `/etc/smallstep/ca.json` or `/etc/step-ca` paths without copying secret content into logs.
5. Service health: check `systemctl status step-ca.service`, `journalctl -u step-ca.service`, and `step ca health --ca-url https://janus.john-stream.com` after trust is bootstrapped.
6. Provisioners: use `step ca provisioner list` to confirm ACME, SSHPOP, and admin JWK availability.
7. SSH cert flow: use `ssh-host-cert-check`, `systemctl status ssh-certs-renew.service`, and `systemctl status ssh-certs-renew.timer`.
8. mTLS flow: use `mtls-check`, `mtls-generate`, and `systemctl status mtls-renew.timer` for host certificate renewal issues.
## Change Safety Rules
- Do not deploy a root private key to Janus unless the task explicitly changes the trust model.
- Do not rename `janus/admin_jwk` without updating all consumers in the same change.
- Do not expose ACME or `/provisioners` endpoints publicly without an explicit migration away from the private/LAN-only model plus policy and endpoint exposure review.
- Do not use certificate templates casually; if templates are added, keep them narrow and use `toJson` for user-controlled values.
- If changing certificate durations, prefer short-lived leaf certs with automated renewal over long-lived leaf certs.
- If changing service ports or proxying, account for Step renewal behavior: mTLS renewal is sensitive to layer-7 proxies unless configured for token-based renewal.
@@ -0,0 +1,54 @@
---
description: "Use when defining or modifying reusable Nix modules outside modules/hosts. Covers flake.modules.nixos, flake.modules.homeManager, and flake.factory.user exports, and explains how shared modules differ from concrete host wiring."
applyTo: 'modules/nixos/**/*.nix, modules/users/**/*.nix, modules/features/**/*.nix, modules/programs/**/*.nix, modules/services/**/*.nix, modules/nix-tools/**/*.nix'
---
# Shared Modules
Files outside `modules/hosts` generally define reusable building blocks, not concrete machines. In this repo, those files usually export one of these surfaces:
- `flake.modules.nixos.<name>` for reusable NixOS modules.
- `flake.modules.homeManager.<name>` for reusable Home Manager modules.
- `config.flake.factory.user` for factories that generate per-user module sets.
- `flake.meta.*` when the file owns reusable metadata that other modules consume.
Keep the boundary between shared modules and hosts clear:
- Shared modules should not define `flake.nixosConfigurations.<name>` or `flake.homeConfigurations.<name>`; those belong in `modules/hosts`.
- Prefer generic options, imports, and composition over host-specific literals.
- If a setting only makes sense for one machine, keep it in that host directory instead of moving it into a shared module.
- When a host imports a shared module, treat the shared module as part of the stable interface that multiple hosts may depend on.
Follow the existing export patterns in this repo:
- Simple reusable modules may export a single module directly, such as `flake.modules.nixos.games`.
- Cross-cutting features often live under `modules/features` even when they export `flake.modules.nixos.*` or `flake.modules.homeManager.*`.
- Program and service integrations commonly export one or both module types from a single file.
- User definitions under `modules/users` may export metadata plus paired NixOS and Home Manager modules for the same user.
For user modules specifically:
- Keep reusable user facts under `flake.meta.users.<name>` when other modules need to reference them.
- Prefer deriving the NixOS side from `self.factory.user` when the file already follows that pattern.
- Keep the user-facing Home Manager module in `flake.modules.homeManager.<name>` and let the factory or host wire it into a concrete configuration.
- Put user-specific authorized keys, identity, and shared defaults here rather than duplicating them across hosts.
Design shared modules as composable interfaces:
- Import other shared modules instead of copying option blocks.
- Add options or parameters when behavior needs to vary between hosts.
- Avoid embedding host-specific paths, hostnames, addresses, or secret file locations unless the file is intentionally host-local.
- Preserve exported attribute names even when the filename is different. In this repo, the path is not always the public API name.
Use nearby files as examples:
- `modules/nixos/games.nix` is a minimal shared NixOS module with no host wiring.
- `modules/features/nixos-base.nix` defines a reusable base system module that other hosts import.
- `modules/users/john.nix` combines `flake.meta.users.john`, a reusable NixOS user module, and a reusable Home Manager user module built around the user factory.
- `modules/nix-tools/user.nix` defines the `flake.factory.user` helper that shared user modules build on.
For reviews and answers, separate these concerns clearly:
- reusable module API and option design in shared module directories
- concrete host assembly in `modules/hosts`
- whether a change increases reuse or accidentally pulls machine-specific behavior into a shared layer
@@ -0,0 +1,72 @@
---
description: "Use when working with SOPS in this repo: editing secrets, adding multiline values, changing recipients, or wiring sops-nix consumers. Captures repo conventions, safe commands, and validation habits."
applyTo: ".sops.yaml, keys/secrets.yaml, modules/hosts/**/secrets.yaml, modules/hosts/**/*.nix, modules/programs/sops.nix, modules/features/**/*.nix, modules/services/**/*.nix"
---
# Using SOPS In This Repo
This repo treats SOPS files as the source of encrypted runtime material, and Nix code as the wiring that exposes those secrets to services. Keep those roles separate: edit secret values with SOPS, consume them through sops-nix paths, and keep recipient policy scoped to the machines or operators that actually need access.
## Mental Model
- Secrets live in encrypted YAML files; Nix modules should refer to secret paths, not decrypted values.
- Host-only credentials belong in `modules/hosts/<host>/secrets.yaml`.
- Shared credentials belong in `keys/secrets.yaml` only when multiple hosts intentionally consume the same material.
- `.sops.yaml` controls who can decrypt each file; keep those recipient sets narrow and explicit.
- Secret key names are an interface. Rename them only when every consumer changes in the same edit.
## Editing Secrets
- Use SOPS tooling for all value changes: `sops <file>`, `edit-secrets`, or `sops set ...`.
- Do not hand-edit `ENC[...]` payloads. That bypasses SOPS and breaks the encrypted document's integrity metadata.
- Keep plaintext out of tracked files. Temporary plaintext files are acceptable only as local working inputs and should be removed after use.
- Prefer `SOPS_EDITOR=nvim sops <file>` for interactive edits when editor choice matters.
For large or generated values, avoid putting the secret directly in shell history:
```bash
sops set --value-file <secrets-file> '["parent"]["key"]' /path/to/plaintext-value
```
If using inline `sops set`, the value argument must be valid JSON.
## Multiline Values
Use YAML literal blocks for private keys, certificates, provisioner keys, and other values where newlines matter:
```yaml
janus:
ssh_user_ca_key: |-
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
```
- Use `|-` when the final trailing newline should be stripped.
- Use `|` when the consuming program expects the final trailing newline.
- Avoid folded style (`>`) for keys and certs because it rewrites line breaks.
## Wiring Secrets Into Nix
- Consume secrets through `config.sops.secrets."<key>".path` or `config.sops.placeholder` where templating requires it.
- Do not read decrypted secret contents during Nix evaluation.
- Set `owner`, `group`, and `mode` explicitly for non-root services.
- Use `path` or `sops.templates` when a service needs a specific file layout instead of copying secret contents into the store or tracked files.
- If moving a secret between shared and host-local files, update `sops.defaultSopsFile`, `mysops.hostSecretFile`, and every affected `sops.secrets` declaration together.
## Recipient And Rotation Practices
- Add dedicated `.sops.yaml` `path_regex` rules for host-local files before broad fallback rules.
- Keep broad fallback rules conservative; do not expand them to grant casual access to every YAML or JSON file.
- Run `sops updatekeys <file>` after changing recipients so data is rewrapped for the new key set.
- Rotate or migrate in order: update recipients, re-encrypt affected files, update consumers, then validate affected hosts.
## Validation Habits
After SOPS or sops-nix changes, check the contract rather than only the syntax:
1. The expected secrets file is still selected by `sops.defaultSopsFile` or `mysops.hostSecretFile`.
2. `.sops.yaml` has a specific rule for any host-local `secrets.yaml` touched.
3. Secret names in YAML still match the `sops.secrets."<key>"` declarations.
4. Services that need restart or reload behavior have `restartUnits` or `reloadUnits` set.
5. Affected host evaluation/build catches missing secret keys before deployment.
@@ -0,0 +1,139 @@
---
description: "Use when modifying the Soteria host definition or host-local secrets under modules/hosts/soteria. Covers host wiring and SSH cert mechanics specific to Soteria."
name: "Soteria Host Instructions"
applyTo: "modules/hosts/soteria/**/*.nix, modules/hosts/soteria/secrets.yaml"
---
# Soteria Host Instructions
Use this instruction when changing Soteria host wiring in `modules/hosts/soteria`.
## Host Intent And Shape
- Treat Soteria as a NixOS service node for Forgejo, RESTic server, mTLS, and a paired Home Manager profile for `john`.
- Keep host identity aligned: `hostname = "soteria"`, `networking.hostName = hostname`, and `flake.nixosConfigurations."${hostname}"`.
- Keep host-local secrets in `modules/hosts/soteria/secrets.yaml` and wire with `sops.defaultSopsFile = ./secrets.yaml`.
- Keep host-specific Home Manager wiring in `flake.modules.homeManager.soteria`, and keep `mysops.hostSecretFile` pointing at the Soteria secrets file.
## Required Module Composition
When editing `modules/hosts/soteria/default.nix`, preserve this composition unless the task explicitly changes architecture:
- Import `nixos.ssh-certs` to enable host SSH cert integration.
- Import `nixos.janus-ca` so Step client trust material and defaults remain available.
- Import `nixos.restic-server` so the RESTic REST server implementation lives in the shared module rather than inline host wiring.
- Keep `ssh-certs.hostname = hostname;` so cert principals match host naming.
## SSH Cert Mechanics On Soteria
Soteria SSH certificate behavior is the result of multiple modules. Keep this flow intact:
1. `soteria/default.nix` imports `nixos.ssh-certs` and sets `ssh-certs.hostname = "soteria"` (via `hostname`).
2. `modules/services/step-ca/ssh-host.nix`:
- Enables `ssh.certificates.enable = true`.
- Requires `sops.secrets."janus/admin_jwk"` (provisioner credential) from Soteria's `secrets.yaml`.
- Defines cert paths at `/etc/ssh/ssh_host_ed25519_key` and `/etc/ssh/ssh_host_ed25519_key-cert.pub`.
- Exposes `ssh-host-cert-renew` and `ssh-host-cert-check` helper binaries.
- Schedules `ssh-certs-renew.timer` every 4h with jitter.
3. `modules/services/ssh.nix` consumes `ssh.certificates.enable` and configures OpenSSH to:
- Set `TrustedUserCAKeys = /etc/ssh/ssh_user_ca_key.pub`.
- Set `HostCertificate = /etc/ssh/ssh_host_ed25519_key-cert.pub`.
- Install the trusted user CA file into `/etc/ssh/ssh_user_ca_key.pub`.
4. `nixos.janus-ca` provides Step CA trust bootstrapping (`/etc/step-ca/defaults.json` and linked root CA material), allowing Step CLI operations to trust and reach the CA endpoint.
Current implementation note:
- `ssh-certs-renew.service` currently checks renewal state via `step ssh needs-renewal` and logs status. It does not directly invoke `ssh-host-cert-renew` in the service script. Preserve this behavior unless the task explicitly asks to change renewal execution semantics.
## SOPS/Secrets Mechanics On Soteria
Soteria secret handling spans host-local secrets, NixOS secret materialization, and Home Manager secret tooling. Keep this flow intact:
1. `modules/hosts/soteria/default.nix` imports `nixos.mysops` and sets `sops.defaultSopsFile = ./secrets.yaml`.
2. The SOPS file `modules/hosts/soteria/secrets.yaml` is the canonical encrypted source for this host's system secrets.
3. Shared modules imported by Soteria declare required secret entries under `sops.secrets` and consume them through `config.sops.secrets.<name>.path`:
- `modules/services/step-ca/ssh-host.nix` declares and consumes `janus/admin_jwk` as the Step provisioner password file.
- `modules/features/forgejo.nix` declares and consumes `forgejo/secret_key`, `forgejo/internal_token`, `forgejo/jwt_secret`, and `forgejo/lfs_jwt_secret`.
4. Secret ownership is module-defined and must stay aligned with service users:
- Forgejo secrets are owned by `config.services.forgejo.user`.
- Step SSH provisioner secret is locked to root ownership and `0400` mode.
5. `flake.modules.homeManager.soteria` imports `homeManager.mysops` and sets `mysops.hostSecretFile` to the same Soteria host secret file path. This drives helper tooling such as `edit-secrets` while Home Manager keeps its own default SOPS context from `modules/programs/sops.nix`.
Current implementation note:
- `restic_password` exists in `modules/hosts/soteria/secrets.yaml`, but it is not currently consumed by the Soteria NixOS module graph.
- Changing, deleting, or renaming seemingly unused keys in `secrets.yaml` should be treated as a compatibility change and confirmed by the task.
## Restic REST Server On Soteria
Soteria's RESTic REST server is implemented by the shared `nixos.restic-server` module and configured by host-local `resticServer` values. Trace it this way:
1. `modules/hosts/soteria/default.nix` imports `nixos.restic-server` and configures the host-local `resticServer` option set with:
- `enable = true`
- `dataDir = "/mnt/restic"`
- `privateRepos = true`
- `listenAddress = "0.0.0.0:8000"`
- `tls.certFile = config.mtls.certFile`
- `tls.keyFile = config.mtls.keyFile`
2. `modules/features/restic.nix` maps those `resticServer` options into `services.restic.server` and appends TLS flags when both TLS paths are set.
3. The shared module keeps the server base behavior in one place:
- `services.restic.server.enable = true`
- `services.restic.server.dataDir = cfg.dataDir`
- `services.restic.server.listenAddress = cfg.listenAddress`
- `services.restic.server.privateRepos = cfg.privateRepos`
- `services.restic.server.extraFlags = cfg.extraFlags ++ tlsFlags`
4. `modules/features/restic.nix` also opens the matching TCP port through `networking.firewall.allowedTCPPorts`, deriving it from `resticServer.listenAddress`.
5. The mTLS renewal hook includes `restic-rest-server.service` in `mtls.renew.reloadUnits`, so certificate rotation reloads the REST server alongside Forgejo.
6. `loginText.extraServiceStatus` exposes the running unit as `restic-rest-server.service`, which is the service name to keep in mind for status and reload behavior.
### Storage Location
- Soteria explicitly sets `resticServer.dataDir = "/mnt/restic"`.
- The shared module maps that value into `services.restic.server.dataDir`, so `/mnt/restic` is now the intended repository storage location for this host.
- The shared module also derives the firewall port from `resticServer.listenAddress`, so Soteria no longer needs a separate `networking.firewall.allowedTCPPorts = [ 8000 ]` line.
- If storage needs to move later, change the `resticServer.dataDir` input or the shared module contract, not ad hoc service cleanup logic.
- `privateRepos = true` constrains repo exposure behavior, but it does not define storage location by itself.
Current implementation note:
- The REST server is TLS-protected but currently passed `--no-auth`, so client access control relies on transport/security model rather than rest-server password auth.
- The shared module default listen address is not Soteria's deployed value; Soteria deliberately overrides it to `0.0.0.0:8000` in the host.
## Forgejo Implementation On Soteria
Soteria's Forgejo setup is split between host-local option values and the shared `nixos.forgejo` module. Trace it this way:
1. `modules/hosts/soteria/default.nix` imports `nixos.forgejo` and enables it with:
- `forgejo.enable = true`
- `forgejo.root_url = "https://forgejo.john-stream.com"`
- `forgejo.https = true`
- `forgejo.port = 443`
2. `modules/features/forgejo.nix` defines `forgejo.port` as the controlling option for the web listener. Its default is `3000`, but Soteria overrides it to `443`.
3. The shared module wires that option into multiple places:
- `services.forgejo.settings.server.HTTP_PORT = cfg.port`
- `networking.firewall.allowedTCPPorts = [ cfg.port ]` when `openFirewall = true`
- If `cfg.port < 1024`, the Forgejo systemd unit gets `CAP_NET_BIND_SERVICE` so it can bind a privileged port.
4. HTTPS on Soteria is terminated directly by Forgejo using mTLS-managed files:
- `services.forgejo.settings.server.PROTOCOL = "https"`
- `KEY_FILE = config.mtls.keyFile`
- `CERT_FILE = config.mtls.certFile`
### Storage Locations
- This repo does not override Forgejo's primary state directory in the Soteria host or in `modules/features/forgejo.nix`.
- That means Forgejo storage follows the underlying NixOS `services.forgejo` defaults, exposed in this config as `config.services.forgejo.stateDir`.
- PostgreSQL storage is also left at the underlying module default and is referenced as `config.services.postgresql.dataDir`.
- The cleanup helpers in `modules/features/forgejo.nix` confirm these are the intended storage anchors because they remove exactly `config.services.forgejo.stateDir` and `config.services.postgresql.dataDir`.
- Forgejo backup dumps are generated by `forgejo-dump.service` using `--work-path ${config.services.forgejo.dump.backupDir}`, so dump staging/output follows the Forgejo module's backup directory setting rather than a Soteria-specific path override.
Current implementation note:
- If you need to change Forgejo storage location on Soteria, do it by setting the underlying Forgejo or PostgreSQL service directory options explicitly, not by changing cleanup scripts alone.
- If you need to change the external Forgejo endpoint, update `forgejo.root_url`, `forgejo.port`, and the Soteria mTLS SAN list together.
## Change Safety Rules
- Do not rename the secret key `janus/admin_jwk` without updating all consumers.
- Do not change SSH host key/cert filenames unless OpenSSH `HostKey`/`HostCertificate` paths are updated together.
- If changing hostnames or domains, update cert principals in `ssh-certs` and corresponding SSH client targets together.
- If adding or removing `nixos.ssh-certs`, explain impact on SSH cert issuance, renewal, and trust in the change summary.
@@ -0,0 +1,54 @@
---
description: "Use when modifying modules/services/step-ca/ssh-host.nix. Covers Step SSH host certificate wiring, secret key contract, and host-side expectations required by consumers."
name: "Step SSH Host Module Instructions"
applyTo: "modules/services/step-ca/ssh-host.nix"
---
# Step SSH Host Module Instructions
Use this instruction when changing the shared NixOS module in modules/services/step-ca/ssh-host.nix.
## Module Contract
- Keep this module reusable across hosts; do not hard-code host-local file paths beyond SSH system paths under /etc/ssh.
- Preserve the option interface unless the task explicitly changes it:
- ssh-certs.hostname (required host identity used for cert principals)
- ssh-certs.provisioner (default: "admin")
- Keep imports = [ inputs.self.modules.nixos.ssh ] so OpenSSH host certificate settings stay composed through the shared SSH module.
## Secret Key Contract
- This module currently consumes `config.sops.secrets."janus/admin_jwk"` as the Step provisioner password file.
- Treat `janus/admin_jwk` as a public contract key name for current consumers. If you rename it, update all consumers and host secret files in the same change.
- Keep secret permissions strict (root ownership and 0400 mode).
- Do not assume a local sops file path in this module. The consuming host must define sops.defaultSopsFile.
## Host Expectations
When this module is enabled by a host, the host is expected to provide:
- A working Step trust/bootstrap path (for example via janus-ca or equivalent trust material).
- `sops.defaultSopsFile` containing `janus.admin_jwk` (or equivalent if contract is intentionally changed everywhere).
- `ssh-certs.hostname` matching deployed host identity and DNS naming expectations.
## SSH Certificate Paths And Principals
- Preserve the default host key path contract at `/etc/ssh/ssh_host_ed25519_key` unless task scope requires coordinated changes.
- Keep host certificate path aligned to `${sshKeyPath}-cert.pub`.
- Keep principal issuance based on hostname and hostname.john-stream.com unless domain policy is intentionally migrated.
- If changing key/cert filenames or principal naming, update `modules/services/ssh.nix` integration and host rollout notes in the same change.
## Renewal Behavior
- Maintain timer/service cadence unless explicitly requested:
- timer OnUnitActiveSec = 4h
- randomized delay enabled
- Keep explicit handling for step ssh needs-renewal return codes to avoid silent failures.
- If changing renewal semantics, document whether the service now only checks state or also performs certificate issuance.
## Validation Checklist
After editing this module, validate at least one consuming host with:
1. nix eval .#nixosConfigurations.janus.config.system.build.toplevel.drvPath
2. nix build .#nixosConfigurations.janus.config.system.build.toplevel --no-link --dry-run
@@ -0,0 +1,65 @@
---
description: "Use when modifying jsl-zsh, home-manager programs.zsh settings, or shell-tools wiring. Explains when to use wrapped jsl-zsh versus pkgs.zsh and how shell-tools composes into user and host configs."
applyTo: 'modules/programs/zsh.nix, modules/features/shell-tools.nix, modules/users/**/*.nix, modules/nix-tools/user.nix, modules/services/ssh.nix, modules/hosts/**/*.nix'
---
# jsl-zsh and shell-tools wiring
This repo intentionally uses two zsh variants for different layers:
- `pkgs.zsh` is the base system shell used for user login shell declarations.
- `inputs.self.packages.<system>.jsl-zsh` is the wrapped interactive shell used by Home Manager and remote shell entrypoints.
Treat these as distinct roles, not interchangeable defaults.
## Canonical definitions
The canonical wrapped shell is defined in `modules/programs/zsh.nix`:
- `perSystem.packages.jsl-zsh` wraps zsh via `wrapperModules.zsh.apply`.
- Wrapper settings, aliases, history behavior, devenv hook, and extra package PATH come from this package definition.
- `binName = "jsl-zsh"` is part of the external contract; keep it stable unless performing an explicit repo-wide rename.
The Home Manager zsh module in the same file is the canonical consumer:
- `flake.modules.homeManager.zsh` sets `programs.zsh.package` to `self.packages.<system>.jsl-zsh`.
- Keep Home Manager interactive zsh bound to the wrapped package, not plain `pkgs.zsh`.
## shell-tools composition
`modules/features/shell-tools.nix` is a composition module, not just a package list:
- `flake.modules.homeManager.shell-tools` imports `homeManager.zsh` and `homeManager.files`.
- It adds the wrapped `shell-tools` package to `home.packages`.
- `home.shell.enableShellIntegration = true` is expected to stay with this module.
If a user module imports `homeManager.shell-tools` (for example `modules/users/john.nix`), that user already receives the Home Manager zsh wiring transitively.
## System-layer expectations
`modules/nix-tools/user.nix` keeps system login semantics separate from Home Manager interactive behavior:
- `users.users.<name>.shell = pkgs.zsh` remains the login shell declaration.
- `programs.zsh.enable = true` enables system zsh support.
- `environment.shells` includes both `${lib.getExe pkgs.zsh}` and `${lib.getExe self.packages.<system>.jsl-zsh}` so wrapped shell paths are recognized when needed.
Do not replace login shell declarations with `jsl-zsh` unless the task is explicitly changing system login policy.
## Host and SSH usage contracts
Hosts may include `selfPkgs.jsl-zsh` in `environment.systemPackages` or `home.packages` so the wrapped shell is available directly.
`modules/services/ssh.nix` includes host entries that use `RemoteCommand = "~/.nix-profile/bin/jsl-zsh"` (and commented variants). This path-level usage means:
- keep the wrapper executable name stable (`jsl-zsh`),
- update SSH remote command references in the same change if shell invocation paths are changed.
## Change guidelines
When editing this area:
- Keep wrapper behavior changes in `packages.jsl-zsh` focused and centralized.
- Keep Home Manager zsh package selection pointed at wrapped `jsl-zsh`.
- Keep system login shell semantics (`pkgs.zsh`) and interactive wrapper semantics (`jsl-zsh`) separated unless intentionally redesigning both layers.
- If modifying `shell-tools`, verify it still composes zsh and tool packages for users importing the module.
- If changing executable names or invocation paths, update all dependent consumers (including SSH remote commands and host package lists) in one coherent change.
+3
View File
@@ -0,0 +1,3 @@
result/
.devenv/
*.gz
+17 -2
View File
@@ -2,13 +2,28 @@ keys:
- &john-p14s age1f6drjusg866yscj8029tk4yfpgecklrvezldm02ankm6h8nnwu5s2u6ahy - &john-p14s age1f6drjusg866yscj8029tk4yfpgecklrvezldm02ankm6h8nnwu5s2u6ahy
- &john-pc age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt - &john-pc age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt
- &test-nix age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0 - &test-nix age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0
- &soteria age1h0prahyukq4l564yqwgcpg3g6gdrjflk0suklussjjrjstxd9uesws8633 - &soteria age1p4gyh5260ewp7t2ctzv5ewj4a06szl389fm4gzy6vltxhjj73ers87u33g
- &janus age1qahhlzeanprtykym9jymk2t95uedr7cwx9sdshx46q2m6u66fucsqua8l3
creation_rules: creation_rules:
- path_regex: janus/secrets\.yaml$
key_groups:
- age:
- *janus
- *john-pc
- *test-nix
- path_regex: keys/secrets\.yaml$
key_groups:
- age:
- *john-p14s
- *john-pc
- *test-nix
- *soteria
- *janus
- path_regex: soteria/secrets\.yaml$ - path_regex: soteria/secrets\.yaml$
key_groups: key_groups:
- age: - age:
- *john-pc
- *soteria - *soteria
- *john-pc
- *test-nix - *test-nix
- path_regex: john-p14s/secrets\.yaml$ - path_regex: john-p14s/secrets\.yaml$
key_groups: key_groups:
+16
View File
@@ -12,6 +12,22 @@ home-manager switch --flake .#desktop
nix flake show --all-systems nix flake show --all-systems
``` ```
```shell
nix run nixpkgs#nh home switch -- --configuration john@john-pc-ubuntu .
```
## Host Intent
| Host key | Type | Intent | Entrypoint |
| -------- | ---- | ------ | ---------- |
| `janus` | NixOS (LXC) | Base homelab/service host with local step-ca trust bootstrap (`janus-ca`) and mTLS support. | `modules/hosts/janus/default.nix` |
| `john-kde` | Home Manager (generic Linux) | KDE-focused user environment profile for `john` with desktop + docker + step client tooling. | `modules/hosts/john-kde/default.nix` |
| `john-p14s` | NixOS (laptop) | Main ThinkPad P14s NixOS workstation using hardware presets and desktop/dev modules. | `modules/hosts/john-p14s/default.nix` |
| `john-pc-ubuntu` | Home Manager (generic Linux) | Ubuntu-hosted home profile for `john`, includes desktop/dev tooling and restic backup client settings. | `modules/hosts/john-pc/default.nix` |
| `omen-nixos` | NixOS | Omen machine wired through shared `omen-nixos` + `john` modules. | `modules/hosts/omen-nixos/default.nix` |
| `soteria` | NixOS + Home Manager | Service node running Forgejo + restic server with mTLS and host-local secrets. | `modules/hosts/soteria/soteria.nix` |
| `test-nix` | NixOS (LXC) | Minimal sandbox/test target for validating module composition and remote push/switch flows. | `modules/hosts/test-nix.nix` |
## Layout ## Layout
- Everything under `./modules` gets auto-imported by `import-tree` - Everything under `./modules` gets auto-imported by `import-tree`
Generated
+57 -97
View File
@@ -3,11 +3,11 @@
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1751685974, "lastModified": 1777699697,
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=", "narHash": "sha256-Eg9b/rq/ECYwNwEXs5i9wHyhxNI0JrYx2srdI2uZMaQ=",
"ref": "refs/heads/main", "ref": "refs/heads/main",
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1", "rev": "382052b74656a369c5408822af3f2501e9b1af81",
"revCount": 92, "revCount": 94,
"type": "git", "type": "git",
"url": "https://git.lix.systems/lix-project/flake-compat.git" "url": "https://git.lix.systems/lix-project/flake-compat.git"
}, },
@@ -18,11 +18,11 @@
}, },
"flake-file": { "flake-file": {
"locked": { "locked": {
"lastModified": 1775848911, "lastModified": 1781217157,
"narHash": "sha256-dqva/tlWxsXj32wYPdt06UrrR4l2QdK9JWyvPooWRi4=", "narHash": "sha256-N3q/SP2Ropk336e9KSgLh7kpROY6P70dprYdbPIfd5c=",
"owner": "vic", "owner": "vic",
"repo": "flake-file", "repo": "flake-file",
"rev": "b36cbd5fc01e9794a001ccb0c58b314efaabae08", "rev": "ce63eaf7ebfe04a176653f66385a7f0a36380cee",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -34,15 +34,15 @@
"flake-parts": { "flake-parts": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"nixpkgs-lib" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1775087534, "lastModified": 1782949081,
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -59,11 +59,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1769996383, "lastModified": 1778716662,
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381", "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -95,11 +95,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1776046499, "lastModified": 1783134515,
"narHash": "sha256-Wzc4nn07/0RL21ypPHRzNDQZcjhIC8LaYo7QJQjM5T4=", "narHash": "sha256-qMoZazubXlXUD9k/syJ/aiWC4X4g73mwVmZ7Z4+rdpM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "287f84846c1eb3b72c986f5f6bebcff0bd67440d", "rev": "b885baad531fa3d3beae2ba9a0712d22974d8016",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -110,11 +110,11 @@
}, },
"import-tree": { "import-tree": {
"locked": { "locked": {
"lastModified": 1773693634, "lastModified": 1778781969,
"narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=", "narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=",
"owner": "vic", "owner": "vic",
"repo": "import-tree", "repo": "import-tree",
"rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1", "rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -125,11 +125,11 @@
}, },
"mnw": { "mnw": {
"locked": { "locked": {
"lastModified": 1770419553, "lastModified": 1780772958,
"narHash": "sha256-b1XqsH7AtVf2dXmq2iyRr2NC1yG7skY7Z6N2MpWHlK4=", "narHash": "sha256-VKKe8r4pwCGWZ3Yr9CPN129R4S3CKLSrlYqdYz3vKpM=",
"owner": "Gerg-L", "owner": "Gerg-L",
"repo": "mnw", "repo": "mnw",
"rev": "2aaffa8030d0b262176146adbb6b0e6374ce2957", "rev": "0871dbf63a53610c95db04439ed8ea4d6ec9c160",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -138,28 +138,6 @@
"type": "github" "type": "github"
} }
}, },
"ndg": {
"inputs": {
"nixpkgs": [
"nvf",
"nixpkgs"
]
},
"locked": {
"lastModified": 1768214250,
"narHash": "sha256-hnBZDQWUxJV3KbtvyGW5BKLO/fAwydrxm5WHCWMQTbw=",
"owner": "feel-co",
"repo": "ndg",
"rev": "a6bd3c1ce2668d096e4fdaaa03ad7f03ba1fbca8",
"type": "github"
},
"original": {
"owner": "feel-co",
"ref": "refs/tags/v2.6.0",
"repo": "ndg",
"type": "github"
}
},
"nixgl": { "nixgl": {
"inputs": { "inputs": {
"flake-utils": "flake-utils", "flake-utils": "flake-utils",
@@ -184,11 +162,11 @@
"nixos-hardware": { "nixos-hardware": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1775490113, "lastModified": 1782562157,
"narHash": "sha256-2ZBhDNZZwYkRmefK5XLOusCJHnoeKkoN95hoSGgMxWM=", "narHash": "sha256-a7+T6QSeowynwZ1ZJJbP8T8ntAytvrui8kFGJmIZt2c=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "c775c2772ba56e906cbeb4e0b2db19079ef11ff7", "rev": "a9cf7546a938c737b079e738de73934a13de9784",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -199,40 +177,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1775423009, "lastModified": 1782175435,
"narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=", "narHash": "sha256-EMzXKmnOtBQ2MnvpiNOm7E+kOMvdPrIKaeg52Tip2Uk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9", "rev": "89570f24e97e614aa34aa9ab1c927b6578a43775",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1775888245,
"narHash": "sha256-qTVvODr6edFBLD2lncXPF8yTQeCafZUuKVtpV3Xb3yM=",
"rev": "13043924aaa7375ce482ebe2494338e058282925",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre978638.13043924aaa7/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1775579569,
"narHash": "sha256-/m3yyS/EnXqoPGBJYVy4jTOsirdgsEZ3JdN2gGkBr14=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dfd9566f82a6e1d55c30f861879186440614696e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -242,23 +191,35 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_2": {
"locked": {
"lastModified": 1782948114,
"narHash": "sha256-VGtEDss4T0hPTnlWiiaa29y/lXM9SxuSMVtzPYf3wS0=",
"rev": "9e92285f211dad236540fd617d7e30e0b99bc0e1",
"type": "tarball",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1026211.9e92285f211d/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
}
},
"nvf": { "nvf": {
"inputs": { "inputs": {
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_2",
"mnw": "mnw", "mnw": "mnw",
"ndg": "ndg",
"nixpkgs": [ "nixpkgs": [
"nixpkgs" "nixpkgs"
], ],
"systems": "systems_2" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1775892726, "lastModified": 1783164413,
"narHash": "sha256-1TK1pe33cEHNvGW41TP5xAzrbG1Gp7LfyFL6c3+xf+I=", "narHash": "sha256-7DMamcvS35/tI+KZKXt45bHAq2Vnucl74Xetvp7DR3I=",
"owner": "notashelf", "owner": "notashelf",
"repo": "nvf", "repo": "nvf",
"rev": "5ab359ee7dfd3fa09a5c6f863efaf810bb9a9436", "rev": "c8386ccfbc7053d454fa2d12d716c6ebb4167d51",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -276,9 +237,6 @@
"nixgl": "nixgl", "nixgl": "nixgl",
"nixos-hardware": "nixos-hardware", "nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_2", "nixpkgs": "nixpkgs_2",
"nixpkgs-lib": [
"nixpkgs"
],
"nvf": "nvf", "nvf": "nvf",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"wrapper-modules": "wrapper-modules", "wrapper-modules": "wrapper-modules",
@@ -292,11 +250,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1775971308, "lastModified": 1783174389,
"narHash": "sha256-VKp9bhVSm0bT6JWctFy06ocqxGGnWHi1NfoE90IgIcY=", "narHash": "sha256-aCWC8ngycU7OdJrU2+Je3qf+1a2ykuBvpPhZT/9tXMc=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "31ac5fe5d015f76b54058c69fcaebb66a55871a4", "rev": "f1406619a3884cd5c47992a70b8b35c9c0fcb4c9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -337,14 +295,16 @@
}, },
"wrapper-modules": { "wrapper-modules": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs_3" "nixpkgs": [
"nixpkgs"
]
}, },
"locked": { "locked": {
"lastModified": 1775940860, "lastModified": 1782135443,
"narHash": "sha256-8Jxnn2uoFhm2H579ycVxFWDtrywJ6Mc8RmqWbZwk5So=", "narHash": "sha256-vAmbArdCyjqpVW+37aCy/PMBOLIqukUXLQuEKLwUhA4=",
"owner": "BirdeeHub", "owner": "BirdeeHub",
"repo": "nix-wrapper-modules", "repo": "nix-wrapper-modules",
"rev": "fb62851ffc5f6a4d53ebc00b93743e29b41e6224", "rev": "6e7f66fa2cdf4d63162580b438f7fcf87c28a46f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -360,11 +320,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1775600302, "lastModified": 1782375095,
"narHash": "sha256-2fgKImv78CXIcfo1RsY7EI4uMZ84x/MggA5rrusYc7c=", "narHash": "sha256-bRepNpAluz2i0IIk7Tmzw/4BkyOrwABvc66NEgXPEhA=",
"owner": "lassulus", "owner": "lassulus",
"repo": "wrappers", "repo": "wrappers",
"rev": "9d8397d8ef1ac35763085f3338589f558128f7db", "rev": "cd780e92c0fa66c2e721e1d91894f3db13b9f2b4",
"type": "github" "type": "github"
}, },
"original": { "original": {
+5 -3
View File
@@ -7,7 +7,7 @@
flake-file.url = "github:vic/flake-file"; flake-file.url = "github:vic/flake-file";
flake-parts = { flake-parts = {
url = "github:hercules-ci/flake-parts"; url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs-lib"; inputs.nixpkgs-lib.follows = "nixpkgs";
}; };
home-manager.url = "github:nix-community/home-manager"; home-manager.url = "github:nix-community/home-manager";
import-tree.url = "github:vic/import-tree"; import-tree.url = "github:vic/import-tree";
@@ -20,7 +20,6 @@
flake = false; flake = false;
}; };
nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
nixpkgs-lib.follows = "nixpkgs";
nvf = { nvf = {
url = "github:notashelf/nvf"; url = "github:notashelf/nvf";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -29,7 +28,10 @@
url = "github:Mic92/sops-nix"; url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
wrapper-modules.url = "github:BirdeeHub/nix-wrapper-modules"; wrapper-modules = {
url = "github:BirdeeHub/nix-wrapper-modules";
inputs.nixpkgs.follows = "nixpkgs";
};
wrappers = { wrappers = {
url = "github:lassulus/wrappers"; url = "github:lassulus/wrappers";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
+39 -21
View File
@@ -9,33 +9,51 @@ api:
gmail_client_secret: ENC[AES256_GCM,data:du2gEY5TQIwpUEvJKDWKY3noLRGeiKek4IMwPUusVx8NMys=,iv:hIYi1xQYf6+hDhK0pNprBYu6wXwRH2yOTwQg6pzQa0A=,tag:sqmQ5GCkKbHpIy2R+Y5G/A==,type:str] gmail_client_secret: ENC[AES256_GCM,data:du2gEY5TQIwpUEvJKDWKY3noLRGeiKek4IMwPUusVx8NMys=,iv:hIYi1xQYf6+hDhK0pNprBYu6wXwRH2yOTwQg6pzQa0A=,tag:sqmQ5GCkKbHpIy2R+Y5G/A==,type:str]
sops: sops:
age: age:
- recipient: age1f6drjusg866yscj8029tk4yfpgecklrvezldm02ankm6h8nnwu5s2u6ahy - enc: |
enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0UEpja2kxdThZVWZhOGVP YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5dTVZQ2hFanhqbVJXTmxG
S0NtSi84MjhnN0RORkh2NjZ4YlYvWS9kZDBNClFzYnVxWnhmQkpCRkRFVUx1RDdX YUFFanBiTzgyek1WZW5YajlaL3hVNElORHpJCm1JN2hxell5dmJVczlBbVI2VktH
ZHFqYXRqYXM0cWJzcU5EeEtSR1BUVzAKLS0tIDdEY2pnVTJqWlNZVkZldXVYVmFH Z21lelF6UDBHcXIyUDE0clpxcEV4MGsKLS0tIG0zMktBVEt1SFZ6aGFaNW0wdzlB
dVNBRUVodU5sRnpVcG1GZ1RiZzhjTXMKefqBvvD/qZwcSHmFjUnleukVRLueG36Y QkpQc3lkNmZCd0d6ZVRCc1ZaWjFYaDAK9RXWeW6dCIhYfVGPywsHlRigORVMuVfl
Q81KlwQweF2F8kHl7Bqsi+3hH1dZZbVm3vjuGpWFOoti7fowUV55Kw== d+Evyp72wV5C1MnhV9jv8O4S8aQkcNae1PALacnFw0GJAI1iXn0bqw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt recipient: age1f6drjusg866yscj8029tk4yfpgecklrvezldm02ankm6h8nnwu5s2u6ahy
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqZFZxbDhVUWFEUGhPMlZI YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBTWt3MEFDL3JGOUw0LzNa
SFdBYkpxSnAxTUZXbjVwQnlZQ3l1SWtuZGg0CmVBdnVHbTNUcmwvK01iMnZKZTJh RmNLMmFRRzh5NEtZWENjNTlrcDg1MHpjWWg0Cjl5THh3ckdyMWVqR0M2Q0Y0czUr
ajFla3kzYUl4ZWY3czA0WUdNM2lpVFUKLS0tIHo5Uk1pV296MXdnUTZGQ25haWZG Y0ZNRGVIbS9TNXlCcFo3R3lPZUl4cW8KLS0tIDN4QmhSYjBPMDZLWURhMndsK2hE
QWZDWGRaRDBhY1ZkZk5oTHY0ZVV2RXMKanv+WWRhf5nl+aw/T6QZFVQQmhV1DZfB WVY1T0JnZ3dmSjNIMDNBYmhMellvMWcKp15FwF7lJnHoZ/tLNQTGvwP7Kffj8hms
jkSzOAKOgPx7toYFmpq9E8fAH+zrMzDbxI2z2uyrOFI6v+QE0Ul/iQ== 6G5ZG2howsNFiRFFdo/uL08ShaycL2PAL/Kge/0pPL1ygiDaDLjGGg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0 recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZcGtMNi9RSG5aTVV5cWdT YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwT1FoTDNmRXM4Tk9HZHNu
NittUXN0Qnpld1YvOU50OUh3Z3ZiSzhHOHdNCnc4TmdYbS9QQnBLbldHSytIdkJl ZSszMEM1R0Z5bzY1ZzRvR3VGR2srVVdXbDBNCmp2c0tacndoQ3RiZUpNTnk1MTNH
R0psQWxkZTgyZTRzckkrTGpyNCsvR2sKLS0tIEdLb05aT2I2S3BKcFRrVmtvTGw5 V0Q4ckhWaDBjemYxZEhrSGJHcVlwYzQKLS0tIHNDZkNrNmZvM2plNEp2TTJmVlpT
Z1orRCtkTDVXSktuck5pTmV4K05qZHMKZlHHu07q+GnyDDgdwW2Ic3P23PmoSPwn U1JhN2MrT2NzeURuVzRIZlNEVEd0S0EKBcTG4hWKdXqNfajVZ5DOClOeZsFYktPb
WuNLZdlZQleROaRb+zpD+9P1HGGJ3mWAlNlnmjGrRk453k1PbBQ5Og== dAauLGwx1zkXbVxopUpH5+vCXp02kvvwgO9kiqJvSf0U+l/5VIp6Ww==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5bTVkK0pQL3kxZzdaN0xq
YU9rVTNvQXE1V1d0Ykx1bEN3cXp4eWdKaWpjCkEzNUs0N1JlMjlrdnA4UUxIRkZ1
aFl2Qk9PeTNTQ0grMjFJMzZvT05adGMKLS0tIEIwd3NyQ0xWWkVBMFl4Z0VUVFBY
eWFlSFYrL1E1aXN2bVp5YTF5VXVOYUEK3NhyHGnY+a/Fj6TJFzljSX+49G9x5F3x
M16Nx7JGsD5IV6GfdiMYSEwgT1pm0/XY8rhvWBO1fArkleMz1HKewg==
-----END AGE ENCRYPTED FILE-----
recipient: age1p4gyh5260ewp7t2ctzv5ewj4a06szl389fm4gzy6vltxhjj73ers87u33g
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwWGpwUkRueVhhcVErQ0ZF
RXdxV1lMNzh6VFhubmc3TDBMaSsrYjhlS1Q0CnlZMmJSaXN5d1daRmpUVTlWeU1i
Nkxsd3JoTGpoelhiTXBMclhoL3RKQlEKLS0tIHRyQjk0akN3WnhKKy82T0ZOYTNK
QU5tYkZuTVc2Yzc0bzJrNXd1aWJMVlEKoMHFqDQYmT+q4eSNtmIb6drI/pFt9Z4W
gHI/QxeWQq6BWssk4EAonRXhIE0iJPOsXz2zyT+dRRDqcKBod9h01w==
-----END AGE ENCRYPTED FILE-----
recipient: age1qahhlzeanprtykym9jymk2t95uedr7cwx9sdshx46q2m6u66fucsqua8l3
lastmodified: "2026-03-15T15:06:29Z" lastmodified: "2026-03-15T15:06:29Z"
mac: ENC[AES256_GCM,data:cF/TJ8VkzrHRUrO5iGdRdlFtqV/5EQ15JwQKIywJvsh0NERK67T21czSP7923MiL0u5QTVPn/rO8R5E/8gBu3r8+fLq+CFl9PDQHEX2JhnYOD5WZR412WMZq3MVR94IMTOrQANMVpS4uhMyvnrqOe4AenxLDyzrYhkwf1KQh4w0=,iv:Qwy8z4uXGMlf+kTMNiE42M9l8LtSJ+O7diknRrsSeYI=,tag:qlCY9r8HnEDmq/jw59C/sg==,type:str] mac: ENC[AES256_GCM,data:cF/TJ8VkzrHRUrO5iGdRdlFtqV/5EQ15JwQKIywJvsh0NERK67T21czSP7923MiL0u5QTVPn/rO8R5E/8gBu3r8+fLq+CFl9PDQHEX2JhnYOD5WZR412WMZq3MVR94IMTOrQANMVpS4uhMyvnrqOe4AenxLDyzrYhkwf1KQh4w0=,iv:Qwy8z4uXGMlf+kTMNiE42M9l8LtSJ+O7diknRrsSeYI=,tag:qlCY9r8HnEDmq/jw59C/sg==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
+19
View File
@@ -0,0 +1,19 @@
{ self, inputs, ... }: {
flake.modules.nixos.audio = { config, pkgs, lib, ... }: {
# Enable sound with pipewire.
services.pulseaudio.enable = false;
security.rtkit.enable = true; # PulseAudio server uses this to acquire realtime priority.
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
# use the example session manager (no others are packaged yet so this is enabled by default,
# no need to redefine it in your config for now)
# media-session.enable = true;
};
};
}
+5
View File
@@ -1,6 +1,7 @@
# This module is for programs with GUIs that run in a desktop environment # This module is for programs with GUIs that run in a desktop environment
{ self, inputs, ... }: { { self, inputs, ... }: {
flake.modules.homeManager.desktop = { config, pkgs, lib, ... }: { flake.modules.homeManager.desktop = { config, pkgs, lib, ... }: {
nixpkgs.config.problems.handlers.sublimetext4.broken = "ignore";
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
brave brave
ghostty ghostty
@@ -10,6 +11,10 @@
home.packages = with pkgs; [ home.packages = with pkgs; [
mangohud mangohud
sublime4 sublime4
proton-vpn
joplin-desktop
discord
github-copilot-cli
]; ];
}; };
} }
-1
View File
@@ -4,7 +4,6 @@
desktopManager.gnome.enable = true; desktopManager.gnome.enable = true;
displayManager.gdm = { displayManager.gdm = {
enable = true; enable = true;
wayland = true;
banner = "Welcome to John's NixOS implementation"; banner = "Welcome to John's NixOS implementation";
}; };
udev.packages = [ udev.packages = [
+15 -2
View File
@@ -1,11 +1,24 @@
# https://github.com/glabrie/dotfiles/blob/main/modules/system/settings/greetd.nix # https://github.com/glabrie/dotfiles/blob/main/modules/system/settings/greetd.nix
{ inputs, ... }: { { inputs, ... }: {
flake.modules.nixos.greetd = { pkgs, lib, ... }: { flake.modules.nixos.greetd = { pkgs, lib, config, ... }:
let
niriPackage = if config.programs.niri.enable then config.programs.niri.package else pkgs.niri;
niriExe = lib.getExe niriPackage;
regreetExe = lib.getExe config.programs.regreet.package;
greeterNiriConfig = pkgs.writeText "niri-greeter.kdl" ''
spawn-at-startup "${regreetExe}"
hotkey-overlay {
skip-at-startup
}
'';
in {
programs.regreet.enable = true;
services.greetd = { services.greetd = {
enable = true; enable = true;
settings = { settings = {
default_session = { default_session = {
command = "${lib.getExe pkgs.tuigreet} --time --remember --cmd niri-session"; command = "${pkgs.dbus}/bin/dbus-run-session -- ${niriExe} --config ${greeterNiriConfig}";
user = "greeter"; user = "greeter";
}; };
}; };
-352
View File
@@ -1,352 +0,0 @@
{ self, inputs, lib, ... }:
let
# Options that will be in common between the nixos module and the home-manager module.
mkOpts = config: let cfg = config.mtls; in {
enable = lib.mkEnableOption "Enable mTLS";
subject = lib.mkOption {
description = "The Common Name, DNS Name, or IP address that will be set as the Subject Common Name for the certificate. If no Subject Alternative Names (SANs) are configured (via the --san flag) then the subject will be set as the only SAN.";
type = lib.types.str;
};
certDir = lib.mkOption {
description = "String path to the directory where the certs will be stored";
type = lib.types.str;
};
caFile = lib.mkOption {
description = "String path for the root CA file";
type = lib.types.str;
default = "${cfg.certDir}/root_ca.crt";
};
keyFile = lib.mkOption {
description = "String path for the private key";
type = lib.types.str;
default = "${cfg.certDir}/key.pem";
};
certFile = lib.mkOption {
description = "String path for the public cert";
type = lib.types.str;
default = "${cfg.certDir}/cert.pem";
};
bundleFile = lib.mkOption {
description = "String path for the mTLS key bundle";
type = lib.types.str;
default = "${cfg.certDir}/mtls.pem";
};
san = lib.mkOption {
description = "List of SAN to give the mTLS cert";
type = lib.types.listOf lib.types.str;
default = [ ];
};
provisioner = lib.mkOption {
type = lib.types.str;
default = "admin";
};
lifetime = lib.mkOption {
type = lib.types.str;
default = "24h";
};
renew = {
enable = lib.mkOption {
description = "Enable automatic mTLS certificate renewal using a systemd timer.";
type = lib.types.bool;
default = true;
};
onCalendar = lib.mkOption {
description = "systemd OnCalendar schedule for mTLS certificate renewal checks.";
type = lib.types.str;
default = "*:1/15";
};
randomizedDelaySec = lib.mkOption {
description = "Randomized delay added to renewal timer runs to avoid synchronized renewals.";
type = lib.types.str;
default = "5m";
};
user = lib.mkOption {
description = "User account to run the mTLS renewal service as.";
type = lib.types.str;
default = "root";
};
group = lib.mkOption {
description = "Group to run the mTLS renewal service as. Defaults to the configured renewal user.";
type = lib.types.nullOr lib.types.str;
default = "mtls";
};
reloadUnits = lib.mkOption {
description = "systemd units to try-reload-or-restart after a successful certificate renewal.";
type = lib.types.listOf lib.types.str;
default = [ ];
};
postCommands = lib.mkOption {
description = "Shell commands to run after a successful certificate renewal.";
type = lib.types.listOf lib.types.lines;
default = [ ];
};
};
};
mkMtlsGenerateScript = {
pkgs,
subject,
provisioner,
san,
certFile,
keyFile,
bundleFile,
lifetime,
user,
group,
}:
let
sanArgs = lib.concatMapStringsSep " " (s: "--san \"${s}\"") san;
in
pkgs.writeShellApplication {
name = "mtls-generate";
runtimeInputs = with pkgs; [ coreutils step-cli ];
text = ''
set -euo pipefail
step ca certificate ${subject} ${certFile} ${keyFile} \
--provisioner ${provisioner} \
--not-before=-5m --not-after=${lifetime} \
${sanArgs} \
"$@"
(umask 077; cat ${certFile} ${keyFile} > ${bundleFile})
chown ${user}:${group} ${certFile} ${keyFile} ${bundleFile}
chmod 640 ${certFile} ${keyFile} ${bundleFile}
printf '\033[32m\033[0m \033[1mmTLS Bundle:\033[0m %s\n' ${lib.escapeShellArg bundleFile}
'';
};
mkMtlsCheckScript = { pkgs, bundleFile }: pkgs.writeShellApplication {
name = "mtls-check";
runtimeInputs = with pkgs; [ openssl ];
text = ''
openssl x509 -noout -in ${bundleFile} \
-subject -issuer \
-ext subjectAltName,extendedKeyUsage \
-enddate
'';
};
mkMtlsRenewScript = {
pkgs,
cfg,
systemctlArgs ? [ ],
}:
let
systemctlCmd = "systemctl ${lib.escapeShellArgs systemctlArgs}";
hasReloadUnits = cfg.renew.reloadUnits != [ ];
renewReloadScript = lib.concatMapStringsSep "\n" (unit: ''
if ${systemctlCmd} --quiet is-active "${unit}"; then
${systemctlCmd} try-reload-or-restart "${unit}"
fi
'') cfg.renew.reloadUnits;
hasPostCommands = cfg.renew.postCommands != [ ];
renewPostCommands = lib.concatStringsSep "\n" cfg.renew.postCommands;
fileOwner = "${cfg.renew.user}:${cfg.renew.group}";
in
pkgs.writeShellApplication {
name = "mtls-renew";
runtimeInputs = with pkgs; [ coreutils step-cli systemd ];
text = ''
set -euo pipefail
YELLOW_BANG="\e[33m!\e[0m"
force=0
while [[ $# -gt 0 ]]; do
case $1 in
--force)
force=1
shift
;;
*)
echo -e "$YELLOW_BANG Warning: ignoring unrecognized argument '$1'"
exit 1
;;
esac
done
if [[ $force -eq 0 ]] && ! step certificate needs-renewal "${cfg.certFile}"; then
echo "Skipping renew"
exit 0
fi
echo "Renewing mTLS certificate"
step ca renew --force "${cfg.certFile}" "${cfg.keyFile}"
(umask 077; cat "${cfg.certFile}" "${cfg.keyFile}" > "${cfg.bundleFile}")
chown ${fileOwner} ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile}
chmod 640 ${cfg.certFile} ${cfg.keyFile} ${cfg.bundleFile}
${lib.optionalString hasReloadUnits ''
echo "Reloading units: ${lib.concatStringsSep ", " cfg.renew.reloadUnits}"
${renewReloadScript}
''}
${lib.optionalString hasPostCommands ''echo "Post commands:" ${renewPostCommands}''}
'';
};
mkNixosMtlsRenewService = { pkgs, cfg, ... }:
{
description = "Renew the mTLS certificate when Smallstep marks it ready";
wantedBy = [ ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
User = cfg.renew.user;
Group = cfg.renew.group;
ExecStart = lib.getExe (mkMtlsRenewScript { inherit pkgs cfg; });
};
};
mkNixosMtlsRenewTimer = {
onCalendar,
randomizedDelaySec,
unit ? "mtls-renew.service",
}: {
description = "Periodic Smallstep renewal for the mTLS certificate";
wantedBy = [ "timers.target" ];
timerConfig = {
Persistent = true;
OnCalendar = onCalendar;
AccuracySec = "1us";
RandomizedDelaySec = randomizedDelaySec;
Unit = unit;
};
};
mkHomeManagerMtlsRenewService = { pkgs, cfg, ... }:
let
renewScript = mkMtlsRenewScript {
inherit pkgs cfg;
systemctlArgs = [ "--user" ];
};
in
{
Unit = {
Description = "Renew the mTLS certificate when Smallstep marks it ready";
After = [ "network-online.target" ];
Wants = [ "network-online.target" ];
};
Service = {
Type = "oneshot";
ExecStart = lib.getExe renewScript;
};
};
mkHomeManagerMtlsRenewTimer = {
onCalendar,
randomizedDelaySec,
unit ? "mtls-renew.service",
}: {
Unit = {
Description = "Periodic Smallstep renewal for the mTLS certificate";
};
Timer = {
Persistent = true;
OnCalendar = onCalendar;
AccuracySec = "1us";
RandomizedDelaySec = randomizedDelaySec;
Unit = unit;
};
Install = {
WantedBy = [ "timers.target" ];
};
};
in
{
flake.modules.nixos.mtls = { config, lib, pkgs, ... }:
let
cfg = config.mtls;
sanArgs = lib.concatMapStringsSep " " (san: "--san \"${san}\"") cfg.san;
in
{
options.mtls = (mkOpts config) // {
certDir = lib.mkOption {
description = "String path to where the mtls certs will be stored.";
type = lib.types.str;
default = "/etc/step-ca/certs";
};
certReaders = lib.mkOption {
description = "";
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
config = lib.mkIf cfg.enable {
users.groups.certReaders = {
name = cfg.renew.group;
members = cfg.certReaders;
};
environment.systemPackages = with pkgs; lib.optionals cfg.enable [
# step-cli
(mkMtlsGenerateScript {
inherit pkgs;
inherit (cfg) subject provisioner san certFile keyFile bundleFile lifetime;
inherit (cfg.renew) user group;
})
(mkMtlsCheckScript { inherit pkgs; inherit (cfg) bundleFile; })
(mkMtlsRenewScript { inherit pkgs cfg; })
];
systemd.tmpfiles.rules = [
"d ${cfg.certDir} 0750 ${cfg.renew.user} ${cfg.renew.group} -"
];
systemd.services.mtls-renew = lib.mkIf cfg.renew.enable
(mkNixosMtlsRenewService { inherit pkgs cfg; });
systemd.timers.mtls-renew = lib.mkIf cfg.renew.enable (mkNixosMtlsRenewTimer {
inherit (cfg.renew) onCalendar randomizedDelaySec;
});
};
};
flake.modules.homeManager.mtls = { config, lib, pkgs, ... }:
let
cfg = config.mtls;
keyFile = cfg.keyFile;
certFile = cfg.certFile;
bundleFile = cfg.bundleFile;
sanArgs = lib.concatMapStringsSep " " (san: "--san \"${san}\"") cfg.san;
in
{
options.mtls = (mkOpts config) // {
certDir = lib.mkOption {
description = "String path to where the mtls certs will be stored.";
type = lib.types.str;
default = "${config.home.homeDirectory}/.step/certs";
};
};
config = {
home.packages = with pkgs; lib.optionals cfg.enable [
# step-cli
(mkMtlsGenerateScript {
inherit (cfg) subject provisioner san lifetime;
inherit (cfg.renew) user group;
inherit pkgs certFile keyFile bundleFile;
})
(mkMtlsCheckScript { inherit pkgs bundleFile; })
(mkMtlsRenewScript { inherit pkgs cfg; systemctlArgs = [ "--user" ]; })
];
systemd.user.tmpfiles.rules = lib.mkIf cfg.enable [
"d ${cfg.certDir} 0700 - - -"
];
systemd.user.services.mtls-renew = lib.mkIf cfg.renew.enable
(mkHomeManagerMtlsRenewService { inherit pkgs cfg; });
systemd.user.timers.mtls-renew = lib.mkIf cfg.renew.enable (mkHomeManagerMtlsRenewTimer {
inherit (cfg.renew) onCalendar randomizedDelaySec;
});
};
};
}
+167
View File
@@ -0,0 +1,167 @@
{ self, inputs, lib, ... }:
let
# Options that will be in common between the nixos module and the home-manager module.
mkOpts = config: let cfg = config.mtls; in {
enable = lib.mkEnableOption "Enable mTLS";
subject = lib.mkOption {
description = "The Common Name, DNS Name, or IP address that will be set as the Subject Common Name for the certificate. If no Subject Alternative Names (SANs) are configured (via the --san flag) then the subject will be set as the only SAN.";
type = lib.types.str;
};
certDir = lib.mkOption {
description = "String path to the directory where the certs will be stored";
type = lib.types.str;
};
caFile = lib.mkOption {
description = "String path for the root CA file";
type = lib.types.str;
default = "${cfg.certDir}/root_ca.crt";
};
keyFile = lib.mkOption {
description = "String path for the private key";
type = lib.types.str;
default = "${cfg.certDir}/key.pem";
};
certFile = lib.mkOption {
description = "String path for the public cert";
type = lib.types.str;
default = "${cfg.certDir}/cert.pem";
};
bundleFile = lib.mkOption {
description = "String path for the mTLS key bundle";
type = lib.types.str;
default = "${cfg.certDir}/mtls.pem";
};
san = lib.mkOption {
description = "List of SAN to give the mTLS cert";
type = lib.types.listOf lib.types.str;
default = [ ];
};
provisioner = lib.mkOption {
type = lib.types.str;
default = "admin";
};
lifetime = lib.mkOption {
type = lib.types.str;
default = "24h";
};
renew = {
enable = lib.mkOption {
description = "Enable automatic mTLS certificate renewal using a systemd timer.";
type = lib.types.bool;
default = cfg.enable;
};
onCalendar = lib.mkOption {
description = "systemd OnCalendar schedule for mTLS certificate renewal checks.";
type = lib.types.str;
default = "*:1/15";
};
randomizedDelaySec = lib.mkOption {
description = "Randomized delay added to renewal timer runs to avoid synchronized renewals.";
type = lib.types.str;
default = "5m";
};
user = lib.mkOption {
description = "User account to run the mTLS renewal service as.";
type = lib.types.str;
default = "root";
};
group = lib.mkOption {
description = "Group to run the mTLS renewal service as. Defaults to the configured renewal user.";
type = lib.types.nullOr lib.types.str;
default = "mtls";
};
reloadUnits = lib.mkOption {
description = "systemd units to try-reload-or-restart after a successful certificate renewal.";
type = lib.types.listOf lib.types.str;
default = [ ];
};
postCommands = lib.mkOption {
description = "Shell commands to run after a successful certificate renewal.";
type = lib.types.listOf lib.types.lines;
default = [ ];
};
};
};
in
{
flake.modules.nixos.mtls = { config, lib, pkgs, ... }:
let
cfg = config.mtls;
mtlsWrappers = inputs.self.wrappers.mtls;
mtlsGenerate = mtlsWrappers.generate.apply {
inherit pkgs;
inherit (cfg) subject;
SANs = cfg.san;
provisioner = "admin";
provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path;
};
mtlsRenew = mtlsWrappers.renew.apply {
inherit pkgs;
};
mtlsCheck = mtlsWrappers.check.apply {
inherit pkgs;
};
in
{
options.mtls = (mkOpts config) // {
certDir = lib.mkOption {
description = "String path to where the mtls certs will be stored.";
type = lib.types.str;
default = "/etc/step-ca/certs";
};
bootstrap = {
enable = lib.mkOption {
description = "Enable initial mTLS issuance when cert material is missing or invalid.";
type = lib.types.bool;
default = false;
};
wantedBy = lib.mkOption {
description = "systemd targets that should pull in mtls-bootstrap.service.";
type = with lib.types; listOf str;
default = [ "multi-user.target" ];
};
after = lib.mkOption {
description = "systemd units/targets that mtls-bootstrap.service should run after.";
type = with lib.types; listOf str;
default = [ "network-online.target" ];
};
wants = lib.mkOption {
description = "systemd units/targets that mtls-bootstrap.service should pull in.";
type = with lib.types; listOf str;
default = [ "network-online.target" ];
};
provisionerPasswordFile = lib.mkOption {
description = "Optional path passed to mtls-generate as --provisioner-password-file for noninteractive issuance.";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
certReaders = lib.mkOption {
description = "";
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
config = lib.mkIf cfg.enable {
users.groups.certReaders = {
name = cfg.renew.group;
members = cfg.certReaders;
};
environment.systemPackages = [
mtlsGenerate.wrapper
mtlsCheck.wrapper
mtlsRenew.wrapper
];
systemd = {
packages = [ mtlsRenew.outputs.systemd-system ];
# Timer-driven oneshot: only the timer is enabled. NixOS does not
# honor the unit's [Install] section for systemd.packages, so the
# wantedBy must be set explicitly here.
timers.mtls-renew.wantedBy = [ "timers.target" ];
};
};
};
}
+125
View File
@@ -0,0 +1,125 @@
{ self, inputs, lib, ... }:
let
mkSANArgs = sans: builtins.concatLists (map (name: [ "--san" name ]) sans);
mkOpts = config: let cfg = config.mtls; in {
certDir = lib.mkOption {
description = "String path to the directory where the certs will be stored";
type = lib.types.str;
default = "/etc/mtls";
};
keyFile = lib.mkOption {
description = "String path for the private key";
type = lib.types.str;
default = "${config.certDir}/key.pem";
};
certFile = lib.mkOption {
description = "String path for the public cert";
type = lib.types.str;
default = "${config.certDir}/cert.pem";
};
bundleFile = lib.mkOption {
description = "String path for the mTLS key bundle";
type = lib.types.str;
default = "${config.certDir}/mtls.pem";
};
subject = lib.mkOption {
description = "Subject for the cert";
type = lib.types.str;
};
provisioner = lib.mkOption {
type = lib.types.nullOr lib.types.str;
};
provisionerPasswordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
};
overwrite = lib.mkEnableOption "Overwrite existing cert file?";
SANs = lib.mkOption {
description = "A list of Subject Alternative Names";
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
in
{
flake.wrappers.mtls = {
generate = inputs.wrappers.lib.wrapModule ({ config, lib, wlib, ... }: {
options = (mkOpts config);
config = {
binName = "mtls-generate";
package = config.pkgs.step-cli;
extraPackages = with config.pkgs; [ coreutils step-cli systemd ];
preHook = "mkdir -p ${config.certDir}";
args = [
"ca" "certificate"
"${config.subject}" "${config.certFile}" "${config.keyFile}"
"--not-before" "-5m"
"--not-after" "24h"
]
++ lib.optionals (config.provisioner != null) [ "--provisioner" "${config.provisioner}" ]
++ lib.optionals (config.provisionerPasswordFile != null) [
"--provisioner-password-file" "${config.provisionerPasswordFile}"
]
++ lib.optionals config.overwrite [ "-f" ]
++ mkSANArgs config.SANs;
postHook = ''
(umask 077; cat "${config.certFile}" "${config.keyFile}" > "${config.bundleFile}")
'';
};
});
renew = inputs.wrappers.lib.wrapModule ({ config, lib, wlib, ... }: {
# https://github.com/Lassulus/wrappers#generating-systemd-services
imports = [ wlib.modules.systemd ];
options = (mkOpts config);
config = {
binName = "mtls-renew";
package = config.pkgs.step-cli;
extraPackages = with config.pkgs; [ coreutils step-cli systemd ];
args = [
"ca" "renew"
"${config.certFile}" "${config.keyFile}"
];
postHook = ''
(umask 077; cat "${config.certFile}" "${config.keyFile}" > "${config.bundleFile}")
'';
systemd = {
description = "Renew the mTLS certificate when Smallstep marks it ready";
documentation = [
"https://smallstep.com/docs/step-ca/certificate-authority-server-production"
];
startLimitIntervalSec = 0;
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = lib.mkDefault "oneshot";
ExecCondition = "${lib.getExe config.pkgs.step-cli} certificate needs-renewal ${config.certFile}";
};
startAt = "hourly";
};
};
});
check = inputs.wrappers.lib.wrapModule ({ config, lib, wlib, ... }: {
options = (mkOpts config);
config = {
binName = "mtls-check";
# This pattern is necessary to wrap packages like openssl that provide more than one binary
package = config.pkgs.symlinkJoin {
name = "openssl";
paths = [ config.pkgs.openssl.bin config.pkgs.openssl.man ];
meta.mainProgram = "openssl";
};
args = [
"x509"
"-noout"
"-in" config.bundleFile
"-subject"
"-issuer"
"-ext" "subjectAltName,extendedKeyUsage"
"-enddate"
];
};
});
};
}
+156
View File
@@ -0,0 +1,156 @@
{ self, inputs, ... }: {
flake.modules.nixos.niri = { pkgs, lib, ... }:
let
niriPkg = self.packages.${pkgs.stdenv.hostPlatform.system}.myNiri;
in
{
programs.niri = {
enable = true;
package = niriPkg;
};
systemd.user.services.niri.enableDefaultPath = false;
};
perSystem = { pkgs, lib, self', ... }:
let
nixGLPackage = inputs.nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
noctaliaPkg = self'.packages.myNoctaliaNixGL;
terminalPkg = pkgs.ghostty;
myNiriUnwrapped = inputs.wrapper-modules.wrappers.niri.wrap {
inherit pkgs;
settings = {
spawn-at-startup = [
(lib.getExe noctaliaPkg)
];
xwayland-satellite.path = lib.getExe pkgs.xwayland-satellite;
input.keyboard.xkb.layout = "us,ua";
layout.gaps = 5;
binds = {
"Mod+Return".spawn-sh = lib.getExe terminalPkg;
# "Mod+Ctrl+Return".spawn-sh = "${lib.getExe noctaliaPkg} ipc call launcher toggle";
"Mod+Space".spawn-sh = "${lib.getExe noctaliaPkg} ipc call launcher toggle";
"Mod+B".spawn-sh = "${lib.getExe pkgs.brave}";
"Mod+E".spawn-sh = "xdg-open .";
"Mod+Alt+L".spawn-sh = "loginctl lock-session";
"Alt+F4".spawn-sh = "${lib.getExe' pkgs.coreutils "true"}";
"Mod+Shift+Q".close-window = { };
"Mod+S".spawn-sh = "${lib.getExe noctaliaPkg} ipc call launcher toggle";
"Mod+1".focus-workspace = 1;
"Mod+2".focus-workspace = 2;
"Mod+3".focus-workspace = 3;
"Mod+4".focus-workspace = 4;
"Mod+5".focus-workspace = 5;
"Mod+6".focus-workspace = 6;
"Mod+7".focus-workspace = 7;
"Mod+8".focus-workspace = 8;
"Mod+9".focus-workspace = 9;
"Mod+Tab".focus-workspace-previous = { };
# "Mod+WheelScrollDown".focus-workspace-down = { };
# "Mod+WheelScrollUp".focus-workspace-up = { };
# "Mod+Up".focus-window-up = { };
# "Mod+Down".focus-window-down = { };
"Mod+Up".focus-workspace-up = { };
"Mod+Down".focus-workspace-down = { };
"Mod+Left".focus-column-left = { };
"Mod+Right".focus-column-right = { };
"Mod+K".focus-window-up = { };
"Mod+J".focus-window-down = { };
"Mod+H".focus-column-left = { };
"Mod+L".focus-column-right = { };
"Mod+Home".focus-column-first = { };
"Mod+End".focus-column-last = { };
# "Mod+Shift+Left".focus-monitor-left = { };
# "Mod+Shift+Right".focus-monitor-right = { };
# "Mod+Shift+Up".focus-monitor-up = { };
# "Mod+Shift+Down".focus-monitor-down = { };
"Mod+Shift+1".move-column-to-workspace = 1;
"Mod+Shift+2".move-column-to-workspace = 2;
"Mod+Shift+3".move-column-to-workspace = 3;
"Mod+Shift+4".move-column-to-workspace = 4;
"Mod+Shift+5".move-column-to-workspace = 5;
"Mod+Shift+6".move-column-to-workspace = 6;
"Mod+Shift+7".move-column-to-workspace = 7;
"Mod+Shift+8".move-column-to-workspace = 8;
"Mod+Shift+9".move-column-to-workspace = 9;
"Mod+Ctrl+Up".move-window-up = { };
"Mod+Ctrl+Down".move-window-down = { };
"Mod+Ctrl+Left".move-column-left = { };
"Mod+Ctrl+Right".move-column-right = { };
"Mod+Ctrl+K".move-window-up = { };
"Mod+Ctrl+J".move-window-down = { };
"Mod+Ctrl+H".move-column-left = { };
"Mod+Ctrl+L".move-column-right = { };
"Mod+Ctrl+Home".move-column-to-first = { };
"Mod+Ctrl+End".move-column-to-last = { };
"Mod+Shift+Ctrl+Left".move-column-to-monitor-left = { };
"Mod+Shift+Ctrl+Right".move-column-to-monitor-right = { };
"Mod+Shift+Ctrl+Up".move-column-to-monitor-up = { };
"Mod+Shift+Ctrl+Down".move-column-to-monitor-down = { };
"Mod+Ctrl+F".expand-column-to-available-width = { };
"Mod+C".center-column = { };
"Mod+Ctrl+C".center-visible-columns = { };
"Mod+Minus".set-column-width = "-10%";
"Mod+Equal".set-column-width = "+10%";
"Mod+Shift+Minus".set-window-height = "-10%";
"Mod+Shift+Equal".set-window-height = "+10%";
"Mod+T".toggle-window-floating = { };
"Mod+F".fullscreen-window = { };
"Mod+W".toggle-column-tabbed-display = { };
"Mod+O".toggle-overview = { };
"Ctrl+Shift+1".screenshot = { };
"Ctrl+Shift+2".screenshot-screen = { };
"Ctrl+Shift+3".screenshot-window = { };
"Mod+Shift+Slash".show-hotkey-overlay = { };
"Mod+Escape".toggle-keyboard-shortcuts-inhibit = { };
"Mod+Shift+P".power-off-monitors = { };
"Ctrl+Alt+Delete".quit = { };
"Mod+WheelScrollRight".focus-column-right = { };
"Mod+WheelScrollLeft".focus-column-left = { };
"Mod+Ctrl+WheelScrollRight".move-column-right = { };
"Mod+Ctrl+WheelScrollLeft".move-column-left = { };
"Mod+Shift+WheelScrollDown".focus-column-right = { };
"Mod+Shift+WheelScrollUp".focus-column-left = { };
# "Mod+Ctrl+Shift+WheelScrollDown".move-column-right = { };
# "Mod+Ctrl+Shift+WheelScrollUp".move-column-left = { };
};
};
};
in {
packages.myNiri = myNiriUnwrapped;
packages.myNiriNixGL = pkgs.symlinkJoin {
name = "my-niri-nixgl";
paths = [ myNiriUnwrapped ];
nativeBuildInputs = [ pkgs.makeWrapper ];
meta.mainProgram = "niri";
postBuild = ''
for exe in "$out"/bin/*; do
if [[ -f "$exe" ]]; then
base="$(basename "$exe")"
mv "$exe" "$out/bin/.''${base}-real"
makeWrapper ${lib.getExe nixGLPackage} "$exe" \
--add-flags "$out/bin/.''${base}-real"
fi
done
'';
};
};
}
+63
View File
@@ -0,0 +1,63 @@
{ self, inputs, ... }: {
flake.modules.nixos.base = { config, pkgs, lib, ... }: {
imports = [
self.modules.nixos.audio
];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nixpkgs.config = {
permittedInsecurePackages = [ "openssl-1.1.1w" ];
allowUnfree = true;
problems.handlers.sublimetext4.broken = "ignore";
};
# Set your time zone.
time.timeZone = "US/Central";
# Select internationalisation properties.
i18n = {
defaultLocale = "en_US.UTF-8";
extraLocaleSettings = {
LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
LC_MEASUREMENT = "en_US.UTF-8";
LC_MONETARY = "en_US.UTF-8";
LC_NAME = "en_US.UTF-8";
LC_NUMERIC = "en_US.UTF-8";
LC_PAPER = "en_US.UTF-8";
LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8";
};
};
fonts.packages = with pkgs; [
nerd-fonts.hack
nerd-fonts.sauce-code-pro
];
# Need for less pain-in-the-ass for doing normal-ish stuff
# This is needed for VSCode remote support. Read: https://nixos.wiki/wiki/Visual_Studio_Code
programs.nix-ld.enable = true;
# Configure network connections interactively with nmcli or nmtui.
networking.networkmanager.enable = true;
services.openssh.enable = true;
security.polkit.enable = true; # polkit
services.gnome.gnome-keyring.enable = true; # secret service
security.pam.services.swaylock = {};
# Enable sound with pipewire.
services.pulseaudio.enable = false;
security.rtkit.enable = true; # PulseAudio server uses this to acquire realtime priority.
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
};
};
}
+34
View File
@@ -0,0 +1,34 @@
{ self, inputs, ... }: {
perSystem = { pkgs, lib, ... }:
let
nixGLPackage = inputs.nixgl.packages.${pkgs.stdenv.hostPlatform.system}.nixGLIntel;
myNoctaliaUnwrapped = inputs.wrapper-modules.wrappers.noctalia-shell.wrap {
inherit pkgs;
runtimePkgs = [
pkgs.qt6.qtwebsockets
];
# settings =
# (builtins.fromJSON
# (builtins.readFile ./noctalia.json)).settings;
};
in {
packages.myNoctalia = myNoctaliaUnwrapped;
packages.myNoctaliaNixGL = pkgs.symlinkJoin {
name = "my-noctalia-nixgl";
paths = [ myNoctaliaUnwrapped ];
nativeBuildInputs = [ pkgs.makeWrapper ];
meta.mainProgram = "noctalia-shell";
postBuild = ''
for exe in "$out"/bin/*; do
if [[ -f "$exe" ]]; then
base="$(basename "$exe")"
mv "$exe" "$out/bin/.''${base}-real"
makeWrapper ${lib.getExe nixGLPackage} "$exe" \
--add-flags "$out/bin/.''${base}-real"
fi
done
'';
};
};
}
+59 -4
View File
@@ -1,10 +1,65 @@
{ self, inputs, ... }: { { self, inputs, ... }: {
flake.modules.nixos.restic-server = { config, pkgs, lib, ... }: { flake.modules.nixos.restic-server = { config, pkgs, lib, ... }:
let
cfg = config.resticServer;
port = builtins.fromJSON (lib.last (lib.splitString ":" cfg.listenAddress));
in {
options.resticServer = {
enable = lib.mkEnableOption "Enable the RESTic REST server";
dataDir = lib.mkOption {
description = "Storage directory for RESTic repositories served by this host.";
type = lib.types.str;
default = "/mnt/restic";
};
listenAddress = lib.mkOption {
description = "Listen address for the RESTic REST server.";
type = lib.types.str;
default = "0.0.0.0:8080";
};
privateRepos = lib.mkOption {
description = "Whether the RESTic server should use private repository mode.";
type = lib.types.bool;
default = true;
};
extraFlags = lib.mkOption {
description = "Additional flags to pass to the RESTic REST server before TLS flags are appended.";
type = lib.types.listOf lib.types.str;
default = [ "--no-auth" ];
};
tls = {
certFile = lib.mkOption {
description = "Path to the TLS certificate file for the RESTic REST server, or null to disable TLS.";
type = lib.types.nullOr lib.types.str;
default = null;
};
keyFile = lib.mkOption {
description = "Path to the TLS private key file for the RESTic REST server, or null to disable TLS.";
type = lib.types.nullOr lib.types.str;
default = null;
};
};
};
config = lib.mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ port ];
services.restic.server = { services.restic.server = {
enable = true; enable = true;
dataDir = "/mnt/restic"; inherit (cfg) dataDir listenAddress privateRepos;
listenAddress = "0.0.0.0:8080"; extraFlags =
extraFlags = [ "--no-auth" ]; cfg.extraFlags
++ lib.optionals (cfg.tls.certFile != null && cfg.tls.keyFile != null) [
"--tls"
"--tls-cert=${cfg.tls.certFile}"
"--tls-key=${cfg.tls.keyFile}"
];
};
}; };
}; };
+67 -25
View File
@@ -1,40 +1,82 @@
# This module provides all the shell options # This module provides all the shell options
{ inputs, lib, ... }: { self, inputs, ... }:
{ {
flake.modules.homeManager.shell-tools = { config, pkgs, ... }: { flake.modules.homeManager.shell-tools =
options.shell.program = lib.mkOption { { config, pkgs, ... }:
type = lib.types.enum [ "bash" "zsh" ]; {
default = "zsh";
description = "Which interactive shell configuration to enable.";
};
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
bash # bash
zsh zsh
# Tools
eza
files files
]; ];
config = {
home.shell.enableShellIntegration = true;
programs.zsh.enable = lib.mkForce (config.shell.program == "zsh");
home.packages = with pkgs; [ home.packages = with pkgs; [
nh
nvd
nix-output-monitor
wget
curl
busybox
gnugrep
dig
btop btop
uv uv
xclip xclip
jq inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.shell-tools
];
home.shell.enableShellIntegration = true;
};
perSystem =
{
system,
pkgs,
self',
...
}:
{
packages.shell-tools = inputs.wrappers.lib.wrapPackage {
inherit pkgs;
# binName = "show-tools";
package = (
pkgs.symlinkJoin {
name = "show-tools";
meta.mainProgram = "show-tools";
paths = with pkgs; [
nh
ripgrep ripgrep
(writeShellScriptBin "ds" ''${lib.getExe pkgs.gdu} -x -I /snap /'') fd
jq
wget
curl
dig
bat
self'.packages.gdu
self'.packages.my-eza
self'.packages.yazi
hostname
iproute2
direnv
(writeShellApplication {
name = "show-tools";
text = ''
IFS=':' read -r -a path_dirs <<< "''${PATH:-}"
for dir in "''${path_dirs[@]}"; do
[[ "$dir" == */bin ]] || continue
[[ -d "$dir" ]] || continue
printf '%s\n' "$dir"/*
done
'';
})
];
}
);
};
packages.gdu = inputs.wrappers.lib.wrapPackage {
inherit pkgs;
package = pkgs.gdu;
args = [
"-x"
"--si"
"--collapse-path"
"--mouse"
"$@"
]; ];
}; };
}; };
+285
View File
@@ -0,0 +1,285 @@
# New attempt at consolidating SSH config that wraps the step client, SSH certs, and all that in a top-level module
{ self, inputs, ... }:
let
caPatterns = [ "*.john-stream.com" "192.168.1.*" "fded:fb16:653e:25da:be24:11ff:*" ];
sshHostCAPath = ../../hosts/janus/public/ssh_host_ca_key.pub;
in
{
flake.modules.nixos.ssh-new = { config, pkgs, lib, ... }:
let
cfg = config.ssh-new;
hostKeyFile = "${cfg.host.configDir}/${cfg.host.keyFile}";
hostCertFile = "${hostKeyFile}-cert.pub";
userCAFile = "${cfg.host.configDir}/${cfg.certificates.user.CAFile}";
CAknownHosts = (lib.genAttrs caPatterns (_: {
certAuthority = true;
publicKey = lib.removeSuffix "\n" (builtins.readFile sshHostCAPath);
}));
wrappers = inputs.self.wrappers;
provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path;
sshHostCertSign = (wrappers.signHostWrapper.apply {
inherit pkgs provisionerPasswordFile;
inherit (cfg.certificates) provisioner;
inherit (cfg.certificates.host) extraPrincipals;
}).wrapper;
sshHostCertRenew = (wrappers.renewHostWrapper.apply {
inherit pkgs;
sshHostKeyFile = hostKeyFile;
overwrite = true;
}).wrapper;
sshHostCertCheck = (wrappers.hostCheckWrapper.apply {
inherit pkgs;
certPath = hostCertFile;
}).wrapper;
sshHostRenewalCheck = (wrappers.renewalCheck.apply {
inherit pkgs;
certPath = hostCertFile;
expires-in = "4h";
}).wrapper;
sshUserCertSign = (wrappers.signUserWrapper.apply {
inherit pkgs provisionerPasswordFile;
inherit (cfg.certificates) provisioner;
validUsers = [ "root" "john" "appdaemon" ];
}).wrapper;
sshUserCertCheck = (wrappers.userCheckWrapper.apply {
inherit pkgs;
certPath = "${cfg.user.keyFile}-cert.pub";
}).wrapper;
in
{
options.ssh-new = {
user = {
keyFile = lib.mkOption {
type = lib.types.str;
default = "$HOME/.ssh/id_ed25519";
};
};
host = {
configDir = lib.mkOption {
type = lib.types.str;
default = "/etc/ssh";
};
keyFile = lib.mkOption {
description = "String path to the host private key file";
type = lib.types.str;
default = "ssh_host_ed25519_key";
};
keyType = lib.mkOption {
description = "OpenSSH host key type for ssh.hostKey.";
type = lib.types.enum [ "ed25519" "rsa" "ecdsa" ];
default = "ed25519";
};
extraSettings = lib.mkOption {
description = "Extra settings to merge";
type = lib.types.attrs;
default = { };
};
};
certificates = {
provisioner = lib.mkOption {
type = lib.types.nullOr lib.types.str;
};
host = {
enable = lib.mkEnableOption "Enable SSH host certs";
autoRenew = lib.mkEnableOption "Auto-renew the SSH host certs with a systemd service/timer";
extraPrincipals = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
user = {
enable = lib.mkEnableOption "Enable SSH user certs";
CAFile = lib.mkOption {
description = "Filename of the SSH user CA with the config directory";
type = lib.types.str;
default = "ssh_user_ca_key.pub";
};
extraPrincipals = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
};
};
config = {
services.openssh = {
enable = true;
openFirewall = true;
hostKeys = [
{
path = hostKeyFile;
type = cfg.host.keyType;
}
];
settings = lib.mkMerge [
{
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
}
cfg.host.extraSettings
(lib.mkIf cfg.certificates.host.enable {
HostCertificate = "${hostKeyFile}-cert.pub";
})
(lib.mkIf cfg.certificates.user.enable {
TrustedUserCAKeys = "${cfg.host.configDir}/${cfg.certificates.user.CAFile}";
})
];
};
environment.etc."ssh/${cfg.certificates.user.CAFile}" = lib.mkIf cfg.certificates.user.enable {
source = ../../hosts/janus/public/ssh_user_ca_key.pub;
};
environment.systemPackages = (
lib.optionals cfg.certificates.host.enable [
sshHostCertSign
sshHostCertRenew
sshHostRenewalCheck
sshHostCertCheck
]
);
sops = lib.mkIf cfg.certificates.host.autoRenew {
secrets."janus/admin_jwk" = {
sopsFile = ../../../keys/secrets.yaml;
owner = "root";
group = "root";
mode = "0400";
};
};
systemd = lib.mkIf cfg.certificates.host.autoRenew {
services.ssh-certs-renew = {
description = "SSH host certificate renewal";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = with pkgs; [ step-cli systemd ];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "root";
ExecCondition = lib.getExe sshHostRenewalCheck;
ExecStart = lib.getExe sshHostCertRenew;
};
};
timers.ssh-certs-renew = {
description = "Periodic Step SSH host certificate renewal";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "4h";
RandomizedDelaySec = "15m";
Persistent = true;
Unit = "ssh-certs-renew.service";
};
};
};
# This winds up in /etc/ssh/ssh_known_hosts, which applies to all users
programs.ssh.knownHosts = lib.mkIf cfg.certificates.user.enable CAknownHosts;
home-manager.users.root = lib.mkIf cfg.certificates.user.enable {
home.stateVersion = lib.mkDefault config.system.stateVersion;
imports = with inputs.self.modules.homeManager; [
ssh-new
];
home.packages = [
sshUserCertSign
sshUserCertCheck
];
ssh-new.certificates.enable = true;
};
};
};
flake.modules.homeManager.ssh-new = { config, pkgs, lib, ... }:
let
cfg = config.ssh-new;
sshHostCAContent = lib.removeSuffix "\n" (builtins.readFile sshHostCAPath);
knownHostsText = lib.concatMapStrings
(pattern: "@cert-authority ${pattern} ${sshHostCAContent}\n")
caPatterns;
in
{
options.ssh-new = {
keyFile = lib.mkOption {
type = lib.types.str;
default = "${config.home.homeDirectory}/.ssh/id_ed25519";
};
certificates = {
enable = lib.mkEnableOption "Enable SSH client certificates";
};
};
config = {
home.file.".ssh/known_hosts" = lib.mkIf cfg.certificates.enable {
text = knownHostsText;
};
programs.ssh = {
enable = true;
enableDefaultConfig = false;
settings = {
# These settings apply to all connections
"*" = lib.mkMerge (
[
# Default settings
{
Compression = false;
ServerAliveInterval = 60;
ServerAliveCountMax = 3;
TCPKeepAlive = "yes";
ConnectTimeout = 3;
PubkeyAuthentication = "yes";
PasswordAuthentication = "no";
PreferredAuthentications = "publickey";
IdentitiesOnly = true;
IdentityFile = cfg.keyFile;
StrictHostKeyChecking = "accept-new";
UserKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts";
HashKnownHosts = lib.mkDefault false;
AddKeysToAgent = lib.mkDefault "yes";
ForwardAgent = lib.mkDefault false;
RequestTTY = lib.mkDefault "auto";
SetEnv.TERM = "xterm-256color";
}
]
# SSH certificate settings
++ lib.optionals cfg.certificates.enable [
{
CertificateFile = "${cfg.keyFile}-cert.pub";
}
]
);
"gitea" = {
HostName = "192.168.1.104";
User = "john";
};
"janus" = {
HostName = "fded:fb16:653e:25da:be24:11ff:fe6b:4d57";
User = "root";
};
"soteria" = {
HostName = "fded:fb16:653e:25da:be24:11ff:fe54:aa39";
User = "root";
};
"hermes" = {
HostName = "192.168.1.150";
User = "root";
};
"jdl-docker" = {
HostName = "jdl-docker.tailcf205.ts.net";
User = "john";
};
};
};
};
};
}
+159
View File
@@ -0,0 +1,159 @@
{ self, inputs, ... }:
let
mkPrincipalArgs = principals:
builtins.concatLists (map (principal: [ "--principal" principal ]) principals);
in
{
flake.wrappers.signHostWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
provisioner = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "admin";
};
provisionerPasswordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
};
extraPrincipals = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
overwrite = lib.mkEnableOption "Overwrite existing cert file?";
};
config = {
binName = "ssh-host-cert-sign";
package = config.pkgs.step-cli;
extraPackages = with config.pkgs; [ hostname iproute2 systemd ];
preHook = ''
HOSTNAME=$(hostname -s)
IP_ADDRESS=$(ip -4 -o addr show scope global | while read -r _ _ _ addr _; do
case "$addr" in
192.168.1.*/*)
printf '%s\n' "''${addr%%/*}"
break
;;
esac
done)
echo "Signing SSH host cert for $HOSTNAME at $IP_ADDRESS"
'';
args =
[
"ssh" "certificate"
"--host" "--sign"
"--principal" "$HOSTNAME"
"--principal" "$IP_ADDRESS"
]
++ lib.optionals (config.provisioner != null) [ "--provisioner" "${config.provisioner}" ]
++ lib.optionals (config.provisionerPasswordFile != null) [
"--provisioner-password-file" "${config.provisionerPasswordFile}"
]
++ lib.optionals config.overwrite [ "-f" ]
++ mkPrincipalArgs config.extraPrincipals;
};
});
flake.wrappers.renewHostWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
sshHostKeyFile = lib.mkOption {
type = lib.types.str;
default = "/etc/ssh/ssh_host_ed25519_key";
};
overwrite = lib.mkEnableOption "Overwrite existing cert file?";
};
config = {
binName = "ssh-host-cert-renew";
package = config.pkgs.step-cli;
extraPackages = with config.pkgs; [ systemd ];
args =
[ "ssh" "renew" "${config.sshHostKeyFile}-cert.pub" "${config.sshHostKeyFile}" ]
++ lib.optionals config.overwrite [ "-f" ];
};
});
flake.wrappers.hostCheckWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
certPath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "/etc/ssh/ssh_host_ed25519_key-cert.pub";
};
};
config = {
binName = "ssh-host-cert-check";
package = config.pkgs.openssh;
exePath = lib.getExe' config.pkgs.openssh "ssh-keygen";
args = [ "-Lf" "${config.certPath}" ];
};
});
flake.wrappers.signUserWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
provisioner = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "admin";
};
provisionerPasswordFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
};
validUsers = lib.mkOption {
description = "A list of the user names that this cert will be valid for";
type = lib.types.listOf lib.types.str;
default = [ ];
};
overwrite = lib.mkEnableOption "Overwrite existing cert file?";
};
config = {
binName = "ssh-user-cert-sign";
package = config.pkgs.step-cli;
args = [ "ssh" "certificate" "--sign" ]
++ lib.optionals (config.provisioner != null) [ "--provisioner" "${config.provisioner}" ]
++ lib.optionals (config.provisionerPasswordFile != null) [
"--provisioner-password-file" "${config.provisionerPasswordFile}"
]
++ lib.optionals config.overwrite [ "-f" ]
++ mkPrincipalArgs config.validUsers;
};
});
flake.wrappers.userCheckWrapper = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
certPath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "$HOME/.ssh/id_ed25519-cert.pub";
};
};
config = {
binName = "ssh-user-cert-check";
package = config.pkgs.openssh;
exePath = lib.getExe' config.pkgs.openssh "ssh-keygen";
args = [ "-Lf" "${config.certPath}" ];
};
});
flake.wrappers.renewalCheck = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
certPath = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = "$HOME/.ssh/id_ed25519-cert.pub";
};
expires-in = lib.mkOption {
type = lib.types.str;
default = "4h";
};
};
config = {
binName = "ssh-renewal-check";
package = config.pkgs.step-cli;
preHook = ''
echo "Checking SSH cert at ${config.certPath}"
'';
args = [
"ssh" "needs-renewal" "${config.certPath}"
"--expires-in" "${config.expires-in}"
];
};
});
}
+168
View File
@@ -0,0 +1,168 @@
{ self, inputs, ... }:
let
defaultCaUrl = "https://janus.john-stream.com/";
defaultFingerprint = "2036c44f7b5901566ff7611ea6c927291ecc6d2dd00779c0eead70ec77fa10d6";
defaultRoot = ../hosts/janus/public/root_ca.crt;
mkStepBootstrap = { pkgs, caUrl, fingerprint, install ? false }:
(inputs.self.wrappers.stepBootstrap.apply {
inherit pkgs install;
ca-url = caUrl;
inherit fingerprint;
}).wrapper;
mkDefaultsText = cfg: builtins.toJSON {
ca-url = cfg.caUrl;
fingerprint = cfg.fingerprint;
root = cfg.root;
};
in
{
flake.modules.nixos.step-client = { config, pkgs, lib, ... }:
let
cfg = config."step-client";
in
{
options."step-client" = {
enable = lib.mkOption {
description = "Enable step-ca client bootstrap";
type = lib.types.bool;
default = true;
};
caUrl = lib.mkOption {
description = "The step-ca URL used for bootstrap and renewal.";
type = lib.types.str;
default = defaultCaUrl;
};
fingerprint = lib.mkOption {
description = "The SHA256 fingerprint of the step-ca root certificate.";
type = lib.types.str;
default = defaultFingerprint;
};
root = lib.mkOption {
description = "The step-ca root certificate used for bootstrap.";
type = lib.types.path;
default = defaultRoot;
};
certDir = lib.mkOption {
description = "Directory used to store root CA material for mTLS and step bootstrap.";
type = lib.types.str;
default = "/etc/step-ca/certs";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
(mkStepBootstrap {
inherit pkgs;
caUrl = cfg.caUrl;
fingerprint = cfg.fingerprint;
})
];
environment.etc."step-ca/defaults.json".text = mkDefaultsText cfg;
systemd.tmpfiles.rules = [
"d ${cfg.certDir} 0750 root root -"
"L+ ${cfg.certDir}/root_ca.crt - - - - ${cfg.root}"
"d /root/.step 0700 root root -"
"d /root/.step/config 0700 root root -"
"d /root/.step/certs 0700 root root -"
"L+ /root/.step/config/defaults.json - - - - /etc/step-ca/defaults.json"
"L+ /root/.step/certs/root_ca.crt - - - - ${cfg.certDir}/root_ca.crt"
];
};
};
flake.modules.homeManager.step-client = { config, pkgs, lib, ... }:
let
cfg = config."step-client";
in
{
options."step-client" = {
enable = lib.mkOption {
description = "Enable step-ca client bootstrap";
type = lib.types.bool;
default = true;
};
caUrl = lib.mkOption {
description = "The step-ca URL used for bootstrap and renewal.";
type = lib.types.str;
default = defaultCaUrl;
};
fingerprint = lib.mkOption {
description = "The SHA256 fingerprint of the step-ca root certificate.";
type = lib.types.str;
default = defaultFingerprint;
};
root = lib.mkOption {
description = "The step-ca root certificate used for bootstrap.";
type = lib.types.path;
default = defaultRoot;
};
certDir = lib.mkOption {
description = "Directory used to store root CA material for mTLS and step bootstrap.";
type = lib.types.str;
default = "${config.home.homeDirectory}/.step/certs";
};
};
config = lib.mkIf cfg.enable (
let
certDirPath = lib.removePrefix "${config.home.homeDirectory}/" cfg.certDir;
in
{
home.packages = [
(mkStepBootstrap {
inherit pkgs;
caUrl = cfg.caUrl;
fingerprint = cfg.fingerprint;
})
];
home.file.".step/config/defaults.json".text = mkDefaultsText cfg;
home.file."${certDirPath}/root_ca.crt".source = cfg.root;
}
);
};
perSystem = { system, pkgs, lib, ... }: {
packages.step-bootstrap = mkStepBootstrap {
inherit pkgs;
caUrl = defaultCaUrl;
fingerprint = defaultFingerprint;
install = true;
};
};
flake.wrappers.stepBootstrap = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
ca-url = lib.mkOption {
type = lib.types.str;
};
fingerprint = lib.mkOption {
type = lib.types.str;
};
install = lib.mkEnableOption "Install the cert to the system trust store";
};
config = {
binName = "step-bootstrap";
package = config.pkgs.step-cli;
args = [
"ca" "bootstrap"
"--ca-url" config.ca-url
"--fingerprint" config.fingerprint
]
++ lib.optional config.install "--install";
};
});
}
+85
View File
@@ -0,0 +1,85 @@
# Janus
Generate passwords:
```shell
mkdir -p /tmp/janus-step-ca-bootstrap && chmod 700 /tmp/janus-step-ca-bootstrap && cd /tmp/janus-step-ca-bootstrap && umask 077 && openssl rand -base64 48 > ca_password.txt && openssl rand -base64 48 > admin_jwk_password.txt
```
Generate the Janus OpenSSH host key for reference (Janus now uses whatever key
already exists on the target at `/etc/ssh/ssh_host_ed25519_key`):
```shell
ssh-keygen -t ed25519 -N '' -C janus@john-stream.com -f /tmp/janus-step-ca-bootstrap/ssh_host_ed25519_key
```
Bootstrap CA materials with SSH enabled:
```shell
STEPPATH=/tmp/janus-step-ca-bootstrap/step step ca init --name Janus --dns janus.john-stream.com --dns 192.168.1.244 --address :443 --provisioner admin --password-file /tmp/janus-step-ca-bootstrap/ca_password.txt --provisioner-password-file /tmp/janus-step-ca-bootstrap/admin_jwk_password.txt --ssh --deployment-type standalone --with-ca-url https://janus.john-stream.com
```
Insert generated runtime CA material into `modules/hosts/janus/secrets.yaml` under `janus`:
- `/tmp/janus-step-ca-bootstrap/ca_password.txt` -> `ca_password`
- `/tmp/janus-step-ca-bootstrap/step/secrets/intermediate_ca_key` -> `intermediate_ca_key`
- `/tmp/janus-step-ca-bootstrap/step/secrets/ssh_host_ca_key` -> `ssh_host_ca_key`
- `/tmp/janus-step-ca-bootstrap/step/secrets/ssh_user_ca_key` -> `ssh_user_ca_key`
- `/tmp/janus-step-ca-bootstrap/step/config/ca.json` -> `admin_provisioner_encrypted_key` (copy `authority.provisioners[].encryptedKey` for the `admin` JWK provisioner)
If you are only validating wiring first, `admin_provisioner_encrypted_key` can be an encrypted placeholder and replaced later.
If rotating provisioner password, also set:
- `/tmp/janus-step-ca-bootstrap/admin_jwk_password.txt` -> `janus.admin_jwk` in `keys/secrets.yaml`
Secret source-of-truth after this split:
- `modules/hosts/janus/secrets.yaml`: Janus runtime CA secrets (`ca_password`, `intermediate_ca_key`, `ssh_host_ca_key`, `ssh_user_ca_key`, `admin_provisioner_encrypted_key`)
- `keys/secrets.yaml`: shared Janus provisioner secret (`janus.admin_jwk`) consumed across hosts
Then update public artifacts in repo from generated output:
- `modules/hosts/janus/public/root_ca.crt` from `/tmp/janus-step-ca-bootstrap/step/certs/root_ca.crt`
- `modules/hosts/janus/public/intermediate_ca.crt` from `/tmp/janus-step-ca-bootstrap/step/certs/intermediate_ca.crt` (public certificate; intentionally committed, not stored in SOPS)
- `modules/hosts/janus/fingerprint` from:
```shell
step certificate fingerprint /tmp/janus-step-ca-bootstrap/step/certs/root_ca.crt
```
- `modules/hosts/janus/public/ssh_user_ca_key.pub` from `/tmp/janus-step-ca-bootstrap/step/certs/ssh_user_ca_key.pub`
- `modules/hosts/janus/public/ssh_host_ca_key.pub` from `/tmp/janus-step-ca-bootstrap/step/certs/ssh_host_ca_key.pub`
## First boot checks
After switching Janus, verify the declarative bootstrap units instead of running
ad hoc issuance commands first:
```shell
systemctl status step-ca.service
systemctl status ssh-certs-renew.service
systemctl status ssh-certs-renew.timer
systemctl status mtls-bootstrap.service
ssh-host-cert-check
mtls-check
```
`ssh-certs-renew.service` issues the SSH host certificate when it is missing
or expiring. `mtls-bootstrap.service` issues the first Janus mTLS bundle
only when the configured certificate files are absent or invalid; recurring mTLS
renewal remains handled by `mtls-renew.timer`.
## Back up the root CA key offline
The root CA private key is **not** deployed and is never needed by the running CA.
It is only used to sign/rotate intermediates. Store it offline (e.g. 1Password)
before wiping the bootstrap directory, otherwise intermediate rotation becomes
impossible and any future rotation forces a full root rotation (redistributing
`root_ca.crt` + `fingerprint` to every client).
Back up, then wipe the bootstrap material:
- `/tmp/janus-step-ca-bootstrap/step/secrets/root_ca_key` -> offline secret store
- `/tmp/janus-step-ca-bootstrap/ca_password.txt` -> offline secret store (root/intermediate key password)
```shell
shred -u /tmp/janus-step-ca-bootstrap/step/secrets/root_ca_key 2>/dev/null || true
rm -rf /tmp/janus-step-ca-bootstrap
```
+42 -87
View File
@@ -2,108 +2,63 @@
let let
username = "john"; username = "john";
hostname = "janus"; hostname = "janus";
ca-url = "https://janus.john-stream.com/"; ipv4 = "192.168.1.32";
fingerprint = builtins.readFile ./fingerprint; ipv6 = "fded:fb16:653e:25da:be24:11ff:fe6b:4d57";
names = [ "${hostname}.john-stream.com" ipv4 ipv6 ];
in in
{ {
flake.modules.nixos.janus-ca =
{ config, lib, ... }:
let
cfg = config.janus-ca;
johnHome = lib.attrByPath [ "users" "users" username "home" ] "/home/${username}" config;
johnGroup = lib.attrByPath [ "users" "users" username "group" ] username config;
cfgInEtc = lib.hasPrefix "/etc/" cfg.certDir;
certDirEtcPath =
if cfgInEtc then
lib.removePrefix "/etc/" cfg.certDir
else
cfg.certDir;
certRootEtcPath = "${certDirEtcPath}/root_ca.crt";
mkStepRules = home: user: group: [
"d ${home}/.step 0700 ${user} ${group} -"
"d ${home}/.step/config 0700 ${user} ${group} -"
"d ${home}/.step/certs 0700 ${user} ${group} -"
"L+ ${home}/.step/config/defaults.json - - - - /etc/step-ca/defaults.json"
"L+ ${home}/.step/certs/root_ca.crt - - - - ${cfg.certDir}/root_ca.crt"
];
in
{
options.janus-ca = {
certDir = lib.mkOption {
description = "String path to where the mtls certs will be stored.";
type = lib.types.str;
default = "/etc/step-ca/certs";
};
};
config = {
environment.etc = lib.mkIf cfgInEtc {
"step-ca/defaults.json".text = builtins.toJSON {
inherit ca-url fingerprint;
root = "/etc/${certRootEtcPath}";
};
"${certRootEtcPath}".source = ./root_ca.crt;
};
systemd.tmpfiles.rules =
mkStepRules johnHome username johnGroup
++ mkStepRules "/root" "root" "root";
};
};
flake.modules.homeManager.janus-ca = { config, ... }: {
home.file.".step/config/defaults.json".text = builtins.toJSON {
inherit ca-url fingerprint;
root = "${config.home.homeDirectory}/.step/certs/root_ca.crt";
};
home.file.".step/certs/root_ca.crt".source = ./root_ca.crt;
};
flake.nixosConfigurations."${hostname}" = inputs.nixpkgs.lib.nixosSystem { flake.nixosConfigurations."${hostname}" = inputs.nixpkgs.lib.nixosSystem {
modules = with inputs.self.modules; [ modules = with inputs.self.modules; [
nixos.lxc nixos.lxc
nixos.mysops nixos.login-text
nixos.step-ssh-host
nixos.janus-ca
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
nixos."${username}" nixos."${username}"
nixos.docker nixos.ssh-new
nixos.login-text nixos.mysops
nixos.step-ca # Runs the step-ca server
nixos.step-client # Uses the step-ca server as a client
nixos.mtls nixos.mtls
{ ({ config, lib, pkgs, ... }: {
networking.hostName = hostname; networking.hostName = hostname;
step-ssh-host = { loginText.extraServiceStatus = {
hostname = hostname; "Step-CA" = "step-ca";
}; };
sops.defaultSopsFile = ./secrets.yaml;
ssh-new.certificates = {
provisioner = "admin";
host = {
enable = true;
extraPrincipals = names;
autoRenew = true;
};
user.enable = true;
};
step-ca = {
rootCertPath = ./public/root_ca.crt;
intermediateCertPath = ./public/intermediate_ca.crt;
dnsNames = names;
secrets = {
sopsFile = ./secrets.yaml;
caPassword = "janus/ca_password";
intermediateKey = "janus/intermediate_ca_key";
sshHostCaKey = "janus/ssh_host_ca_key";
sshUserCaKey = "janus/ssh_user_ca_key";
adminProvisionerEncryptedKey = "janus/admin_provisioner_encrypted_key";
};
};
step-client.caUrl = "https://${ipv4}/";
mtls = { mtls = {
enable = true; enable = true;
subject = hostname; subject = hostname;
san = [ san = names;
"${hostname}.john-stream.com" bootstrap = {
"192.168.1.244" enable = true;
]; after = [ "sops-nix.service" "step-ca.service" ];
wants = [ "step-ca.service" ];
provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path;
}; };
home-manager.users."${username}" = {
imports = with inputs.self.modules.homeManager; [
mysops
step-ssh-user
];
shell.program = "zsh";
docker.enable = true;
}; };
} })
]; ];
}; };
perSystem = { system, pkgs, lib, ... }: {
packages.janus-ca = inputs.wrappers.lib.wrapPackage {
inherit pkgs;
package = pkgs.step-cli;
binName = "janus-cert";
args = [
"ca" "certificate"
"--ca-url=${ca-url}"
];
};
};
} }
@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBuzCCAWKgAwIBAgIQIW2tnzaR3eHv3EzooE+QGjAKBggqhkjOPQQDAjAoMQ4w
DAYDVQQKEwVKYW51czEWMBQGA1UEAxMNSmFudXMgUm9vdCBDQTAeFw0yNTEyMTgw
NjAyMjlaFw0zNTEyMTYwNjAyMjlaMDAxDjAMBgNVBAoTBUphbnVzMR4wHAYDVQQD
ExVKYW51cyBJbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AARyjfmHDMD+aOIESYF9v9Pe68aRgINpjRskGdELz4Y5+x7QNHvbZPEdkox0Qbcp
XETmrcrw6fxBnImYJM5/IU0do2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBgNVHQ4EFgQUZN0zt5HKZLj0YtGTI7ROw8wLCw8wHwYDVR0j
BBgwFoAUaOeW8ssqNrHjz/vM8/rjOJlzVfswCgYIKoZIzj0EAwIDRwAwRAIgE6W7
yPG7tpLdwAUVF8BtOZV8TEDwmS1gudrcEbgXyXcCIETTUmM4GlN+zxMGYKs6OybN
IxTY4WmviiXMsDoGWm0B
-----END CERTIFICATE-----
@@ -0,0 +1 @@
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=
+40
View File
@@ -0,0 +1,40 @@
janus:
ca_password: ENC[AES256_GCM,data:z6i0ELrn6XwVbJ7gP1GkpWe0Xw==,iv:3pZs/kzPp6bL8iv8jITZnAHuCycbSjhWswgzuJuLvjE=,tag:3gUftdyVXPVWe5dGxh9dvw==,type:str]
intermediate_ca_key: ENC[AES256_GCM,data:tHP5V/yyrx7ydebg+cN30sLlRhnn01oWogywCUVm4YpKbWTJASaH4G74aaqPAGqETD1Go0Y5xR4DRkBJHBlTc3qzNj5/pzrSKwqfc537SCSUX79hTzw6MvTvukmbg4c/vF0ts5vZPYyi6mYwiAD2Ouk8HwU+AZOlCSm0VWL6rFjmq++fX4jhV0JRpcViBD8tjXpElpT/btyPAAXjaJgqMMZKALfwnAgXXrdy5E5FsuegQRfBadCDLsv0A+qq14CnstfPRkYAKqAhTL2HlmKKVtRiWTMZ6D14dYteUW+NQ33zKVIDz2a0M8tCp+41bDQWC9KrxrKcNVWc6ajAW11DzufP+lVhyWoZF+jkHZjWuy0lsgAUsTvZggLPU6euXHAjDeO4Z3ZQHvViVM9pVnsMzN6/w0i7gHSBFBw=,iv:actN7L8G7xbh9dYq7zQoOvnDYtcqAodV1V5Yu1q5elY=,tag:vas5E1ywf3sP1knVd7iE7A==,type:str]
ssh_host_ca_key: ENC[AES256_GCM,data:eD0aZskYgsN7NWWo98VzrJGfQsbXLPUv9tWz8/GPU9UJcDaDmjEOs96cdW08IvINZXTMJtUscMGmewWX5mg1zevYGBYF8xgfdzT1pao5tw/F079aLc8HNxRn1s+4lL/GINtRXXK13tzYTPeI6Z1g4AE8UqVDZ/65+oLzkzWljE93UM6G9K78+ulBZq27Www0+dqT00n03YSfDCO8LgEJjxo03aShLXth+nT2SGZDfH9sILkZaDOeqOgQpW43L9rU+5yBCcGgCkBuhb8jMu2yStLCRxDewTNcxDYvIsNr1T/7jammRgUn+lYNcKgHeZS81dgNQkXmzRwErYbAx22zCaHdmSlH7RvF0olVCWzgXY4uSjbMFmQqNVVlj7UpYXRIIWnmiiwiBJQlmCJbm/OjS43dTYwZZ0waRIs=,iv:0TTQe2DC6EFiNhFEA3DVv2yu487pr4iX7siooA3EU94=,tag:mlZIquPBWR/5OVU7ZW0Uqg==,type:str]
ssh_user_ca_key: ENC[AES256_GCM,data:CrjtkGfaUklNsOfIWhHwJXzBCJN2RvJKZQLueqAaOmY5niMdVRRnQahtMCtyJ7QxONs26VZkdcLmq4mfHWUA8lo4Q5ssFvqWvIcHvj4J0i0eB4RqQev2KGo/lg+GybZK2opBWS5z7msfPI94Fgu12qN46brBdbgVgclxqS23MR+A8d+VTcUYRcrPLhf4VTPH8eNGnhlM47tjzp7PjpMOumpkjbrNnNyPG3rVzvH0zbCc2HhJxtstEdoLT1u+1FxphR6kroRd/F+xQAY82A4UYVuHfaNkc8thHLA+wbGf5ReRZode5vCz4ErEWpJhnltU8YVgMDQSqE5gDuxWFv+P17hUdNXUmVI8QBiyu0sI8oNIBT6xDvK69rgzKaEy8MON/2E31CpQALokEiDB1Xn04W3rdnSVS6m84ck=,iv:lGrSv/0qCXha7rK/nEtZa0cH2wKtGxKigUsxqahk3SE=,tag:ZmxenXfXt9Pa4euZIgt1bw==,type:str]
admin_provisioner_encrypted_key: ENC[AES256_GCM,data:lv85vgW77Qiy4UOcHt5SaVvuGmBsDIdjTxbUUaxGeP2H+XghTVuHZ/oLNXaPYqBFKKFqctG7FeL3/Gnm5wD7DXn9z6Cwc9eSZjNHUEiYcQLThBHDUOArk/CHhWvuAhWbuT5yBwVJkUmBpO5ZR3slRAh8RS/EBhb3iOXGW63i2Bj1kFAyf4XucmWY8lkRRpIj11gkWA1gVZqJkQI5AJDf4ikvYI/5p33wtQFpClsKJsJRzaoCee+m3w2Obq8hYMdS1mGnXm1L3tbkAj0130dl4/eOvOZq7le2S5Fo0WAFFyk1nhNz771fRI3WtiA85IHC9Nd3v6jGek+PVclk0D4rfvlQTF1yOpemtcsX2BbAbbB2j7sJ5a5bM9O5lotBoFlGQP/co5EU7Ki0+sTYdQxsHVSQhvsCTv5IhSMSjrzkUVN0RV7EwSkKdLK65Q8bR1pmOdXVDAPsyjB4tUJlwWKisrkAc5oyM+mI0Lalz7nvbTYvgtiS0vcBnGmRwcxDC5q//BTm4RhAB2e/My0MadVxF3aEn6fMaoK+3ef1zAToBRG8bV1vC+trhz3ucPqvMNPxjrSQpOdxzJyqlPgfKFVRI/DEQhO0XWpnU3h7wA1mV/dyjeosFVydfKN5iOLl+xF3VgRpisV2oE0Fgas6JkOGwy/d+MpkgNz/Amr+9phZA/bXl2lpDYIKj6aBduqizI4jV1jpM2FHtv99Jkg24DKIcPSZVvzVYr6j8FMmgaxOsA3Z72GlleLdwBKtuBC0GvU=,iv:kcJEhE1d4vYLHk+W2ay2rid+KCuarAIxUZSl/0wi5W4=,tag:7VARgV4a1nT+beIYWelqyw==,type:str]
ssh_host_ed25519_key: ENC[AES256_GCM,data:6NxQm3pHzHmBkGJq/YLswi7uyMXzbzrsBPoqhpFA25PmHJBeIUVi0sIWPsdGDemx8dzEZ3oEB4dGbZ8JSvywpnSdvIzUhWK21BZLpbutUDBFtG68tkFK5N7dJ/dlA000oNEq5JiqrlKdFuVVjtUj7BK887a5xK7gyYfiqvv4E5dWv9IGk7lCHb/tjKkXGpYTRg1MVv2wSoVGRRSW1/97s2gAFMzeG48yJlwfiCQERXpHBte2rW5vZrOyR57J0ienf75wYwz6dLJuM1YZLKP9Ob8hCs00PEyxq7+JZuX/tx2yRiYKlgCYhtVbLbnjriPnSpKB/Xn/5t2FGP3ogfWUx+xkUm8lHDPbzrI86LNpFo016Ge2gDCDLvqLr9o4SDqnwmJfiAdMQokH7rJuQOiqk8gnWQCk8cvKc3vdYE0F6TnjbnZHMU4JbnmCr40xkBXkya/9jxVAC8RYfaW88hmAfQ9mYSdnbbUipC9Hv9Qhwst+Yqe/HxwaWfyimE0CpxOopwUT2qT/+ZO8Qjy/HnK4,iv:jlWTcKt/fvrry/OTamtKHNZAZmlxAj6TgGoPDor4HeY=,tag:btP8xFMEDzd/Nzr4EqIDZg==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRZ1pQQW1rekYzUDFGcXBW
M2RHSXUvbCtvemtHYUljNmxXOCt4dEczYUU0CkdFWDRBYWRhQStYb1RtbEFmZVk4
bWxMTnhKVXlZWDRlcWZHWWhaSC9KMW8KLS0tIER6VWJGY28ramk2NDhZa0x5RU1s
YmU5S1FCVkJyNTNIVXhJSkdQVHYxMVkKEkbFmD4a7FWC8cKpZu2ZVot5ibheTQ1h
H6uqwAYT/UIXqnceyZyoT9JRcZyQy7xAUAJzQMe+TxUr+8iHAm7aBg==
-----END AGE ENCRYPTED FILE-----
recipient: age1qahhlzeanprtykym9jymk2t95uedr7cwx9sdshx46q2m6u66fucsqua8l3
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqWjBiVDJSZFRQYis3dSt5
OFFMbGhyaHgxSDBjdnVMSlZ5d3B0eEhVM0VVCmw4TXk2a0s3ZDRCeHhEVFpGRkpl
b1lSQUJPL0hNRk50UEhHejNkL2VRd1EKLS0tICtXaWVMN0FSejNsNGNTOGh1WG13
aFJINlNKOVNhc3VBOU00VXYyMTRFdVkKV251o5CegRdDxvhpOJjK5ITp/+DVBE9o
R3eAsTXG5SSN50rcZYjwoEgYUeiRB4dQ/NYw6lVDUFrOf/a7sAr32g==
-----END AGE ENCRYPTED FILE-----
recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYYjlQRGVmRnRGbjNNT3dC
cWJCWmtrM1BnbE80QnpTWXdHMytqSmFHOVhjCm9LYzd4R2s1TENIVkFYK3g4STRk
cGIxMS9RQ2Y1dmR6RjZYeVF5LzRoelkKLS0tIEdjaGk2WnNCRVA0S1dxRzJyWFc3
Yzh0MlFSZTR0Z0RhUnhHQzRSbFRXaUEK+5jadT4PMWPrljNcXVW3S1yJ1nI0iei8
htf09S2S5jraIFkl2x6Rv8IqOnKVPPgTZkvZqPMQoM4zcs+o1/lnug==
-----END AGE ENCRYPTED FILE-----
recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0
lastmodified: "2026-07-05T01:59:20Z"
mac: ENC[AES256_GCM,data:s4HAH/T9iqHooXXswMoeC1ttJwpyXQgnzy4Op2WPP55nowM//itnxpBzaqlMQUuuqMi4kd8wD/AakrlPRsbsBjgxnPYqmBKQA5eg+3a8QSf31HcLEbhtOsi38jOG2L2VMkdVfihvbe8NdFw4AD1tPviDhs7t9zjWEH1r/eCu+as=,iv:+FmOEuFM0V1ID3zkvNXF8m0ukQpq0Nmbdp0s4/WR/PY=,tag:/e4xkd2bdfC5fdHXJB4NAA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.1
+75
View File
@@ -0,0 +1,75 @@
{ withSystem, self, inputs, ... }:
let
username = "john";
hostname = "john-kde";
in
{
flake.modules.homeManager."${hostname}" = { config, pkgs, lib, ... }:
let
selfPkgs = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
flakeDir = "${config.xdg.configHome}/home-manager";
in
{
imports = with inputs.self.modules.homeManager; [
rebuild
john
# mtls
# restic
docker
desktop
step-client
# mysops
# myPackage
# myStepClient
];
# TODO: make this more restrictive, rather than allowing all unfree packages
nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [ "openssl-1.1.1w" ];
targets.genericLinux.enable = true;
home.username = "${username}";
home.homeDirectory = "/home/${username}";
home.packages = with pkgs; [
selfPkgs.jsl-zsh
selfPkgs.my-neovim
# selfPkgs.step-bootstrap
# selfPkgs.wg-platform
# self'.packages.myWrappedPackage
# (inputs.self.wrappers.test-push.apply {
# inherit pkgs flakeDir;
# host = testHost;
# target = testTarget;
# }).wrapper
];
homeManagerFlakeDir = flakeDir;
docker.enable = true;
ssh = {
certificates.enable = true;
knownHosts = [
"fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh"
];
matchSets = {
certs = true;
appdaemon = true;
homelab = true;
dev = true;
tailscale = true;
};
};
};
flake.homeConfigurations."john@john-kde" = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = import inputs.nixpkgs.outPath {
localSystem.system = "x86_64-linux";
config = {
allowUnfree = true;
permittedInsecurePackages = [ "openssl-1.1.1w" ];
problems.handlers.sublimetext4.broken = "ignore";
};
};
modules = [ inputs.self.modules.homeManager."${hostname}" ]; # Uses the module defined above
};
}
+8 -74
View File
@@ -5,60 +5,34 @@
hostname = "john-p14s"; hostname = "john-p14s";
homeDirectory = config.home-manager.users.john.home.homeDirectory; homeDirectory = config.home-manager.users.john.home.homeDirectory;
flakeDir = "${homeDirectory}/Documents/dendritic"; flakeDir = "${homeDirectory}/Documents/dendritic";
my-neovim = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.my-neovim; selfPkgs = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
in in
{ {
imports = [ imports = [
self.modules.nixos.base
self.modules.nixos.p14sHardware self.modules.nixos.p14sHardware
self.modules.nixos.onepassword
]; ];
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nixpkgs.config = {
permittedInsecurePackages = [ "openssl-1.1.1w" ];
allowUnfree = true;
};
rebuild.flakeDir = flakeDir; rebuild.flakeDir = flakeDir;
networking = { networking.hostName = hostname;
hostName = hostname;
networkmanager.enable = true;
};
# Enable automatic login for the user.
# services.displayManager.autoLogin.enable = true;
# services.displayManager.autoLogin.user = "john";
programs.zsh.enable = true; programs.zsh.enable = true;
services.openssh.enable = true;
services.tailscale.enable = true; services.tailscale.enable = true;
# List packages installed in system profile. To search, run: # List packages installed in system profile. To search, run:
# $ nix search wget # $ nix search wget
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
wget
cacert
busybox
dig
samba samba
my-neovim selfPkgs.my-neovim
selfPkgs.wg-platform
selfPkgs.jsl-zsh
]; ];
security.pam.services.swaylock = {}; security.pam.services.swaylock = {};
security.pam.services.swaylock.fprintAuth = true; security.pam.services.swaylock.fprintAuth = true;
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
# Certain features, including CLI integration and system authentication support,
# require enabling PolKit integration on some desktop environments (e.g. Plasma).
polkitPolicyOwners = [ "john" ];
# TODO this should not be a hardcoded username
};
# This is needed for VSCode remote support. Read: https://nixos.wiki/wiki/Visual_Studio_Code
programs.nix-ld.enable = true;
# This value determines the NixOS release from which the default # This value determines the NixOS release from which the default
# settings for stateful data, like file locations and database versions # settings for stateful data, like file locations and database versions
# on your system were taken. It's perfectly fine and recommended to leave # on your system were taken. It's perfectly fine and recommended to leave
@@ -67,43 +41,10 @@
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "24.05"; # Did you read the comment? system.stateVersion = "24.05"; # Did you read the comment?
# Set your time zone.
time.timeZone = "America/Chicago";
# Select internationalisation properties.
i18n = {
defaultLocale = "en_US.UTF-8";
extraLocaleSettings = {
LC_ADDRESS = "en_US.UTF-8";
LC_IDENTIFICATION = "en_US.UTF-8";
LC_MEASUREMENT = "en_US.UTF-8";
LC_MONETARY = "en_US.UTF-8";
LC_NAME = "en_US.UTF-8";
LC_NUMERIC = "en_US.UTF-8";
LC_PAPER = "en_US.UTF-8";
LC_TELEPHONE = "en_US.UTF-8";
LC_TIME = "en_US.UTF-8";
};
};
services.libinput.enable = true; # Enable touchpad support (enabled default in most desktopManager). services.libinput.enable = true; # Enable touchpad support (enabled default in most desktopManager).
services.fprintd.enable = true; # Enables fingerprint sensor services.fprintd.enable = true; # Enables fingerprint sensor
# Enable sound with pipewire.
services.pulseaudio.enable = false;
security.rtkit.enable = true; # PulseAudio server uses this to acquire realtime priority.
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
# use the example session manager (no others are packaged yet so this is enabled by default,
# no need to redefine it in your config for now)
# media-session.enable = true;
};
home-manager.users.root = { home-manager.users.root = {
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
@@ -115,19 +56,12 @@
gnome gnome
desktop desktop
mysops mysops
step-client
rebuild rebuild
{ {
my-vscode.enable = true; my-vscode.enable = true;
mysops.hostSecretFile = "${flakeDir}/modules/hosts/john-p14s/secrets.yaml"; mysops.hostSecretFile = "${flakeDir}/modules/hosts/john-p14s/secrets.yaml";
homeManagerFlakeDir = "${flakeDir}"; homeManagerFlakeDir = "${flakeDir}";
shell.program = "zsh";
home.packages = with pkgs; [
bash
discord
my-neovim
proton-vpn
joplin-desktop
];
ssh.certificates.enable = true; ssh.certificates.enable = true;
ssh.matchSets = { ssh.matchSets = {
appdaemon = true; appdaemon = true;
+1 -1
View File
@@ -12,7 +12,6 @@
"${inputs.nixos-hardware}/lenovo/thinkpad/p14s/amd/gen4" "${inputs.nixos-hardware}/lenovo/thinkpad/p14s/amd/gen4"
] ++ (with self.modules.nixos; [ ] ++ (with self.modules.nixos; [
p14sConfiguration p14sConfiguration
janus-ca
rebuild rebuild
sudo sudo
john john
@@ -20,6 +19,7 @@
steam steam
wireguard wireguard
mtls mtls
step-client
# greetd # greetd
# niri # niri
]); ]);
+43 -50
View File
@@ -3,11 +3,8 @@ let
username = "john"; username = "john";
hostname = "john-pc-ubuntu"; hostname = "john-pc-ubuntu";
# testTarget = "fded:fb16:653e:25da:be24:11ff:fe89:1cc3"; # soteria
# testTarget = "fded:fb16:653e:25da:be24:11ff:fea0:753f"; # test-nix
testHost = "soteria"; # which host to test build testHost = "soteria"; # which host to test build
testTarget = "test-nix"; testTarget = "test-nix";
in in
{ {
flake.modules.homeManager."${hostname}" = { config, pkgs, lib, ... }: flake.modules.homeManager."${hostname}" = { config, pkgs, lib, ... }:
@@ -15,23 +12,20 @@ in
selfPkgs = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; selfPkgs = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
resticPasswordFile = "${config.xdg.configHome}/restic/password.txt"; resticPasswordFile = "${config.xdg.configHome}/restic/password.txt";
flakeDir = "${config.xdg.configHome}/home-manager/jsl-dendritic"; flakeDir = "${config.xdg.configHome}/home-manager/jsl-dendritic";
test-push = with pkgs; writeShellApplication {
name = "test-push";
runtimeInputs = [ nh ];
text = ''nh os switch ${flakeDir}#${testHost} --target-host root@${testTarget} -e none'';
};
in in
{ {
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
rebuild rebuild
john john
mysops
janus-ca
step-ssh-user
mtls mtls
restic restic
docker docker
desktop desktop
step-client
mysops
# zed-editor
# myPackage
# myStepClient
]; ];
# TODO: make this more restrictive, rather than allowing all unfree packages # TODO: make this more restrictive, rather than allowing all unfree packages
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
@@ -42,36 +36,29 @@ in
home.username = "${username}"; home.username = "${username}";
home.homeDirectory = "/home/${username}"; home.homeDirectory = "/home/${username}";
home.packages = with pkgs; [ home.packages = with pkgs; [
nixos-rebuild nil # Nix language server
test-push selfPkgs.jsl-zsh
selfPkgs.neovim-min (inputs.self.wrappers.test-push.apply {
# ${selfPkgs}.my-neovim inherit pkgs flakeDir;
# selfPkgs.richPrinter host = testHost;
selfPkgs.janus-ca target = testTarget;
}).wrapper
]; ];
shell.program = "zsh";
homeManagerFlakeDir = flakeDir; homeManagerFlakeDir = flakeDir;
docker.enable = true; docker.enable = true;
ssh-new = {
step-ssh-user = {
enable = true;
principals = ["root" "${username}" "appdaemon"];
provisioner = "admin";
};
ssh = {
certificates.enable = true; certificates.enable = true;
knownHosts = [
"fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh"
];
matchSets = {
certs = true;
appdaemon = true;
homelab = true;
dev = true;
};
}; };
# ssh = {
# matchSets = {
# certs = true;
# appdaemon = true;
# homelab = true;
# dev = true;
# tailscale = true;
# };
# };
# This provides the keys at build time and will be included in the nix store # This provides the keys at build time and will be included in the nix store
sops.defaultSopsFile = ../../../keys/secrets.yaml; sops.defaultSopsFile = ../../../keys/secrets.yaml;
@@ -96,22 +83,28 @@ in
"/home/john/john-nas" "/home/john/john-nas"
]; ];
}; };
mtls = { # mtls = {
enable = true; # enable = true;
subject = hostname; # subject = hostname;
san = [ # san = [
"${hostname}" # "${hostname}"
"192.168.1.85" # "192.168.1.85"
"spiffe://john-stream.com/ubuntu" # "spiffe://john-stream.com/ubuntu"
]; # ];
lifetime = "1h"; # lifetime = "1h";
renew.onCalendar = "*:1/10"; # renew.onCalendar = "*:1/10";
}; # };
}; };
flake.homeConfigurations."john@john-pc-ubuntu" = withSystem "x86_64-linux" (ctx@{ config, inputs', ...}: flake.homeConfigurations."john@john-pc-ubuntu" = inputs.home-manager.lib.homeManagerConfiguration {
inputs.home-manager.lib.homeManagerConfiguration { pkgs = import inputs.nixpkgs.outPath {
pkgs = inputs'.nixpkgs.legacyPackages; localSystem.system = "x86_64-linux";
config = {
allowUnfree = true;
permittedInsecurePackages = [ "openssl-1.1.1w" ];
problems.handlers.sublimetext4.broken = "ignore";
};
};
modules = [ inputs.self.modules.homeManager."${hostname}" ]; modules = [ inputs.self.modules.homeManager."${hostname}" ];
}); };
} }
+33
View File
@@ -0,0 +1,33 @@
{ self, inputs, ... }: {
flake.wrappers.test-push = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
flakeDir = lib.mkOption {
type = lib.types.str;
};
host = lib.mkOption {
type = lib.types.str;
};
target = lib.mkOption {
type = lib.types.str;
};
sshUser = lib.mkOption {
type = lib.types.str;
default = "root";
};
elevationStrategy = lib.mkOption {
type = lib.types.str;
default = "none";
};
};
config = {
binName = "test-push";
package = config.pkgs.nh;
args = [
"os" "switch" "${config.flakeDir}#${config.host}"
"--target-host" "${config.sshUser}@${config.target}"
"--elevation-strategy" "${config.elevationStrategy}"
];
};
});
}
+123
View File
@@ -0,0 +1,123 @@
{ self, inputs, ... }: {
flake.modules.nixos.omen-nixos = { pkgs, lib, ... }: {
# import any other modules from here
imports = [
self.modules.nixos.omen-nixos-hardware
self.modules.nixos.base
self.modules.nixos.greetd
self.modules.nixos.niri
self.modules.nixos.onepassword
self.modules.nixos.steam
];
# Use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Use latest kernel.
boot.kernelPackages = pkgs.linuxPackages_latest;
networking.hostName = "omen-nixos"; # Define your hostname.
# Configure network proxy if necessary
# networking.proxy.default = "http://user:password@proxy:port/";
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
# Select internationalisation properties.
# i18n.defaultLocale = "en_US.UTF-8";
# console = {
# font = "Lat2-Terminus16";
# keyMap = "us";
# useXkbConfig = true; # use xkb.options in tty.
# };
# Enable CUPS to print documents.
# services.printing.enable = true;
# Enable sound.
# services.pulseaudio.enable = true;
# OR
# services.pipewire = {
# enable = true;
# pulse.enable = true;
# };
# Enable touchpad support (enabled default in most desktopManager).
# services.libinput.enable = true;
# Define a user account. Don't forget to set a password with passwd.
# users.users.alice = {
# isNormalUser = true;
# extraGroups = [ "wheel" ]; # Enable sudo for the user.
# packages = with pkgs; [
# tree
# ];
# };
# programs.firefox.enable = true;
# List packages installed in system profile.
# You can use https://search.nixos.org/ to find more packages (and options).
environment.systemPackages = with pkgs; [
# vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
wget
git
micro
nh
];
# Some programs need SUID wrappers, can be configured further or are
# started in user sessions.
# programs.mtr.enable = true;
# programs.gnupg.agent = {
# enable = true;
# enableSSHSupport = true;
# };
# List services that you want to enable:
# Enable the OpenSSH daemon.
services.openssh.enable = true;
# Open ports in the firewall.
# networking.firewall.allowedTCPPorts = [ ... ];
# networking.firewall.allowedUDPPorts = [ ... ];
# Or disable the firewall altogether.
# networking.firewall.enable = false;
# Copy the NixOS configuration file and link it from the resulting system
# (/run/current-system/configuration.nix). This is useful in case you
# accidentally delete configuration.nix.
# system.copySystemConfiguration = true;
# This option defines the first version of NixOS you have installed on this particular machine,
# and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
#
# Most users should NEVER change this value after the initial install, for any reason,
# even if you've upgraded your system to a new NixOS release.
#
# This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
# so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
# to actually do that.
#
# This value being lower than the current NixOS release does NOT mean your system is
# out of date, out of support, or vulnerable.
#
# Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
# and migrated your data accordingly.
#
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "26.05"; # Did you read the comment?
# ...
home-manager.users.john.imports = with inputs.self.modules.homeManager; [
desktop
# rebuild
{
my-vscode.enable = true;
}
];
};
}
+8
View File
@@ -0,0 +1,8 @@
{ self, inputs, ... }: {
flake.nixosConfigurations.omen-nixos = inputs.nixpkgs.lib.nixosSystem {
modules = [
self.modules.nixos.omen-nixos
self.modules.nixos.john
];
};
}
+28
View File
@@ -0,0 +1,28 @@
{ self, inputs, ... }: {
flake.modules.nixos.omen-nixos-hardware = { config, lib, pkgs, modulesPath, ... }: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems = {
"/" = {
device = "/dev/disk/by-uuid/35f77d1a-346c-4c52-83b2-7d25e2ac9fe1";
fsType = "ext4";
};
"/mnt/shared" = {
device = "/dev/disk/by-uuid/216e8dca-170d-4377-bf1a-69b574e1778c";
fsType = "ext4";
};
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
};
}
+124
View File
@@ -0,0 +1,124 @@
{ withSystem, self, inputs, lib, ... }:
# Intent: NixOS service node for Forgejo + restic with mTLS, host-local secrets, and a paired Home Manager profile for john.
let
username = "john";
hostname = "soteria";
ipv4 = "192.168.1.233";
ipv6 = "fded:fb16:653e:25da:be24:11ff:fe54:aa39";
names = [ "${hostname}.john-stream.com" ipv4 ipv6 ];
in
{
flake.nixosConfigurations."${hostname}" = inputs.nixpkgs.lib.nixosSystem {
modules = with inputs.self.modules; [
nixos.lxc
nixos.login-text
inputs.home-manager.nixosModules.home-manager
nixos."${username}"
nixos.ssh-new
nixos.mtls
nixos.mysops
nixos.docker
nixos.step-client
nixos.forgejo
nixos.restic-server
# nixos.restic-envoy
({ config, pkgs, ... }: {
networking.hostName = hostname;
# Removes password for sudo
security.sudo-rs.extraRules = lib.mkAfter [
{
users = [ username ];
commands = [
{
command = "ALL";
options = [ "NOPASSWD" ];
}
];
}
];
# users.users."${username}".extraGroups = [ "mtls" ];
mtls = {
enable = true;
subject = hostname;
san = names;
# lifetime = "12h";
# renew.onCalendar = "*:3/15";
# renew.reloadUnits = [ "forgejo.service" "restic-rest-server.service" ];
# certReaders = [ config.services.forgejo.user "restic" ];
};
# forgejo = {
# enable = true;
# root_url = "https://forgejo.john-stream.com";
# https = true;
# port = 443;
# };
# resticServer = {
# enable = true;
# dataDir = "/mnt/restic";
# privateRepos = true;
# listenAddress = "0.0.0.0:8000";
# tls = {
# certFile = config.mtls.certFile;
# keyFile = config.mtls.keyFile;
# };
# };
loginText.extraServiceStatus = {
Docker = "docker";
"mTLS Renewal" = "mtls-renew.timer";
"Forgejo" = "forgejo.service";
"Forgejo Backup" = "forgejo-dump.timer";
"Restic REST Server" = "restic-rest-server.service";
};
ssh-new.certificates = {
provisioner = "admin";
host = {
enable = true;
extraPrincipals = names;
autoRenew = true;
};
user.enable = true;
};
# This provides the secrets at install time
sops.defaultSopsFile = ./secrets.yaml;
home-manager.users."${username}".imports = [ inputs.self.modules.homeManager.soteria ];
environment.systemPackages = [
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.my-neovim
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.jsl-zsh
];
})
];
};
flake.modules.homeManager.soteria = { config, pkgs, lib, ... }: {
imports = with inputs.self.modules.homeManager; [
rebuild
mysops
step-client
({ config, pkgs, lib, ... }: {
homeManagerFlakeDir = "${config.xdg.configHome}/home-manager";
docker.enable = true;
# This will provide the edit-secrets script targeting this file
mysops.hostSecretFile = "${config.homeManagerFlakeDir}/modules/hosts/soteria/secrets.yaml";
})
];
};
# flake.homeConfigurations.soteria = withSystem "x86_64-linux" (ctx@{ config, inputs', ...}:
# inputs.home-manager.lib.homeManagerConfiguration {
# pkgs = inputs'.nixpkgs.legacyPackages;
# modules = [
# inputs.self.modules.homeManager."${username}"
# inputs.self.modules.homeManager.soteria
# ];
# });
}
+21 -23
View File
@@ -1,5 +1,3 @@
janus:
admin_jwk: ENC[AES256_GCM,data:2XcN5X77wTQ+OUXa4C9xAErGvrwJKseHjCcgoj6jt+c=,iv:9x+M1wM0dYND1JYkJjM4N8pSOok2OF+P9vmm9NCumTI=,tag:dTCfh+KB1iRJBVoNsGqvuw==,type:str]
forgejo: forgejo:
#ENC[AES256_GCM,data:/wtm0uXbiWFoGNWtlTzVuNxBR7CPm2FMB98t3AxSj5V5rltLvzF9BjgWoJCiX3ltzmU=,iv:xaZXbUIGJHxPrLRQzEQI7hgRgc0y061jIhoE3zlcMaA=,tag:fQGheCIdBKm6wE+vtj7g6A==,type:comment] #ENC[AES256_GCM,data:/wtm0uXbiWFoGNWtlTzVuNxBR7CPm2FMB98t3AxSj5V5rltLvzF9BjgWoJCiX3ltzmU=,iv:xaZXbUIGJHxPrLRQzEQI7hgRgc0y061jIhoE3zlcMaA=,tag:fQGheCIdBKm6wE+vtj7g6A==,type:comment]
secret_key: ENC[AES256_GCM,data:/jcyeDcsryLqu9Q3VnNaENb71/Tl5JUr0zDzxt8L5UCtnJ/YHA6MKItrQ9ZHFv1XROtnSfZl9D5kKomeM8EVqA==,iv:HxMKAMVQ08gkq6SWENj0/d8i9PhcgPCp5eqbztj9bSg=,tag:nO2dFWT/vfgGc/9JIDZrGw==,type:str] secret_key: ENC[AES256_GCM,data:/jcyeDcsryLqu9Q3VnNaENb71/Tl5JUr0zDzxt8L5UCtnJ/YHA6MKItrQ9ZHFv1XROtnSfZl9D5kKomeM8EVqA==,iv:HxMKAMVQ08gkq6SWENj0/d8i9PhcgPCp5eqbztj9bSg=,tag:nO2dFWT/vfgGc/9JIDZrGw==,type:str]
@@ -11,33 +9,33 @@ forgejo:
restic_password: ENC[AES256_GCM,data:u7QOZXJkxVG4J75K5nphb2uJGdz6jbWuVSsKKu+41fshp7cVoRijtr/Cs02LjVse,iv:bt1W2FeBTG6ypBFYzMPXPIkYTSn0uHURY2ui6MRgYY8=,tag:DObAMws/zQcM+UKUe9EECA==,type:str] restic_password: ENC[AES256_GCM,data:u7QOZXJkxVG4J75K5nphb2uJGdz6jbWuVSsKKu+41fshp7cVoRijtr/Cs02LjVse,iv:bt1W2FeBTG6ypBFYzMPXPIkYTSn0uHURY2ui6MRgYY8=,tag:DObAMws/zQcM+UKUe9EECA==,type:str]
sops: sops:
age: age:
- recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt - enc: |
enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5QThHOFdVQ0F6ZDE3QXdL YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLM3NML2ozWExSTlY3ZXJH
QVF0WW5yRkNtNXIyRm1LeEhwY2dRRWo0WENZCmZoWmlXZDh4ZGlUR2JYOUxuaVAx ZEpaN1RIOWVjNXRRQTlLQVZKTUUvZnBxREd3Cmp6b29aRDlUdDFwMVhwS2thODNO
WjhXVHBMNWdrdkgvTENJR2RpWldkNTAKLS0tIEF1bS9JSnFTbytBa1U5RjVzWkd3 QzNWSVIrak1uMGRuUlBONkR2aW1nbXMKLS0tIDh2YmFaRXBvZ0Q2blhjTVphL3J3
S0pKVVI2RFN5a1BMYXBEY3VOUUM5QTAKNarYZm9DKRQhosSJBn9yryFxDkmFTV/o TkROaHdwWWN4YnkyczZLbDJYRTNrQTQKvHSXjP9EZkq9DrPZZHTIlFrPUhez6jZT
i8b/tZ1ZybjEXX+X1EsgylM5u8iKkbEyUzqKO0E0gpg4qXSsJaMMYQ== LJvkmQgKluer7+lWr8XuuqtQsvhtef1di82SBG6C6YK0zWIxrMjFkA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1h0prahyukq4l564yqwgcpg3g6gdrjflk0suklussjjrjstxd9uesws8633 recipient: age1p4gyh5260ewp7t2ctzv5ewj4a06szl389fm4gzy6vltxhjj73ers87u33g
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhdEN2M1lkMFoyQzg3bCsz YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwWThXZGtFSjFZVU1UWG9v
cVB0NUlkV1JPRjQ1TW5RSVFHUjdNMERoRXlzCk9tc25iMG00TkZjWjk2S2cyVE1J Wk9sVEVWemtVWTJ6TGlOd3ZhVUZBYnJ1OFFRClpFNmEwTG1PR0pQUFJuTTVVZGRD
cUFITDh2SkZGN0lpeWJVcWM3V3JrTTQKLS0tIENOWVBzTFYrdHRsMERwV0RKS01j bnpPdjZGVUJvS0NyWGJQbEV4cUlHZncKLS0tIDFNU1VxNjZYSks1OTMrblJPbDI1
QVI5TVl4L0dwOWpoQ1I2THZ6cWovWGMKIo1x1ZbdTyr/dNlPhvuomfk2MoPLsHyU bHIrUnhsMWNhWVpVc3hCazlKTlV5YncKgR/AbgCE9JU8U8PH3ouYTZJOqL7rRODr
N3CP3Scu3aZ7vqVua7uwtv4xQqyQI/yOnFjwxrVYPJ+N5Y/b4wsC6A== 5D2zT9pcTC3LQGZrhNtgjAi6IucU4yDAU7UhT3D7cZawwFTKVaoplw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
- recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0 recipient: age1ykcs39e62pz3xu6cedg8ea685kv5d5qsrhgkndygzm8rx30xd5ys5t3qxt
enc: | - enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGSGQvL2ZYM2dtV1JWaDR1 YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3K054SHE0V1RCYlRlTXJT
RjJnN0YwWFM4d1IwTElqUnZQQkVDTmdydUJjCjdCUWxCVDNXUHVLd2ZHcDA5L0Mw bXo4ZnE1cFozY3R2dmg5S1BacC9xb2oweWpBClJJVnkrSytFaG8xR1l4SU5FNGxi
cEVxVE84L2xmQnRoYmsxVEdMSEZjdGcKLS0tIHViRVJyTWt4Q3JrMWtYaS9QdHlS UndnNzYwMUlXSjRYK3c3MzI4NmRVRVEKLS0tIFQ4NFdPeGI4LytWK0JPcUV4VjM2
Yjd0MUcxcExvWVpCOUR3MkdZdGQyWUkKnru0Y2A98+0Mps7EtVK7ct3vPqIGveUt b1dtYWdFTTFQcW5PQml2OWs1Z0VZOFUKrDX7y3cU8KPRLKlO/HI/jdynqwO1TAS0
E5fzpcKvdefzObrx7BPTwJ19t2fZg/dSi7HKwx3vmKZSzyQaqJOzsg== WY4F0qFKmWwsJJjbnjdpYYP3O1/8RNeXjnUg8fGYY75sf7iAYWSe/w==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
recipient: age1gvplss0ddmyf6vpjy363wu3n057vhm0j6n7tc94cxd8kadapypws5mtaj0
lastmodified: "2026-04-04T23:18:43Z" lastmodified: "2026-04-04T23:18:43Z"
mac: ENC[AES256_GCM,data:qBgeli5lHb4pyA8nAADBuRBAaq8VbAIsFI37OZtgnbnoHW2crxo3YC+EknaIYnZpZ48kwVhQS5lGRjI6JsWWhTH3+LVAhTmS2Qj/pZTD/JDLK6XJGXS4U9nB7m9aGYyW8gFCy9/DfoJWGsS//+ZmUikKPfd5kMZgh1zGoYCIGug=,iv:h+2fA+bO2SMCNrEslP36x3BPRaIy25cU/DNX8CYSC6A=,tag:RvVyUZ4ONRaKaqGiT31eUQ==,type:str] mac: ENC[AES256_GCM,data:qBgeli5lHb4pyA8nAADBuRBAaq8VbAIsFI37OZtgnbnoHW2crxo3YC+EknaIYnZpZ48kwVhQS5lGRjI6JsWWhTH3+LVAhTmS2Qj/pZTD/JDLK6XJGXS4U9nB7m9aGYyW8gFCy9/DfoJWGsS//+ZmUikKPfd5kMZgh1zGoYCIGug=,iv:h+2fA+bO2SMCNrEslP36x3BPRaIy25cU/DNX8CYSC6A=,tag:RvVyUZ4ONRaKaqGiT31eUQ==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
-123
View File
@@ -1,123 +0,0 @@
{ self, inputs, lib, ... }:
let
username = "john";
hostname = "soteria";
in
{
flake.nixosConfigurations."${hostname}" = inputs.nixpkgs.lib.nixosSystem {
modules = with inputs.self.modules; [
nixos.lxc
nixos."${username}"
nixos.mysops
nixos.step-ssh-host
nixos.login-text
nixos.docker
nixos.mtls
nixos.janus-ca
nixos.forgejo
# nixos.restic-server
# nixos.restic-envoy
({ config, pkgs, ... }: {
networking.hostName = hostname;
time.timeZone = "America/Chicago";
# Removes password for sudo
security.sudo-rs.extraRules = lib.mkAfter [
{
users = [ username ];
commands = [
{
command = "ALL";
options = [ "NOPASSWD" ];
}
];
}
];
users.users."${username}".extraGroups = [ "mtls" ];
mtls = {
enable = true;
certDir = config.janus-ca.certDir;
subject = hostname;
san = [
"${hostname}.john-stream.com"
# "192.168.1.142"
"forgejo.john-stream.com"
"192.168.1.244"
];
lifetime = "12h";
renew.onCalendar = "*:3/15";
renew.reloadUnits = [ "forgejo.service" "restic-rest-server.service" ];
certReaders = [ config.services.forgejo.user "restic" ];
};
forgejo = {
enable = true;
root_url = "https://forgejo.john-stream.com";
https = true;
port = 443;
};
networking.firewall.allowedTCPPorts = [ 8000 ];
services.restic.server = {
enable = true;
privateRepos = true;
listenAddress = "0.0.0.0:8000";
extraFlags = [
"--no-auth"
"--tls"
"--tls-cert=${config.mtls.certFile}"
"--tls-key=${config.mtls.keyFile}"
];
};
loginText.extraServiceStatus = {
Docker = "docker";
"mTLS Renewal" = "mtls-renew.timer";
Forgejo = "forgejo.service";
"Forgejo Backup" = "forgejo-dump.timer";
"Restic REST Server" = "restic-rest-server.service";
};
step-ssh-host.hostname = hostname;
# This provides the secrets at install time
sops.defaultSopsFile = ./secrets.yaml;
programs.zsh.enable = true;
home-manager.users."${username}" = {
imports = with inputs.self.modules; [
homeManager."${hostname}"
];
};
environment.systemPackages = [
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.janus-ca
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.my-neovim
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.jsl-zsh
];
})
];
};
flake.modules.homeManager."${hostname}" = { config, pkgs, lib, ... }: {
imports = with inputs.self.modules; [
homeManager.rebuild
homeManager.mysops
];
homeManagerFlakeDir = "${config.xdg.configHome}/home-manager";
shell.program = "zsh";
docker.enable = true;
# This will provide the edit-secrets script targeting this file
mysops.hostSecretFile = "${config.homeManagerFlakeDir}/modules/hosts/soteria/secrets.yaml";
};
flake.homeConfigurations."${hostname}" = inputs.home-manager.lib.homeManagerConfiguration {
pkgs = import inputs.nixpkgs { system = "x86_64-linux"; };
modules = with inputs.self.modules; [
homeManager."${username}"
homeManager."${hostname}"
];
};
}
-30
View File
@@ -1,30 +0,0 @@
{ inputs, ... }:
let
username = "john";
hostname = "test-nix";
in
{
flake.nixosConfigurations."${hostname}" = inputs.nixpkgs.lib.nixosSystem {
modules = with inputs.self.modules; [
nixos.lxc
nixos.mysops
nixos.step-ssh-host
inputs.home-manager.nixosModules.home-manager
nixos."${username}"
nixos.docker
{
home-manager.users."${username}" = {
imports = with inputs.self.modules.homeManager; [
mysops
];
shell.program = "zsh";
docker.enable = true;
ssh.matchSets = {
certs = true;
homelab = true;
};
};
}
];
};
}
+54 -38
View File
@@ -40,7 +40,6 @@
{ config, pkgs, lib, ... }: { config, pkgs, lib, ... }:
let let
flakeDir = config.homeManagerFlakeDir; flakeDir = config.homeManagerFlakeDir;
hostnameCmd = "$(${lib.getExe pkgs.hostname} -s)";
flake-parts-check = with pkgs; writeShellApplication { flake-parts-check = with pkgs; writeShellApplication {
name = "flake-parts-check"; name = "flake-parts-check";
@@ -52,22 +51,6 @@
''; '';
}; };
nhms = with pkgs; writeShellApplication {
name = "nhms";
runtimeInputs = [ coreutils hostname nh ];
text = ''
HOSTNAME=$(hostname -s)
echo "Switching to the $HOSTNAME home-manager profile"
nh home switch ${flakeDir} -c "$HOSTNAME" "$@"
'';
};
nhmu = with pkgs; writeShellApplication {
name = "nhmu";
runtimeInputs = [ nhms ];
text = ''nhms --update'';
};
test-build = with pkgs; writeShellApplication { test-build = with pkgs; writeShellApplication {
name = "test-build"; name = "test-build";
runtimeInputs = [ coreutils nix hostname ]; runtimeInputs = [ coreutils nix hostname ];
@@ -81,24 +64,6 @@
nix eval "${flakeDir}#nixosConfigurations.$HOSTNAME.config.system.build.toplevel.drvPath" nix eval "${flakeDir}#nixosConfigurations.$HOSTNAME.config.system.build.toplevel.drvPath"
''; '';
}; };
cleanup = with pkgs; writeShellApplication {
name = "cleanup";
runtimeInputs = [ coreutils home-manager nix ];
text = ''
set -e
DAYS=$1
if [ -z "$DAYS" ]; then
echo "usage: cleanup <days>"
exit 1
fi
home-manager expire-generations "-$DAYS days"
nix profile wipe-history --older-than "''${DAYS}d"
nix store gc
nix store optimise
'';
};
in in
{ {
options = { options = {
@@ -120,13 +85,64 @@
name = "build-tools"; name = "build-tools";
paths = [ paths = [
flake-parts-check flake-parts-check
nhms
nhmu
test-build test-build
cleanup (inputs.self.wrappers.home-switch.apply {
inherit pkgs flakeDir;
}).wrapper
(inputs.self.wrappers.home-switch.apply {
binName = lib.mkForce "nhmu";
inherit pkgs flakeDir;
extraOptions = [ "--update" ];
}).wrapper
(inputs.wrappers.lib.wrapPackage {
binName = "cleanup";
inherit pkgs;
package = nh;
args = [ "clean" "user" "--keep-since" "3days" ];
})
]; ];
}) })
]; ];
}; };
}; };
flake.wrappers.home-switch = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
flakeDir = lib.mkOption {
type = lib.types.str;
};
user = lib.mkOption {
type = lib.types.str;
default = "$(whoami)";
};
hostname = lib.mkOption {
type = lib.types.str;
default = "$(hostname -s)";
};
configuration = lib.mkOption {
type = lib.types.str;
default = "${config.user}@${config.hostname}";
};
extraOptions = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
config = {
binName = "nhms";
extraPackages = with config.pkgs; [ coreutils hostname nh ];
preHook = ''
CONFIG=${config.configuration}
echo "Switching to $CONFIG from ${config.flakeDir}"
'';
package = config.pkgs.nh;
args = [
"home" "switch"
"--configuration" "${config.configuration}"
"$@"
"${config.flakeDir}"
] ++ config.extraOptions;
};
});
} }
+15 -3
View File
@@ -14,17 +14,27 @@
inputs.home-manager.nixosModules.home-manager inputs.home-manager.nixosModules.home-manager
]; ];
environment.shells = [
"${lib.getExe pkgs.zsh}"
"${lib.getExe inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.jsl-zsh}"
];
users.groups."${username}" = {};
users.users."${username}" = { users.users."${username}" = {
isNormalUser = true; isNormalUser = true;
group = username;
home = "/home/${username}"; home = "/home/${username}";
shell = lib.mkIf config.programs.zsh.enable pkgs.zsh; shell = pkgs.zsh;
extraGroups = [ "input" "networkmanager" ] extraGroups = [ "input" "networkmanager" "video" "render" ]
++ lib.optional isAdmin "wheel" ++ lib.optional isAdmin "wheel"
++ lib.optional config.virtualisation.docker.enable "docker" ++ lib.optional config.virtualisation.docker.enable "docker"
++ lib.optional (isAdmin && config.services.forgejo.enable) config.services.forgejo.group ++ lib.optional (isAdmin && config.services.forgejo.enable) config.services.forgejo.group
++ lib.optional (isAdmin && config.services.postgresql.enable) "postgres"; ++ lib.optional (isAdmin && config.services.postgresql.enable) "postgres";
}; };
programs.zsh.enable = true;
security.sudo-rs.enable = lib.mkIf isAdmin true; security.sudo-rs.enable = lib.mkIf isAdmin true;
home-manager.useGlobalPkgs = true; home-manager.useGlobalPkgs = true;
@@ -33,7 +43,9 @@
imports = [ self.modules.homeManager."${username}" ]; imports = [ self.modules.homeManager."${username}" ];
home.username = "${username}"; home.username = "${username}";
home.homeDirectory = "/home/${username}"; home.homeDirectory = "/home/${username}";
# home.packages = homePackages; home.packages = with pkgs; [
# fzf zoxide starship
];
}; };
}; };
}; };
+10 -1
View File
@@ -1,5 +1,5 @@
{ self, inputs, ... }: { { self, inputs, ... }: {
flake-file.inputs = { config.flake-file.inputs = {
wrapper-modules = { wrapper-modules = {
url = "github:BirdeeHub/nix-wrapper-modules"; url = "github:BirdeeHub/nix-wrapper-modules";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -9,4 +9,13 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
options = {
# This is what allows wrappers to be defined in flake.wrappers.<wrapper-name> throughout different flake-parts modules
flake = inputs.flake-parts.lib.mkSubmoduleOptions {
wrappers = inputs.nixpkgs.lib.mkOption {
default = {};
};
};
};
} }
+2 -2
View File
@@ -2,8 +2,8 @@
flake.modules.nixos.login-text = { config, lib, ... }: flake.modules.nixos.login-text = { config, lib, ... }:
let let
defaultServiceStatus = { defaultServiceStatus = {
SSH = "sshd.socket"; "SSH Socket" = "sshd.socket";
"SSH Cert Renewal" = "step-ssh-host-renew.timer"; "SSH Cert Renewal" = "ssh-certs-renew.timer";
}; };
in { in {
options.loginText.extraServiceStatus = lib.mkOption { options.loginText.extraServiceStatus = lib.mkOption {
+16 -2
View File
@@ -1,13 +1,27 @@
{ inputs, ... }: { inputs, ... }:
{ {
flake.modules.nixos.lxc = { pkgs, lib, ...}: { flake.modules.nixos.lxc = { pkgs, lib, ...}:
let
selfPackages = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
rootShellPath = lib.getExe' selfPackages.jsl-zsh-tty "jsl-zsh-tty";
in
{
imports = with inputs.self.modules.nixos; [ imports = with inputs.self.modules.nixos; [
({ modulesPath, ... }: { imports = [ "${modulesPath}/virtualisation/proxmox-lxc.nix" ]; }) ({ modulesPath, ... }: { imports = [ "${modulesPath}/virtualisation/proxmox-lxc.nix" ]; })
]; ];
nixpkgs.hostPlatform = lib.mkForce "x86_64-linux"; nixpkgs.hostPlatform = lib.mkForce "x86_64-linux";
system.stateVersion = "25.11"; system.stateVersion = "25.11";
time.timeZone = "US/Central";
nix.settings.experimental-features = [ "nix-command" "flakes" ]; nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [ git zsh ]; environment.systemPackages = with pkgs; [
git
selfPackages.jsl-zsh-tty
];
environment.shells = lib.mkAfter [ rootShellPath ];
users.users.root.shell = lib.mkForce rootShellPath;
networking.nameservers = [ "192.168.1.150" ];
networking.dhcpcd.extraConfig = "nohook resolv.conf";
# security.sudo-rs.enable = true; # security.sudo-rs.enable = true;
programs.nix-ld.enable = true; programs.nix-ld.enable = true;
+1 -2
View File
@@ -1,10 +1,9 @@
{ {
flake.modules.homeManager.bash = { pkgs, lib, config, ... }: flake.modules.homeManager.bash = { pkgs, lib, config, ... }:
{ {
programs.bash = lib.mkIf (config.shell.program == "bash") { programs.bash = {
enable = true; enable = true;
enableCompletion = true; enableCompletion = true;
package = pkgs.bash;
}; };
}; };
} }
+12 -4
View File
@@ -5,19 +5,27 @@
enable = true; enable = true;
enableBashIntegration = true; enableBashIntegration = true;
enableZshIntegration = true; enableZshIntegration = true;
git = true; package = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.my-eza;
icons = "auto"; };
colors = "auto"; };
extraOptions = [
perSystem = { system, pkgs, ... }: {
packages.my-eza = inputs.wrappers.lib.wrapPackage {
inherit pkgs;
package = pkgs.eza;
args = [
"--all" "--all"
"--long" "--long"
"--group-directories-first" "--group-directories-first"
"--icons=auto"
"--color=auto"
"--sort=type" "--sort=type"
"--dereference" "--dereference"
"--octal-permissions" "--octal-permissions"
"--smart-group" "--smart-group"
"--no-time" "--no-time"
"--git" "--git"
"$@"
]; ];
}; };
}; };
-7
View File
@@ -14,12 +14,5 @@
ignorecase = true; ignorecase = true;
}; };
}; };
home.packages = with pkgs; [
gdu
# (writeShellScriptBin "lfcd" ''
# . <(${lib.getExe pkgs.lf} -print-last-dir | sed 's/^/cd /')
# '')
];
}; };
} }
+20 -4
View File
@@ -9,7 +9,7 @@
flake.modules.homeManager.ghostty = { config, pkgs, lib, ... }: flake.modules.homeManager.ghostty = { config, pkgs, lib, ... }:
let let
ghosttyPackage = pkgs.symlinkJoin { ghosttyX11 = pkgs.symlinkJoin {
name = "ghostty-x11"; name = "ghostty-x11";
paths = [ (config.lib.nixGL.wrap pkgs.ghostty) ]; paths = [ (config.lib.nixGL.wrap pkgs.ghostty) ];
nativeBuildInputs = [ pkgs.makeWrapper ]; nativeBuildInputs = [ pkgs.makeWrapper ];
@@ -24,6 +24,10 @@
TERMINAL = "ghostty"; TERMINAL = "ghostty";
}; };
home.packages = with pkgs; [
inputs.self.packages.${system}.ghosttyGPUCheck
];
targets.genericLinux.nixGL = { targets.genericLinux.nixGL = {
packages = inputs.nixgl.packages.${pkgs.stdenv.hostPlatform.system}; packages = inputs.nixgl.packages.${pkgs.stdenv.hostPlatform.system};
defaultWrapper = "mesa"; defaultWrapper = "mesa";
@@ -33,9 +37,9 @@
programs.ghostty = { programs.ghostty = {
enable = true; enable = true;
enableZshIntegration = true; enableZshIntegration = true;
package = ghosttyPackage; package = ghosttyX11;
settings = { settings = {
command = "TERM=xterm-256color ${lib.getExe pkgs.zsh}"; command = "TERM=xterm-256color ${lib.getExe inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.jsl-zsh}";
font-size = 12; font-size = 12;
font-family = "Source Code Pro"; font-family = "Source Code Pro";
theme = "Catppuccin Mocha"; theme = "Catppuccin Mocha";
@@ -72,7 +76,7 @@
# https://github.com/ghostty-org/ghostty/discussions/3763#discussioncomment-11699970 # https://github.com/ghostty-org/ghostty/discussions/3763#discussioncomment-11699970
xdg.desktopEntries."com.mitchellh.ghostty" = xdg.desktopEntries."com.mitchellh.ghostty" =
let let
ghosttyCmd = lib.getExe ghosttyPackage; ghosttyCmd = lib.getExe ghosttyX11;
in in
{ {
name = "Ghostty"; name = "Ghostty";
@@ -100,4 +104,16 @@
}; };
}; };
}; };
perSystem = { system, pkgs, lib, ... }: {
packages.ghosttyGPUCheck = pkgs.writeShellApplication {
name = "ghostty-gpu-check";
runtimeInputs = with pkgs; [
bash
gnugrep
procps # for pgrep, pidof, etc.
];
text = builtins.readFile ../../scripts/ghostty-gpu-check.sh;
};
};
} }
+105 -80
View File
@@ -6,12 +6,9 @@
}; };
}; };
perSystem = { system, pkgs, config, ... }: { perSystem = { system, pkgs, config, ... }:
packages.my-neovim = ((inputs.nvf.lib.neovimConfiguration { let
inherit pkgs; commonNeovimModule = {
modules = [
{
# https://nvf.notashelf.dev/search.html
config.vim = { config.vim = {
options = { options = {
number = true; number = true;
@@ -27,7 +24,6 @@
syntaxHighlighting = true; syntaxHighlighting = true;
# Enable custom theming options
theme.enable = true; theme.enable = true;
theme.name = "catppuccin"; theme.name = "catppuccin";
theme.style = "mocha"; theme.style = "mocha";
@@ -36,81 +32,63 @@
# git.neogit.enable = true; # git.neogit.enable = true;
# https://github.com/akinsho/toggleterm.nvim # https://github.com/akinsho/toggleterm.nvim
terminal.toggleterm.enable = true; terminal.toggleterm = {
terminal.toggleterm.lazygit.enable = true;
terminal.toggleterm.lazygit.direction = "float";
terminal.toggleterm.lazygit.mappings.open = "<C-g>";
utility.nix-develop.enable = true;
utility.oil-nvim.enable = true;
utility.oil-nvim.gitStatus.enable = true;
filetree.neo-tree = {
enable = true; enable = true;
}; lazygit = {
# lazy = {
# enable = true;
# };
# globals = {
# SimpylFold_docstring_preview = 1;
# SimpylFold_fold_blank = 0;
# };
# extraPlugins = with pkgs.vimPlugins; {
# SimpylFold.package = SimpylFold;
# };
telescope = {
enable = true; enable = true;
extensions = [ package = pkgs.lazygit;
{ direction = "float";
name = "fzf"; mappings.open = "<C-g>";
packages = [pkgs.vimPlugins.telescope-fzf-native-nvim];
setup = {fzf = {fuzzy = true;};};
}
];
}; };
# Enable Treesitter
treesitter = {
enable = true;
grammars = with pkgs.vimPlugins.nvim-treesitter-parsers; [ python ];
}; };
lsp.enable = true;
languages = { languages = {
enableTreesitter = true; enableTreesitter = true;
enableFormat = true; enableFormat = true;
markdown = { nix.enable = true;
enable = true;
extensions = {
# render-markdown-nvim.enable = true;
markview-nvim.enable = true;
};
};
bash.enable = true;
css.enable = true;
yaml.enable = true; yaml.enable = true;
toml.enable = true; toml.enable = true;
};
};
};
nix = { telescopeModule = {
config.vim = {
telescope = {
enable = true; enable = true;
extensions = [
{
name = "fzf";
packages = [ pkgs.vimPlugins.telescope-fzf-native-nvim ];
setup = {
fzf.fuzzy = true;
};
}
];
}; };
python = { keymaps = [
enable = true; {
dap.enable = true; desc = "Key Maps [Telescope]";
format.type = [ "ruff" ]; key = "<leader>fkm";
mode = "n";
silent = false;
action = "<cmd>:Telescope keymaps<CR>";
}
{
desc = "Toggle Filesystem Tree [NeoTree]";
key = "<C-b>";
mode = [ "n" "v" "t" ];
silent = false;
action = "<cmd>:Neotree toggle filesystem left action=show<CR>";
}
];
}; };
}; };
keymapsModule = {
config.vim = {
keymaps = [ keymaps = [
{ {
desc = "Edit key mappings"; desc = "Edit key mappings";
@@ -126,33 +104,80 @@
silent = false; silent = false;
action = "<cmd>:TermExec cmd='clear && nhms && exit' name='Nix Home Manager Switch' direction=float<CR>"; action = "<cmd>:TermExec cmd='clear && nhms && exit' name='Nix Home Manager Switch' direction=float<CR>";
} }
{
desc = "Key Maps [Telescope]";
key = "<leader>fkm";
mode = "n";
silent = false;
action = "<cmd>:Telescope keymaps<CR>";
}
{
desc = "Toggle Filesystem Tree [NeoTree]";
key = "<C-b>";
mode = [ "n" "v" "t" ];
silent = false;
action = "<cmd>:Neotree toggle filesystem left action=show<CR>";
}
{ {
key = "<C-`>"; key = "<C-`>";
mode = ["n" "v" "t"]; mode = [ "n" "v" "t" ];
silent = false; silent = false;
action = "<cmd>:ToggleTerm<CR>"; action = "<cmd>:ToggleTerm<CR>";
} }
]; ];
}; };
};
in {
packages.my-neovim = ((inputs.nvf.lib.neovimConfiguration {
inherit pkgs;
modules = [
commonNeovimModule
telescopeModule
keymapsModule
{
# https://nvf.notashelf.dev/search.html
config.vim = {
utility.nix-develop.enable = true;
utility.oil-nvim.enable = true;
utility.oil-nvim.gitStatus.enable = true;
filetree.neo-tree = {
enable = true;
};
# Enable Treesitter
treesitter = {
enable = true;
grammars = with pkgs.vimPlugins.nvim-treesitter-parsers; [ python ];
};
lsp.enable = true;
languages = {
markdown = {
enable = true;
extensions = {
# render-markdown-nvim.enable = true;
markview-nvim.enable = true;
};
};
bash.enable = true;
css.enable = true;
yaml.enable = true;
toml.enable = true;
python = {
enable = true;
dap.enable = true;
format.type = [ "ruff" ];
};
};
};
} }
]; ];
}).neovim).overrideAttrs (old: { }).neovim).overrideAttrs (old: {
pname = "my-neovim"; pname = "my-neovim";
version = "custom"; version = "1.0.0";
});
packages.neovim-min = ((inputs.nvf.lib.neovimConfiguration {
inherit pkgs;
modules = [
commonNeovimModule
telescopeModule
keymapsModule
];
}).neovim).overrideAttrs (old: {
pname = "neovim-min";
version = "1.0.0";
}); });
}; };
} }
-28
View File
@@ -1,28 +0,0 @@
{ self, inputs, ... }: {
flake.modules.nixos.niri = { pkgs, lib, ... }: {
programs.niri = {
enable = true;
package = self.packages.${pkgs.stdenv.hostPlatform.system}.myNiri;
};
};
perSystem = { pkgs, lib, self', ... }: {
packages.myNiri = inputs.wrapper-modules.wrappers.niri.wrap {
inherit pkgs;
env.RUST_BACKTRACE = "full";
settings = {
spawn-at-startup = [
"${lib.getExe self'.packages.myNoctalia}"
];
xwayland-satellite.path = lib.getExe pkgs.xwayland-satellite;
input.keyboard.xkb.layout = "us,ua";
layout.gaps = 5;
binds = {
"Mod+Return".spawn-sh = lib.getExe pkgs.ghostty;
"Mod+Q".close-window = null;
"Mod+S".spawn-sh = "${lib.getExe self'.packages.myNoctalia} ipc call launcher toggle";
};
};
};
};
}
-8
View File
@@ -1,8 +0,0 @@
{ self, inputs, ... }: {
perSystem = { pkgs, ... }: {
packages.myNoctalia = inputs.wrapper-modules.wrappers.noctalia-shell.wrap {
inherit pkgs;
# settings = (builtins.fromJSON (builtins.readFile ./noctalia.json)).settings;
};
};
}
+15 -1
View File
@@ -1,10 +1,24 @@
{ self, inputs, ... }: { { self, inputs, ... }: {
flake.modules.nixos.onepassword = { config, ... }: {
programs._1password.enable = true;
programs._1password-gui = {
enable = true;
# Certain features, including CLI integration and system authentication support,
# require enabling PolKit integration on some desktop environments (e.g. Plasma).
polkitPolicyOwners = [ "john" ];
# TODO this should not be a hardcoded username
};
};
flake.modules.homeManager.onepassword = { config, ... }: { flake.modules.homeManager.onepassword = { config, ... }: {
home.file.".config/1Password/ssh/agent.toml".text = '' home.file.".config/1Password/ssh/agent.toml".text = ''
# https://developer.1password.com/docs/ssh/agent/config # https://developer.1password.com/docs/ssh/agent/config
[[ssh-keys]] [[ssh-keys]]
vault = "Private" vault = "Private"
''; '';
programs.ssh.matchBlocks."*".identityAgent = "${config.home.homeDirectory}/.1password/agent.sock";
programs.ssh.settings."*" = {
IdentityAgent = "${config.home.homeDirectory}/.1password/agent.sock";
};
}; };
} }
+54 -53
View File
@@ -1,4 +1,4 @@
{ inputs, ... }: { self, inputs, ... }:
let let
inputs' = inputs; # save a reference before it's shadowed inputs' = inputs; # save a reference before it's shadowed
in in
@@ -14,18 +14,10 @@ in
imports = [ inputs.sops-nix.nixosModules.sops ]; imports = [ inputs.sops-nix.nixosModules.sops ];
}; };
# Define the homeModules that are used by flake-parts flake.modules.homeManager.mysops =
# https://flake.parts/options/home-manager.html#opt-flake.modules.homeManager { config, pkgs, lib, ... }:
flake.modules.homeManager.mysops = { inputs, config, pkgs, lib, ... }:
let let
cfg = config.mysops; cfg = config.mysops;
sopsBin = lib.getExe pkgs.sops;
sopsConfigPath = ../../.sops.yaml;
sopsSecretsPath = ../../keys/secrets.yaml;
editScript = lib.optional (cfg.hostSecretFile != null) (pkgs.writeShellScriptBin "edit-secrets" ''
${sopsBin} --config ${sopsConfigPath} ${cfg.hostSecretFile}
'');
in in
{ {
imports = [ imports = [
@@ -34,11 +26,6 @@ in
]; ];
options.mysops = { options.mysops = {
ageKeyFile = lib.mkOption {
description = "Default location for the age key";
type = lib.types.str;
default = "${config.xdg.configHome}/sops/age/keys.txt";
};
hostSecretFile = lib.mkOption { hostSecretFile = lib.mkOption {
description = "Path to the secrets file for this host. Used to create the edit-secrets script"; description = "Path to the secrets file for this host. Used to create the edit-secrets script";
type = lib.types.nullOr lib.types.str; type = lib.types.nullOr lib.types.str;
@@ -48,50 +35,64 @@ in
config = config =
let let
echo = lib.getExe' pkgs.coreutils "echo"; identityFile = config.ssh-new.keyFile;
dirname = lib.getExe' pkgs.coreutils "dirname"; my-sops = (inputs.self.wrappers.mySops.apply {
mkdir = lib.getExe' pkgs.coreutils "mkdir"; inherit pkgs;
show-age-key = (pkgs.writeShellScriptBin "show-age-key" '' sshKey = identityFile;
${lib.getExe' pkgs.age "age-keygen"} -y ${cfg.ageKeyFile} }).wrapper;
'');
in in
{ {
home.packages = with pkgs; [
eza
age
sops # This is necessary to make the sops binary available
ssh-to-age
(writeShellScriptBin "gen-age-key" ''
set -eu
if [ ! -f "${config.ssh.identityFile}" ]; then
${echo} "SSH identity file not found: ${config.ssh.identityFile}" >&2
exit 1
fi
if [ -e "${cfg.ageKeyFile}" ]; then
${echo} "Refusing to overwrite existing age key file: ${cfg.ageKeyFile}" >&2
exit 1
fi
${mkdir} -p "$(${dirname} "${cfg.ageKeyFile}")"
${lib.getExe pkgs.ssh-to-age} -i ${config.ssh.identityFile} -private-key > ${cfg.ageKeyFile}
${echo} -n "Created ${cfg.ageKeyFile}: "
${echo} $(${lib.getExe show-age-key})
'')
show-age-key
(writeShellScriptBin "ls-secrets" "${lib.getExe pkgs.eza} -alT --follow-symlinks ~/.config/sops-nix/secrets")
] ++ editScript;
home.shellAliases.sops = "${sopsBin} --config ${sopsConfigPath}";
# Option definitions for the sops home-manager module: # Option definitions for the sops home-manager module:
# https://github.com/Mic92/sops-nix/blob/master/modules/home-manager/sops.nix # https://github.com/Mic92/sops-nix/blob/master/modules/home-manager/sops.nix
sops = { sops = {
defaultSopsFile = sopsSecretsPath; defaultSopsFile = ../../keys/secrets.yaml;
defaultSopsFormat = "yaml"; defaultSopsFormat = "yaml";
age.sshKeyPaths = [ "${config.ssh.identityFile}" ]; age.sshKeyPaths = [ identityFile ];
};
home.packages = with pkgs; [
my-sops
(inputs.wrappers.lib.wrapPackage {
binName = "ls-secrets";
inherit pkgs;
package = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.my-eza;
args = [
"-T" "--follow-symlinks"
"${config.xdg.configHome}/sops-nix/secrets"
];
})
]
++ lib.optional (cfg.hostSecretFile != null) (inputs.wrappers.lib.wrapPackage {
binName = "edit-secrets";
inherit pkgs;
package = my-sops;
args = [ cfg.hostSecretFile ];
});
}; };
}; };
flake.wrappers.mySops = inputs.wrappers.lib.wrapModule ({config, lib, wlib, ... }: {
options = {
sshKey = lib.mkOption {
type = lib.types.str;
description = "String path to the SSH key to use for creating an AGE key at runtime";
}; };
};
config = {
# binName = "my-sops";
package = config.pkgs.sops;
extraPackages = with config.pkgs; [ coreutils ssh-to-age ];
preHook = ''
AGE_KEY=$(umask 077; mktemp)
ssh-to-age -private-key -i ${config.sshKey} > "$AGE_KEY"
'';
flags."--config" = "${../../.sops.yaml}";
postHook = ''
rm "$AGE_KEY"
echo "Removed $AGE_KEY"
'';
};
});
} }
+114
View File
@@ -0,0 +1,114 @@
{ self, inputs, ... }: {
perSystem = { system, pkgs, lib, ... }: {
packages.starship = (inputs.wrappers.wrapperModules.starship.apply {
inherit pkgs;
settings = lib.recursiveUpdate (lib.importTOML (pkgs.fetchurl {
url = https://starship.rs/presets/toml/catppuccin-powerline.toml;
sha256 = "1jzbbzm3nwdlldq43aj3csp0ps23fsa6x2225z85l0s9qbj4cdy2";
})) {
palette = "catppuccin_mocha";
add_newline = true;
line_break.disabled = false;
git_status.diverged = "\${ahead_count}\${behind_count}";
cmd_duration.format = "󰔛 $duration";
cmd_duration.show_notifications = false;
hostname = {
disabled = false;
ssh_symbol = "🌐";
format = "[$ssh_symbol$hostname]($style)";
style = "bg:red fg:crust";
};
os.symbols.NixOS = " ";
format = lib.replaceStrings ["\n"] [""] ''
[](red)
$os
$username
$hostname
[](bg:peach fg:red)
$directory
[](bg:yellow fg:peach)
$git_branch
$git_status
[](fg:yellow bg:green)
$c
$rust
$golang
$nodejs
$php
$java
$kotlin
$haskell
$python
[](fg:green bg:sapphire)
$conda
[](fg:sapphire bg:lavender)
$time
[ ](fg:lavender)
$cmd_duration
$line_break
$character
'';
};
}).wrapper;
packages.starship-ascii = (inputs.wrappers.wrapperModules.starship.apply {
inherit pkgs;
settings = {
add_newline = false;
format = "$username$hostname$directory$git_branch$git_status$cmd_duration$line_break$character";
username = {
show_always = true;
format = "[$user]($style)";
style_user = "bold blue";
style_root = "bold red";
};
hostname = {
disabled = false;
ssh_only = false;
format = "[@$hostname]($style) ";
style = "bold blue";
};
directory = {
truncation_length = 3;
truncate_to_repo = true;
format = "[ in $path]($style) ";
style = "bold cyan";
};
git_branch = {
symbol = "on ";
format = "[$symbol$branch]($style) ";
style = "bold yellow";
};
git_status = {
format = "([$all_status$ahead_behind]($style) )";
style = "bold red";
ahead = "ahead:$count ";
behind = "behind:$count ";
diverged = "diverged:$ahead_count/$behind_count ";
up_to_date = "";
};
cmd_duration = {
min_time = 2000;
show_notifications = false;
format = "took [$duration]($style) ";
style = "bold green";
};
line_break.disabled = false;
character = {
success_symbol = "[\\$](bold green) ";
error_symbol = "[\\$](bold red) ";
vicmd_symbol = "[\\$](bold yellow) ";
};
};
}).wrapper;
};
}
-37
View File
@@ -1,37 +0,0 @@
{ self, inputs, ... }: {
#
# Home Manager Module
#
flake.modules.homeManager.step-ssh-user = { config, pkgs, lib, ... }:
let
cfg = config.step-ssh-user;
firstPrincipal = lib.head cfg.principals;
principalArgs = lib.concatMapStringsSep " "
(principal: "--principal \"${principal}\"") cfg.principals;
in
{
options.step-ssh-user = {
enable = lib.mkEnableOption "opionated step client config for SSH certs";
provisioner = lib.mkOption {
type = lib.types.str;
default = "admin";
};
principals = lib.mkOption {
type = lib.types.listOf lib.types.str;
};
};
config = lib.mkIf cfg.enable {
sops.secrets."janus/admin_jwk".mode = "0400";
home.packages = with pkgs; [
(writeShellScriptBin "sign-ssh-cert" ''
${lib.getExe pkgs.step-cli} ssh certificate \
--sign \
${principalArgs} \
--provisioner "${cfg.provisioner}" \
--provisioner-password-file "${config.sops.secrets."janus/admin_jwk".path}" \
"${firstPrincipal}" "${config.ssh.identityFile}.pub"
'')
];
};
};
}
+37 -28
View File
@@ -2,39 +2,13 @@
flake.modules.nixos.wireguard = { config, pkgs, lib, ... }: flake.modules.nixos.wireguard = { config, pkgs, lib, ... }:
let let
wgInterface = "platform"; wgInterface = "platform";
systemctl = lib.getExe' pkgs.systemd "systemctl";
journalctl = lib.getExe' pkgs.systemd "journalctl";
mkConnect = interface:
let
serviceName = "wg-quick-${interface}";
service = "${serviceName}.service";
in
pkgs.writeShellScriptBin "wg-connect-${interface}" ''
${systemctl} start ${service}
start_time=$(${systemctl} show -p ActiveEnterTimestamp ${serviceName} | cut -d= -f2)
${journalctl} -u ${service} --since "$start_time" --no-pager
'';
mkDisconnect = interface:
let
serviceName = "wg-quick-${interface}";
service = "${serviceName}.service";
in
pkgs.writeShellScriptBin "wg-disconnect-${interface}" ''
STOPTIME=$(${lib.getExe' pkgs.coreutils "date"} '+%Y-%m-%d %H:%M:%S')
${systemctl} stop ${service}
start_time=$(${systemctl} show -p ActiveEnterTimestamp ${serviceName} | cut -d= -f2)
${journalctl} -u ${service} --since "$STOPTIME" --no-pager
'';
in in
{ {
imports = [ inputs.sops-nix.nixosModules.sops ]; imports = [ inputs.sops-nix.nixosModules.sops ];
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
wireguard-tools wireguard-tools # https://github.com/WireGuard/wireguard-tools
wg-netmanager # wg-netmanager # https://github.com/gin66/wg_netmanager
(mkConnect "platform")
(mkDisconnect "platform")
]; ];
sops.secrets.wireguard_private_key = { }; sops.secrets.wireguard_private_key = { };
@@ -56,4 +30,39 @@
}; };
}; };
}; };
perSystem = { system, pkgs, lib, ... }:
let
connect = pkgs.writeShellApplication {
name = "wg-platform-connect";
text = ''
sudo systemctl start wg-quick-platform.service
START_TIME=$(sudo systemctl show -p ActiveEnterTimestamp wg-quick-platform | cut -d= -f2)
journalctl -u wg-quick-platform --since "$START_TIME" --no-pager
'';
};
disconnect = pkgs.writeShellApplication {
name = "wg-platform-disconnect";
text = ''
STOP_TIME=$(date '+%Y-%m-%d %H:%M:%S')
systemctl stop wg-quick-platform.service
journalctl -u wg-quick-platform.service --since "$STOP_TIME" --no-pager
'';
};
in
{
packages.wg-platform = inputs.wrappers.lib.wrapPackage {
inherit pkgs;
runtimeInputs = with pkgs; [ coreutils systemd wireguard-tools ];
package = pkgs.symlinkJoin {
name = "wg-platform";
meta.mainProgram = "wg-platform-connect";
paths = [
connect
disconnect
];
};
};
};
} }
+11
View File
@@ -0,0 +1,11 @@
{ self, inputs, ... }: {
# https://github.com/Lassulus/wrappers/blob/main/modules/yazi/module.nix
perSystem = { system, pkgs, lib, ... }: {
packages.yazi = (inputs.wrappers.wrapperModules.yazi.apply {
inherit pkgs;
settings = {
mgr.ratio = [ 1 4 3 ];
};
}).wrapper;
};
}
+39
View File
@@ -0,0 +1,39 @@
{ self, ... }:
let
packageName = "zed-editor";
vulkanIcd = "/usr/share/vulkan/icd.d/nvidia_icd.json";
eglVendor = "/usr/share/glvnd/egl_vendor.d/10_nvidia.json";
in
{
perSystem = { pkgs, ... }: {
packages."${packageName}" = pkgs.symlinkJoin {
name = "zed-editor-host-gpu";
paths = [ pkgs.zed-editor ];
nativeBuildInputs = [ pkgs.makeWrapper ];
meta = pkgs.zed-editor.meta // {
mainProgram = "zeditor";
};
postBuild = ''
for exe in $out/bin/*; do
wrapProgram "$exe" \
--unset WAYLAND_DISPLAY \
--unset GDK_BACKEND \
--set VK_DRIVER_FILES ${vulkanIcd} \
--set VK_ICD_FILENAMES ${vulkanIcd} \
--set __EGL_VENDOR_LIBRARY_FILENAMES ${eglVendor} \
--set __GLX_VENDOR_LIBRARY_NAME nvidia
done
'';
};
};
flake.modules.homeManager.zed-editor = { pkgs, ... }: {
home.packages = [ pkgs.vulkan-tools ];
programs.zed-editor = {
enable = true;
package = self.packages.${pkgs.stdenv.hostPlatform.system}."${packageName}";
installRemoteServer = true;
};
};
}
+113 -163
View File
@@ -1,188 +1,138 @@
{ self, inputs, ... }: { self, inputs, ... }:
let let
username = "john"; username = "john";
historySize = 10000;
homeEndKeyBindings = ''
# Normalize common Home/End escape sequences across terminal emulators.
bindkey "^[[H" beginning-of-line
bindkey "^[[F" end-of-line
bindkey "^[[1~" beginning-of-line
bindkey "^[[4~" end-of-line
bindkey "^[[7~" beginning-of-line
bindkey "^[[8~" end-of-line
bindkey "^[OH" beginning-of-line
bindkey "^[OF" end-of-line
bindkey "^[[3~" delete-char
# Normalize common Ctrl+Arrow sequences for word-wise movement.
bindkey "^[[1;5D" backward-word
bindkey "^[[1;5C" forward-word
bindkey "^[[5D" backward-word
bindkey "^[[5C" forward-word
'';
in in
{ {
flake.modules = { flake.modules = {
nixos.zsh = { pkgs, ... }: { nixos.zsh = { pkgs, ... }:
users.users."${username}".shell = pkgs.zsh; let
selfPackages = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
in
{
users.users."${username}".shell = selfPackages.jsl-zsh;
programs.zsh.enable = true; programs.zsh.enable = true;
# Already being imported by the john.nix module
# home-manager.sharedModules = [
# inputs.self.modules.homeManager.zsh
# ];
}; };
homeManager.zsh = { pkgs, config, ... }: { homeManager.zsh = { pkgs, config, ... }:
let
selfPackages = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
in
{
programs.zsh = { programs.zsh = {
enable = true; enable = true;
package = pkgs.zsh; package = selfPackages.jsl-zsh;
enableCompletion = true;
autosuggestion.enable = true;
# syntaxHighlighting.enable = true;
initContent = "HOST=$(hostname -s)";
dotDir = "${config.xdg.configHome}/zsh"; dotDir = "${config.xdg.configHome}/zsh";
};
};
};
perSystem = { config, self', pkgs, lib, ... }:
let
ignorePatterns = [
"ls" "eza" "history" "clear"
];
integrationPackages = with pkgs; [
fzf
zoxide
];
extraToolPackages = with pkgs; [
lazygit
lazydocker
devenv
self'.packages.shell-tools
self'.packages.neovim-min
];
aliasStr = lib.concatStringsSep "\n" (
lib.mapAttrsToList (k: v: "alias -- ${lib.escapeShellArg k}=${lib.escapeShellArg v}") {
ls = "eza";
ll = "eza -l";
la = "eza -a";
lt = "eza --tree";
lla = "eza -la";
ds = "gdu -i /snap /";
ld = "lazydocker";
});
mkJslZsh = { binName, starshipPackage }:
let
loginBootstrapPath = lib.makeBinPath (integrationPackages ++ [ starshipPackage ] ++ extraToolPackages);
in
(inputs.wrappers.wrapperModules.zsh.apply {
inherit pkgs binName;
env = {
LANG = "en_US.UTF-8";
COLORTERM = "truecolor";
DEVENV_SHELL_TYPE = "zsh";
};
settings = {
completion = {
enable = true;
extraCompletions = true;
caseInsensitive = true;
fuzzySearch = true;
};
autoSuggestions = {
enable = true;
strategy = [ "history" "completion" ];
};
history = { history = {
append = true; append = true;
ignoreAllDups = true; expanded = true;
ignorePatterns = [
"history"
"ls"
"eza"
"clear"
];
save = 1000;
size = 1000;
share = true; share = true;
}; ignoreAllDups = true;
oh-my-zsh = { ignoreSpace = true;
enable = true;
# theme = "risto";
theme = "agnoster";
plugins = [
"sudo"
"dotenv"
"git"
"ssh"
"ssh-agent"
];
};
};
}; };
}; };
extraRC = ''
${homeEndKeyBindings}
perSystem = { system, pkgs, ... }: { # Login shells may reset PATH before integrations run.
packages.jsl-zsh = inputs.wrapper-modules.wrappers.zsh.wrap { export PATH=${lib.escapeShellArg loginBootstrapPath}:$PATH
inherit pkgs;
extraPackages = with pkgs; [
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}.neovim-min
btop
coreutils
curl
wget
yazi
zsh
];
};
packages.neovim-min = ((inputs.nvf.lib.neovimConfiguration { source <(fzf --zsh)
inherit pkgs; eval "$(zoxide init zsh)"
modules = [ eval "$(starship init zsh)"
HISTFILE=$HOME/.config/zsh/.zsh_history
SAVEHIST=${toString historySize}
HISTORY_IGNORE=${lib.escapeShellArg "(${lib.concatStringsSep "|" ignorePatterns})"}
HOSTNAME=$(hostname -s)
${aliasStr}
eval "$(devenv hook zsh)"
'';
extraPackages = extraToolPackages;
}).wrapper;
in
{ {
# https://nvf.notashelf.dev/search.html packages.jsl-zsh = mkJslZsh {
config.vim = { binName = "jsl-zsh";
options = { starshipPackage = self'.packages.starship;
number = true;
relativenumber = true;
expandtab = true;
shiftwidth = 4;
tabstop = 4;
softtabstop = 4;
wrap = true;
linebreak = true;
}; };
syntaxHighlighting = true; packages.jsl-zsh-tty = mkJslZsh {
binName = "jsl-zsh-tty";
# Enable custom theming options starshipPackage = self'.packages.starship-ascii;
theme.enable = true;
theme.name = "catppuccin";
theme.style = "mocha";
git.enable = true;
# git.neogit.enable = true;
extraPlugins = with pkgs.vimPlugins; {
icons = {
package = nvim-web-devicons;
}; };
octo = {
package = octo-nvim;
setup = "require('octo').setup {}";
after = ["telescope" "icons"];
};
};
# https://github.com/akinsho/toggleterm.nvim
terminal.toggleterm.enable = true;
terminal.toggleterm.lazygit.enable = true;
terminal.toggleterm.lazygit.direction = "float";
terminal.toggleterm.lazygit.mappings.open = "<C-g>";
utility.nix-develop.enable = true;
filetree.neo-tree.enable = true;
telescope = {
enable = true;
extensions = [
{
name = "fzf";
packages = [pkgs.vimPlugins.telescope-fzf-native-nvim];
setup = {fzf = {fuzzy = true;};};
}
];
};
languages = {
enableTreesitter = true;
enableFormat = true;
markdown = {
enable = true;
extensions = {
markview-nvim.enable = true;
};
};
bash.enable = true;
yaml.enable = true;
toml.enable = true;
nix.enable = true;
};
keymaps = [
{
desc = "Edit key mappings";
key = "<leader>ekm";
mode = [ "n" ];
silent = false;
action = "<cmd>:edit +/keymaps /home/john/.config/home-manager/jsl-dendritic/modules/programs/neovim.nix<CR>";
}
{
desc = "Home Manager Switch";
key = "<leader>nhms";
mode = [ "n" ];
silent = false;
action = "<cmd>:TermExec cmd='clear && nhms && exit' name='Nix Home Manager Switch' direction=float<CR>";
}
{
desc = "Key Maps [Telescope]";
key = "<leader>fkm";
mode = "n";
silent = false;
action = "<cmd>:Telescope keymaps<CR>";
}
{
desc = "Toggle Filesystem Tree [NeoTree]";
key = "<C-b>";
mode = [ "n" "v" "t" ];
silent = false;
action = "<cmd>:Neotree toggle filesystem left action=show<CR>";
}
{
key = "<C-`>";
mode = ["n" "v" "t"];
silent = false;
action = "<cmd>:ToggleTerm<CR>";
}
];
};
}
];
}).neovim).overrideAttrs (old: {
pname = "neovim-min";
version = "custom";
});
}; };
} }
-202
View File
@@ -1,202 +0,0 @@
{ inputs, ... }:
let
userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
in
{
flake.modules.nixos.ssh = { config, pkgs, lib, ... }:
let
cfg = config.ssh;
configDir = "/etc/ssh";
in
{
options.ssh = {
hostKey = lib.mkOption {
description = "String path to the host private key file";
type = lib.types.str;
default = "ssh_host_ed25519_key";
};
certificates = {
enable = lib.mkEnableOption "Enable SSH host certificates";
userCA = lib.mkOption {
description = "Content for the SSH user CA file (public key)";
type = lib.types.path;
default = ../hosts/janus/ssh_user_ca.pub;
};
userCAFile = lib.mkOption {
description = "String path to the SSh user CA";
type = lib.types.str;
default = "ssh_user_ca.pub";
};
};
};
config = {
services.openssh = {
enable = true;
# require public key authentication for better security
settings = lib.mkMerge [
{
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
HostKey = "${configDir}/${cfg.hostKey}";
}
(lib.mkIf cfg.certificates.enable {
TrustedUserCAKeys = "${configDir}/${cfg.certificates.userCAFile}";
HostCertificate = "${configDir}/${cfg.hostKey}-cert.pub";
})
];
};
environment.etc."ssh/${cfg.certificates.userCAFile}" = lib.mkIf cfg.certificates.enable {
source = cfg.certificates.userCA;
};
programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable {
"192.168.1.*" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
};
"*.john-stream.com" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
};
};
};
};
flake.modules.homeManager.ssh = { config, pkgs, lib, ... }:
let
cfg = config.ssh;
configDir = "${config.home.homeDirectory}/.ssh";
identityFile = cfg.identityFile;
publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub";
in
{
options.ssh = with lib; {
identityFile = mkOption {
# Intentionally not using a path type here because that will end up with the private key getting copied into the store
type = types.str;
default = "${config.home.homeDirectory}/.ssh/id_ed25519";
description = "Path to the SSH identity file.";
};
certificates = {
enable = mkEnableOption "Enable SSH client certificates";
};
knownHostsFile = mkOption {
type = types.str;
default = "${configDir}/known_hosts";
};
knownHosts = mkOption {
description = "";
type = types.listOf types.str;
default = [ ];
};
matchSets = {
appdaemon = mkEnableOption "Enable AppDaemon SSH targets";
certs = mkEnableOption "Enable Janus and Soteria SSH targets";
homelab = mkEnableOption "Enable various Homelab targets";
dev = mkEnableOption "Enable development targets";
};
};
# All this stuff has to be wrapped in a config attribute because of the presence of the options here?
config = let
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
in {
home.file.".ssh/known_hosts" = {
text = lib.concatStringsSep "\n" (
cfg.knownHosts ++ lib.optionals cfg.certificates.enable [
"@cert-authority 192.168.1.* ${sshHostCAPubKey}"
"@cert-authority *.john-stream.com ${sshHostCAPubKey}"
]
);
};
programs.ssh = {
enable = true;
enableDefaultConfig = false;
extraConfig = ''
SetEnv TERM="xterm-256color"
'';
matchBlocks = lib.mkMerge [
{
"john-pc-ubuntu" = {
hostname = "192.168.1.85";
};
"*" = lib.mkMerge [
{
user = "john";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
identitiesOnly = true;
inherit identityFile;
hashKnownHosts = false;
userKnownHostsFile = cfg.knownHostsFile;
addKeysToAgent = "yes";
forwardAgent = false;
}
(lib.mkIf cfg.certificates.enable { inherit certificateFile; })
];
}
(lib.mkIf cfg.matchSets.appdaemon {
"appdaemon" = {
hostname = "192.168.1.242";
user = "appdaemon";
};
"ad-nix" = {
hostname = "192.168.1.201";
user = "appdaemon";
};
})
(lib.mkIf cfg.matchSets.certs {
"janus" = {
hostname = "janus.john-stream.com";
user = "root";
};
"soteria" = {
hostname = "soteria.john-stream.com";
user = "john";
};
})
(lib.mkIf cfg.matchSets.homelab {
"docs" = {
hostname = "192.168.1.110";
user = "root";
};
"gitea" = {
hostname = "192.168.1.104";
user = "john";
};
"hermes" = {
hostname = "192.168.1.150";
user = "root";
};
"panoptes" = {
hostname = "192.168.1.107";
user = "panoptes";
};
})
(lib.mkIf cfg.matchSets.dev {
"test-nix" = {
hostname = "fded:fb16:653e:25da:be24:11ff:fea0:753f";
user = "john";
};
})
];
};
};
};
}
-129
View File
@@ -1,129 +0,0 @@
{
"root": "/etc/step-ca/certs/root_ca.crt",
"federatedRoots": null,
"crt": "/etc/step-ca/certs/intermediate_ca.crt",
"key": "/etc/step-ca/secrets/intermediate_ca_key",
"address": ":443",
"insecureAddress": "",
"dnsNames": [
"janus.john-stream.com",
"192.168.1.113"
],
"ssh": {
"hostKey": "/etc/step-ca/secrets/ssh_host_ca_key",
"userKey": "/etc/step-ca/secrets/ssh_user_ca_key"
},
"logger": {
"format": "text"
},
"db": {
"type": "badgerv2",
"dataSource": "/var/lib/step-ca/db",
"badgerFileLoadingMode": ""
},
"authority": {
"provisioners": [
{
"type": "ACME",
"name": "acme"
},
{
"type": "SSHPOP",
"name": "sshpop",
"claims": {
"enableSSHCA": true
}
},
{
"type": "JWK",
"name": "admin",
"key": {
"use": "sig",
"kty": "EC",
"kid": "xoxgOJFbveSLIL2gm1Yu5ZiRb9v8Jxe44F56i3v-Nf8",
"crv": "P-256",
"alg": "ES256",
"x": "zFO8hPx_eH0Iyz7UJI-w8ODMusEKCZ28M76sGWmWYxA",
"y": "XIWLLyKDzqxV9UH-2KeAkKPDrgLoPrxxW9-PzkXggME"
},
"encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjYwMDAwMCwicDJzIjoiUVJnTnJVTF9KcmxJYkJMVTlGNVRPZyJ9.DMu7xBNCq5pr-_--YTxNr5Hrcqy6ZmSVHsWurfVXL7Hk0Q3vyYRxiw.h-CnFiYc-DhxThI3.plx3_Qa_0kU-2TwnqFNfAfGnCpfQ2e0iiCMLruNHbLMnHeXQ1BysHBqps45_02zZXIRdHoDgYGtXRSfcdUYYoS0pLoPzC6m301ZFNSAFdRVlSZ3Q6VmWdixPXXnEB4EgSKTT_wxR33L8t9OpFzD85KfY-b_Un1l99ufjCnfg-EYkcICTn_G4-8bcW3eFIvJ6setzu-l0jHMhLQdIweqncn9on9xBXBD-ANhZfP95P2BJt-APqCi8eqiAvn_vClovdg0PxzRwOVDvWREz66FDw-HTU7xDtGO9hACopT5tfZOXDoykgZw1mJsq9NEq9ZzvKG2hvyk1UXtExxrNtFo.5q1OfGU4Amo4Si-vpeI42g",
"claims": {
"enableSSHCA": true,
"disableRenewal": false,
"allowRenewalAfterExpiry": false,
"disableSmallstepExtensions": false
},
"options": {
"x509": {},
"ssh": {}
}
}
],
"template": {},
"backdate": "1m0s"
},
"tls": {
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
],
"minVersion": 1.2,
"maxVersion": 1.3,
"renegotiation": false
},
"templates": {
"ssh": {
"user": [
{
"name": "config.tpl",
"type": "snippet",
"template": "templates/ssh/config.tpl",
"path": "~/.ssh/config",
"comment": "#"
},
{
"name": "step_includes.tpl",
"type": "prepend-line",
"template": "templates/ssh/step_includes.tpl",
"path": "${STEPPATH}/ssh/includes",
"comment": "#"
},
{
"name": "step_config.tpl",
"type": "file",
"template": "templates/ssh/step_config.tpl",
"path": "ssh/config",
"comment": "#"
},
{
"name": "known_hosts.tpl",
"type": "file",
"template": "templates/ssh/known_hosts.tpl",
"path": "ssh/known_hosts",
"comment": "#"
}
],
"host": [
{
"name": "sshd_config.tpl",
"type": "snippet",
"template": "templates/ssh/sshd_config.tpl",
"path": "/etc/ssh/sshd_config",
"comment": "#",
"requires": [
"Certificate",
"Key"
]
},
{
"name": "ca.tpl",
"type": "snippet",
"template": "templates/ssh/ca.tpl",
"path": "/etc/ssh/ca.pub",
"comment": "#"
}
]
}
},
"commonName": "Step Online CA"
}
+46 -49
View File
@@ -1,94 +1,91 @@
{ inputs, ... }: { { inputs, ... }: {
flake.modules.nixos.step-ssh-host = { config, pkgs, lib, ... }: flake.modules.nixos.ssh-certs = { config, pkgs, lib, ... }:
let let
cfg = config.step-ssh-host; cfg = config.ssh-certs;
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path; wrappers = inputs.self.wrappers;
sshKeyPath = "/etc/ssh/ssh_host_ed25519_key"; sshKeyPath = "/etc/ssh/ssh_host_ed25519_key";
sshCertPath = "${sshKeyPath}-cert.pub"; sshCertPath = "${sshKeyPath}-cert.pub";
sshHostCertSign = (wrappers.signHostWrapper.apply {
inherit pkgs;
inherit (cfg) provisioner extraPrincipals;
provisionerPasswordFile = config.sops.secrets."janus/admin_jwk".path;
}).wrapper;
sshHostCertRenew = (wrappers.renewHostWrapper.apply {
inherit pkgs;
sshHostKeyFile = sshKeyPath;
overwrite = true;
}).wrapper;
sshHostCertCheck = (wrappers.hostCheckWrapper.apply {
inherit pkgs;
certPath = sshCertPath;
}).wrapper;
sshHostRenewalCheck = (wrappers.renewalCheck.apply {
inherit pkgs;
certPath = sshCertPath;
expires-in = "4h";
}).wrapper;
in in
{ {
# NixOS Options # NixOS Options
options.step-ssh-host = { options.ssh-certs = {
hostname = lib.mkOption {
description = "Networking host name to register with the CA";
type = lib.types.str;
};
provisioner = lib.mkOption { provisioner = lib.mkOption {
description = "Provisioner inside Step CA to use for the SSH certificates"; description = "Provisioner inside Step CA to use for the SSH certificates";
type = lib.types.str; type = lib.types.str;
default = "admin"; default = "admin";
}; };
extraPrincipals = lib.mkOption {
description = "Additional SSH host certificate principals to include per host";
type = with lib.types; listOf str;
default = [ ];
};
}; };
imports = with inputs.self.modules.nixos; [ ssh ]; # imports = with inputs.self.modules.nixos; [ ssh ];
# NixOS Config # NixOS Config
config = { config = {
ssh.certificates.enable = true; # ssh.certificates.enable = true;
sops.secrets."janus/admin_jwk" = { sops.secrets."janus/admin_jwk" = {
# Shared provisioner credential is intentionally centralized.
sopsFile = ../../../keys/secrets.yaml;
owner = "root"; owner = "root";
group = "root"; group = "root";
mode = "0400"; mode = "0400";
}; };
networking.nameservers = [ "192.168.1.150" ]; networking.nameservers = [ "192.168.1.150" ];
networking.dhcpcd.extraConfig = "nohook resolv.conf"; networking.dhcpcd.extraConfig = "nohook resolv.conf";
environment.systemPackages = with pkgs; [ environment.systemPackages = [
# step-cli sshHostCertSign
(writeShellScriptBin "ssh-host-cert-renew" '' sshHostCertRenew
${lib.getExe pkgs.step-cli} ssh certificate \ sshHostRenewalCheck
--host --sign \ sshHostCertCheck
--provisioner "${cfg.provisioner}" \
--provisioner-password-file "${provisionerPasswordPath}" \
--principal "${cfg.hostname}" \
--principal "${cfg.hostname}.john-stream.com" \
"${cfg.hostname}" "${sshKeyPath}.pub"
'')
(writeShellScriptBin "ssh-host-cert-check" "${lib.getExe' pkgs.openssh "ssh-keygen"} -Lf ${sshCertPath}")
]; ];
systemd.services.step-ssh-host-renew = { systemd.services.ssh-certs-renew = {
description = "Renew Step SSH host certificate if needed"; description = "SSH host certificate renewal";
wantedBy = [ ]; wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wants = [ "network-online.target" ]; wants = [ "network-online.target" ];
path = with pkgs; [ coreutils systemd step-cli openssh ]; path = with pkgs; [ step-cli systemd ];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
User = "root"; User = "root";
Group = "root"; Group = "root";
ExecCondition = lib.getExe sshHostRenewalCheck;
ExecStart = lib.getExe sshHostCertRenew;
}; };
script = ''
set -euo pipefail
if ${lib.getExe pkgs.step-cli} ssh needs-renewal "${sshCertPath}" --expires-in "4h"; then
echo "Renewing SSH host certificate"
else
rc=$?
if [ "$rc" -eq 1 ]; then
echo "SSH host cert does not need renewal"
exit 0
fi
if [ "$rc" -eq 2 ]; then
echo "SSH host cert missing: ${sshCertPath}" >&2
exit 1
fi
echo "step ssh needs-renewal failed with rc=$rc" >&2
exit "$rc"
fi
'';
}; };
systemd.timers.step-ssh-host-renew = { systemd.timers.ssh-certs-renew = {
description = "Periodic Step SSH host certificate renewal"; description = "Periodic Step SSH host certificate renewal";
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
timerConfig = { timerConfig = {
OnBootSec = "5m"; OnBootSec = "5m";
OnUnitActiveSec = "4h"; OnUnitActiveSec = "4h";
RandomizedDelaySec = "15m"; RandomizedDelaySec = "15m";
Persistent = true; Persistent = true;
Unit = "step-ssh-host-renew.service"; Unit = "ssh-certs-renew.service";
}; };
}; };
}; };
+177 -12
View File
@@ -1,25 +1,190 @@
{ inputs, ... }: { inputs, ... }:
let
ipAddress = "0.0.0.0";
in
{ {
flake.modules.nixos.step-ca = { pkgs, ... }: { flake.modules.nixos.step-ca = { config, pkgs, lib, ... }:
let
cfg = config.step-ca;
caAddress = "0.0.0.0";
caPort = 443;
caPasswordPath = (lib.getAttr cfg.secrets.caPassword config.sops.secrets).path;
intermediateKeyPath = (lib.getAttr cfg.secrets.intermediateKey config.sops.secrets).path;
sshHostCaKeyPath = (lib.getAttr cfg.secrets.sshHostCaKey config.sops.secrets).path;
sshUserCaKeyPath = (lib.getAttr cfg.secrets.sshUserCaKey config.sops.secrets).path;
adminProvisionerEncryptedKeyValue =
(lib.getAttr cfg.secrets.adminProvisionerEncryptedKey config.sops.placeholder);
renderedStepCaConfig = builtins.toJSON {
root = cfg.rootCertPath;
crt = cfg.intermediateCertPath;
key = intermediateKeyPath;
address = "${caAddress}:${toString caPort}";
dnsNames = cfg.dnsNames;
ssh = {
hostKey = sshHostCaKeyPath;
userKey = sshUserCaKeyPath;
};
db = {
type = "badgerv2";
dataSource = "/var/lib/step-ca/db";
};
authority = {
backdate = "1m0s";
provisioners = [
{
type = "ACME";
name = "acme";
}
{
type = "SSHPOP";
name = "sshpop";
claims.enableSSHCA = true;
}
{
type = "JWK";
name = "admin";
key = {
use = "sig";
kty = "EC";
kid = "xoxgOJFbveSLIL2gm1Yu5ZiRb9v8Jxe44F56i3v-Nf8";
crv = "P-256";
alg = "ES256";
x = "zFO8hPx_eH0Iyz7UJI-w8ODMusEKCZ28M76sGWmWYxA";
y = "XIWLLyKDzqxV9UH-2KeAkKPDrgLoPrxxW9-PzkXggME";
};
encryptedKey = adminProvisionerEncryptedKeyValue;
claims = {
enableSSHCA = true;
disableRenewal = false;
allowRenewalAfterExpiry = false;
disableSmallstepExtensions = false;
};
options = {
x509 = { };
ssh = { };
};
}
];
};
tls = {
cipherSuites = [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
];
minVersion = 1.2;
maxVersion = 1.3;
renegotiation = false;
};
};
in
{
options.step-ca = {
rootCertPath = lib.mkOption {
description = "Path to the Step CA root certificate served by this host.";
type = lib.types.path;
};
intermediateCertPath = lib.mkOption {
description = "Path to the Step CA intermediate certificate served by this host. This is public material and does not need to be stored in SOPS.";
type = lib.types.path;
};
dnsNames = lib.mkOption {
description = "DNS names and IP SANs advertised by this Step CA instance.";
type = with lib.types; listOf str;
};
secrets = {
sopsFile = lib.mkOption {
description = "Host-local SOPS file that stores Step CA secret material.";
type = lib.types.path;
};
caPassword = lib.mkOption {
description = "SOPS key for the Step CA intermediate password.";
type = lib.types.str;
};
intermediateKey = lib.mkOption {
description = "SOPS key for the Step CA intermediate private key.";
type = lib.types.str;
};
sshHostCaKey = lib.mkOption {
description = "SOPS key for the Step SSH host CA private key.";
type = lib.types.str;
};
sshUserCaKey = lib.mkOption {
description = "SOPS key for the Step SSH user CA private key.";
type = lib.types.str;
};
adminProvisionerEncryptedKey = lib.mkOption {
description = "SOPS key for the Step CA admin provisioner encrypted key.";
type = lib.types.str;
};
};
};
config = {
# Placeholders are expected initially until real material is inserted into sops.
sops.secrets."${cfg.secrets.caPassword}" = {
sopsFile = cfg.secrets.sopsFile;
owner = "step-ca";
group = "step-ca";
mode = "0400";
restartUnits = [ "step-ca.service" ];
};
sops.secrets."${cfg.secrets.intermediateKey}" = {
sopsFile = cfg.secrets.sopsFile;
owner = "step-ca";
group = "step-ca";
mode = "0400";
restartUnits = [ "step-ca.service" ];
};
sops.secrets."${cfg.secrets.sshHostCaKey}" = {
sopsFile = cfg.secrets.sopsFile;
owner = "step-ca";
group = "step-ca";
mode = "0400";
restartUnits = [ "step-ca.service" ];
};
sops.secrets."${cfg.secrets.sshUserCaKey}" = {
sopsFile = cfg.secrets.sopsFile;
owner = "step-ca";
group = "step-ca";
mode = "0400";
restartUnits = [ "step-ca.service" ];
};
sops.secrets."${cfg.secrets.adminProvisionerEncryptedKey}" = {
sopsFile = cfg.secrets.sopsFile;
owner = "step-ca";
group = "step-ca";
mode = "0400";
restartUnits = [ "step-ca.service" ];
};
sops.templates."step-ca-config" = {
owner = "step-ca";
group = "step-ca";
mode = "0400";
content = renderedStepCaConfig;
};
# https://github.com/NixOS/nixpkgs/blob/nixos-23.05/nixos/modules/services/security/step-ca.nix # https://github.com/NixOS/nixpkgs/blob/nixos-23.05/nixos/modules/services/security/step-ca.nix
services.step-ca = { services.step-ca = {
enable = true; enable = true;
openFirewall = true; openFirewall = true;
address = ipAddress; address = caAddress;
port = 8443; port = caPort;
intermediatePasswordFile = caPasswordPath;
};
environment.etc."smallstep/ca.json".source =
lib.mkForce config.sops.templates."step-ca-config".path;
systemd.services.step-ca.restartTriggers =
lib.mkAfter [ config.sops.templates."step-ca-config".path ];
# https://smallstep.com/docs/step-ca/configuration/#configuration-options
settings = {
root = "";
crt = "";
};
};
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
step-ca step-ca
step-cli step-cli
]; ];
}; };
};
} }
+31 -20
View File
@@ -1,6 +1,10 @@
{ self, inputs, lib, ... }: { self, inputs, ... }:
let let
username = "john"; username = "john";
baseUserModules = self.factory.user {
username = username;
isAdmin = true;
};
in in
{ {
flake.meta.users."${username}" = { flake.meta.users."${username}" = {
@@ -8,23 +12,17 @@ in
name = "John Lancaster"; name = "John Lancaster";
inherit username; inherit username;
key = ""; key = "";
keygrip = [ keygrip = [ ];
];
authorizedKeys = [ authorizedKeys = [
# "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIAUa4dcg1TWc4pW++uodyhX4eOqrX/QYIxFWtEP7HFJ john@john-pc-ubuntu" # Shared keys for every host can go here.
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOkGLo4N/L3RYvaIZ1FmePlxa1HK0fMciZxKtRhN58F root@janus" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIAUa4dcg1TWc4pW++uodyhX4eOqrX/QYIxFWtEP7HFJ john@john-pc-ubuntu"
]; ];
}; };
flake.modules = lib.mkMerge [ flake.modules = {
(self.factory.user { nixos."${username}" = { config, pkgs, ... }: {
username = username;
isAdmin = true;
})
{
nixos."${username}" = {
imports = [ imports = [
inputs.home-manager.nixosModules.home-manager baseUserModules.nixos."${username}"
]; ];
users.users."${username}" = { users.users."${username}" = {
openssh.authorizedKeys.keys = inputs.self.meta.users."${username}".authorizedKeys; openssh.authorizedKeys.keys = inputs.self.meta.users."${username}".authorizedKeys;
@@ -32,17 +30,30 @@ in
}; };
# This module will be imported by the user factory # This module will be imported by the user factory
homeManager."${username}" = with inputs.self.meta.users."${username}"; { homeManager."${username}" = { pkgs, ... }:
with inputs.self.meta.users."${username}";
let
selfPkgs = inputs.self.packages.${pkgs.stdenv.hostPlatform.system};
in {
home.stateVersion = "25.11"; home.stateVersion = "25.11";
xdg.enable = true;
programs.git.settings.user.name = name;
programs.git.settings.user.email = email;
imports = with inputs.self.modules.homeManager; [ imports = with inputs.self.modules.homeManager; [
ssh ssh-new
shell-tools shell-tools
git git
]; ];
}; xdg.enable = true;
} home.packages = [
selfPkgs.neovim-min
]; ];
home.sessionVariables = {
EDITOR = "nvim";
VISUAL = "nvim";
GIT_EDITOR = "nvim";
SOPS_EDITOR = "nvim";
};
programs.git.settings.user.name = name;
programs.git.settings.user.email = email;
programs.git.settings.core.editor = "nvim";
};
};
} }
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env bash
set -u
# --- pretty output helpers ---
BOLD="\033[1m"
DIM="\033[2m"
GREEN="\033[32m"
YELLOW="\033[33m"
RED="\033[31m"
CYAN="\033[36m"
RESET="\033[0m"
section() {
echo
echo -e "${BOLD}${CYAN}==> $1${RESET}"
}
ok() { echo -e "${GREEN}[OK]${RESET} $1"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $1"; }
err() { echo -e "${RED}[ERR]${RESET} $1"; }
info() { echo -e "${DIM}$1${RESET}"; }
has_cmd() {
command -v "$1" >/dev/null 2>&1
}
found_process=0
found_render_node=0
found_gpu_libs=0
software_rasterizer=0
section "1) Find a running Ghostty process"
info "What this does: tries the same two process lookups you ran (pgrep and pidof)."
info "Good outcome: at least one PID is returned."
echo -e "${DIM}\$ pgrep -x ghostty${RESET}"
PGREP_OUT="$(pgrep -x ghostty || true)"
if [[ -n "${PGREP_OUT}" ]]; then
echo "${PGREP_OUT}"
else
echo "<no output>"
fi
echo -e "${DIM}\$ pidof ghostty${RESET}"
PIDOF_OUT="$(pidof ghostty || true)"
if [[ -n "${PIDOF_OUT}" ]]; then
echo "${PIDOF_OUT}"
else
echo "<no output>"
fi
if [[ -n "${PGREP_OUT}" || -n "${PIDOF_OUT}" ]]; then
found_process=1
ok "Ghostty process detected."
else
warn "No Ghostty process found yet by either command."
fi
section "2) Set GHOSTTY_PID"
info "What this does: picks one Ghostty PID for all later checks."
info "Good outcome: GHOSTTY_PID is set to a valid running Ghostty process."
# Prefer step-1 outputs first (avoids re-running lookups that may block on some systems).
GHOSTTY_PID=""
if [[ -n "${PGREP_OUT}" ]]; then
GHOSTTY_PID="$(printf '%s\n' "${PGREP_OUT}" | head -n1)"
info "Using PID from step-1 pgrep output."
elif [[ -n "${PIDOF_OUT}" ]]; then
GHOSTTY_PID="$(printf '%s\n' "${PIDOF_OUT}" | awk '{print $1}')"
info "Using PID from step-1 pidof output."
else
echo -e "${DIM}\$ pgrep -xo ghostty${RESET}"
if has_cmd timeout; then
PGREP_RAW="$(timeout 3s pgrep -xo ghostty 2>&1)"
pgrep_status=$?
if (( pgrep_status == 124 )); then
warn "pgrep timed out after 3 seconds."
fi
else
PGREP_RAW="$(pgrep -xo ghostty 2>&1)"
pgrep_status=$?
fi
if (( pgrep_status == 0 )); then
GHOSTTY_PID="$(printf '%s\n' "${PGREP_RAW}" | head -n1)"
else
if [[ -n "${PGREP_RAW}" ]]; then
warn "[pgrep] ${PGREP_RAW}"
fi
warn "pgrep exit code: ${pgrep_status} (1 usually means no matching process)."
echo -e "${DIM}\$ pidof ghostty${RESET}"
PIDOF_RAW="$(pidof ghostty 2>&1)"
pidof_status=$?
if (( pidof_status == 0 )); then
GHOSTTY_PID="$(printf '%s\n' "${PIDOF_RAW}" | awk '{print $1}')"
else
if [[ -n "${PIDOF_RAW}" ]]; then
warn "[pidof] ${PIDOF_RAW}"
fi
warn "pidof exit code: ${pidof_status}"
GHOSTTY_PID=""
fi
fi
fi
if [[ -z "${GHOSTTY_PID}" ]]; then
err "Could not find a running Ghostty process to inspect."
echo "Open Ghostty, then run this script again."
exit 1
fi
export GHOSTTY_PID
ok "Using GHOSTTY_PID=${GHOSTTY_PID}"
section "3) Check for an open GPU render device"
info "What this does: inspects /proc/<pid>/fd for /dev/dri entries used for rendering."
info "Good outcome: see /dev/dri/renderD* (for example renderD128)."
if has_cmd rg; then
echo -e "${DIM}\$ sudo ls -l /proc/\$GHOSTTY_PID/fd | rg dri${RESET}"
DRI_LINES="$(sudo ls -l "/proc/${GHOSTTY_PID}/fd" | rg dri || true)"
else
warn "'rg' not found; using grep instead"
echo -e "${DIM}\$ sudo ls -l /proc/\$GHOSTTY_PID/fd | grep dri${RESET}"
DRI_LINES="$(sudo ls -l "/proc/${GHOSTTY_PID}/fd" | grep dri || true)"
fi
if [[ -n "${DRI_LINES}" ]]; then
echo "${DRI_LINES}"
else
echo "<no dri-related file descriptors found>"
fi
if echo "${DRI_LINES}" | grep -q '/dev/dri/renderD'; then
found_render_node=1
ok "Found /dev/dri/renderD*: strong sign Ghostty is using GPU render infrastructure."
elif [[ -n "${DRI_LINES}" ]]; then
warn "Found /dev/dri entries, but no renderD node in this output."
else
warn "No /dev/dri entries seen for this process."
fi
section "4) Check mapped graphics libraries"
info "What this does: reads process memory mappings for GL/EGL/Vulkan/Mesa libraries."
info "Good outcome: matches like libGL, libEGL, libvulkan, mesa, libgbm, libgallium appear."
echo -e "${DIM}\$ sudo grep -E 'libEGL|libGL|libvulkan|mesa' /proc/\$GHOSTTY_PID/maps | head -n 30${RESET}"
LIB_LINES="$(sudo grep -E 'libEGL|libGL|libvulkan|mesa' "/proc/${GHOSTTY_PID}/maps" | head -n 30 || true)"
if [[ -n "${LIB_LINES}" ]]; then
echo "${LIB_LINES}"
found_gpu_libs=1
ok "Graphics stack libraries are mapped into Ghostty."
else
echo "<no matches>"
warn "No GL/EGL/Vulkan/Mesa mappings matched this filter."
fi
section "5) Optional probe for software rasterizer vs hardware driver hints"
info "What this does: scans for common software rasterizer strings (llvmpipe/swrast) and driver names."
info "Good outcome: no llvmpipe/swrast matches. Driver-name matches can vary by stack."
echo -e "${DIM}\$ sudo grep -Ei 'llvmpipe|swrast|iris|radeonsi|nouveau|zink|nvidia|_dri\\.so' /proc/\$GHOSTTY_PID/maps${RESET}"
PROBE_LINES="$(sudo grep -Ei 'llvmpipe|swrast|iris|radeonsi|nouveau|zink|nvidia|_dri\.so' "/proc/${GHOSTTY_PID}/maps" || true)"
if [[ -n "${PROBE_LINES}" ]]; then
echo "${PROBE_LINES}"
if echo "${PROBE_LINES}" | grep -Eiq 'llvmpipe|swrast'; then
software_rasterizer=1
warn "Software rasterizer markers detected (llvmpipe/swrast)."
else
ok "Driver-related markers detected and no software rasterizer markers found."
fi
else
ok "No probe matches found. This is often fine; absence of llvmpipe/swrast is usually good."
fi
section "Final interpretation"
if (( found_process == 1 )); then
ok "Process check: PASS"
else
warn "Process check: no process found initially (script still proceeded once PID was resolved)."
fi
if (( found_render_node == 1 )); then
ok "Render node check: PASS (/dev/dri/renderD* present)"
else
warn "Render node check: no explicit renderD node found"
fi
if (( found_gpu_libs == 1 )); then
ok "Graphics library check: PASS"
else
warn "Graphics library check: no matches"
fi
if (( software_rasterizer == 1 )); then
warn "Software rasterizer check: POSSIBLE software rendering path"
else
ok "Software rasterizer check: no llvmpipe/swrast markers detected"
fi
echo
if (( found_render_node == 1 && found_gpu_libs == 1 && software_rasterizer == 0 )); then
ok "Overall: strong evidence Ghostty is using GPU acceleration."
else
warn "Overall: mixed signals. Consider running a live GPU activity monitor during terminal redraw stress."
fi
ok "Summary complete for PID ${GHOSTTY_PID}"