Compare commits
188 commits
server-ins
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eab0d47ae | ||
|
|
f8a873bd06 | ||
|
|
e2cf93e7d6 | ||
|
|
610454f0d2 | ||
|
|
0cdb4b8697 | ||
|
|
df18b1cfaf | ||
|
|
bbe05c971d | ||
| 09f191d10b | |||
|
|
05896f6d3b | ||
|
|
cc8cc05a08 | ||
| 680c20483c | |||
|
|
592e989b03 | ||
|
|
9283643e07 | ||
|
|
e43a5eb880 | ||
|
|
dc7ef47681 | ||
|
|
09d25a1899 | ||
|
|
ba51b6bcf7 | ||
|
|
b2df891b20 | ||
|
|
8fcb43f279 | ||
|
|
1204584ae4 | ||
|
|
cda9c4cf0f | ||
|
|
3dcbdd408a | ||
|
|
b11add8525 | ||
|
|
9793d5ef7c | ||
|
|
cbf0defa34 | ||
|
|
2e9441f367 | ||
|
|
1b0eb5835d | ||
|
|
0c11628f73 | ||
|
|
5d4f2048a6 | ||
|
|
0f34d2508d | ||
|
|
4fab9a28a2 | ||
|
|
fc9894c32f | ||
|
|
e8158e6c0f | ||
|
|
dc7895e3b2 | ||
|
|
3b6f4545b4 | ||
|
|
40cc62f65b | ||
|
|
83dd92d738 | ||
|
|
067bab125b | ||
|
|
851ee8ea1d | ||
|
|
fb99ef3cff | ||
|
|
c5cabe7531 | ||
| 814993e66b | |||
| ccf9eb2859 | |||
|
|
eee28d3e9a | ||
| 327bdc11fe | |||
| 647d748d30 | |||
|
|
4525e73f1a | ||
| 082529dac9 | |||
|
|
73d4225f9b | ||
|
|
4debab6f69 | ||
|
|
1744d776e2 | ||
|
|
3de1747e92 | ||
|
|
7f8badf1d1 | ||
| 4e01e62cc0 | |||
| 8a91f3db88 | |||
|
|
4600a8e5ca | ||
|
|
d0e9b3f907 | ||
|
|
a9bb775b7d | ||
|
|
e952667623 | ||
|
|
c04b463ad0 | ||
| 9ad8d71f61 | |||
|
|
69d982d0fa | ||
|
|
3604c08650 | ||
|
|
f419fed7eb | ||
|
|
08495161ae | ||
|
|
6d9ccf5d4e | ||
|
|
4d2e40455d | ||
|
|
8056e510c5 | ||
|
|
f599a76aba | ||
|
|
0b20c375b5 | ||
|
|
2aec4d4d5e | ||
|
|
d787b0ea48 | ||
|
|
a7dd6284d8 | ||
|
|
af9f735abc | ||
|
|
771cc58076 | ||
|
|
b8bc17f385 | ||
|
|
644420481e | ||
|
|
3b5288a48c | ||
|
|
bce34985eb | ||
|
|
4332dfcbb5 | ||
|
|
ba277b3f49 | ||
|
|
244988d52d | ||
|
|
f4738584c3 | ||
|
|
914a825587 | ||
|
|
7141582f75 | ||
|
|
47fc658523 | ||
|
|
6ef7112ae0 | ||
|
|
e8d4bbf24b | ||
|
|
b0c8664f5c | ||
|
|
754cb0d274 | ||
|
|
22808f39fa | ||
|
|
1d4c6c8f4f | ||
|
|
d184064bfd | ||
|
|
c4c40e80d5 | ||
|
|
6846faa5f1 | ||
|
|
41b3d217f8 | ||
|
|
0cd4947282 | ||
|
|
b66dd1d30c | ||
|
|
32cb3b7510 | ||
|
|
84da9ed8f5 | ||
|
|
7d3fd2d8cf | ||
|
|
c6cb19eff6 | ||
|
|
88c51399d0 | ||
|
|
9921a7f9f1 | ||
|
|
29ff1c9be7 | ||
|
|
663be7872a | ||
|
|
c3742db32e | ||
|
|
14e60ca839 | ||
|
|
9566986ade | ||
|
|
7f40280700 | ||
|
|
6500ad39bf | ||
|
|
40627405f7 | ||
|
|
975b2a3ee9 | ||
|
|
5e7b76bdcf | ||
|
|
c69c7c9b11 | ||
|
|
00ab64d83c | ||
|
|
c434a479a5 | ||
|
|
af486e8a33 | ||
|
|
7ad82a41b1 | ||
|
|
d0d25160c8 | ||
|
|
a36b90e656 | ||
|
|
5db45664ab | ||
|
|
b667f7c247 | ||
|
|
d1b0742f32 | ||
|
|
72d8714e51 | ||
|
|
be6dde6f0a | ||
|
|
76f63f0ae3 | ||
|
|
c31ca7d473 | ||
|
|
f0d52aed04 | ||
|
|
300849b8c6 | ||
|
|
4bccb6e6a8 | ||
|
|
1c7794e904 | ||
|
|
74eb3a9c40 | ||
|
|
0985503002 | ||
|
|
3813206a3e | ||
|
|
52649f500a | ||
|
|
369e96cbd7 | ||
|
|
4544635ad6 | ||
|
|
4d6b64dee9 | ||
|
|
8ce36f8726 | ||
|
|
f3854af82a | ||
|
|
0de86837df | ||
|
|
49165590a6 | ||
|
|
cfa2834516 | ||
|
|
b04b53f9c4 | ||
|
|
d1ab7d9a69 | ||
|
|
5fcb54cc63 | ||
|
|
14c29945eb | ||
|
|
a5f0d36d82 | ||
|
|
c43cd0ee17 | ||
|
|
1bfd96c0d0 | ||
|
|
9f73571f55 | ||
|
|
420f3881b5 | ||
|
|
92593c7d0a | ||
|
|
245eb912a9 | ||
|
|
ef6e303a60 | ||
|
|
f327b8e868 | ||
|
|
c7793b68ea | ||
|
|
d4dbd73a8c | ||
|
|
2c9cf1e8b4 | ||
|
|
42462f57a2 | ||
|
|
33e2e327b5 | ||
|
|
384b84fec2 | ||
|
|
81c510ca16 | ||
|
|
d7bd99744c | ||
|
|
d9e569d477 | ||
|
|
ee4c2db93f | ||
|
|
e997a83c93 | ||
|
|
533e5810a9 | ||
|
|
6c057d945e | ||
|
|
e44ef1fdcc | ||
|
|
69e07dbc14 | ||
|
|
657e250f75 | ||
|
|
cea6913cf3 | ||
|
|
84715596f5 | ||
|
|
ee2fa1e5f1 | ||
|
|
e2b820aac0 | ||
|
|
d8e5cbe26a | ||
|
|
afbc87be2b | ||
|
|
bded1b359d | ||
|
|
309d97c708 | ||
|
|
be4233a53b | ||
|
|
463249961e | ||
|
|
befe2f8a5b | ||
|
|
f9edde90e4 | ||
|
|
82ce5a7fe8 | ||
|
|
ca0d38316f | ||
|
|
b311e21d5b |
168 changed files with 5005 additions and 1505 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -11,3 +11,12 @@ env/
|
||||||
|
|
||||||
# Installer ISO live WiFi (SSID/PSK); see docs/server-installer-usb.md
|
# Installer ISO live WiFi (SSID/PSK); see docs/server-installer-usb.md
|
||||||
nixos/installer-wifi.nix
|
nixos/installer-wifi.nix
|
||||||
|
|
||||||
|
# Nix build output symlink
|
||||||
|
result
|
||||||
|
|
||||||
|
# OpenClaw: Telegram user ID (not committed to public repo)
|
||||||
|
nixos/hosts/openclaw-allow-from.nix
|
||||||
|
|
||||||
|
# Archived / local-only directories
|
||||||
|
openclaw-documents-repo/
|
||||||
|
|
|
||||||
57
AGENTS.md
57
AGENTS.md
|
|
@ -1,53 +1,24 @@
|
||||||
# Agent Instructions
|
# Agent Instructions
|
||||||
|
|
||||||
## Nix/Darwin Rebuilds
|
See **CLAUDE.md** for build commands, rebuild protocol, flake architecture, repo rules, and SSH key strategy. This file covers agent-specific operational details.
|
||||||
|
|
||||||
**IMPORTANT**: When making changes to Nix configuration files (e.g., `nixos/home/danny/home.nix`, `nixos/flake.nix`, etc.), **always ask the user to rebuild** before assuming packages are available.
|
## Running commands on sunken-ship
|
||||||
|
|
||||||
To rebuild:
|
From the Mac, agents can SSH to sunken-ship:
|
||||||
```bash
|
|
||||||
cd ~/dotfiles/nixos
|
|
||||||
darwin-rebuild switch --flake .
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not automatically run rebuild commands - ask the user first.
|
|
||||||
|
|
||||||
## Repo is public
|
|
||||||
|
|
||||||
No keys, tokens, or identifying secrets in the repo. Prefer `scp` or config outside the repo.
|
|
||||||
|
|
||||||
## SSH keys (one key per purpose)
|
|
||||||
|
|
||||||
We use **one key per purpose**, not one per machine: separate keys for server access, GitHub, Forgejo (and other forges if needed). Benefits: limit blast radius if a key is compromised; clear revocation; clear which key is for what.
|
|
||||||
|
|
||||||
- **Key names:** e.g. `id_ed25519_github`, `id_ed25519_forgejo`, `id_ed25519_servers` (Ed25519 preferred).
|
|
||||||
- **Config:** Use `~/.ssh/config` with `IdentityFile` and `IdentitiesOnly yes` per host so the right key is used. Keys and sensitive config stay outside the repo.
|
|
||||||
- **Server / NixOS:** Use actual key names on the machine (e.g. `id_ed25519_github`), not a generic `id_ed25519` (see Learnings below).
|
|
||||||
|
|
||||||
## Server installer USB (new machines only)
|
|
||||||
|
|
||||||
- Build: from **Linux** `cd ~/dotfiles/nixos && nix build .#installer-iso` (ISO is x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux.
|
|
||||||
|
|
||||||
## Learnings (NixOS server)
|
|
||||||
|
|
||||||
- Minimal ISO: use Ethernet or the graphical installer (Wi‑Fi on minimal is fiddly).
|
|
||||||
- Server hardware: stub in repo; user replaces with `nixos-generate-config --show-hardware-config` from the server.
|
|
||||||
- Root password: console only; set danny’s password as root once for sudo.
|
|
||||||
- SSH keys: use actual key names on the machine (e.g. `id_ed25519_github`), not assumed `id_ed25519`.
|
|
||||||
|
|
||||||
## Server (sunken-ship)
|
|
||||||
|
|
||||||
- **Commit and push** before testing on the server; it clones/pulls from origin.
|
|
||||||
- Bootstrap: server has no git until first rebuild. Use `nix run --extra-experimental-features "nix-command flakes" nixpkgs#git` to clone. Enable flakes in the daemon via `server-configuration-with-flakes.nix`: scp to server `/tmp/configuration.nix`, on server `sudo cp` to `/etc/nixos/configuration.nix`, then `sudo nixos-rebuild switch`. Then build flake and run `switch-to-configuration switch` (see nixos/readme.md).
|
|
||||||
- Auto-rebuild timer (`dotfiles-rebuild`) only runs after the system has been switched to the flake config. Check with `systemctl is-active dotfiles-rebuild.timer` on the server.
|
|
||||||
|
|
||||||
### Running commands on sunken-ship
|
|
||||||
|
|
||||||
From the Mac (where the dotfiles workspace lives), agents can SSH to sunken-ship to run commands. Use the sunken-ship key and the host alias or IP the user has configured (e.g. `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship` or `danny@192.168.1.x`). Example:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship 'hostname; ip addr'
|
ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship 'hostname; ip addr'
|
||||||
```
|
```
|
||||||
|
|
||||||
Rebuild on the server (flake is in `nixos/`): `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi (see [docs/sunken-ship-wifi.md](docs/sunken-ship-wifi.md)); it remains reachable when ethernet is unplugged.
|
Rebuild on the server: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship'`. The server has WiFi; it remains reachable when ethernet is unplugged. Preferred from the mac: `nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines update sunken-ship --flake ~/dotfiles`.
|
||||||
|
|
||||||
|
## Server installer USB (new machines only)
|
||||||
|
|
||||||
|
Build from **Linux**: `cd ~/dotfiles && nix build .#installer-iso` (x86_64-linux only; cannot build on macOS). Or use official NixOS minimal ISO, write to USB, boot server, clone repo, run [scripts/nixos-server-install.sh](scripts/nixos-server-install.sh). See [docs/server-installer-usb.md](docs/server-installer-usb.md). Optional live WiFi: add `nixos/installer-wifi.nix` (gitignored) when building custom ISO on Linux.
|
||||||
|
|
||||||
|
## Learnings (NixOS server)
|
||||||
|
|
||||||
|
- Minimal ISO: use Ethernet or the graphical installer (Wi‑Fi on minimal is fiddly).
|
||||||
|
- Server hardware: stub in repo; user replaces with `nixos-generate-config --show-hardware-config` from the server.
|
||||||
|
- Root password: console only; set danny's password as root once for sudo.
|
||||||
|
- SSH keys: use actual key names on the machine (e.g. `id_ed25519_github`), not assumed `id_ed25519`.
|
||||||
|
|
|
||||||
63
CLAUDE.md
63
CLAUDE.md
|
|
@ -2,21 +2,28 @@
|
||||||
|
|
||||||
## Build commands
|
## Build commands
|
||||||
|
|
||||||
|
The flake lives at the repo root (`~/dotfiles/flake.nix`) — clan-cli doesn't handle flakes in subdirs.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# macOS (from ~/dotfiles/nixos)
|
# macOS (from ~/dotfiles)
|
||||||
darwin-rebuild switch --flake .
|
darwin-rebuild switch --flake .
|
||||||
|
|
||||||
# NixOS server (SSH from mac, or on server)
|
# NixOS servers (SSH from mac, or on server)
|
||||||
sudo nixos-rebuild switch --flake .#sunken-ship
|
sudo nixos-rebuild switch --flake .#sunken-ship
|
||||||
|
sudo nixos-rebuild switch --flake .#phantom-ship
|
||||||
|
|
||||||
# WSL
|
# WSL
|
||||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl
|
sudo nixos-rebuild switch --flake ~/dotfiles#wsl
|
||||||
|
|
||||||
# Update flake + rebuild (fish alias: nixupdate)
|
# Update flake + rebuild (fish alias: nixupdate)
|
||||||
cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air
|
cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air
|
||||||
|
|
||||||
# Installer ISO (Linux only, cannot build on macOS)
|
# Installer ISO (Linux only, cannot build on macOS)
|
||||||
cd ~/dotfiles/nixos && nix build .#installer-iso
|
cd ~/dotfiles && nix build .#installer-iso
|
||||||
|
|
||||||
|
# Clan push update (from mac; builds on target so aarch64-darwin → x86_64-linux works)
|
||||||
|
nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \
|
||||||
|
machines update sunken-ship --flake ~/dotfiles
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rebuild protocol
|
## Rebuild protocol
|
||||||
|
|
@ -28,13 +35,13 @@ cd ~/dotfiles/nixos && nix build .#installer-iso
|
||||||
- **Flake:** `nixos/flake.nix` — single flake for all hosts
|
- **Flake:** `nixos/flake.nix` — single flake for all hosts
|
||||||
- **Inputs:** nixpkgs-unstable, nix-darwin, home-manager, nixos-wsl, disko, zen-browser
|
- **Inputs:** nixpkgs-unstable, nix-darwin, home-manager, nixos-wsl, disko, zen-browser
|
||||||
- **Host configs** in `nixos/hosts/`:
|
- **Host configs** in `nixos/hosts/`:
|
||||||
- `macos.nix` — Apple Silicon MacBook Air (aarch64-darwin, nix-darwin)
|
- `daniel-macbook-air.nix` — hostname `Daniel-Macbook-Air` (aarch64-darwin, nix-darwin)
|
||||||
- `sunken-ship.nix` — NixOS home server (x86_64-linux)
|
- `sunken-ship.nix` — NixOS home server (x86_64-linux, WiFi + AirPlay)
|
||||||
|
- `phantom-ship.nix` — NixOS home server (x86_64-linux, Ethernet)
|
||||||
- `wsl.nix` — WSL (x86_64-linux)
|
- `wsl.nix` — WSL (x86_64-linux)
|
||||||
- `macbookair.nix` — old MacBook Air NixOS/WSL config
|
- `server-install.nix` — disko-install target (LUKS)
|
||||||
- `server-install.nix` — disko-install target (LUKS + WiFi)
|
- **Home Manager:** integrated on macOS, WSL, and sunken-ship; user config in `nixos/home/danny/home.nix`
|
||||||
- **Home Manager:** integrated via `home-manager.darwinModules.home-manager` on macOS; user config in `nixos/home/danny/home.nix`
|
- **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/ollama.nix`
|
||||||
- **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/tmux.nix`, `nixos/ollama.nix`
|
|
||||||
- **Darwin config name:** `Daniel-Macbook-Air` (must match in rebuild commands)
|
- **Darwin config name:** `Daniel-Macbook-Air` (must match in rebuild commands)
|
||||||
|
|
||||||
## Repo rules
|
## Repo rules
|
||||||
|
|
@ -46,13 +53,39 @@ cd ~/dotfiles/nixos && nix build .#installer-iso
|
||||||
## Server (sunken-ship)
|
## Server (sunken-ship)
|
||||||
|
|
||||||
- SSH: `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship`
|
- SSH: `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship`
|
||||||
- Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship'`
|
- Remote rebuild: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship'`
|
||||||
- Auto-rebuild timer: `dotfiles-rebuild` — only active after flake config switch. Check with `systemctl is-active dotfiles-rebuild.timer`.
|
- Auto-rebuild timer: `dotfiles-rebuild` — every 15 min. Check with `systemctl is-active dotfiles-rebuild.timer`.
|
||||||
- Server has WiFi; stays reachable when ethernet is unplugged.
|
- WiFi connected; stays reachable when ethernet is unplugged.
|
||||||
|
- Services: UxPlay (AirPlay receiver on Scarlett Solo)
|
||||||
|
|
||||||
|
## Server (phantom-ship)
|
||||||
|
|
||||||
|
- SSH: `ssh danny@phantom-ship`
|
||||||
|
- Remote rebuild: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#phantom-ship'`
|
||||||
|
- Auto-rebuild timer: same pattern as sunken-ship.
|
||||||
|
- Ethernet only (no WiFi).
|
||||||
|
|
||||||
## Ollama
|
## Ollama
|
||||||
|
|
||||||
Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Enabled on macOS via `nixos/hosts/macos.nix`. Runs as a launchd user agent with `ollama serve`.
|
Custom nix-darwin module at `nixos/ollama.nix` (upstream PR not yet merged). Enabled on macOS via `nixos/hosts/daniel-macbook-air.nix`. Runs as a launchd user agent with `ollama serve`.
|
||||||
|
|
||||||
|
## Alacritty (macOS)
|
||||||
|
|
||||||
|
Terminal colors follow **System Settings → Appearance**: `programs.alacritty` imports `~/.config/alacritty/active-colors.toml`; `scripts/alacritty-sync-system-theme.sh` copies Catppuccin latte/mocha there when the OS mode changes. **nix-darwin** `launchd.user.agents.alacritty-system-theme` polls every 30s; **fish** runs the same script on interactive startup. After changing Nix, one `darwin-rebuild switch`. Details: `assets/alacritty/README.md`.
|
||||||
|
|
||||||
|
## clan.lol
|
||||||
|
|
||||||
|
**CLI invocation:** clan-cli is not installed globally. Run ad-hoc via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- machines list --flake ~/dotfiles
|
||||||
|
```
|
||||||
|
|
||||||
|
Flake lives at the repo root (not `nixos/`) — clan-cli silently ignores `?dir=` so a subdir flake breaks `clan machines update`.
|
||||||
|
|
||||||
|
**`enableRecommendedDefaults = false`:** we opted out fleet-wide because clan's defaults flip to `systemd-networkd` + `systemd-resolved` + `boot.initrd.systemd`, which breaks dnsmasq (NAT DNS on phantom-ship) and navidrome's resolv.conf bind-mount on sunken-ship. Revisit per-service in a later pass — the defaults also include handy extras (tcpdump, htop, curl, jq, nixos-facter). Option defined in `nixosModules/clanCore/defaults.nix` + `nixosModules/clanCore/networking.nix` inside the `clan-core` flake.
|
||||||
|
|
||||||
|
**Deployment:** `dotfiles-rebuild` timer (every 15 min pull) is still the source of truth. `clan machines update` works as a push escape hatch; dm-pull-deploy replaces the timer in a later stage.
|
||||||
|
|
||||||
## Shell
|
## Shell
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Extension of [dannydannydanny/methodology](https://github.com/DannyDannyDanny/me
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- [firefox-scrolling](firefox-scrolling.md) via terminal
|
- [firefox-scrolling](firefox-scrolling.md) via terminal
|
||||||
- Server: [server](server.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). New server install (USB, LUKS, WiFi): [docs/server-installer-usb.md](docs/server-installer-usb.md).
|
- Server: [server-quickstart](server-quickstart.md); NixOS flake and bootstrap [nixos/readme.md](nixos/readme.md). SSH and secrets: [docs/ssh-and-secrets.md](docs/ssh-and-secrets.md). New server install (USB, LUKS, WiFi): [docs/server-installer-usb.md](docs/server-installer-usb.md).
|
||||||
- nvim checkhealth; tmux setup; [fonts](https://www.programmingfonts.org/) / nerdfonts; [HN: home server](https://news.ycombinator.com/item?id=34271167)
|
- nvim checkhealth; tmux setup; [fonts](https://www.programmingfonts.org/) / nerdfonts; [HN: home server](https://news.ycombinator.com/item?id=34271167)
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
@ -25,7 +25,7 @@ nix-shell -p gh git
|
||||||
gh auth login
|
gh auth login
|
||||||
gh repo clone dannydannydanny/dotfiles && cd dotfiles
|
gh repo clone dannydannydanny/dotfiles && cd dotfiles
|
||||||
# git checkout <branch> # if needed
|
# git checkout <branch> # if needed
|
||||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl
|
sudo nixos-rebuild switch --flake ~/dotfiles#wsl
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clone via SSH
|
### Clone via SSH
|
||||||
|
|
@ -40,9 +40,10 @@ ssh-add ~/.ssh/id_ed25519_github
|
||||||
git clone git@github.com:DannyDannyDanny/dotfiles.git && cd dotfiles
|
git clone git@github.com:DannyDannyDanny/dotfiles.git && cd dotfiles
|
||||||
git config user.name "DannyDannyDanny"
|
git config user.name "DannyDannyDanny"
|
||||||
git config user.email "dth@taiga.ai"
|
git config user.email "dth@taiga.ai"
|
||||||
bash install.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Apply machine config from `nixos/` (see [CLAUDE.md](CLAUDE.md) for macOS rebuild commands or [nixos/readme.md](nixos/readme.md) for NixOS).
|
||||||
|
|
||||||
## Good reads
|
## Good reads
|
||||||
|
|
||||||
- [TODOs aren't for doing](https://sophiebits.com/2025/07/21/todos-arent-for-doing)
|
- [TODOs aren't for doing](https://sophiebits.com/2025/07/21/todos-arent-for-doing)
|
||||||
|
|
|
||||||
9
TODO.md
9
TODO.md
|
|
@ -1,7 +1,6 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
1. Create a setup/boot USB that: installs NixOS on the server with encryption and WiFi configured from the start; only required input is the server's name (e.g. sunken-ship).
|
- [ ] **USB installer**: Refine the installer USB workflow (`scripts/nixos-server-install.sh`, `disko-server.nix`, `installer-iso.nix`). Goal: boot USB, provide hostname, get a LUKS-encrypted NixOS server with WiFi ready to go.
|
||||||
* I have a set wifi SSID/PSK, assume servers will start up and be able to reach this wifi.
|
- [ ] **Encrypt sunken-ship**: Currently running on plain ext4. Needs reinstall with LUKS via disko, or in-place migration (backup, reformat, restore).
|
||||||
* I don't know how to go about the rest of this.
|
- [ ] **Tailscale**: Investigate setting up Tailscale mesh VPN across devices (sunken-ship, Mac, iPhone). Would allow SSH, AirPlay, and Claude Code remote sessions from anywhere. Free tier, ~5 lines of NixOS config. See: https://tailscale.com
|
||||||
|
- [ ] **Server alerting**: Get notified when a server goes down (power loss, crash, etc). Options: simple ping-based cron on Mac sending macOS notifications, or lightweight uptime monitor (Uptime Kuma on one of the servers).
|
||||||
2. Encrypt sunken-ship (LUKS); update hardware/config for encrypted root and boot.
|
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,54 @@
|
||||||
# Unified Theme Switching
|
# Alacritty + system appearance (macOS)
|
||||||
|
|
||||||
Unified theme switching that works across platforms (WSL and macOS) for Neovim, Alacritty, and Windows Terminal.
|
Alacritty follows **System Settings → Appearance** automatically. No `darwin-rebuild` when you change light/dark.
|
||||||
|
|
||||||
**This solution uses a single `theme` command that detects the platform and switches themes appropriately.**
|
## How it works
|
||||||
|
|
||||||
## How It Works
|
1. Home Manager installs Catppuccin palettes as `~/.config/alacritty/catppuccin-{latte,mocha}-colors.toml` and a generated `alacritty.toml` that sets `general.import` to `active-colors.toml`.
|
||||||
|
2. `scripts/alacritty-sync-system-theme.sh` copies the matching palette to `active-colors.toml`. Alacritty’s `live_config_reload` picks it up immediately.
|
||||||
|
3. **nix-darwin** runs that script from a user LaunchAgent every 30s (`nixos/hosts/daniel-macbook-air.nix`: `launchd.user.agents.alacritty-system-theme`). It is also installed on `PATH` as `alacritty-sync-system-theme`.
|
||||||
|
4. **Fish** runs the same script in the background when you open an interactive shell on Darwin, so changes apply quickly without waiting for the next poll.
|
||||||
|
|
||||||
1. The `theme` command detects the platform (WSL vs macOS)
|
## Optional manual LaunchAgent
|
||||||
2. **On WSL:** Updates Neovim, Windows Terminal, and Windows system theme
|
|
||||||
3. **On macOS:** Updates Neovim and Alacritty themes via Nix configuration
|
|
||||||
4. Uses the same `nvim_color_scheme` file for Neovim on both platforms
|
|
||||||
|
|
||||||
## Setup
|
If you are not using the nix-darwin agent, you can load `assets/launchd/com.user.alacritty-theme-sync.plist` (adjust paths if needed). **Do not** load both the nix-darwin agent and this plist or you will run two pollers.
|
||||||
|
|
||||||
1. **The configuration is already set up!** The `theme` command is available as a fish alias.
|
If you previously used the old plist label `com.user.alacritty-theme-sync` and switch to nix-darwin only:
|
||||||
|
|
||||||
2. **To switch themes, use the unified command:**
|
|
||||||
```bash
|
|
||||||
theme light # Switch to light theme
|
|
||||||
theme dark # Switch to dark theme
|
|
||||||
theme toggle # Toggle between light and dark themes
|
|
||||||
theme status # Show current theme status
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Unified Theme Command
|
|
||||||
```bash
|
```bash
|
||||||
# Switch to light theme (works on WSL and macOS)
|
launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/com.user.alacritty-theme-sync.plist 2>/dev/null || true
|
||||||
theme light
|
```
|
||||||
|
|
||||||
# Switch to dark theme (works on WSL and macOS)
|
## `theme` command (Neovim / WSL)
|
||||||
|
|
||||||
|
The fish alias `theme` still updates `~/.local/share/nvim_color_scheme` (and Windows Terminal on WSL). On macOS, **Alacritty ignores** `theme light|dark` for terminal colors—it only follows System Settings. Neovim stays on whatever you set with `theme`; the Alacritty sync script does not touch the nvim file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
theme light # Neovim (+ WSL terminal); macOS Alacritty unchanged (uses Appearance)
|
||||||
theme dark
|
theme dark
|
||||||
|
|
||||||
# Toggle between light and dark themes
|
|
||||||
theme toggle
|
theme toggle
|
||||||
|
|
||||||
# Show current theme status
|
|
||||||
theme status
|
theme status
|
||||||
```
|
```
|
||||||
|
|
||||||
### What Gets Updated
|
|
||||||
|
|
||||||
**On WSL:**
|
|
||||||
- Neovim theme (via `~/.local/share/nvim_color_scheme`)
|
|
||||||
- Windows Terminal settings
|
|
||||||
- Windows system theme
|
|
||||||
- Windows sound scheme
|
|
||||||
|
|
||||||
**On macOS:**
|
|
||||||
- Neovim theme (via `~/.local/share/nvim_color_scheme`)
|
|
||||||
- Alacritty theme (via Nix configuration)
|
|
||||||
|
|
||||||
### Manual Configuration (macOS only)
|
|
||||||
You can also manually edit `nixos/home/danny/home.nix` and change:
|
|
||||||
```nix
|
|
||||||
isLightTheme = true; # for light theme
|
|
||||||
isLightTheme = false; # for dark theme
|
|
||||||
```
|
|
||||||
Then run: `cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air`
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
- `scripts/theme.sh` - **Main unified theme switching script**
|
- `assets/alacritty/catppuccin-latte-colors.toml` / `catppuccin-mocha-colors.toml` — palette fragments
|
||||||
- `scripts/switch-alacritty-theme.sh` - Alacritty-specific theme switching (used by theme.sh)
|
- `scripts/alacritty-sync-system-theme.sh` — detect macOS appearance, copy palette, refresh nvim marker
|
||||||
- `scripts/detect-system-theme.sh` - Detects current macOS system theme (for reference)
|
- `scripts/sync-alacritty-theme.sh` — thin wrapper (backwards compatible)
|
||||||
- `nixos/fish.nix` - Contains the `theme` fish alias
|
- `nixos/home/danny/home.nix` — `programs.alacritty` + `xdg.configFile` for palettes
|
||||||
- `nixos/home/danny/home.nix` - Contains the conditional Alacritty configuration
|
- `nixos/hosts/daniel-macbook-air.nix` — LaunchAgent + `alacritty-sync-system-theme` in `environment.systemPackages`
|
||||||
- `bashscripts/wsl_theme.sh` - Legacy WSL script (replaced by theme.sh)
|
- `nixos/fish.nix` — optional shell-open sync on Darwin
|
||||||
|
|
||||||
## Theme Colors
|
After changing Nix config, run `darwin-rebuild switch` once (see repo `AGENTS.md`).
|
||||||
|
|
||||||
|
## Theme colors
|
||||||
|
|
||||||
### Catppuccin Latte (Light)
|
### Catppuccin Latte (Light)
|
||||||
|
|
||||||
- Background: `#eff1f5` (base)
|
- Background: `#eff1f5` (base)
|
||||||
- Foreground: `#4c4f69` (text)
|
- Foreground: `#4c4f69` (text)
|
||||||
- Accent colors optimized for light backgrounds
|
|
||||||
|
|
||||||
### Catppuccin Mocha (Dark)
|
### Catppuccin Mocha (Dark)
|
||||||
|
|
||||||
- Background: `#1e1e2e` (base)
|
- Background: `#1e1e2e` (base)
|
||||||
- Foreground: `#cdd6f4` (text)
|
- Foreground: `#cdd6f4` (text)
|
||||||
- Accent colors optimized for dark backgrounds
|
|
||||||
|
|
||||||
## Integration with NixOS
|
|
||||||
|
|
||||||
The solution uses Nix's conditional configuration in `home.nix`:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
colors = let
|
|
||||||
isLightTheme = true; # Change this to switch themes
|
|
||||||
|
|
||||||
lightColors = { /* Catppuccin Latte colors */ };
|
|
||||||
darkColors = { /* Catppuccin Mocha colors */ };
|
|
||||||
in if isLightTheme then lightColors else darkColors;
|
|
||||||
```
|
|
||||||
|
|
||||||
This approach:
|
|
||||||
- ✅ Works with Spotlight/Applications folder launches
|
|
||||||
- ✅ No complex file reading or external dependencies
|
|
||||||
- ✅ Integrates cleanly with NixOS configuration
|
|
||||||
- ✅ Simple and reliable - just change a boolean and rebuild
|
|
||||||
- ✅ Easy to understand and maintain
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# Catppuccin Mocha (Dark) theme for Alacritty
|
|
||||||
[colors.primary]
|
|
||||||
background = "0x1e1e2e" # base
|
|
||||||
foreground = "0xcdd6f4" # text
|
|
||||||
|
|
||||||
[colors.cursor]
|
|
||||||
text = "0x1e1e2e" # base
|
|
||||||
cursor = "0xf5e0dc" # rosewater
|
|
||||||
|
|
||||||
[colors.normal]
|
|
||||||
black = "0x45475a" # surface1
|
|
||||||
red = "0xf38ba8" # red
|
|
||||||
green = "0xa6e3a1" # green
|
|
||||||
yellow = "0xf9e2af" # yellow
|
|
||||||
blue = "0x89b4fa" # blue
|
|
||||||
magenta = "0xf5c2e7" # pink
|
|
||||||
cyan = "0x94e2d5" # teal
|
|
||||||
white = "0xbac2de" # subtext1
|
|
||||||
|
|
||||||
[colors.bright]
|
|
||||||
black = "0x585b70" # surface2
|
|
||||||
red = "0xf38ba8" # red
|
|
||||||
green = "0xa6e3a1" # green
|
|
||||||
yellow = "0xf9e2af" # yellow
|
|
||||||
blue = "0x89b4fa" # blue
|
|
||||||
magenta = "0xf5c2e7" # pink
|
|
||||||
cyan = "0x94e2d5" # teal
|
|
||||||
white = "0xa6adc8" # subtext0
|
|
||||||
29
assets/alacritty/catppuccin-latte-colors.toml
Normal file
29
assets/alacritty/catppuccin-latte-colors.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Catppuccin Latte — imported by main alacritty.toml; swapped by sync script.
|
||||||
|
|
||||||
|
[colors.primary]
|
||||||
|
background = "#eff1f5"
|
||||||
|
foreground = "#4c4f69"
|
||||||
|
|
||||||
|
[colors.cursor]
|
||||||
|
text = "#eff1f5"
|
||||||
|
cursor = "#dc8a78"
|
||||||
|
|
||||||
|
[colors.normal]
|
||||||
|
black = "#5c5f77"
|
||||||
|
red = "#d20f39"
|
||||||
|
green = "#40a02b"
|
||||||
|
yellow = "#df8e1d"
|
||||||
|
blue = "#1e40af"
|
||||||
|
magenta = "#ea76cb"
|
||||||
|
cyan = "#179299"
|
||||||
|
white = "#acb0be"
|
||||||
|
|
||||||
|
[colors.bright]
|
||||||
|
black = "#6c6f85"
|
||||||
|
red = "#d20f39"
|
||||||
|
green = "#40a02b"
|
||||||
|
yellow = "#df8e1d"
|
||||||
|
blue = "#1e40af"
|
||||||
|
magenta = "#ea76cb"
|
||||||
|
cyan = "#179299"
|
||||||
|
white = "#bcc0cc"
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# Catppuccin Latte (Light) theme for Alacritty
|
|
||||||
[colors.primary]
|
|
||||||
background = "0xeff1f5" # base
|
|
||||||
foreground = "0x4c4f69" # text
|
|
||||||
|
|
||||||
[colors.cursor]
|
|
||||||
text = "0xeff1f5" # base
|
|
||||||
cursor = "0xdc8a78" # rosewater
|
|
||||||
|
|
||||||
[colors.normal]
|
|
||||||
black = "0x5c5f77" # surface1
|
|
||||||
red = "0xd20f39" # red
|
|
||||||
green = "0x40a02b" # green
|
|
||||||
yellow = "0xdf8e1d" # yellow
|
|
||||||
blue = "0x1e40af" # blue
|
|
||||||
magenta = "0xea76cb" # pink
|
|
||||||
cyan = "0x179299" # teal
|
|
||||||
white = "0xacb0be" # subtext1
|
|
||||||
|
|
||||||
[colors.bright]
|
|
||||||
black = "0x6c6f85" # surface2
|
|
||||||
red = "0xd20f39" # red
|
|
||||||
green = "0x40a02b" # green
|
|
||||||
yellow = "0xdf8e1d" # yellow
|
|
||||||
blue = "0x1e40af" # blue
|
|
||||||
magenta = "0xea76cb" # pink
|
|
||||||
cyan = "0x179299" # teal
|
|
||||||
white = "0xbcc0cc" # subtext0
|
|
||||||
29
assets/alacritty/catppuccin-mocha-colors.toml
Normal file
29
assets/alacritty/catppuccin-mocha-colors.toml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Catppuccin Mocha — imported by main alacritty.toml; swapped by sync script.
|
||||||
|
|
||||||
|
[colors.primary]
|
||||||
|
background = "#1e1e2e"
|
||||||
|
foreground = "#cdd6f4"
|
||||||
|
|
||||||
|
[colors.cursor]
|
||||||
|
text = "#1e1e2e"
|
||||||
|
cursor = "#f5e0dc"
|
||||||
|
|
||||||
|
[colors.normal]
|
||||||
|
black = "#45475a"
|
||||||
|
red = "#f38ba8"
|
||||||
|
green = "#a6e3a1"
|
||||||
|
yellow = "#f9e2af"
|
||||||
|
blue = "#89b4fa"
|
||||||
|
magenta = "#f5c2e7"
|
||||||
|
cyan = "#94e2d5"
|
||||||
|
white = "#bac2de"
|
||||||
|
|
||||||
|
[colors.bright]
|
||||||
|
black = "#585b70"
|
||||||
|
red = "#f38ba8"
|
||||||
|
green = "#a6e3a1"
|
||||||
|
yellow = "#f9e2af"
|
||||||
|
blue = "#89b4fa"
|
||||||
|
magenta = "#f5c2e7"
|
||||||
|
cyan = "#94e2d5"
|
||||||
|
white = "#a6adc8"
|
||||||
|
|
@ -4,28 +4,18 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>Label</key>
|
<key>Label</key>
|
||||||
<string>com.user.alacritty-theme-sync</string>
|
<string>com.user.alacritty-theme-sync</string>
|
||||||
|
|
||||||
<key>ProgramArguments</key>
|
<key>ProgramArguments</key>
|
||||||
<array>
|
<array>
|
||||||
<string>/Users/danny/dotfiles/scripts/sync-alacritty-theme.sh</string>
|
<string>/bin/bash</string>
|
||||||
|
<string>/Users/danny/dotfiles/scripts/alacritty-sync-system-theme.sh</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>StartInterval</key>
|
<key>StartInterval</key>
|
||||||
<integer>30</integer>
|
<integer>30</integer>
|
||||||
|
|
||||||
<key>RunAtLoad</key>
|
<key>RunAtLoad</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<key>StandardOutPath</key>
|
<key>StandardOutPath</key>
|
||||||
<string>/tmp/alacritty-theme-sync.log</string>
|
<string>/tmp/alacritty-theme-sync.log</string>
|
||||||
|
|
||||||
<key>StandardErrorPath</key>
|
<key>StandardErrorPath</key>
|
||||||
<string>/tmp/alacritty-theme-sync-error.log</string>
|
<string>/tmp/alacritty-theme-sync-error.log</string>
|
||||||
|
|
||||||
<key>EnvironmentVariables</key>
|
|
||||||
<dict>
|
|
||||||
<key>PATH</key>
|
|
||||||
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
53
assets/zed/settings.json
Normal file
53
assets/zed/settings.json
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Zed settings — tracked in dotfiles, symlinked into ~/.config/zed/settings.json
|
||||||
|
// by home-manager (xdg.configFile in nixos/home/danny/home.nix).
|
||||||
|
//
|
||||||
|
// Because this is a symlink to a nix-store file, editing it from inside Zed
|
||||||
|
// will fail (read-only). Edit THIS file in dotfiles, commit, and rebuild
|
||||||
|
// (`darwin-rebuild switch --flake .`). To see Zed's full default settings,
|
||||||
|
// run `zed: open default settings` from the command palette.
|
||||||
|
{
|
||||||
|
"sticky_scroll": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"edit_predictions": {
|
||||||
|
"provider": "ollama"
|
||||||
|
},
|
||||||
|
"buffer_font_family": "JetBrains Mono",
|
||||||
|
"cli_default_open_behavior": "existing_window",
|
||||||
|
"project_panel": {
|
||||||
|
"dock": "left"
|
||||||
|
},
|
||||||
|
"outline_panel": {
|
||||||
|
"dock": "left"
|
||||||
|
},
|
||||||
|
"collaboration_panel": {
|
||||||
|
"dock": "left"
|
||||||
|
},
|
||||||
|
"git_panel": {
|
||||||
|
"dock": "left"
|
||||||
|
},
|
||||||
|
"agent": {
|
||||||
|
"dock": "right",
|
||||||
|
"default_model": {
|
||||||
|
"provider": "ollama",
|
||||||
|
"model": "llama3.2:latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disable_ai": false,
|
||||||
|
"minimap": {
|
||||||
|
"show": "auto"
|
||||||
|
},
|
||||||
|
"telemetry": {
|
||||||
|
"diagnostics": false,
|
||||||
|
"metrics": false
|
||||||
|
},
|
||||||
|
"base_keymap": "VSCode",
|
||||||
|
"vim_mode": true,
|
||||||
|
"ui_font_size": 16,
|
||||||
|
"buffer_font_size": 15,
|
||||||
|
"theme": {
|
||||||
|
"mode": "system",
|
||||||
|
"light": "One Light",
|
||||||
|
"dark": "One Dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Two-word hostnames, non-human / non-specific.
|
Two-word hostnames, non-human / non-specific.
|
||||||
|
|
||||||
- **Ships / sea:** sunken-ship, phantom-ship, rusty-anchor, salty-wind, stormy-wave, calm-harbor, distant-shore, foreign-port, wooden-hull, anchor-chain
|
- **Ships / sea:** sunken-ship ✓, phantom-ship ✓, **rusty-anchor** (next), salty-wind, stormy-wave, calm-harbor, distant-shore, foreign-port, wooden-hull, anchor-chain
|
||||||
- **Prison / stone:** prison-rock, cold-stone, iron-chain, damp-cell, guard-tower, midnight-bell, stony-corridor, broken-chain
|
- **Prison / stone:** prison-rock, cold-stone, iron-chain, damp-cell, guard-tower, midnight-bell, stony-corridor, broken-chain
|
||||||
- **Secrets / treasure:** buried-treasure, secret-cave, forgotten-tunnel, hidden-key, rusty-sword, faded-parchment, ancient-map, broken-seal, buried-chest
|
- **Secrets / treasure:** buried-treasure, secret-cave, forgotten-tunnel, hidden-key, rusty-sword, faded-parchment, ancient-map, broken-seal, buried-chest
|
||||||
- **Atmosphere:** strange-companion, masked-ball, poison-vial
|
- **Atmosphere:** strange-companion, masked-ball, poison-vial
|
||||||
|
|
|
||||||
|
|
@ -1,180 +1,163 @@
|
||||||
# Server installer USB (NixOS + LUKS + WiFi)
|
# Server installer USB (NixOS + LUKS)
|
||||||
|
|
||||||
Bootable USB that installs NixOS on a new server with disk encryption (LUKS) and optional WiFi from first boot. Only required input is the hostname (and LUKS passphrase when disko creates the volume). Existing hosts are not modified.
|
Bootable USB that installs NixOS on a new server with disk encryption (LUKS). The install script handles partitioning, encryption, dotfiles cloning, SSH key setup, and hardware config generation. Only required inputs: hostname, LUKS passphrase, and target disk.
|
||||||
|
|
||||||
## Quick path: boot USB → WiFi → SSH in → run bootstrap
|
## Quick path (Ethernet server like phantom-ship)
|
||||||
|
|
||||||
1. Boot the target machine from the NixOS installer USB.
|
### Prep (on sunken-ship or any Linux box)
|
||||||
2. On the live system, connect to Wi‑Fi (or plug in Ethernet). Check internet (e.g. `ping -c 2 8.8.8.8`).
|
|
||||||
3. On the **live** system, start SSH and set a password for the `nixos` user so you can log in from your Mac:
|
1. Download the [NixOS minimal ISO](https://nixos.org/download.html#nixos-iso) on sunken-ship.
|
||||||
|
2. Plug in USB and write the ISO:
|
||||||
|
```bash
|
||||||
|
# Find your USB device (e.g. /dev/sdc)
|
||||||
|
lsblk
|
||||||
|
sudo dd if=nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M
|
||||||
|
sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install (on the new server)
|
||||||
|
|
||||||
|
3. Boot the new machine from USB, plug in Ethernet, verify connectivity (`ping 8.8.8.8`).
|
||||||
|
4. Start SSH on the live system so you can paste commands from your Mac:
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl start sshd
|
sudo systemctl start sshd
|
||||||
sudo passwd nixos
|
sudo passwd nixos
|
||||||
hostname -I
|
hostname -I # note the IP
|
||||||
```
|
```
|
||||||
Note the IP from `hostname -I`.
|
5. From your **Mac**, scp your SSH public key and SSH in:
|
||||||
4. From your **Mac**: `ssh nixos@<IP>` (use the password you set). Now you can paste the bootstrap command instead of typing on the machine.
|
|
||||||
5. In that SSH session, run the bootstrap (installs NixOS with LUKS; prompts for hostname, disk, **danny password**, LUKS passphrase, then once more LUKS to set the password on disk):
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
scp ~/.ssh/id_ed25519_phantom_ship.pub nixos@<IP>:/tmp/key.pub
|
||||||
|
ssh nixos@<IP>
|
||||||
```
|
```
|
||||||
6. When it finishes, reboot and remove the USB. Unlock LUKS at boot, then log in as **danny** with the password you set during the install.
|
6. Run the bootstrap (one command):
|
||||||
|
```bash
|
||||||
|
curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/bootstrap-install.sh | \
|
||||||
|
INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash
|
||||||
|
```
|
||||||
|
This will prompt for: target disk, optional danny password, confirmation, and LUKS passphrase (twice: once for disko, once for post-install provisioning).
|
||||||
|
|
||||||
## Option A: Official NixOS ISO (works from macOS)
|
The script automatically:
|
||||||
|
- Partitions and encrypts the disk (LUKS + ext4)
|
||||||
|
- Installs NixOS with the hostname
|
||||||
|
- Clones dotfiles to `/etc/dotfiles`
|
||||||
|
- Installs your SSH public key
|
||||||
|
- Generates `phantom-ship-hardware.nix`
|
||||||
|
|
||||||
You **cannot** build the custom installer ISO on macOS (it is x86_64-linux only and `--system` is restricted). Use the official NixOS minimal ISO instead:
|
7. Reboot, remove USB, unlock LUKS.
|
||||||
|
|
||||||
1. Download the [minimal ISO](https://nixos.org/download.html#nixos-iso) (e.g. `nixos-minimal-*-x86_64-linux.iso`).
|
### After first boot
|
||||||
2. Write it to your USB (on macOS: `diskutil unmountDisk diskN`, then `sudo dd if=path/to/nixos-minimal-*.iso of=/dev/rdiskN bs=4m`).
|
|
||||||
3. Boot the server from the USB. Attach Ethernet or use the **graphical** ISO if you need Wi‑Fi on the live system.
|
8. SSH in: `ssh danny@phantom-ship`
|
||||||
4. On the live system, clone this repo and run the install script (see [Install on the server](#install-on-the-server) below). The script runs `disko-install` and does LUKS + hostname; no custom ISO needed.
|
9. First rebuild to switch from generic `server-install` to `phantom-ship` config:
|
||||||
|
```bash
|
||||||
|
cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#phantom-ship
|
||||||
|
```
|
||||||
|
10. Commit the generated `phantom-ship-hardware.nix` back to the repo.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
All optional; skip interactive prompts or add automation:
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `INSTALLER_HOSTNAME` | Skip hostname prompt |
|
||||||
|
| `INSTALLER_DISK` | Skip disk prompt (validated as block device) |
|
||||||
|
| `SSH_PUBKEY_FILE` | Path to `.pub` file; installed to danny's `authorized_keys` |
|
||||||
|
| `FLAKE_REF` | Override flake reference (default: auto-detect from repo) |
|
||||||
|
| `INSTALLER_SYSTEM_CONFIG_FILE` | JSON file merged into `--system-config` (e.g. WiFi config) |
|
||||||
|
|
||||||
|
## Option A: Official NixOS ISO (recommended)
|
||||||
|
|
||||||
|
Cannot build the custom ISO on macOS (x86_64-linux only). Use the official NixOS minimal ISO:
|
||||||
|
|
||||||
|
1. Download from [nixos.org](https://nixos.org/download.html#nixos-iso).
|
||||||
|
2. Write to USB from sunken-ship or any Linux box.
|
||||||
|
3. Boot, connect Ethernet, run bootstrap.
|
||||||
|
|
||||||
## Option B: Custom ISO (build on Linux only)
|
## Option B: Custom ISO (build on Linux only)
|
||||||
|
|
||||||
The custom ISO adds Wi‑Fi kernel modules and optional live Wi‑Fi; it must be built on **x86_64-linux** (or with a Nix remote builder configured for that system). Building on macOS will fail.
|
Adds WiFi kernel modules for servers that need WiFi on the live system.
|
||||||
|
|
||||||
### Build from sunken-ship (one command from your Mac)
|
### Build from sunken-ship
|
||||||
|
|
||||||
When the server is on the same network, run from the dotfiles repo:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./scripts/build-installer-iso-on-server.sh
|
./scripts/build-installer-iso-on-server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
This pushes the branch, SSHs to sunken-ship, clones the repo there, runs `nix build .#installer-iso`, and copies the ISO back to the current directory. Optional: `./scripts/build-installer-iso-on-server.sh sunken-ship /path/to/output`.
|
### Build directly on Linux
|
||||||
|
|
||||||
### Build directly on a Linux machine
|
|
||||||
|
|
||||||
From a Linux box (or on sunken-ship after SSH in):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/dotfiles/nixos
|
cd ~/dotfiles && nix build .#installer-iso
|
||||||
nix build .#installer-iso
|
# Write to USB:
|
||||||
|
sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M
|
||||||
```
|
```
|
||||||
|
|
||||||
The image is at `result/iso/nixos-minimal-*.iso`. Write it to a USB stick (replace `sdX` with your device, e.g. `sda`):
|
## Live-system WiFi (optional, custom ISO only)
|
||||||
|
|
||||||
```bash
|
The minimal installer ISO runs NetworkManager, so live-system WiFi must be a
|
||||||
# Linux
|
declarative NetworkManager profile. `networking.wireless` / wpa_supplicant does
|
||||||
sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress
|
**not** work here — NixOS asserts you cannot combine `networking.networkmanager`
|
||||||
sync
|
with `networking.wireless.networks`.
|
||||||
```
|
|
||||||
|
|
||||||
On macOS, use the disk number (e.g. `4` for `disk4`):
|
Create `nixos/installer-wifi.nix` (gitignored — it holds the PSK):
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/rdisk4 bs=4m
|
|
||||||
diskutil eject disk4
|
|
||||||
```
|
|
||||||
|
|
||||||
Or adapt [scripts/make-ubuntu-usb.sh](../scripts/make-ubuntu-usb.sh) for the NixOS ISO path.
|
|
||||||
|
|
||||||
## Live-system WiFi (optional)
|
|
||||||
|
|
||||||
So the live system can reach the network (and fetch the flake) without Ethernet, add WiFi to the ISO at **build time**. Do not put SSID/PSK in the repo.
|
|
||||||
|
|
||||||
1. Create **`nixos/installer-wifi.nix`** (gitignored) with your network:
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
networking.wireless.enable = true;
|
networking.networkmanager.ensureProfiles.profiles.installer-wifi = {
|
||||||
networking.wireless.networks."YourSSID".psk = "your-password";
|
connection = {
|
||||||
|
id = "installer-wifi";
|
||||||
|
type = "wifi";
|
||||||
|
};
|
||||||
|
wifi = {
|
||||||
|
mode = "infrastructure";
|
||||||
|
ssid = "YourSSID";
|
||||||
|
};
|
||||||
|
wifi-security = {
|
||||||
|
auth-alg = "open";
|
||||||
|
key-mgmt = "wpa-psk";
|
||||||
|
psk = "your-password";
|
||||||
|
};
|
||||||
|
ipv4.method = "auto";
|
||||||
|
ipv6.method = "auto";
|
||||||
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Add it to the flake for the installer ISO only. In `nixos/flake.nix`, change the `installer-iso` modules to:
|
`flake-modules/installer-iso.nix` auto-includes this file when present (via a
|
||||||
|
`builtins.pathExists` check) — no flake edit needed. Because the file is
|
||||||
|
gitignored, the flake only sees it once it is staged:
|
||||||
|
|
||||||
```nix
|
- **`build-installer-iso-on-server.sh`** copies the file to the build host and
|
||||||
installer-iso = nixpkgs.lib.nixosSystem {
|
runs `git add -f` automatically.
|
||||||
system = "x86_64-linux";
|
- For a **direct `nix build`**, run `git add -f nixos/installer-wifi.nix` first
|
||||||
modules = [ ./installer-iso.nix ./installer-wifi.nix ]; # add installer-wifi.nix
|
(staging is enough — never commit it; it contains the PSK).
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Ensure `nixos/installer-wifi.nix` is in `.gitignore`, then rebuild the ISO.
|
Then rebuild the ISO on Linux.
|
||||||
|
|
||||||
If you skip this, use Ethernet on the live system or the graphical NixOS installer to join Wi‑Fi, then run the install script.
|
## Installed-system WiFi (optional)
|
||||||
|
|
||||||
## Install on the server
|
Pass a JSON file with wireless config:
|
||||||
|
|
||||||
1. Boot the server from the USB.
|
|
||||||
2. If you did not bake WiFi into the ISO, attach Ethernet or (on graphical installer) join Wi‑Fi so the machine has network.
|
|
||||||
3. Run **one** of the following (shortest first).
|
|
||||||
|
|
||||||
**Shortest — fetch and run (no clone step):**
|
|
||||||
Exact URL (watch for typos: **.com** not .con, **usb** not ush, **DannyDannyDanny** with three capital Ds):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi.json INSTALLER_HOSTNAME=my-server ./scripts/nixos-server-install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see `bash: 404: command not found`, the URL was wrong or the branch doesn’t exist. Check the URL, or verify first: `curl -sL "THE_URL_ABOVE" | head -1` should show `#!/bin/bash`, not HTML.
|
|
||||||
|
|
||||||
To type less, create a [git.io](https://git.io) short link once (paste the raw URL above), then on the machine run: `curl -sL https://git.io/YOUR_CODE | sudo bash`.
|
|
||||||
|
|
||||||
**Alternative — clone then run** (if you prefer not to pipe curl to bash):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- clone https://github.com/USER/REPO.git /tmp/dotfiles && cd /tmp/dotfiles && git checkout server-installer-usb && sudo ./scripts/nixos-server-install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you see `command not found` when running the script, use `sudo bash ./scripts/nixos-server-install.sh` instead of `sudo ./scripts/...`.
|
|
||||||
|
|
||||||
4. When prompted: enter **hostname** (e.g. `phantom-ship`), then **target disk** (default `/dev/sda`), then **y** to proceed. When disko creates the LUKS volume, enter your encryption passphrase.
|
|
||||||
5. When the script finishes, remove the USB and reboot. The new NixOS system will have LUKS root and the hostname you chose.
|
|
||||||
|
|
||||||
## WiFi on the installed system (optional)
|
|
||||||
|
|
||||||
To have WiFi configured from first boot (no manual step after reboot):
|
|
||||||
|
|
||||||
1. Create a JSON file **outside the repo** with the config to merge (hostname is set by the script from the prompt):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"networking": {
|
|
||||||
"wireless": {
|
|
||||||
"networks": {
|
|
||||||
"YourSSID": { "psk": "your-password" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Copy that file onto the live system (e.g. put it on the USB or scp it). If the script is run with `jq` available and `INSTALLER_SYSTEM_CONFIG_FILE` set to that file, the script will merge it and set the hostname:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi-config.json ./scripts/nixos-server-install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you omit this, the installed system still has `networking.wireless.enable = true`. Add credentials after first boot (e.g. [imperative wpa_supplicant config](sunken-ship-wifi.md)).
|
|
||||||
|
|
||||||
## Manual install (without the script)
|
## Manual install (without the script)
|
||||||
|
|
||||||
You can run disko-install yourself:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo nix run github:nix-community/disko/latest#disko-install -- \
|
sudo nix run github:nix-community/disko/latest#disko-install -- \
|
||||||
--flake 'path:/tmp/dotfiles/nixos#server-install' \
|
--flake 'path:/tmp/dotfiles#server-install' \
|
||||||
--disk main /dev/sda \
|
--disk main /dev/sda \
|
||||||
--system-config '{"networking":{"hostName":"my-server"}}'
|
--system-config '{"networking":{"hostName":"my-server"}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Adjust the flake path and `--system-config` (e.g. add WiFi) as needed.
|
|
||||||
|
|
||||||
## After install
|
|
||||||
|
|
||||||
- Add your SSH key: from your machine `scp ~/.ssh/id_ed25519_servers.pub danny@NEW-SERVER:/tmp/`, then on the server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`.
|
|
||||||
- To switch this machine to another host config in the same flake (e.g. a full server profile), clone the repo on the new system and run `sudo nixos-rebuild switch --flake /path/to/nixos#other-host`.
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
| Step | Action |
|
| Step | Action |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| **From macOS** | Use Option A: download official NixOS minimal ISO, write to USB, boot server, clone repo, run install script. |
|
| **Prep** | Download NixOS minimal ISO on sunken-ship, write to USB |
|
||||||
| **From Linux** | Option B: `nix build .#installer-iso` in `nixos/`, then write `result/iso/*.iso` to USB. |
|
| **Boot** | Boot new server from USB, plug Ethernet |
|
||||||
| Optional live WiFi | (Custom ISO only) Add `installer-wifi.nix` (gitignored), include in flake, rebuild on Linux. |
|
| **Install** | `curl ... \| INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash` |
|
||||||
| Boot | Boot server from USB |
|
| **Reboot** | Remove USB, unlock LUKS |
|
||||||
| Install | On live system: `curl -sL https://raw.githubusercontent.com/.../server-installer-usb/scripts/bootstrap-install.sh | sudo bash` (or clone then `sudo ./scripts/nixos-server-install.sh`) |
|
| **First rebuild** | `sudo nixos-rebuild switch --flake /etc/dotfiles#phantom-ship` |
|
||||||
| Optional installed WiFi | Set `INSTALLER_SYSTEM_CONFIG_FILE` to a JSON file with wireless config |
|
| **Commit** | Push generated `phantom-ship-hardware.nix` to repo |
|
||||||
| Reboot | Remove USB, reboot; set root password if needed, add SSH keys |
|
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@ nix shell nixpkgs#wpa_supplicant -c wpa_passphrase "YOUR_SSID" "YOUR_PASSWORD"
|
||||||
|
|
||||||
## Rebuild (after changing Nix config)
|
## Rebuild (after changing Nix config)
|
||||||
|
|
||||||
From the server (flake is in `nixos/`):
|
From the server (flake is at the repo root):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship
|
cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verify
|
## Verify
|
||||||
|
|
|
||||||
254
flake-modules/clan.nix
Normal file
254
flake-modules/clan.nix
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
# clan.lol wiring for the homelab.
|
||||||
|
#
|
||||||
|
# Declares `sunken-ship` and `phantom-ship` as clan machines. Each machine's
|
||||||
|
# `imports` list is the NixOS module set that used to live in its own
|
||||||
|
# flake-module. clan-core produces `flake.nixosConfigurations.<name>` from
|
||||||
|
# these, which is why the old per-host flake-modules were removed.
|
||||||
|
#
|
||||||
|
# The mac stays outside the clan — admin only, uses `clan machines update`
|
||||||
|
# to push to the servers.
|
||||||
|
{ config, inputs, ... }:
|
||||||
|
let
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
hmModule = { user, homeDirectory, stateVersion ? null, userImports ? [ ] }:
|
||||||
|
import ../lib/home-manager-user.nix {
|
||||||
|
inherit lib user homeDirectory stateVersion userImports;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ZT IPv6 addresses of the two clan machines. Clan publishes these as
|
||||||
|
# generated vars at vars/per-machine/<host>/zerotier/zerotier-ip/value;
|
||||||
|
# duplicated here so we can drop them into /etc/hosts at module-eval time.
|
||||||
|
sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33";
|
||||||
|
phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc";
|
||||||
|
vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3";
|
||||||
|
distantShoreZTv6 = "fdd5:53a2:de33:d269:6499:93b6:ef1a:c3b3";
|
||||||
|
foreignPortZTv6 = "fdd5:53a2:de33:d269:6499:9389:9b18:6c52";
|
||||||
|
|
||||||
|
# Shared across both servers: /etc/hosts entries so data-mesher's
|
||||||
|
# libp2p /dns/<machine>.clan/... bootstrap multiaddrs resolve over ZT.
|
||||||
|
clanHostsModule = {
|
||||||
|
networking.hosts = {
|
||||||
|
"${sunkenShipZTv6}" = [ "sunken-ship.clan" ];
|
||||||
|
"${phantomShipZTv6}" = [ "phantom-ship.clan" ];
|
||||||
|
"${vpsRelayZTv6}" = [ "vps-relay.clan" ];
|
||||||
|
"${distantShoreZTv6}" = [ "distant-shore.clan" ];
|
||||||
|
"${foreignPortZTv6}" = [ "foreign-port.clan" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = [ inputs.clan-core.flakeModules.default ];
|
||||||
|
|
||||||
|
clan = {
|
||||||
|
meta.name = "homelab";
|
||||||
|
# data-mesher uses `<machine>.${domain}` as a libp2p /dns/ multiaddr.
|
||||||
|
# We don't run a DNS server for "clan" — per-machine networking.hosts
|
||||||
|
# entries (via clanHostsModule) resolve it to the host's ZT IPv6.
|
||||||
|
meta.domain = "clan";
|
||||||
|
|
||||||
|
# Inventory machines — required for `inventory.instances` role bindings
|
||||||
|
# to resolve. Host-specific NixOS config lives under `machines.<name>`
|
||||||
|
# below.
|
||||||
|
inventory.machines.sunken-ship = { };
|
||||||
|
inventory.machines.phantom-ship = { };
|
||||||
|
inventory.machines.vps-relay = { };
|
||||||
|
inventory.machines.distant-shore = { };
|
||||||
|
inventory.machines.foreign-port = { };
|
||||||
|
|
||||||
|
# ZeroTier mesh VPN. sunken-ship is the controller (manages network
|
||||||
|
# membership); phantom-ship is a peer. The mac joins manually as an
|
||||||
|
# external ZT client and is authorized on the controller by node ID.
|
||||||
|
inventory.instances.zerotier = {
|
||||||
|
module.name = "zerotier";
|
||||||
|
module.input = "clan-core";
|
||||||
|
roles.controller.machines.sunken-ship = { };
|
||||||
|
roles.peer.machines.phantom-ship = { };
|
||||||
|
roles.peer.machines.sunken-ship = { };
|
||||||
|
roles.peer.machines.vps-relay = { };
|
||||||
|
roles.peer.machines.distant-shore = { };
|
||||||
|
roles.peer.machines.foreign-port = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
# data-mesher — signed-file gossip protocol over libp2p (port 7946).
|
||||||
|
# Underpins dm-pull-deploy below. Files are registered + their allowed
|
||||||
|
# signers managed automatically via clan service exports.
|
||||||
|
# sunken-ship is the bootstrap node; phantom-ship joins via its
|
||||||
|
# /dns/sunken-ship.clan/... multiaddr (resolved via /etc/hosts).
|
||||||
|
inventory.instances.data-mesher = {
|
||||||
|
module.name = "data-mesher";
|
||||||
|
module.input = "clan-core";
|
||||||
|
roles.default.machines.sunken-ship = { };
|
||||||
|
roles.default.machines.phantom-ship = { };
|
||||||
|
roles.default.machines.distant-shore = { };
|
||||||
|
roles.default.machines.foreign-port = { };
|
||||||
|
roles.bootstrap.machines.sunken-ship = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
# dm-pull-deploy — pull-based NixOS deploy via data-mesher gossip.
|
||||||
|
# Our clan-community input is pinned to the branch that sanitizes
|
||||||
|
# machine.name for the status file name (upstream PR pending).
|
||||||
|
# sunken-ship is the push node; both servers run the default watcher
|
||||||
|
# with action="switch".
|
||||||
|
inventory.instances.dm-pull-deploy = {
|
||||||
|
module.name = "dm-pull-deploy";
|
||||||
|
module.input = "clan-community";
|
||||||
|
roles.push.machines.sunken-ship.settings = {
|
||||||
|
gitUrl = "https://github.com/DannyDannyDanny/dotfiles.git";
|
||||||
|
branch = "main";
|
||||||
|
};
|
||||||
|
roles.default.machines.sunken-ship.settings.action = "switch";
|
||||||
|
roles.default.machines.phantom-ship.settings.action = "switch";
|
||||||
|
roles.default.machines.distant-shore.settings.action = "switch";
|
||||||
|
roles.default.machines.foreign-port.settings.action = "switch";
|
||||||
|
};
|
||||||
|
|
||||||
|
# `clan machines update` connection target. Priority 2000 > ZT's 900
|
||||||
|
# and overrides the ZT service's root@ default. Using the ZT IPv6 as
|
||||||
|
# the host makes updates work regardless of LAN DNS / mDNS state.
|
||||||
|
inventory.instances.internet = {
|
||||||
|
module.name = "internet";
|
||||||
|
module.input = "clan-core";
|
||||||
|
roles.default.machines.sunken-ship.settings = {
|
||||||
|
host = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33";
|
||||||
|
user = "danny";
|
||||||
|
};
|
||||||
|
roles.default.machines.phantom-ship.settings = {
|
||||||
|
host = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc";
|
||||||
|
user = "danny";
|
||||||
|
};
|
||||||
|
# Using public IPv4 while ZT identity is being bootstrapped on the
|
||||||
|
# VPS. Swap to ZT IPv6 (fdd5:53a2:de33:d269:6499:9305:339f:2ed3)
|
||||||
|
# after the first clan update uploads SOPS keys and zerotierone
|
||||||
|
# restarts with the clan-managed identity.
|
||||||
|
roles.default.machines.vps-relay.settings = {
|
||||||
|
host = "89.167.39.251";
|
||||||
|
user = "danny";
|
||||||
|
};
|
||||||
|
# distant-shore: LAN IP for the first update (not yet on ZT). Swap to
|
||||||
|
# its generated ZT IPv6 after it joins the mesh, like the others.
|
||||||
|
roles.default.machines.distant-shore.settings = {
|
||||||
|
host = "192.168.1.182";
|
||||||
|
user = "danny";
|
||||||
|
};
|
||||||
|
# foreign-port: WiFi-only LAN IP for the first update; swap to its
|
||||||
|
# generated ZT IPv6 after it joins the mesh.
|
||||||
|
roles.default.machines.foreign-port.settings = {
|
||||||
|
host = "192.168.1.223";
|
||||||
|
user = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Preserve current network / init stack (no systemd-networkd/resolved,
|
||||||
|
# no boot.initrd.systemd, no extra debug packages). Revisit per-service
|
||||||
|
# in later stages rather than flipping this fleet-wide.
|
||||||
|
machines.sunken-ship = {
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
clan.core.enableRecommendedDefaults = false;
|
||||||
|
clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]";
|
||||||
|
clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]";
|
||||||
|
}
|
||||||
|
clanHostsModule
|
||||||
|
../nixos/hosts/sunken-ship.nix
|
||||||
|
config.flake.nixosModules.server-debug-tools
|
||||||
|
config.flake.nixosModules.monitoring-node-exporter
|
||||||
|
config.flake.nixosModules.monitoring-prometheus-server
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(hmModule {
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/home/danny";
|
||||||
|
stateVersion = "25.11";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
machines.vps-relay = {
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
clan.core.enableRecommendedDefaults = false;
|
||||||
|
# Initial install uses --target-host override; subsequent
|
||||||
|
# updates go over ZT IPv6 (set once generated, via the
|
||||||
|
# internet instance above).
|
||||||
|
}
|
||||||
|
clanHostsModule
|
||||||
|
../nixos/hosts/vps-relay.nix
|
||||||
|
config.flake.nixosModules.monitoring-node-exporter
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(hmModule {
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/home/danny";
|
||||||
|
stateVersion = "25.11";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# distant-shore — ThinkPad X13 Gen 2, WiFi, Secure Boot via shim+MOK
|
||||||
|
# (installed standalone, then migrated into clan). targetHost is the LAN
|
||||||
|
# IP for the first `clan machines update`; switch to its ZT IPv6 once the
|
||||||
|
# mesh is up. buildHost = sunken-ship: it's an x86_64 builder whose key is
|
||||||
|
# already in distant-shore's authorized_keys, so the closure copy works
|
||||||
|
# (building on distant-shore itself needs a fragile self-SSH).
|
||||||
|
machines.distant-shore = {
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
clan.core.enableRecommendedDefaults = false;
|
||||||
|
clan.core.networking.targetHost = "danny@192.168.1.182";
|
||||||
|
clan.core.networking.buildHost = "danny@sunken-ship";
|
||||||
|
}
|
||||||
|
clanHostsModule
|
||||||
|
../nixos/hosts/distant-shore.nix
|
||||||
|
config.flake.nixosModules.monitoring-node-exporter
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(hmModule {
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/home/danny";
|
||||||
|
stateVersion = "25.11";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# foreign-port — WiFi-only laptop server, locally-signed boot chain
|
||||||
|
# (installed standalone, migrated into clan). targetHost is the LAN IP
|
||||||
|
# for the first `clan machines update`; switch to its ZT IPv6 once the
|
||||||
|
# mesh is up. buildHost = sunken-ship for the closure copy (avoids
|
||||||
|
# self-SSH).
|
||||||
|
machines.foreign-port = {
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
clan.core.enableRecommendedDefaults = false;
|
||||||
|
clan.core.networking.targetHost = "danny@192.168.1.223";
|
||||||
|
clan.core.networking.buildHost = "danny@sunken-ship";
|
||||||
|
}
|
||||||
|
clanHostsModule
|
||||||
|
../nixos/hosts/foreign-port.nix
|
||||||
|
config.flake.nixosModules.monitoring-node-exporter
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(hmModule {
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/home/danny";
|
||||||
|
stateVersion = "25.11";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
machines.phantom-ship = {
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
clan.core.enableRecommendedDefaults = false;
|
||||||
|
clan.core.networking.targetHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]";
|
||||||
|
clan.core.networking.buildHost = "danny@[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]";
|
||||||
|
}
|
||||||
|
clanHostsModule
|
||||||
|
inputs.nix-openclaw.nixosModules.openclaw-gateway
|
||||||
|
../nixos/hosts/phantom-ship.nix
|
||||||
|
config.flake.nixosModules.server-debug-tools
|
||||||
|
config.flake.nixosModules.monitoring-node-exporter
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(hmModule {
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/home/danny";
|
||||||
|
stateVersion = "25.11";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
22
flake-modules/daniel-macbook-air.nix
Normal file
22
flake-modules/daniel-macbook-air.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{ inputs, ... }: {
|
||||||
|
flake.darwinConfigurations."Daniel-Macbook-Air" = inputs.nix-darwin.lib.darwinSystem {
|
||||||
|
modules = [
|
||||||
|
# Overlay: make zen-browser available as pkgs.zen-browser
|
||||||
|
{ nixpkgs.overlays = [ (final: prev: {
|
||||||
|
zen-browser = inputs.zen-browser.packages.${final.stdenv.hostPlatform.system}.default;
|
||||||
|
}) ];
|
||||||
|
}
|
||||||
|
|
||||||
|
../nixos/hosts/daniel-macbook-air.nix
|
||||||
|
../nixos/fish.nix
|
||||||
|
|
||||||
|
inputs.home-manager.darwinModules.home-manager
|
||||||
|
(import ../lib/home-manager-user.nix {
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
user = "danny";
|
||||||
|
homeDirectory = "/Users/danny";
|
||||||
|
userImports = [ ../nixos/home/danny/home.nix ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
15
flake-modules/installer-iso.nix
Normal file
15
flake-modules/installer-iso.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ inputs, self, ... }: {
|
||||||
|
# Custom minimal installer ISO (build with: nix build .#installer-iso).
|
||||||
|
# nixos/installer-wifi.nix (gitignored) is auto-included when present, to
|
||||||
|
# preconfigure live-system WiFi. See docs/server-installer-usb.md.
|
||||||
|
flake.nixosConfigurations.installer-iso = inputs.nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [ ../nixos/installer-iso.nix ]
|
||||||
|
++ inputs.nixpkgs.lib.optional
|
||||||
|
(builtins.pathExists ../nixos/installer-wifi.nix)
|
||||||
|
../nixos/installer-wifi.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
flake.packages.x86_64-linux.installer-iso =
|
||||||
|
self.nixosConfigurations.installer-iso.config.system.build.isoImage;
|
||||||
|
}
|
||||||
9
flake-modules/nixos-modules.nix
Normal file
9
flake-modules/nixos-modules.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Expose reusable NixOS modules via `flake.nixosModules`.
|
||||||
|
#
|
||||||
|
# Consume from a host's flake-module via:
|
||||||
|
# modules = [ config.flake.nixosModules.server-debug-tools ];
|
||||||
|
{ ... }: {
|
||||||
|
flake.nixosModules.server-debug-tools = ../modules/server-debug-tools.nix;
|
||||||
|
flake.nixosModules.monitoring-node-exporter = ../modules/monitoring-node-exporter.nix;
|
||||||
|
flake.nixosModules.monitoring-prometheus-server = ../modules/monitoring-prometheus-server.nix;
|
||||||
|
}
|
||||||
11
flake-modules/server-install.nix
Normal file
11
flake-modules/server-install.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{ inputs, ... }: {
|
||||||
|
# For disko-install: LUKS + WiFi; hostname/WiFi via --system-config.
|
||||||
|
flake.nixosConfigurations.server-install = inputs.nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
inputs.disko.nixosModules.disko
|
||||||
|
../nixos/disko-server.nix
|
||||||
|
../nixos/hosts/server-install.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
19
flake-modules/wsl.nix
Normal file
19
flake-modules/wsl.nix
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{ inputs, ... }: {
|
||||||
|
flake.nixosConfigurations.wsl = inputs.nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
inputs.nixos-wsl.nixosModules.default
|
||||||
|
inputs.vscode-server.nixosModules.default
|
||||||
|
../nixos/hosts/wsl.nix
|
||||||
|
../nixos/fish.nix
|
||||||
|
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(import ../lib/home-manager-user.nix {
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
user = "dth";
|
||||||
|
homeDirectory = "/home/dth";
|
||||||
|
userImports = [ ../nixos/home/danny/home.nix ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
665
flake.lock
generated
Normal file
665
flake.lock
generated
Normal file
|
|
@ -0,0 +1,665 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"clan-community": {
|
||||||
|
"inputs": {
|
||||||
|
"clan-core": [
|
||||||
|
"clan-core"
|
||||||
|
],
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1779453564,
|
||||||
|
"narHash": "sha256-q7iVGGhZYtAwsjf7sIKcYD5IgsTTTobWP/EStaDCUZc=",
|
||||||
|
"rev": "81e4c9cded645d0384812dd6b8f05bd2475ffe64",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/api/v1/repos/clan/clan-community/archive/81e4c9cded645d0384812dd6b8f05bd2475ffe64.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/clan/clan-community/archive/main.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"clan-core": {
|
||||||
|
"inputs": {
|
||||||
|
"data-mesher": "data-mesher",
|
||||||
|
"disko": "disko",
|
||||||
|
"flake-parts": [
|
||||||
|
"flake-parts"
|
||||||
|
],
|
||||||
|
"nix-darwin": "nix-darwin",
|
||||||
|
"nix-select": "nix-select",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"sops-nix": "sops-nix",
|
||||||
|
"systems": "systems",
|
||||||
|
"treefmt-nix": "treefmt-nix_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778462753,
|
||||||
|
"narHash": "sha256-/9qWZbrwoVWP0YWuC1Z5HMEb/oy6rNsjypUKTuk1PB4=",
|
||||||
|
"rev": "09551fdb27a7e5712bef371e9271034d503242ed",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/09551fdb27a7e5712bef371e9271034d503242ed.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/clan/clan-core/archive/main.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data-mesher": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": [
|
||||||
|
"clan-core",
|
||||||
|
"flake-parts"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-core",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"treefmt-nix": [
|
||||||
|
"clan-core",
|
||||||
|
"treefmt-nix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776654564,
|
||||||
|
"narHash": "sha256-5bpzOOXsaAr4g25/ghtKdYO17xg0l+MieCcWgqx24eY=",
|
||||||
|
"rev": "ad23733ebc47284dc1158db43218cf4027824aee",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/ad23733ebc47284dc1158db43218cf4027824aee.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/clan/data-mesher/archive/main.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disko": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-core",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776613567,
|
||||||
|
"narHash": "sha256-gC9Cp5ibBmGD5awCA9z7xy6MW6iJufhazTYJOiGlCUI=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"rev": "32f4236bfc141ae930b5ba2fb604f561fed5219d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disko_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777713215,
|
||||||
|
"narHash": "sha256-8GzXDOXckDWwST8TY5DbwYFjdvQLlP7K9CLSVx6iTTo=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"rev": "63b4e7e6cf75307c1d26ac3762b886b5b0247267",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "disko",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767039857,
|
||||||
|
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"clan-community",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775087534,
|
||||||
|
"narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777988971,
|
||||||
|
"narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681202837,
|
||||||
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778444552,
|
||||||
|
"narHash": "sha256-f18pIiR9q/p1vHY93gmAum7aHhQOG49oGvAB9+lptRo=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "dcebe66f958673729896eec2de4abfd86ef22d21",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nix-openclaw",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767909183,
|
||||||
|
"narHash": "sha256-u/bcU0xePi5bgNoRsiqSIwaGBwDilKKFTz3g0hqOBAo=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "cd6e96d56ed4b2a779ac73a1227e0bb1519b3509",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"home-manager_3": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"zen-browser",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777594677,
|
||||||
|
"narHash": "sha256-h90sHwoRJLRvaTpZroTvU2JRHDFj0czUafM8eqLe1RI=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"rev": "899c08a15beae5da51a5cecd6b2b994777a948da",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "home-manager",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"import-tree": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1773693634,
|
||||||
|
"narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=",
|
||||||
|
"owner": "vic",
|
||||||
|
"repo": "import-tree",
|
||||||
|
"rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "vic",
|
||||||
|
"repo": "import-tree",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-darwin": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-core",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775037210,
|
||||||
|
"narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
|
||||||
|
"owner": "nix-darwin",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"rev": "06648f4902343228ce2de79f291dd5a58ee12146",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-darwin",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-darwin_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777780666,
|
||||||
|
"narHash": "sha256-8wURyQMdDkGUarSTKOGdCuFfYiwa3HbzwscUfn3STDE=",
|
||||||
|
"owner": "nix-darwin",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"rev": "8c62fba0854ba15c8917aed18894dbccb48a3777",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-darwin",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "nix-darwin",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-openclaw": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"home-manager": "home-manager_2",
|
||||||
|
"nix-openclaw-tools": "nix-openclaw-tools",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"qmd": "qmd"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778353239,
|
||||||
|
"narHash": "sha256-g0yC+loN19X3Xyn6RuBHeWzevH7Qymt0REW+kyGuCLY=",
|
||||||
|
"owner": "openclaw",
|
||||||
|
"repo": "nix-openclaw",
|
||||||
|
"rev": "e2ea91056fdd0836bef96326a2b687277dbe3e1c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "openclaw",
|
||||||
|
"repo": "nix-openclaw",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-openclaw-tools": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778060041,
|
||||||
|
"narHash": "sha256-tXWkN1VnwFG8XlRqW/e7VwbKnUfyU9tB7YDm9QHJXTY=",
|
||||||
|
"owner": "openclaw",
|
||||||
|
"repo": "nix-openclaw-tools",
|
||||||
|
"rev": "4c1cee3c7eaf68f9de0f756be1484534f5bb5f34",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "openclaw",
|
||||||
|
"repo": "nix-openclaw-tools",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nix-select": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763303120,
|
||||||
|
"narHash": "sha256-yxcNOha7Cfv2nhVpz9ZXSNKk0R7wt4AiBklJ8D24rVg=",
|
||||||
|
"rev": "3d1e3860bef36857a01a2ddecba7cdb0a14c35a9",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/api/v1/repos/clan/nix-select/archive/3d1e3860bef36857a01a2ddecba7cdb0a14c35a9.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://git.clan.lol/clan/nix-select/archive/main.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixos-wsl": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777732699,
|
||||||
|
"narHash": "sha256-2uX/XtOWZ/oy2rerRynVhqVA//ZXZ3Fo60PikLHEPQc=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "NixOS-WSL",
|
||||||
|
"rev": "5482f113fd31ebac131d1ebeb2ae90bf0d5e41f5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "main",
|
||||||
|
"repo": "NixOS-WSL",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767364772,
|
||||||
|
"narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776169885,
|
||||||
|
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778274207,
|
||||||
|
"narHash": "sha256-I4puXmX1iovcCHZlRmztO3vW0mAbbRvq4F8wgIMQ1MM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b3da656039dc7a6240f27b2ef8cc6a3ef3bccae7",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1682134069,
|
||||||
|
"narHash": "sha256-TnI/ZXSmRxQDt2sjRYK/8j8iha4B4zP2cnQCZZ3vp7k=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "fd901ef4bf93499374c5af385b2943f5801c0833",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qmd": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"nix-openclaw",
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nix-openclaw",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775429264,
|
||||||
|
"narHash": "sha256-bqIVaNRTa8H5vrw3RwsD7QdtTa0xNvRuEVzlzE1hIBQ=",
|
||||||
|
"owner": "tobi",
|
||||||
|
"repo": "qmd",
|
||||||
|
"rev": "65cd1b3fd02891d1ee0eefa751620918664fa321",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tobi",
|
||||||
|
"ref": "v2.1.0",
|
||||||
|
"repo": "qmd",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"clan-community": "clan-community",
|
||||||
|
"clan-core": "clan-core",
|
||||||
|
"disko": "disko_2",
|
||||||
|
"flake-parts": "flake-parts_2",
|
||||||
|
"home-manager": "home-manager",
|
||||||
|
"import-tree": "import-tree",
|
||||||
|
"nix-darwin": "nix-darwin_2",
|
||||||
|
"nix-openclaw": "nix-openclaw",
|
||||||
|
"nixos-wsl": "nixos-wsl",
|
||||||
|
"nixpkgs": "nixpkgs_3",
|
||||||
|
"vscode-server": "vscode-server",
|
||||||
|
"zen-browser": "zen-browser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sops-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-core",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776119890,
|
||||||
|
"narHash": "sha256-Zm6bxLNnEOYuS/SzrAGsYuXSwk3cbkRQZY0fJnk8a5M=",
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"rev": "d4971dd58c6627bfee52a1ad4237637c0a2fb0cd",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1774449309,
|
||||||
|
"narHash": "sha256-brhZ8DmuGtzkCYHJg4HEd602amKm89Y9ytsFZ5uWD1w=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "c29398b59d2048c4ab79345812849c9bd15e9150",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"ref": "future-26.11",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-community",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775125835,
|
||||||
|
"narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "75925962939880974e3ab417879daffcba36c4a3",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix_2": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"clan-core",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775636079,
|
||||||
|
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vscode-server": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": "nixpkgs_4"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1770124655,
|
||||||
|
"narHash": "sha256-yHmd2B13EtBUPLJ+x0EaBwNkQr9LTne1arLVxT6hSnY=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixos-vscode-server",
|
||||||
|
"rev": "92ce71c3ba5a94f854e02d57b14af4997ab54ef0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixos-vscode-server",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zen-browser": {
|
||||||
|
"inputs": {
|
||||||
|
"home-manager": "home-manager_3",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1778394798,
|
||||||
|
"narHash": "sha256-/jR8bModWv0ji305ecMgAB+2eaXLZiYdH+9Z4JIRkuA=",
|
||||||
|
"owner": "0xc000022070",
|
||||||
|
"repo": "zen-browser-flake",
|
||||||
|
"rev": "45bc54456044b96492923739bfae633e1a4352e1",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "0xc000022070",
|
||||||
|
"repo": "zen-browser-flake",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
44
flake.nix
Normal file
44
flake.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
nixos-wsl.url = "github:nix-community/NixOS-WSL/main";
|
||||||
|
vscode-server.url = "github:nix-community/nixos-vscode-server";
|
||||||
|
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||||
|
|
||||||
|
# Auto-loads every .nix file under ./flake-modules as a flake-parts module.
|
||||||
|
import-tree.url = "github:vic/import-tree";
|
||||||
|
|
||||||
|
nix-darwin.url = "github:nix-darwin/nix-darwin/master";
|
||||||
|
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
home-manager.url = "github:nix-community/home-manager";
|
||||||
|
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
zen-browser.url = "github:0xc000022070/zen-browser-flake";
|
||||||
|
zen-browser.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
disko.url = "github:nix-community/disko";
|
||||||
|
disko.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
nix-openclaw.url = "github:openclaw/nix-openclaw";
|
||||||
|
nix-openclaw.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
|
||||||
|
clan-core.url = "https://git.clan.lol/clan/clan-core/archive/main.tar.gz";
|
||||||
|
clan-core.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
clan-core.inputs.flake-parts.follows = "flake-parts";
|
||||||
|
|
||||||
|
# clan-community: dm-pull-deploy etc. Back on upstream main since
|
||||||
|
# clan/clan-community#25 (machine.name hyphen sanitization) merged.
|
||||||
|
clan-community.url = "https://git.clan.lol/clan/clan-community/archive/main.tar.gz";
|
||||||
|
clan-community.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
clan-community.inputs.clan-core.follows = "clan-core";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs @ { flake-parts, import-tree, ... }:
|
||||||
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
systems = [ "x86_64-linux" "aarch64-darwin" ];
|
||||||
|
imports = [ (import-tree ./flake-modules) ];
|
||||||
|
};
|
||||||
|
}
|
||||||
35
lib/home-manager-user.nix
Normal file
35
lib/home-manager-user.nix
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
# Shared home-manager wiring for NixOS and nix-darwin hosts.
|
||||||
|
#
|
||||||
|
# Usage (from a flake-module):
|
||||||
|
# modules = [
|
||||||
|
# inputs.home-manager.nixosModules.home-manager # or .darwinModules
|
||||||
|
# (import ../lib/home-manager-user.nix {
|
||||||
|
# lib = inputs.nixpkgs.lib;
|
||||||
|
# user = "danny";
|
||||||
|
# homeDirectory = "/home/danny";
|
||||||
|
# stateVersion = "25.11"; # optional
|
||||||
|
# userImports = [ ../home/danny/home.nix ]; # optional
|
||||||
|
# })
|
||||||
|
# ];
|
||||||
|
{ lib
|
||||||
|
, user
|
||||||
|
, homeDirectory
|
||||||
|
, stateVersion ? null
|
||||||
|
, userImports ? [ ]
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
home-manager.useGlobalPkgs = true;
|
||||||
|
home-manager.useUserPackages = true;
|
||||||
|
# Automatically back up files before home-manager overwrites them.
|
||||||
|
home-manager.backupFileExtension = "backup";
|
||||||
|
home-manager.users.${user} = { ... }: {
|
||||||
|
imports = userImports;
|
||||||
|
home = {
|
||||||
|
username = user;
|
||||||
|
# Force an absolute path even if another module sets a bad value.
|
||||||
|
homeDirectory = lib.mkForce homeDirectory;
|
||||||
|
} // lib.optionalAttrs (stateVersion != null) {
|
||||||
|
stateVersion = stateVersion;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
12
modules/monitoring-node-exporter.nix
Normal file
12
modules/monitoring-node-exporter.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Prometheus node_exporter — exposes host metrics on :9100, scoped to the
|
||||||
|
# ZeroTier mesh so only sunken-ship (the Prometheus server) can scrape it.
|
||||||
|
{ ... }: {
|
||||||
|
services.prometheus.exporters.node = {
|
||||||
|
enable = true;
|
||||||
|
port = 9100;
|
||||||
|
listenAddress = "[::]";
|
||||||
|
enabledCollectors = [ "systemd" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 9100 ];
|
||||||
|
}
|
||||||
134
modules/monitoring-prometheus-server.nix
Normal file
134
modules/monitoring-prometheus-server.nix
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
# Prometheus + Alertmanager + Grafana on sunken-ship.
|
||||||
|
#
|
||||||
|
# Scrape targets are the clan ZeroTier IPv6s — kept in sync with
|
||||||
|
# vars/per-machine/<host>/zerotier/zerotier-ip/value.
|
||||||
|
#
|
||||||
|
# Telegram receiver uses the existing @HarakatBot. Drop the bot token at
|
||||||
|
# /etc/alertmanager/telegram-token (mode 0400, root) before rebuild — same
|
||||||
|
# manual-secret pattern as the other Telegram bots in the repo.
|
||||||
|
#
|
||||||
|
# Routing: critical alerts repeat every 1h, everything else every 4h.
|
||||||
|
{ ... }:
|
||||||
|
let
|
||||||
|
sunkenShipZTv6 = "fdd5:53a2:de33:d269:6499:93d5:53a2:de33";
|
||||||
|
phantomShipZTv6 = "fdd5:53a2:de33:d269:6499:936c:48a:bbdc";
|
||||||
|
vpsRelayZTv6 = "fdd5:53a2:de33:d269:6499:9305:339f:2ed3";
|
||||||
|
|
||||||
|
target = ip: "[${ip}]:9100";
|
||||||
|
in {
|
||||||
|
services.prometheus = {
|
||||||
|
enable = true;
|
||||||
|
port = 9090;
|
||||||
|
listenAddress = "[::1]";
|
||||||
|
|
||||||
|
globalConfig = {
|
||||||
|
scrape_interval = "30s";
|
||||||
|
evaluation_interval = "30s";
|
||||||
|
};
|
||||||
|
|
||||||
|
scrapeConfigs = [{
|
||||||
|
job_name = "node";
|
||||||
|
static_configs = [{
|
||||||
|
targets = [
|
||||||
|
(target sunkenShipZTv6)
|
||||||
|
(target phantomShipZTv6)
|
||||||
|
(target vpsRelayZTv6)
|
||||||
|
];
|
||||||
|
labels.job = "node";
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
|
||||||
|
ruleFiles = [
|
||||||
|
(builtins.toFile "host-rules.yml" (builtins.toJSON {
|
||||||
|
groups = [{
|
||||||
|
name = "hosts";
|
||||||
|
rules = [{
|
||||||
|
alert = "HostDown";
|
||||||
|
expr = ''up{job="node"} == 0'';
|
||||||
|
for = "5m";
|
||||||
|
labels.severity = "critical";
|
||||||
|
annotations = {
|
||||||
|
summary = "{{ $labels.instance }} is down";
|
||||||
|
description = "{{ $labels.instance }} has been unreachable for 5 minutes.";
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
|
||||||
|
alertmanagers = [{
|
||||||
|
static_configs = [{ targets = [ "[::1]:9093" ]; }];
|
||||||
|
}];
|
||||||
|
|
||||||
|
alertmanager = {
|
||||||
|
enable = true;
|
||||||
|
port = 9093;
|
||||||
|
listenAddress = "[::1]";
|
||||||
|
configuration = {
|
||||||
|
route = {
|
||||||
|
receiver = "telegram-default";
|
||||||
|
group_by = [ "alertname" ];
|
||||||
|
group_wait = "30s";
|
||||||
|
group_interval = "5m";
|
||||||
|
repeat_interval = "4h";
|
||||||
|
routes = [{
|
||||||
|
matchers = [ ''severity="critical"'' ];
|
||||||
|
receiver = "telegram-critical";
|
||||||
|
group_wait = "10s";
|
||||||
|
group_interval = "1m";
|
||||||
|
repeat_interval = "1h";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
receivers = [
|
||||||
|
{
|
||||||
|
name = "telegram-default";
|
||||||
|
telegram_configs = [{
|
||||||
|
bot_token_file = "/etc/alertmanager/telegram-token";
|
||||||
|
chat_id = 66070351;
|
||||||
|
api_url = "https://api.telegram.org";
|
||||||
|
parse_mode = "";
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "telegram-critical";
|
||||||
|
telegram_configs = [{
|
||||||
|
bot_token_file = "/etc/alertmanager/telegram-token";
|
||||||
|
chat_id = 66070351;
|
||||||
|
api_url = "https://api.telegram.org";
|
||||||
|
parse_mode = "";
|
||||||
|
message = ''
|
||||||
|
CRITICAL: {{ .CommonLabels.alertname }}
|
||||||
|
{{ range .Alerts }}{{ .Annotations.summary }}
|
||||||
|
{{ .Annotations.description }}
|
||||||
|
{{ end }}'';
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.grafana = {
|
||||||
|
enable = true;
|
||||||
|
settings.server = {
|
||||||
|
http_addr = "::";
|
||||||
|
http_port = 3000;
|
||||||
|
domain = "sunken-ship.clan";
|
||||||
|
};
|
||||||
|
# Drop a random 32+ char string at /etc/grafana/secret-key (mode 0400,
|
||||||
|
# owned by grafana:grafana) before rebuild — same manual-secret pattern
|
||||||
|
# as /etc/alertmanager/telegram-token. Used to encrypt secrets stored
|
||||||
|
# in Grafana's DB; nothing to rotate on a fresh install.
|
||||||
|
settings.security.secret_key = "$__file{/etc/grafana/secret-key}";
|
||||||
|
provision.datasources.settings.datasources = [{
|
||||||
|
name = "Prometheus";
|
||||||
|
type = "prometheus";
|
||||||
|
url = "http://[::1]:9090";
|
||||||
|
isDefault = true;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Grafana on the ZeroTier mesh only. Prometheus + Alertmanager bind to
|
||||||
|
# localhost so they're not reachable off-host.
|
||||||
|
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 ];
|
||||||
|
}
|
||||||
15
modules/server-debug-tools.nix
Normal file
15
modules/server-debug-tools.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# A small set of network/process debugging tools that we'd otherwise
|
||||||
|
# pick up from `clan.core.enableRecommendedDefaults = true`. The full
|
||||||
|
# clan defaults also flip systemd-networkd / systemd-resolved on, which
|
||||||
|
# breaks dnsmasq + navidrome's resolv.conf bind-mount, so we opted out
|
||||||
|
# fleet-wide and added just the useful packages explicitly here.
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
htop # process monitor
|
||||||
|
tcpdump # packet capture
|
||||||
|
dnsutils # dig, nslookup, host
|
||||||
|
jq # JSON parser
|
||||||
|
curl # HTTP client
|
||||||
|
];
|
||||||
|
}
|
||||||
31
nixos/disko-cloud.nix
Normal file
31
nixos/disko-cloud.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Disko layout for cloud VPS installs (e.g. Hetzner Cloud).
|
||||||
|
# GPT with a 1MB BIOS boot partition (for GRUB on a BIOS system) + root.
|
||||||
|
# No LUKS — the provider has physical disk access anyway and there's
|
||||||
|
# no operator present at boot to enter a passphrase.
|
||||||
|
{
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = "/dev/sda";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
# GRUB BIOS boot partition — holds stage-1.5 bootloader code.
|
||||||
|
# Type EF02. No filesystem.
|
||||||
|
BIOSBOOT = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
37
nixos/disko-distant-shore.nix
Normal file
37
nixos/disko-distant-shore.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Declarative disk layout for distant-shore (ThinkPad X13 Gen 2 — 256 GB
|
||||||
|
# SK Hynix NVMe). UEFI/systemd-boot, no encryption: it's a headless,
|
||||||
|
# WiFi-only server that must reboot unattended (clan dm-pull-deploy), so
|
||||||
|
# a LUKS passphrase prompt at boot would hang it. Mirrors sunken-ship's
|
||||||
|
# plain-ext4 choice. Device is wiped + repartitioned at install time by
|
||||||
|
# clan/nixos-anywhere.
|
||||||
|
{
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = "/dev/nvme0n1";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [ "fmask=0022" "dmask=0022" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
36
nixos/disko-foreign-port.nix
Normal file
36
nixos/disko-foreign-port.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Declarative disk layout for distant-shore. UEFI/systemd-boot, no
|
||||||
|
# encryption: it's a headless, WiFi-only server that must reboot
|
||||||
|
# unattended (clan dm-pull-deploy), so a LUKS passphrase prompt at boot
|
||||||
|
# would hang it. Mirrors sunken-ship's plain-ext4 choice. Device is wiped
|
||||||
|
# + repartitioned at install time by clan/nixos-anywhere.
|
||||||
|
{
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = "/dev/nvme0n1";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [ "fmask=0022" "dmask=0022" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
weather = "curl wttr.in/?T";
|
weather = "curl wttr.in/?T";
|
||||||
# TODO: rename and move 25_flakes into dotfiles
|
# TODO: rename and move 25_flakes into dotfiles
|
||||||
nide = "nix develop ~/python-projects/25_flakes/$(basename (pwd)) -c $(which fish)";
|
nide = "nix develop ~/python-projects/25_flakes/$(basename (pwd)) -c $(which fish)";
|
||||||
nixupdate = "cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air";
|
nixupdate = "cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air";
|
||||||
};
|
};
|
||||||
interactiveShellInit = ''
|
interactiveShellInit = ''
|
||||||
function fish_user_key_bindings
|
function fish_user_key_bindings
|
||||||
|
|
@ -24,6 +24,43 @@
|
||||||
|
|
||||||
set fish_greeting 🐟: (set_color yellow; date +%T; set_color green; date --iso-8601 2>/dev/null; or date +%F; set_color normal)
|
set fish_greeting 🐟: (set_color yellow; date +%T; set_color green; date --iso-8601 2>/dev/null; or date +%F; set_color normal)
|
||||||
|
|
||||||
|
# gco: smart `git checkout` — if the branch is checked out in another
|
||||||
|
# worktree, cd there instead of failing with "already used by worktree at".
|
||||||
|
function gco --description 'git checkout, but cd into worktree if the branch lives there'
|
||||||
|
if test (count $argv) -eq 0
|
||||||
|
git checkout
|
||||||
|
return $status
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l branch $argv[1]
|
||||||
|
set -l target_ref "refs/heads/$branch"
|
||||||
|
set -l wt_path ""
|
||||||
|
set -l current_wt ""
|
||||||
|
for line in (git worktree list --porcelain 2>/dev/null)
|
||||||
|
switch $line
|
||||||
|
case 'worktree *'
|
||||||
|
set current_wt (string replace -r '^worktree ' "" -- $line)
|
||||||
|
case "branch $target_ref"
|
||||||
|
set wt_path $current_wt
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l here (git rev-parse --show-toplevel 2>/dev/null)
|
||||||
|
if test -n "$wt_path"; and test "$wt_path" != "$here"
|
||||||
|
echo "→ cd $wt_path (branch '$branch' is checked out in another worktree)"
|
||||||
|
cd $wt_path
|
||||||
|
return $status
|
||||||
|
end
|
||||||
|
|
||||||
|
git checkout $argv
|
||||||
|
end
|
||||||
|
|
||||||
|
# Alacritty palette follows macOS appearance; refresh when opening a shell (LaunchAgent also polls).
|
||||||
|
if test (uname -s) = Darwin
|
||||||
|
bash ~/dotfiles/scripts/alacritty-sync-system-theme.sh >/dev/null 2>&1 &
|
||||||
|
end
|
||||||
|
|
||||||
# name: Default
|
# name: Default
|
||||||
# author: Lily Ballard
|
# author: Lily Ballard
|
||||||
# edits: DannyDannyDanny
|
# edits: DannyDannyDanny
|
||||||
|
|
|
||||||
254
nixos/flake.lock
generated
254
nixos/flake.lock
generated
|
|
@ -1,254 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"disko": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773506317,
|
|
||||||
"narHash": "sha256-qWKbLUJpavIpvOdX1fhHYm0WGerytFHRoh9lVck6Bh0=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "878ec37d6a8f52c6c801d0e2a2ad554c75b9353c",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1767039857,
|
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681202837,
|
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773810247,
|
|
||||||
"narHash": "sha256-6Vz1Thy/1s7z+Rq5OfkWOBAdV4eD+OrvDs10yH6xJzQ=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "d47357a4c806d18a3e853ad2699eaec3c01622e7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"zen-browser",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773422513,
|
|
||||||
"narHash": "sha256-MPjR48roW7CUMU6lu0+qQGqj92Kuh3paIulMWFZy+NQ=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "ef12a9a2b0f77c8fa3dda1e7e494fca668909056",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix-darwin": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773000227,
|
|
||||||
"narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=",
|
|
||||||
"owner": "nix-darwin",
|
|
||||||
"repo": "nix-darwin",
|
|
||||||
"rev": "da529ac9e46f25ed5616fd634079a5f3c579135f",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-darwin",
|
|
||||||
"ref": "master",
|
|
||||||
"repo": "nix-darwin",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixos-wsl": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773603777,
|
|
||||||
"narHash": "sha256-oXSEbMR/IuHYk9nvrbRhaYBxVK5s63DH2UGOZT2ok48=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "NixOS-WSL",
|
|
||||||
"rev": "0efe7af73d6e4a8d447a22936c5526d73822b0a7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"ref": "main",
|
|
||||||
"repo": "NixOS-WSL",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773282481,
|
|
||||||
"narHash": "sha256-b/GV2ysM8mKHhinse2wz+uP37epUrSE+sAKXy/xvBY4=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "fe416aaedd397cacb33a610b33d60ff2b431b127",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773628058,
|
|
||||||
"narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1682134069,
|
|
||||||
"narHash": "sha256-TnI/ZXSmRxQDt2sjRYK/8j8iha4B4zP2cnQCZZ3vp7k=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "fd901ef4bf93499374c5af385b2943f5801c0833",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"id": "nixpkgs",
|
|
||||||
"type": "indirect"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"disko": "disko",
|
|
||||||
"home-manager": "home-manager",
|
|
||||||
"nix-darwin": "nix-darwin",
|
|
||||||
"nixos-wsl": "nixos-wsl",
|
|
||||||
"nixpkgs": "nixpkgs_2",
|
|
||||||
"vscode-server": "vscode-server",
|
|
||||||
"zen-browser": "zen-browser"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"vscode-server": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770124655,
|
|
||||||
"narHash": "sha256-yHmd2B13EtBUPLJ+x0EaBwNkQr9LTne1arLVxT6hSnY=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-vscode-server",
|
|
||||||
"rev": "92ce71c3ba5a94f854e02d57b14af4997ab54ef0",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "nixos-vscode-server",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zen-browser": {
|
|
||||||
"inputs": {
|
|
||||||
"home-manager": "home-manager_2",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773737882,
|
|
||||||
"narHash": "sha256-P6k0BtT1/idYveVRdcwAZk8By9UjZW8XOMhSoS6wTBY=",
|
|
||||||
"owner": "0xc000022070",
|
|
||||||
"repo": "zen-browser-flake",
|
|
||||||
"rev": "a7f1db35d74faf04e5189b3a32f890186ace5c28",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "0xc000022070",
|
|
||||||
"repo": "zen-browser-flake",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
134
nixos/flake.nix
134
nixos/flake.nix
|
|
@ -1,134 +0,0 @@
|
||||||
{
|
|
||||||
inputs = {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
|
||||||
nixos-wsl.url = "github:nix-community/NixOS-WSL/main";
|
|
||||||
vscode-server.url = "github:nix-community/nixos-vscode-server";
|
|
||||||
|
|
||||||
nix-darwin.url = "github:nix-darwin/nix-darwin/master";
|
|
||||||
nix-darwin.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
|
|
||||||
home-manager.url = "github:nix-community/home-manager";
|
|
||||||
home-manager.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
|
|
||||||
zen-browser.url = "github:0xc000022070/zen-browser-flake";
|
|
||||||
zen-browser.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
|
|
||||||
disko.url = "github:nix-community/disko";
|
|
||||||
disko.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = {
|
|
||||||
nixpkgs,
|
|
||||||
nixos-wsl,
|
|
||||||
vscode-server,
|
|
||||||
nix-darwin,
|
|
||||||
self,
|
|
||||||
home-manager,
|
|
||||||
zen-browser,
|
|
||||||
disko,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
nixosConfigurations = {
|
|
||||||
wsl = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [
|
|
||||||
nixos-wsl.nixosModules.default
|
|
||||||
vscode-server.nixosModules.default
|
|
||||||
./hosts/wsl.nix
|
|
||||||
./tmux.nix
|
|
||||||
# TODO: handle all user-level programs via home-manager
|
|
||||||
# ./neovim.nix # Now handled via home-manager
|
|
||||||
./fish.nix
|
|
||||||
# home-manager.nixosModules.default
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
macbookair = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [
|
|
||||||
nixos-wsl.nixosModules.default
|
|
||||||
vscode-server.nixosModules.default
|
|
||||||
./hosts/macbookair.nix
|
|
||||||
./hardware-configuration.nix
|
|
||||||
./tmux.nix
|
|
||||||
# TODO: handle all user-level programs via home-manager
|
|
||||||
# ./neovim.nix # Now handled via home-manager
|
|
||||||
./fish.nix
|
|
||||||
# home-manager.nixosModules.default
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
sunken-ship = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [
|
|
||||||
./hosts/sunken-ship.nix
|
|
||||||
|
|
||||||
# Home Manager on NixOS
|
|
||||||
home-manager.nixosModules.home-manager
|
|
||||||
({ lib, ... }: {
|
|
||||||
home-manager.useGlobalPkgs = true;
|
|
||||||
home-manager.useUserPackages = true;
|
|
||||||
home-manager.backupFileExtension = "backup";
|
|
||||||
home-manager.users.danny = { ... }: {
|
|
||||||
home.username = "danny";
|
|
||||||
home.homeDirectory = lib.mkForce "/home/danny";
|
|
||||||
home.stateVersion = "25.11";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# For disko-install: LUKS + WiFi; hostname/WiFi via --system-config.
|
|
||||||
server-install = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [
|
|
||||||
disko.nixosModules.disko
|
|
||||||
./disko-server.nix
|
|
||||||
./hosts/server-install.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Custom minimal installer ISO (build with: nix build .#installer-iso).
|
|
||||||
# Optional: add ./installer-wifi.nix (gitignored) to modules for live WiFi.
|
|
||||||
installer-iso = nixpkgs.lib.nixosSystem {
|
|
||||||
system = "x86_64-linux";
|
|
||||||
modules = [ ./installer-iso.nix ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
packages.x86_64-linux.installer-iso =
|
|
||||||
self.nixosConfigurations.installer-iso.config.system.build.isoImage;
|
|
||||||
|
|
||||||
# macOS (nix-darwin) configuration
|
|
||||||
darwinConfigurations."Daniel-Macbook-Air" = nix-darwin.lib.darwinSystem {
|
|
||||||
specialArgs = { inherit zen-browser; };
|
|
||||||
modules = [
|
|
||||||
./hosts/macos.nix
|
|
||||||
./fish.nix
|
|
||||||
|
|
||||||
# Home Manager on macOS
|
|
||||||
home-manager.darwinModules.home-manager
|
|
||||||
({ lib, zen-browser, ... }: {
|
|
||||||
home-manager.useGlobalPkgs = true;
|
|
||||||
home-manager.useUserPackages = true;
|
|
||||||
# Automatically backup files before home-manager overwrites them
|
|
||||||
home-manager.backupFileExtension = "backup";
|
|
||||||
# Pass flake inputs to home-manager modules (e.g. home.nix)
|
|
||||||
home-manager.extraSpecialArgs = { inherit zen-browser; };
|
|
||||||
home-manager.users.danny = { ... }: {
|
|
||||||
|
|
||||||
# Force an absolute path even if another module sets a bad value.
|
|
||||||
home.username = "danny";
|
|
||||||
home.homeDirectory = lib.mkForce "/Users/danny";
|
|
||||||
imports = [
|
|
||||||
./home/danny/home.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
|
||||||
# and may be overwritten by future invocations. Please make changes
|
|
||||||
# to /etc/nixos/configuration.nix instead.
|
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports =
|
|
||||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ "kvm-intel" "wl" ];
|
|
||||||
boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ];
|
|
||||||
|
|
||||||
fileSystems."/" =
|
|
||||||
{ device = "/dev/disk/by-uuid/bf59e35a-f96d-489d-9b14-93f67d5e294d";
|
|
||||||
fsType = "ext4";
|
|
||||||
};
|
|
||||||
|
|
||||||
boot.initrd.luks.devices."luks-5b4978ab-ee25-4a85-8f56-0bdbe932f154".device = "/dev/disk/by-uuid/5b4978ab-ee25-4a85-8f56-0bdbe932f154";
|
|
||||||
|
|
||||||
fileSystems."/boot" =
|
|
||||||
{ device = "/dev/disk/by-uuid/691B-AF9A";
|
|
||||||
fsType = "vfat";
|
|
||||||
options = [ "fmask=0022" "dmask=0022" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
swapDevices =
|
|
||||||
[ { device = "/dev/disk/by-uuid/08f3fa7a-1e84-4819-b696-1536bc44ef99"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
|
||||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
|
||||||
# still possible to use this option, but it's recommended to use it in conjunction
|
|
||||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
|
||||||
networking.useDHCP = lib.mkDefault true;
|
|
||||||
# networking.interfaces.wlp2s0b1.useDHCP = lib.mkDefault true;
|
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ pkgs, lib, zen-browser ? null, ... }:
|
{ pkgs, lib, config, ... }:
|
||||||
{
|
{
|
||||||
# TODO: remove next two lines from here or from flake.nix
|
# TODO: remove next two lines from here or from flake.nix
|
||||||
# home.username = "danny";
|
# home.username = "danny";
|
||||||
|
|
@ -9,6 +9,24 @@
|
||||||
# Import neovim configuration
|
# Import neovim configuration
|
||||||
imports = [ ../../neovim.nix ];
|
imports = [ ../../neovim.nix ];
|
||||||
|
|
||||||
|
# ZeroTier SSH aliases — managed drop-in under ~/.ssh/config.d/.
|
||||||
|
# For this to take effect, your ~/.ssh/config must include:
|
||||||
|
# Include ~/.ssh/config.d/*
|
||||||
|
# near the top (before any host-specific blocks).
|
||||||
|
home.file.".ssh/config.d/zerotier".text = ''
|
||||||
|
Host sunken-ship-zt fdd5:53a2:de33:d269:6499:93d5:53a2:de33
|
||||||
|
HostName fdd5:53a2:de33:d269:6499:93d5:53a2:de33
|
||||||
|
User danny
|
||||||
|
IdentityFile ~/.ssh/id_ed25519_sunken_ship
|
||||||
|
IdentitiesOnly yes
|
||||||
|
|
||||||
|
Host phantom-ship-zt fdd5:53a2:de33:d269:6499:936c:48a:bbdc
|
||||||
|
HostName fdd5:53a2:de33:d269:6499:936c:48a:bbdc
|
||||||
|
User danny
|
||||||
|
IdentityFile ~/.ssh/id_ed25519_phantom_ship
|
||||||
|
IdentitiesOnly yes
|
||||||
|
'';
|
||||||
|
|
||||||
# tmux (user-level; same config on macOS and NixOS if you reuse this file)
|
# tmux (user-level; same config on macOS and NixOS if you reuse this file)
|
||||||
programs.tmux = {
|
programs.tmux = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -43,6 +61,12 @@
|
||||||
bind -r C-h select-window -t :-
|
bind -r C-h select-window -t :-
|
||||||
bind -r C-l select-window -t :+
|
bind -r C-l select-window -t :+
|
||||||
|
|
||||||
|
# Resize pane shortcuts
|
||||||
|
bind -r H resize-pane -L 10
|
||||||
|
bind -r J resize-pane -D 10
|
||||||
|
bind -r K resize-pane -U 10
|
||||||
|
bind -r L resize-pane -R 10
|
||||||
|
|
||||||
# split with dash and vbar
|
# split with dash and vbar
|
||||||
bind | split-window -h -c "#{pane_current_path}"
|
bind | split-window -h -c "#{pane_current_path}"
|
||||||
bind - split-window -v -c "#{pane_current_path}"
|
bind - split-window -v -c "#{pane_current_path}"
|
||||||
|
|
@ -64,6 +88,35 @@
|
||||||
catppuccin
|
catppuccin
|
||||||
tmux-fzf
|
tmux-fzf
|
||||||
extrakto
|
extrakto
|
||||||
|
# tmux-resurrect: prefix + Ctrl-s saves, prefix + Ctrl-r restores.
|
||||||
|
# Snapshot lives at ~/.tmux/resurrect/last (window layout, working
|
||||||
|
# dirs, pane contents if enabled). Survives force-quits / reboots
|
||||||
|
# / kernel panics.
|
||||||
|
#
|
||||||
|
# @resurrect-processes: programs to restart on restore. Default
|
||||||
|
# list covers vim/emacs/less/top/etc. but NOT nvim, claude, or
|
||||||
|
# ssh. The "~name->cmd" form re-runs the original argv; bare
|
||||||
|
# names match argv-less invocations. Without this, restored panes
|
||||||
|
# come back as plain fish prompts in the right directory.
|
||||||
|
{
|
||||||
|
plugin = resurrect;
|
||||||
|
extraConfig = ''
|
||||||
|
set -g @resurrect-capture-pane-contents 'on'
|
||||||
|
set -g @resurrect-strategy-nvim 'session'
|
||||||
|
set -g @resurrect-processes 'nvim "~nvim->nvim *" claude "~claude->claude --continue" ssh "~ssh->ssh *"'
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
# tmux-continuum: auto-saves every 15min and auto-restores on
|
||||||
|
# tmux server start. With this, the next force-quit just costs
|
||||||
|
# you up to 15min of recent terminal activity, not the whole
|
||||||
|
# workspace.
|
||||||
|
{
|
||||||
|
plugin = continuum;
|
||||||
|
extraConfig = ''
|
||||||
|
set -g @continuum-restore 'on'
|
||||||
|
set -g @continuum-save-interval '15'
|
||||||
|
'';
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -112,15 +165,31 @@
|
||||||
executable = true;
|
executable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# Alacritty terminal configuration with conditional theme switching
|
# Palette fragments: synced to system appearance (see scripts/alacritty-sync-system-theme.sh).
|
||||||
|
xdg.configFile."alacritty/catppuccin-latte-colors.toml".source =
|
||||||
|
../../../assets/alacritty/catppuccin-latte-colors.toml;
|
||||||
|
xdg.configFile."alacritty/catppuccin-mocha-colors.toml".source =
|
||||||
|
../../../assets/alacritty/catppuccin-mocha-colors.toml;
|
||||||
|
|
||||||
|
# Zed: settings.json is a read-only symlink to assets/zed/settings.json.
|
||||||
|
# To change a setting, edit the asset file and rebuild — editing via Zed's
|
||||||
|
# UI will fail because the target is in the nix store.
|
||||||
|
xdg.configFile."zed/settings.json".source = ../../../assets/zed/settings.json;
|
||||||
|
|
||||||
|
# Alacritty: base config + imported active-colors.toml (updated without rebuild)
|
||||||
programs.alacritty = {
|
programs.alacritty = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
|
general = {
|
||||||
|
live_config_reload = true;
|
||||||
|
import = [ "${config.xdg.configHome}/alacritty/active-colors.toml" ];
|
||||||
|
};
|
||||||
window = {
|
window = {
|
||||||
padding = { x = 8; y = 8; };
|
padding = { x = 8; y = 8; };
|
||||||
dynamic_padding = true;
|
dynamic_padding = true;
|
||||||
decorations = "buttonless";
|
decorations = "buttonless";
|
||||||
opacity = 0.95;
|
decorations_theme_variant = "None";
|
||||||
|
opacity = 1.0;
|
||||||
startup_mode = "Maximized";
|
startup_mode = "Maximized";
|
||||||
option_as_alt = "Both";
|
option_as_alt = "Both";
|
||||||
};
|
};
|
||||||
|
|
@ -134,51 +203,29 @@
|
||||||
program = "${pkgs.fish}/bin/fish";
|
program = "${pkgs.fish}/bin/fish";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
# Conditional colors based on system theme
|
|
||||||
colors = let
|
|
||||||
# Set this to true for light theme, false for dark theme
|
|
||||||
# You can change this and run 'darwin-rebuild switch' to switch themes
|
|
||||||
isLightTheme = true;
|
|
||||||
|
|
||||||
# Catppuccin Latte (Light) colors
|
|
||||||
lightColors = {
|
|
||||||
primary = { background = "0xeff1f5"; foreground = "0x4c4f69"; };
|
|
||||||
cursor = { text = "0xeff1f5"; cursor = "0xdc8a78"; };
|
|
||||||
normal = {
|
|
||||||
black = "0x5c5f77"; red = "0xd20f39"; green = "0x40a02b"; yellow = "0xdf8e1d";
|
|
||||||
blue = "0x1e40af"; magenta = "0xea76cb"; cyan = "0x179299"; white = "0xacb0be";
|
|
||||||
};
|
|
||||||
bright = {
|
|
||||||
black = "0x6c6f85"; red = "0xd20f39"; green = "0x40a02b"; yellow = "0xdf8e1d";
|
|
||||||
blue = "0x1e40af"; magenta = "0xea76cb"; cyan = "0x179299"; white = "0xbcc0cc";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Catppuccin Mocha (Dark) colors
|
|
||||||
darkColors = {
|
|
||||||
primary = { background = "0x1e1e2e"; foreground = "0xcdd6f4"; };
|
|
||||||
cursor = { text = "0x1e1e2e"; cursor = "0xf5e0dc"; };
|
|
||||||
normal = {
|
|
||||||
black = "0x45475a"; red = "0xf38ba8"; green = "0xa6e3a1"; yellow = "0xf9e2af";
|
|
||||||
blue = "0x89b4fa"; magenta = "0xf5c2e7"; cyan = "0x94e2d5"; white = "0xbac2de";
|
|
||||||
};
|
|
||||||
bright = {
|
|
||||||
black = "0x585b70"; red = "0xf38ba8"; green = "0xa6e3a1"; yellow = "0xf9e2af";
|
|
||||||
blue = "0x89b4fa"; magenta = "0xf5c2e7"; cyan = "0x94e2d5"; white = "0xa6adc8";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in if isLightTheme then lightColors else darkColors;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Writable copy (not a symlink to the store — cp in the sync script must replace a real file).
|
||||||
|
home.activation.alacrittySystemTheme = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||||
|
MOCHA="${../../../assets/alacritty/catppuccin-mocha-colors.toml}"
|
||||||
|
ACTIVE="${config.xdg.configHome}/alacritty/active-colors.toml"
|
||||||
|
$DRY_RUN_CMD mkdir -p "${config.xdg.configHome}/alacritty"
|
||||||
|
if [ ! -f "$ACTIVE" ]; then
|
||||||
|
$DRY_RUN_CMD cp "$MOCHA" "$ACTIVE"
|
||||||
|
$DRY_RUN_CMD chmod 0644 "$ACTIVE"
|
||||||
|
fi
|
||||||
|
$DRY_RUN_CMD ${pkgs.bash}/bin/bash "${../../../scripts/alacritty-sync-system-theme.sh}" || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
# TODO: Put user-installed binaries here if you want HM to own them (optional)
|
# TODO: Put user-installed binaries here if you want HM to own them (optional)
|
||||||
# Fonts
|
# Fonts
|
||||||
fonts.fontconfig.enable = true;
|
fonts.fontconfig.enable = true;
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
# Zen Browser (Firefox fork; from flake, supports aarch64-darwin)
|
# Zen Browser (Firefox fork; from flake overlay, supports aarch64-darwin)
|
||||||
] ++ (lib.optionals (zen-browser != null) [
|
] ++ (lib.optionals (pkgs ? zen-browser) [
|
||||||
zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default
|
pkgs.zen-browser
|
||||||
]) ++ (with pkgs; [
|
]) ++ (with pkgs; [
|
||||||
# Google Fonts (includes Michroma)
|
# Google Fonts (includes Michroma)
|
||||||
google-fonts
|
google-fonts
|
||||||
|
|
@ -215,12 +262,15 @@
|
||||||
# alacritty # TODO: configured via programs.alacritty above, so not needed here
|
# alacritty # TODO: configured via programs.alacritty above, so not needed here
|
||||||
# warp-terminal # TODO: Bloat
|
# warp-terminal # TODO: Bloat
|
||||||
# vscodium # TODO: Bloat
|
# vscodium # TODO: Bloat
|
||||||
# zed-editor # TODO: Bloat
|
zed-editor
|
||||||
code-cursor
|
code-cursor
|
||||||
cursor-cli
|
cursor-cli
|
||||||
|
cinny-desktop # Matrix client (Tauri wrapper around the Cinny web app)
|
||||||
|
dfu-util # USB DFU firmware flasher (Flipper Zero etc.)
|
||||||
discord
|
discord
|
||||||
mapscii
|
mapscii
|
||||||
mpv
|
mpv
|
||||||
|
# uhk-agent # UHK keyboard configuration GUI + CLI — removed, nixpkgs marks x86_64-linux only TODO
|
||||||
]);
|
]);
|
||||||
|
|
||||||
# First HM version for this user config; bump only if you understand the migration notes.
|
# First HM version for this user config; bump only if you understand the migration notes.
|
||||||
|
|
|
||||||
109
nixos/hosts/daniel-macbook-air.nix
Normal file
109
nixos/hosts/daniel-macbook-air.nix
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
alacrittySyncSystemTheme = pkgs.writeShellScriptBin "alacritty-sync-system-theme"
|
||||||
|
(builtins.readFile ../../scripts/alacritty-sync-system-theme.sh);
|
||||||
|
|
||||||
|
# nix-darwin's nix.gc / nix.optimise require nix.enable; with Determinate (nix.enable = false)
|
||||||
|
# we schedule the same commands via launchd using nixpkgs' nix CLI (same defaults as upstream modules).
|
||||||
|
nixGcInterval = [{ Weekday = 7; Hour = 3; Minute = 15; }];
|
||||||
|
nixOptimiseInterval = [{ Weekday = 7; Hour = 4; Minute = 15; }];
|
||||||
|
in {
|
||||||
|
# Apple Silicon + nix-darwin basics
|
||||||
|
nixpkgs.hostPlatform = "aarch64-darwin";
|
||||||
|
nix.enable = false; # Determinate manages Nix
|
||||||
|
|
||||||
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
system.primaryUser = "danny";
|
||||||
|
|
||||||
|
# Shells (fish config is in fish.nix, imported via flake.nix)
|
||||||
|
environment.shells = [ pkgs.fish ];
|
||||||
|
users.users.danny.shell = pkgs.fish;
|
||||||
|
|
||||||
|
# ollama
|
||||||
|
imports = [../ollama.nix];
|
||||||
|
services.ollama = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Networking (macOS-safe)
|
||||||
|
networking = {
|
||||||
|
# Set if you want a specific hostname in macOS UI as well:
|
||||||
|
hostName = "Daniel-Macbook-Air";
|
||||||
|
knownNetworkServices = [ "Wi-Fi" "Thunderbolt Bridge" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
homebrew = {
|
||||||
|
enable = true;
|
||||||
|
casks = [
|
||||||
|
"google-chrome"
|
||||||
|
"disk-inventory-x" # Apple Silicon uses Homebrew; nixpkgs package is x86_64-darwin only.
|
||||||
|
"qflipper" # Flipper Zero firmware updater GUI
|
||||||
|
"zerotier-one" # Clan homelab overlay — authorize on sunken-ship controller
|
||||||
|
# "uhk-agent" # Ultimate Hacking Keyboard configuration — removed, nixpkgs marks x86_64-linux only TODO
|
||||||
|
];
|
||||||
|
onActivation.cleanup = "zap";
|
||||||
|
};
|
||||||
|
|
||||||
|
# macOS niceties
|
||||||
|
security.pam.services.sudo_local.touchIdAuth = true;
|
||||||
|
|
||||||
|
system.defaults = {
|
||||||
|
# Keyboard
|
||||||
|
NSGlobalDomain = {
|
||||||
|
AppleShowAllExtensions = true;
|
||||||
|
ApplePressAndHoldEnabled = true;
|
||||||
|
"com.apple.mouse.tapBehavior" = 1;
|
||||||
|
"com.apple.sound.beep.volume" = 0.0;
|
||||||
|
"com.apple.sound.beep.feedback" = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Finder & Dock
|
||||||
|
finder.AppleShowAllExtensions = true;
|
||||||
|
dock.autohide = true;
|
||||||
|
dock.mru-spaces = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
# User-specific packages and environment variables are now in home-manager (home.nix)
|
||||||
|
# Only system-level packages should remain here if needed
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
alacrittySyncSystemTheme
|
||||||
|
pkgs.feishin # Subsonic/Navidrome desktop music player
|
||||||
|
];
|
||||||
|
|
||||||
|
# Poll macOS appearance; updates ~/.config/alacritty/active-colors.toml (Alacritty live_config_reload).
|
||||||
|
launchd.user.agents.alacritty-system-theme = {
|
||||||
|
serviceConfig = {
|
||||||
|
RunAtLoad = true;
|
||||||
|
StartInterval = 30;
|
||||||
|
ProgramArguments = [ "${alacrittySyncSystemTheme}/bin/alacritty-sync-system-theme" ];
|
||||||
|
StandardOutPath = "/tmp/alacritty-theme-sync.log";
|
||||||
|
StandardErrorPath = "/tmp/alacritty-theme-sync-error.log";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
launchd.daemons = {
|
||||||
|
nix-gc-determ = {
|
||||||
|
command =
|
||||||
|
"${lib.getExe' pkgs.nix "nix-collect-garbage"} --delete-older-than 14d";
|
||||||
|
serviceConfig = {
|
||||||
|
RunAtLoad = false;
|
||||||
|
StartCalendarInterval = nixGcInterval;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
nix-store-optimise-determ = {
|
||||||
|
command = "${lib.getExe' pkgs.nix "nix-store"} --optimise";
|
||||||
|
serviceConfig = {
|
||||||
|
RunAtLoad = false;
|
||||||
|
StartCalendarInterval = nixOptimiseInterval;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Keep for darwin as well (tracks defaults across upgrades)
|
||||||
|
# current max per nix-darwin; bump only if a release notes says so
|
||||||
|
system.stateVersion = 6;
|
||||||
|
|
||||||
|
}
|
||||||
18
nixos/hosts/distant-shore-hardware.nix
Normal file
18
nixos/hosts/distant-shore-hardware.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
|
# and may be overwritten by future invocations. Please make changes
|
||||||
|
# to /etc/nixos/configuration.nix instead.
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports =
|
||||||
|
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
114
nixos/hosts/distant-shore.nix
Normal file
114
nixos/hosts/distant-shore.nix
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
# NixOS server on a ThinkPad X13 Gen 2 (Intel i5-1145G7, 16 GB).
|
||||||
|
# WiFi-only, headless, unattended auto-rebuild via clan dm-pull-deploy.
|
||||||
|
# No LUKS (mirrors sunken-ship) so reboots don't block on a passphrase.
|
||||||
|
#
|
||||||
|
# Blank-slate server for now — no application services. Give it a purpose
|
||||||
|
# later (just add services here and let dm-pull-deploy roll it out).
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./distant-shore-hardware.nix
|
||||||
|
../disko-distant-shore.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
# Secure Boot is enforced and the BIOS supervisor password is unknown, so
|
||||||
|
# we can't enrol our own SB keys. Instead, shim (MS-signed) is placed on
|
||||||
|
# the ESP and chain-loads systemd-boot; the NVRAM boot entry points at
|
||||||
|
# shim. We manage that entry imperatively via efibootmgr; letting bootctl
|
||||||
|
# touch EFI variables would replace it on every rebuild.
|
||||||
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
boot.loader.efi.efiSysMountPoint = "/boot"; # matches disko ESP mountpoint
|
||||||
|
|
||||||
|
# --- Secure Boot via shim + MOK (no firmware key enrolment possible) ------
|
||||||
|
# The firmware trusts Microsoft-signed shim; shim trusts our enrolled MOK.
|
||||||
|
# On every bootloader install we: (1) sign systemd-boot with the MOK and
|
||||||
|
# drop it where shim chain-loads it (grubx64.efi), (2) install shim as the
|
||||||
|
# firmware-booted binary (+ MokManager), (3) MOK-sign every kernel image
|
||||||
|
# systemd-boot will hand off to (shim verifies them via the shim-lock
|
||||||
|
# protocol). Re-runs on each nixos-rebuild so auto-deployed generations
|
||||||
|
# stay bootable. Keys + shim live in /etc/secrets (outside the repo).
|
||||||
|
boot.loader.systemd-boot.extraInstallCommands = ''
|
||||||
|
# NixOS's bootloader-install systemd unit runs with a minimal PATH that
|
||||||
|
# doesn't include coreutils, so use absolute paths for cp/mv.
|
||||||
|
KEY=/etc/secrets/MOK.key
|
||||||
|
CRT=/etc/secrets/MOK.crt
|
||||||
|
sb() { ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$2" "$1"; }
|
||||||
|
# systemd-boot -> shim's chain-load target
|
||||||
|
sb /boot/EFI/systemd/systemd-bootx64.efi /boot/EFI/BOOT/grubx64.efi
|
||||||
|
# shim (MS-signed) is what the firmware boots; MokManager beside it
|
||||||
|
${pkgs.coreutils}/bin/cp -f /etc/secrets/shimx64.efi /boot/EFI/BOOT/BOOTX64.EFI
|
||||||
|
${pkgs.coreutils}/bin/cp -f /etc/secrets/mmx64.efi /boot/EFI/BOOT/mmx64.efi
|
||||||
|
# MOK-sign each kernel (skip already-signed; never touch initrds)
|
||||||
|
for k in /boot/EFI/nixos/*bzImage.efi; do
|
||||||
|
[ -e "$k" ] || continue
|
||||||
|
if ! ${pkgs.sbsigntool}/bin/sbverify --cert "$CRT" "$k" >/dev/null 2>&1; then
|
||||||
|
${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$k.tmp" "$k" \
|
||||||
|
&& ${pkgs.coreutils}/bin/mv -f "$k.tmp" "$k"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
networking.hostName = "distant-shore";
|
||||||
|
# WiFi via NetworkManager. The wpa_supplicant stack hit two issues on this
|
||||||
|
# box: (1) it strips CAP_CHOWN so wpa couldn't create its ctrl_interface,
|
||||||
|
# and (2) dhcpcd didn't grab a lease after the (late) association at boot,
|
||||||
|
# needing a manual restart — fatal for an unattended headless server. NM
|
||||||
|
# handles association + DHCP atomically and connected first-try here.
|
||||||
|
# The PSK stays out of the repo: it's substituted from /etc/secrets/nm.env
|
||||||
|
# ($PSK_INTENO) into the declared profile at activation.
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
networking.networkmanager.ensureProfiles.environmentFiles = [ "/etc/secrets/nm.env" ];
|
||||||
|
networking.networkmanager.ensureProfiles.profiles."Inteno-89FE" = {
|
||||||
|
connection = { id = "Inteno-89FE"; type = "wifi"; autoconnect = true; };
|
||||||
|
wifi = { ssid = "Inteno-89FE"; mode = "infrastructure"; };
|
||||||
|
wifi-security = { key-mgmt = "wpa-psk"; psk = "$PSK_INTENO"; };
|
||||||
|
ipv4.method = "auto";
|
||||||
|
ipv6.method = "auto";
|
||||||
|
};
|
||||||
|
hardware.enableRedistributableFirmware = true; # iwlwifi for the Intel AX201 WiFi
|
||||||
|
time.timeZone = "Europe/Copenhagen";
|
||||||
|
|
||||||
|
# It's a laptop acting as a server: keep running with the lid shut.
|
||||||
|
services.logind.settings.Login.HandleLidSwitch = "ignore";
|
||||||
|
services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore";
|
||||||
|
|
||||||
|
# Reduce screen burn-in / power: blank the TTY after a minute.
|
||||||
|
boot.kernelParams = [ "consoleblank=60" ];
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI)
|
||||||
|
nix.settings.trusted-users = [ "root" "danny" ];
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
|
||||||
|
users.users.danny = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" "video" "audio" ];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
# Mac admin / fleet key (~/.ssh/id_ed25519_sunken_ship) — the key the
|
||||||
|
# Mac uses to reach the fleet; clan machines update relies on it.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin"
|
||||||
|
# Per-host key (~/.ssh/id_ed25519_distant_shore) — plain `ssh distant-shore`.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH61JOiOOPrAXekakAwTJg5yCSDfOIjlSvMYkpXrarAf distant-shore"
|
||||||
|
# sunken-ship (dm-pull-deploy push node) — reach distant-shore over ZT.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
users.users.root.openssh.authorizedKeys.keys =
|
||||||
|
config.users.users.danny.openssh.authorizedKeys.keys;
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
# mokutil — manage MOK enrolment for the shim chain; sbsigntool — inspect
|
||||||
|
# signatures on bootloader/kernel images when debugging.
|
||||||
|
environment.systemPackages = with pkgs; [ git mokutil sbsigntool ];
|
||||||
|
}
|
||||||
18
nixos/hosts/foreign-port-hardware.nix
Normal file
18
nixos/hosts/foreign-port-hardware.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
|
# and may be overwritten by future invocations. Please make changes
|
||||||
|
# to /etc/nixos/configuration.nix instead.
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports =
|
||||||
|
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [ "xhci_pci" "thunderbolt" "nvme" ];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
111
nixos/hosts/foreign-port.nix
Normal file
111
nixos/hosts/foreign-port.nix
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# NixOS laptop server. WiFi-only, headless, unattended auto-rebuild via
|
||||||
|
# clan dm-pull-deploy. No LUKS (mirrors sunken-ship) so reboots don't
|
||||||
|
# block on a passphrase.
|
||||||
|
#
|
||||||
|
# Blank-slate server for now — no application services. Give it a purpose
|
||||||
|
# later (just add services here and let dm-pull-deploy roll it out).
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./foreign-port-hardware.nix
|
||||||
|
../disko-foreign-port.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
# Firmware-locked Secure Boot: we can't enrol our own keys into the
|
||||||
|
# firmware key DB, so a vendor-signed shim is the firmware-booted binary
|
||||||
|
# and chain-loads a locally-signed systemd-boot + kernel. The NVRAM
|
||||||
|
# entry points at shim; bootctl is kept away from EFI variables so
|
||||||
|
# rebuilds don't clobber the entry.
|
||||||
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
boot.loader.efi.efiSysMountPoint = "/boot"; # matches disko ESP mountpoint
|
||||||
|
|
||||||
|
# --- Locally-signed boot chain --------------------------------------------
|
||||||
|
# On every bootloader install: re-sign systemd-boot and every kernel
|
||||||
|
# image, refresh the shim binary on the ESP, and place the helper binary
|
||||||
|
# beside it. Re-runs on each nixos-rebuild so auto-deployed generations
|
||||||
|
# stay bootable. Signing material lives in /etc/secrets, never the repo.
|
||||||
|
boot.loader.systemd-boot.extraInstallCommands = ''
|
||||||
|
# NixOS's bootloader-install systemd unit runs with a minimal PATH that
|
||||||
|
# doesn't include coreutils, so use absolute paths for cp/mv.
|
||||||
|
KEY=/etc/secrets/MOK.key
|
||||||
|
CRT=/etc/secrets/MOK.crt
|
||||||
|
sb() { ${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$2" "$1"; }
|
||||||
|
# systemd-boot -> shim's chain-load target
|
||||||
|
sb /boot/EFI/systemd/systemd-bootx64.efi /boot/EFI/BOOT/grubx64.efi
|
||||||
|
# shim is the firmware-booted binary; helper binary sits beside it
|
||||||
|
${pkgs.coreutils}/bin/cp -f /etc/secrets/shimx64.efi /boot/EFI/BOOT/BOOTX64.EFI
|
||||||
|
${pkgs.coreutils}/bin/cp -f /etc/secrets/mmx64.efi /boot/EFI/BOOT/mmx64.efi
|
||||||
|
# sign each kernel (skip if already signed; leave initrds untouched)
|
||||||
|
for k in /boot/EFI/nixos/*bzImage.efi; do
|
||||||
|
[ -e "$k" ] || continue
|
||||||
|
if ! ${pkgs.sbsigntool}/bin/sbverify --cert "$CRT" "$k" >/dev/null 2>&1; then
|
||||||
|
${pkgs.sbsigntool}/bin/sbsign --key "$KEY" --cert "$CRT" --output "$k.tmp" "$k" \
|
||||||
|
&& ${pkgs.coreutils}/bin/mv -f "$k.tmp" "$k"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
|
||||||
|
networking.hostName = "foreign-port";
|
||||||
|
# WiFi via NetworkManager. The wpa_supplicant stack hit two issues on this
|
||||||
|
# box: (1) it strips CAP_CHOWN so wpa couldn't create its ctrl_interface,
|
||||||
|
# and (2) dhcpcd didn't grab a lease after the (late) association at boot,
|
||||||
|
# needing a manual restart — fatal for an unattended headless server. NM
|
||||||
|
# handles association + DHCP atomically and connected first-try here.
|
||||||
|
# The PSK stays out of the repo: it's substituted from /etc/secrets/nm.env
|
||||||
|
# ($PSK_INTENO) into the declared profile at activation.
|
||||||
|
networking.networkmanager.enable = true;
|
||||||
|
networking.networkmanager.ensureProfiles.environmentFiles = [ "/etc/secrets/nm.env" ];
|
||||||
|
networking.networkmanager.ensureProfiles.profiles."Inteno-89FE-5GHz" = {
|
||||||
|
connection = { id = "Inteno-89FE-5GHz"; type = "wifi"; autoconnect = true; };
|
||||||
|
wifi = { ssid = "Inteno-89FE-5GHz"; mode = "infrastructure"; };
|
||||||
|
wifi-security = { key-mgmt = "wpa-psk"; psk = "$PSK_INTENO"; };
|
||||||
|
ipv4.method = "auto";
|
||||||
|
ipv6.method = "auto";
|
||||||
|
};
|
||||||
|
hardware.enableRedistributableFirmware = true; # WiFi firmware blobs
|
||||||
|
time.timeZone = "Europe/Copenhagen";
|
||||||
|
|
||||||
|
# It's a laptop acting as a server: keep running with the lid shut.
|
||||||
|
services.logind.settings.Login.HandleLidSwitch = "ignore";
|
||||||
|
services.logind.settings.Login.HandleLidSwitchExternalPower = "ignore";
|
||||||
|
|
||||||
|
# Reduce screen burn-in / power: blank the TTY after a minute.
|
||||||
|
boot.kernelParams = [ "consoleblank=60" ];
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI)
|
||||||
|
nix.settings.trusted-users = [ "root" "danny" ];
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
|
||||||
|
users.users.danny = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" "video" "audio" ];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
# Mac admin / fleet key (~/.ssh/id_ed25519_sunken_ship) — the key the
|
||||||
|
# Mac uses to reach the fleet; clan machines update relies on it.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin"
|
||||||
|
# TODO: add a per-host key (~/.ssh/id_ed25519_foreign_port) for
|
||||||
|
# plain `ssh foreign-port`. Generate when convenient.
|
||||||
|
# sunken-ship (dm-pull-deploy push node) — reach foreign-port over ZT.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
users.users.root.openssh.authorizedKeys.keys =
|
||||||
|
config.users.users.danny.openssh.authorizedKeys.keys;
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
# mokutil + sbsigntool — manage the shim trust chain and inspect signed
|
||||||
|
# bootloader/kernel images when debugging.
|
||||||
|
environment.systemPackages = with pkgs; [ git mokutil sbsigntool ];
|
||||||
|
}
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
# Edit this configuration file to define what should be installed on
|
|
||||||
# your system. Help is available in the configuration.nix(5) man page
|
|
||||||
# and in the NixOS manual (accessible by running ‘nixos-help’).
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Bootloader.
|
|
||||||
boot.loader.systemd-boot.enable = true;
|
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
|
||||||
|
|
||||||
boot.initrd.luks.devices."luks-04715655-635c-46ee-8100-1a5a4f3700a5".device = "/dev/disk/by-uuid/04715655-635c-46ee-8100-1a5a4f3700a5";
|
|
||||||
networking.hostName = "nixos"; # Define your hostname.
|
|
||||||
# NOTE: You can not use networking.networkmanager with networking.wireless
|
|
||||||
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
|
|
||||||
|
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ]; # for vscode remote server
|
|
||||||
|
|
||||||
# Configure network proxy if necessary
|
|
||||||
# networking.proxy.default = "http://user:password@proxy:port/";
|
|
||||||
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
|
|
||||||
|
|
||||||
# Enable networking
|
|
||||||
networking.networkmanager.enable = true;
|
|
||||||
|
|
||||||
# Set your time zone.
|
|
||||||
time.timeZone = "Europe/Copenhagen";
|
|
||||||
|
|
||||||
# Select internationalisation properties.
|
|
||||||
i18n.defaultLocale = "en_DK.UTF-8";
|
|
||||||
|
|
||||||
i18n.extraLocaleSettings = {
|
|
||||||
LC_ADDRESS = "da_DK.UTF-8";
|
|
||||||
LC_IDENTIFICATION = "da_DK.UTF-8";
|
|
||||||
LC_MEASUREMENT = "da_DK.UTF-8";
|
|
||||||
LC_MONETARY = "da_DK.UTF-8";
|
|
||||||
LC_NAME = "da_DK.UTF-8";
|
|
||||||
LC_NUMERIC = "da_DK.UTF-8";
|
|
||||||
LC_PAPER = "da_DK.UTF-8";
|
|
||||||
LC_TELEPHONE = "da_DK.UTF-8";
|
|
||||||
LC_TIME = "da_DK.UTF-8";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Enable the X11 windowing system.
|
|
||||||
services.xserver.enable = true;
|
|
||||||
|
|
||||||
# Enable the KDE Plasma Desktop Environment.
|
|
||||||
services.displayManager.sddm.enable = true;
|
|
||||||
services.desktopManager.plasma6.enable = true;
|
|
||||||
|
|
||||||
# Configure keymap in X11
|
|
||||||
services.xserver = {
|
|
||||||
xkb.layout = "us";
|
|
||||||
xkb.variant = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
programs.nix-ld.enable = true;
|
|
||||||
# TODO: move to home manager (?)
|
|
||||||
programs = {
|
|
||||||
direnv = {
|
|
||||||
enable = true;
|
|
||||||
enableFishIntegration = true;
|
|
||||||
nix-direnv.enable = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Enable CUPS to print documents.
|
|
||||||
services.printing.enable = true;
|
|
||||||
|
|
||||||
# Enable sound with pipewire.
|
|
||||||
services.pulseaudio.enable = false;
|
|
||||||
hardware.alsa.enable = false;
|
|
||||||
security.rtkit.enable = true;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Enable touchpad support (enabled default in most desktopManager).
|
|
||||||
# services.xserver.libinput.enable = true;
|
|
||||||
|
|
||||||
# Define a user account. Don't forget to set a password with ‘passwd’.
|
|
||||||
users.users.dth = {
|
|
||||||
isNormalUser = true;
|
|
||||||
description = "dth";
|
|
||||||
extraGroups = [ "networkmanager" "wheel" ];
|
|
||||||
# TODO: use home manager to define user packages
|
|
||||||
packages = with pkgs; [
|
|
||||||
vlc # video player
|
|
||||||
# kate # editor
|
|
||||||
ripgrep # faster grep
|
|
||||||
nextcloud-client # private cloud
|
|
||||||
# digikam # photo / video management
|
|
||||||
# thunderbird # bloat
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Install firefox.
|
|
||||||
programs.firefox.enable = true;
|
|
||||||
|
|
||||||
# install kde partition manager
|
|
||||||
programs.partition-manager.enable = true;
|
|
||||||
|
|
||||||
# TODO: install gnome disk manager
|
|
||||||
# programs.gnome-disks.enable = true;
|
|
||||||
|
|
||||||
# Allow unfree packages
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
|
||||||
nixpkgs.config.permittedInsecurePackages = [
|
|
||||||
"broadcom-sta-6.30.223.271-59-6.18.10"
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.kernelModules = [ "wl" ];
|
|
||||||
|
|
||||||
# List packages installed in system profile. To search, run:
|
|
||||||
# $ nix search wget
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
|
|
||||||
# tmux # activated in tmux.nix
|
|
||||||
# vim # using neovim in stead
|
|
||||||
# neovim # activated in neovim.nix
|
|
||||||
|
|
||||||
git # version control
|
|
||||||
gh # github cli tool
|
|
||||||
|
|
||||||
claude-code #anthropic's agentic coding cli
|
|
||||||
|
|
||||||
ripgrep # faster grep
|
|
||||||
busybox # useful programs e.g. tree, unzip etc
|
|
||||||
openssl # cryptography swiss army knife
|
|
||||||
xdg-utils # terminal desktop intergrations (i.e. allow terminal to open browser)
|
|
||||||
xclip # terminal clipboard integration (i.e. allow terminal to r/w clipboard)
|
|
||||||
|
|
||||||
fastfetch # system info
|
|
||||||
btop # resource monitor
|
|
||||||
wget # downloader
|
|
||||||
tldr # community driven manpage alternative
|
|
||||||
|
|
||||||
ntfs3g # mount NTFS drives on linux
|
|
||||||
gptfdisk # formatting drives - like fdisk but better
|
|
||||||
# this stuff runs gparted
|
|
||||||
|
|
||||||
# gimp # bloat image editing
|
|
||||||
# blender # bloat 3D modelling
|
|
||||||
# inkscape # bloat vector graphics / drawing
|
|
||||||
kdePackages.kdenlive # bloat video editor
|
|
||||||
|
|
||||||
# desktop applications
|
|
||||||
thunderbird # email / calendar
|
|
||||||
telegram-desktop # instant messager
|
|
||||||
|
|
||||||
cowsay
|
|
||||||
lolcat
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
# firefox smooth scrolling
|
|
||||||
environment.sessionVariables = {
|
|
||||||
MOZ_USE_XINPUT2 = "1";
|
|
||||||
};
|
|
||||||
|
|
||||||
# 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;
|
|
||||||
|
|
||||||
# This value determines the NixOS release from which the default
|
|
||||||
# settings for stateful data, like file locations and database versions
|
|
||||||
# on your system were taken. It‘s perfectly fine and recommended to leave
|
|
||||||
# this value at the release version of the first install of this system.
|
|
||||||
# Before changing this value read the documentation for this option
|
|
||||||
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
|
|
||||||
system.stateVersion = "23.11"; # Did you read the comment?
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
{ config, lib, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Apple Silicon + nix-darwin basics
|
|
||||||
nixpkgs.hostPlatform = "aarch64-darwin";
|
|
||||||
nix.enable = false; # Determinate manages Nix
|
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
|
||||||
|
|
||||||
system.primaryUser = "danny";
|
|
||||||
|
|
||||||
# Shells (fish config is in fish.nix, imported via flake.nix)
|
|
||||||
environment.shells = [ pkgs.fish ];
|
|
||||||
users.users.danny.shell = pkgs.fish;
|
|
||||||
|
|
||||||
# ollama
|
|
||||||
imports = [../ollama.nix];
|
|
||||||
services.ollama = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Networking (macOS-safe)
|
|
||||||
networking = {
|
|
||||||
# Set if you want a specific hostname in macOS UI as well:
|
|
||||||
hostName = "Daniel-Macbook-Air";
|
|
||||||
knownNetworkServices = [ "Wi-Fi" "Thunderbolt Bridge" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# macOS niceties
|
|
||||||
security.pam.services.sudo_local.touchIdAuth = true;
|
|
||||||
|
|
||||||
system.defaults = {
|
|
||||||
# Keyboard
|
|
||||||
NSGlobalDomain = {
|
|
||||||
AppleShowAllExtensions = true;
|
|
||||||
ApplePressAndHoldEnabled = true;
|
|
||||||
"com.apple.mouse.tapBehavior" = 1;
|
|
||||||
"com.apple.sound.beep.volume" = 0.0;
|
|
||||||
"com.apple.sound.beep.feedback" = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Finder & Dock
|
|
||||||
finder.AppleShowAllExtensions = true;
|
|
||||||
dock.autohide = true;
|
|
||||||
dock.mru-spaces = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
# User-specific packages and environment variables are now in home-manager (home.nix)
|
|
||||||
# Only system-level packages should remain here if needed
|
|
||||||
|
|
||||||
# Keep for darwin as well (tracks defaults across upgrades)
|
|
||||||
# current max per nix-darwin; bump only if a release notes says so
|
|
||||||
system.stateVersion = 6;
|
|
||||||
|
|
||||||
}
|
|
||||||
31
nixos/hosts/phantom-ship-hardware.nix
Normal file
31
nixos/hosts/phantom-ship-hardware.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by nixos-generate-config on phantom-ship (cleaned of chroot bind-mount duplicates)
|
||||||
|
{ config, lib, pkgs, modulesPath, ... }:
|
||||||
|
{
|
||||||
|
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
|
||||||
|
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" "rtsx_pci_sdmmc" ];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
boot.initrd.luks.devices."crypted".device = "/dev/disk/by-uuid/796c1fcd-af8b-449a-90f2-9ebcb9640462";
|
||||||
|
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "/dev/mapper/crypted";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = {
|
||||||
|
device = "/dev/disk/by-uuid/70E3-E5D8";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [ "fmask=0022" "dmask=0022" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [ ];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
}
|
||||||
664
nixos/hosts/phantom-ship.nix
Normal file
664
nixos/hosts/phantom-ship.nix
Normal file
|
|
@ -0,0 +1,664 @@
|
||||||
|
# NixOS server: SSH, auto-rebuild, NAT for rusty-anchor, OpenClaw gateway.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Telegram user ID(s) - gitignored, not committed to public repo.
|
||||||
|
# Create openclaw-allow-from.nix with e.g.: [ 12345678 ]
|
||||||
|
allowFromPath = ./openclaw-allow-from.nix;
|
||||||
|
openclawAllowFrom = if builtins.pathExists allowFromPath then import allowFromPath else [ ];
|
||||||
|
|
||||||
|
haraGmailMcp = pkgs.callPackage ../pkgs/hara-gmail-mcp { };
|
||||||
|
haraMcpServersJson = builtins.toJSON {
|
||||||
|
mcpServers = {
|
||||||
|
gmail = {
|
||||||
|
command = "${haraGmailMcp}/bin/hara-gmail-mcp";
|
||||||
|
args = [ ];
|
||||||
|
env = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./phantom-ship-hardware.nix
|
||||||
|
../pkgs/hara-gmail-mcp/module.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.hostName = "phantom-ship";
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
networking.wireless.enable = true; # credentials in /etc/wpa_supplicant.conf (outside repo)
|
||||||
|
|
||||||
|
# NAT: share WiFi internet to rusty-anchor over ethernet
|
||||||
|
networking.nat = {
|
||||||
|
enable = true;
|
||||||
|
externalInterface = "wlp1s0";
|
||||||
|
internalInterfaces = [ "enp0s31f6" ];
|
||||||
|
};
|
||||||
|
networking.interfaces.enp0s31f6.ipv4.addresses = [{
|
||||||
|
address = "10.0.0.1";
|
||||||
|
prefixLength = 24;
|
||||||
|
}];
|
||||||
|
services.dnsmasq = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
interface = "enp0s31f6";
|
||||||
|
dhcp-range = "10.0.0.10,10.0.0.50,24h";
|
||||||
|
dhcp-option = [ "3,10.0.0.1" "6,10.0.0.1" ]; # gateway + DNS
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall.trustedInterfaces = [ "enp0s31f6" ];
|
||||||
|
|
||||||
|
# KomTolk (:8080), Shelfish (:8081), Scuttle (:8082), Bananasimulator
|
||||||
|
# (:8083), Forgejo (:3000), Escape Hormuz (:8090), bon (:8091),
|
||||||
|
# notes (:8092), TDPixi (:8093) are reachable only over the ZeroTier mesh —
|
||||||
|
# the vps-relay Caddy reverse-proxies into them. Same pattern as
|
||||||
|
# sunken-ship's bbbot. Not in global allowedTCPPorts, so the WAN side
|
||||||
|
# stays closed.
|
||||||
|
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 3000 8080 8081 8082 8083 8084 8090 8091 8092 8093 ];
|
||||||
|
|
||||||
|
hardware.enableRedistributableFirmware = true; # iwlwifi (Intel 8260) + GPU + BT firmware
|
||||||
|
|
||||||
|
boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in
|
||||||
|
|
||||||
|
# Turn off panel backlight after boot so the screen actually dims.
|
||||||
|
systemd.services.server-backlight-off = {
|
||||||
|
description = "Turn off panel backlight after console idle (reduce burn-in)";
|
||||||
|
after = [ "multi-user.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = ''
|
||||||
|
${pkgs.coreutils}/bin/sleep 65
|
||||||
|
for d in /sys/class/backlight/*; do
|
||||||
|
[ -f "$d/brightness" ] && echo 0 > "$d/brightness" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
time.timeZone = "Europe/Copenhagen";
|
||||||
|
|
||||||
|
nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" "openclaw-2026.4.12" ];
|
||||||
|
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "claude-code" ];
|
||||||
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI)
|
||||||
|
system.stateVersion = "24.11";
|
||||||
|
|
||||||
|
users.users.danny = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
# Password is locked (key-only SSH). Use NixOS installer or recovery to reset if needed.
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
# Mac admin (~/.ssh/id_ed25519_phantom_ship on Daniel-Macbook-Air).
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDNl6PrKcEhmYJVqSXNcFU6cba3neekLBGnQCkD7lWAc danny@phantom-ship"
|
||||||
|
# Self-loopback (clan ssh-ng:// back to this host).
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyEX8De/b+sMAxUZIqqiPphcrWCoAsN5p8gRFubzqvB danny@phantom-ship"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# root needs the mac admin key so `clan machines update` can SSH to
|
||||||
|
# root@<host> for SOPS upload.
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDNl6PrKcEhmYJVqSXNcFU6cba3neekLBGnQCkD7lWAc danny@phantom-ship"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Key-only auth; no password or keyboard-interactive.
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Passwordless sudo for wheel.
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
git # clone/bootstrap and dm-pull-deploy
|
||||||
|
nodejs # npm for openclaw plugin installs
|
||||||
|
python3 # node-gyp dependency for openclaw plugins
|
||||||
|
wakeonlan # wake rusty-anchor: wakeonlan 00:16:cb:87:20:ba
|
||||||
|
bun # runtime for claude-code channel plugins
|
||||||
|
claude-code # Claude Code CLI (channels replaces openclaw)
|
||||||
|
openai-whisper # voice message transcription
|
||||||
|
ffmpeg # audio decoding for whisper
|
||||||
|
];
|
||||||
|
|
||||||
|
# OpenClaw AI gateway — DISABLED. Replaced by Claude Code Channels below.
|
||||||
|
# Config kept for easy rollback during validation; will be fully removed in a
|
||||||
|
# follow-up commit once Channels is proven stable. Workspace state at
|
||||||
|
# /var/lib/openclaw/ is preserved and also committed to vimwiki/openclaw/.
|
||||||
|
# Secrets (not in repo): /etc/openclaw/telegram-bot-token, /etc/openclaw/env (ANTHROPIC_API_KEY)
|
||||||
|
services.openclaw-gateway = {
|
||||||
|
enable = false;
|
||||||
|
environmentFiles = [ "/etc/openclaw/env" ];
|
||||||
|
servicePath = [ pkgs.git pkgs.nodejs pkgs.openai-whisper ];
|
||||||
|
config = {
|
||||||
|
gateway.mode = "local";
|
||||||
|
channels.telegram = {
|
||||||
|
tokenFile = "/etc/openclaw/telegram-bot-token";
|
||||||
|
allowFrom = openclawAllowFrom;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Claude Code Channels — Telegram bridge for @HarakatBot.
|
||||||
|
# Uses claude.ai subscription auth (long-lived OAuth token) to bypass
|
||||||
|
# the API rate limits OpenClaw was hitting.
|
||||||
|
# Secret (not in repo): /etc/claude-channels/env (CLAUDE_CODE_OAUTH_TOKEN)
|
||||||
|
# Plugin + pairing state lives at /home/danny/.claude/ (set up interactively).
|
||||||
|
systemd.services.claude-channels = {
|
||||||
|
description = "Claude Code Channels (Telegram bridge for @HarakatBot)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pkgs.claude-code pkgs.bun pkgs.git pkgs.util-linux ];
|
||||||
|
environment = {
|
||||||
|
HOME = "/home/danny";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = "danny";
|
||||||
|
Group = "users";
|
||||||
|
WorkingDirectory = "/home/danny";
|
||||||
|
EnvironmentFile = "/etc/claude-channels/env";
|
||||||
|
# claude needs a PTY; wrap with script(1). /dev/null discards the typescript.
|
||||||
|
# Permission bypass lives in ~/.claude/settings.json (permissions.defaultMode)
|
||||||
|
# — using the CLI flag triggers an interactive warning dialog at startup.
|
||||||
|
ExecStart = ''${pkgs.util-linux}/bin/script -qfc "${pkgs.claude-code}/bin/claude --channels plugin:telegram@claude-plugins-official --mcp-config /etc/hara/mcp-servers.json" /dev/null'';
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 5;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# OpenClaw gateway needs write access to its config dir and repo clones;
|
||||||
|
# shelfish wants its DB outside the rsynced code dir.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /etc/openclaw 0775 root openclaw - -"
|
||||||
|
"d /var/lib/openclaw/repos 0750 openclaw openclaw - -"
|
||||||
|
"d /home/danny/.local/share/shelfish 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/scuttle 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/bananasimulator 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/bananasimulator-beta 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/komtolk 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/escape_hormuz 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/scuttle/tiles 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/bon 0755 danny users - -"
|
||||||
|
"d /home/danny/.local/share/bon/images 0755 danny users - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Hara Gmail MCP server (path 1: IMAP+SMTP). Replaced by an OAuth2
|
||||||
|
# Gmail+Calendar server in path 2.
|
||||||
|
services.hara-gmail-mcp = {
|
||||||
|
enable = true;
|
||||||
|
package = haraGmailMcp;
|
||||||
|
accounts = [
|
||||||
|
{
|
||||||
|
email = "powerhouseplayer@gmail.com";
|
||||||
|
password_file = "/etc/openclaw/gmail-powerhouseplayer-app-password";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
email = "wildstylewarrior@gmail.com";
|
||||||
|
password_file = "/etc/openclaw/gmail-wildstylewarrior-app-password";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
email = "danielth95@gmail.com";
|
||||||
|
password_file = "/etc/openclaw/gmail-danielth95-app-password";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# MCP server registry consumed by claude-channels via --mcp-config.
|
||||||
|
environment.etc."hara/mcp-servers.json" = {
|
||||||
|
text = haraMcpServersJson;
|
||||||
|
mode = "0644";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Git config for the openclaw user: credential helper reads PAT from file.
|
||||||
|
# PAT (not in repo): /etc/openclaw/github-token (fine-grained, scoped to specific repos)
|
||||||
|
environment.etc."openclaw/gitconfig" = {
|
||||||
|
text = ''
|
||||||
|
[user]
|
||||||
|
name = OpenClaw Bot
|
||||||
|
email = noreply@openclaw.local
|
||||||
|
[credential "https://github.com"]
|
||||||
|
helper = "!f() { echo username=x-access-token; echo password=$(cat /etc/openclaw/github-token); }; f"
|
||||||
|
[safe]
|
||||||
|
directory = /var/lib/openclaw/repos
|
||||||
|
'';
|
||||||
|
mode = "0644";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Harden the openclaw-gateway systemd service (only when enabled).
|
||||||
|
systemd.services.openclaw-gateway = lib.mkIf config.services.openclaw-gateway.enable {
|
||||||
|
environment.GIT_CONFIG_GLOBAL = "/etc/openclaw/gitconfig";
|
||||||
|
serviceConfig = {
|
||||||
|
ProtectHome = "read-only";
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
PrivateTmp = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
ReadWritePaths = [ "/var/lib/openclaw" "/etc/openclaw" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Shipyard — Telegram bot that lists Danny's mini-apps and collects feedback.
|
||||||
|
# Code deployed out-of-band via rsync to /home/danny/shipyard/
|
||||||
|
# (staying in-tree in ~/python-projects/26_shipyard/ until spun out to its own repo).
|
||||||
|
# Bot token (not in repo): ~danny/.secrets/telegram-bot-token-shipyard
|
||||||
|
# Data (feedback.jsonl, feedback.db, pointer cache, feedback_media/):
|
||||||
|
# ~danny/.local/share/shipyard/
|
||||||
|
#
|
||||||
|
# Feedback now accepts photos / voice / video / docs / stickers etc.
|
||||||
|
# Phase A captures + stores raw files; Phase B derives OCR text
|
||||||
|
# (tesseract), speech transcripts (whisper-cpp), poster frames
|
||||||
|
# (ffmpeg) and PDF text (pdftotext) — all via subprocess, so each
|
||||||
|
# tool degrades gracefully if missing.
|
||||||
|
systemd.services.shipyard = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
python-telegram-bot
|
||||||
|
httpx
|
||||||
|
pillow # EXIF strip on captured photos
|
||||||
|
]);
|
||||||
|
# tesseract with English + Russian tessdata — vyscul writes in
|
||||||
|
# Russian, screenshots can land in either language.
|
||||||
|
tesseractWithLangs = pkgs.tesseract.override {
|
||||||
|
enableLanguages = [ "eng" "rus" ];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
description = "Shipyard Telegram bot (mini-app launcher + feedback)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [
|
||||||
|
pythonEnv
|
||||||
|
pkgs.ffmpeg # video/animation posters, sticker decode
|
||||||
|
tesseractWithLangs # photo OCR
|
||||||
|
pkgs.whisper-cpp # voice/audio transcription
|
||||||
|
pkgs.poppler-utils # pdftotext (document handling)
|
||||||
|
];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
# Owner-only commands (/admin, /grant, /revoke) — anyone else gets ignored.
|
||||||
|
SHIPYARD_OWNER_ID = "66070351"; # @DannyDannyDanny
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/shipyard";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python bot.py";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Shelfish — Goodreads-flavoured book club Mini App.
|
||||||
|
# Public traffic comes through vps-relay's Caddy → ZeroTier → here.
|
||||||
|
# See vps-relay.nix for the public-facing virtualHost. We never expose
|
||||||
|
# this host's IP directly.
|
||||||
|
# Code deployed out-of-band via rsync to /home/danny/shelfish/
|
||||||
|
# (staying in-tree in ~/python-projects/27_shelfish/ until spun out).
|
||||||
|
# Auth: validates Telegram WebApp initData against shipyard's bot token
|
||||||
|
# (the bot that publishes shelfish via shipyard's project list).
|
||||||
|
# DB lives outside the rsynced code dir so deploys don't clobber state.
|
||||||
|
# (tmpfiles rule for the DB dir is bundled into the OpenClaw block above.)
|
||||||
|
systemd.services.shelfish = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Shelfish FastAPI server (book club Mini App)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
SH_DB_PATH = "/home/danny/.local/share/shelfish/shelfish.db";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/shelfish";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8081";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Scuttle — topdown tilt-to-move multiplayer Mini App.
|
||||||
|
# Same vps-relay-fronted ZT path as shelfish; binds to :: so the
|
||||||
|
# ZeroTier IPv6 address can reach it.
|
||||||
|
# Code rsync'd from ~/python-projects/26_scuttle/ to /home/danny/scuttle/
|
||||||
|
# DB at ~/.local/share/scuttle/scuttle.db.
|
||||||
|
systemd.services.scuttle = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
websockets
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Scuttle FastAPI + WebSocket game server (geo: Østerbro)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
SC_DB_PATH = "/home/danny/.local/share/scuttle/scuttle.db";
|
||||||
|
SC_TILES_DIR = "/home/danny/.local/share/scuttle/tiles";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/scuttle";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8082";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bananasimulator — the actual project at https://bananasimulator.dannydannydanny.me
|
||||||
|
# (was a placeholder in shipyard's apps.json for ages). You ARE a banana.
|
||||||
|
# Code rsync'd from ~/python-projects/26_bananasimulator/ to /home/danny/bananasimulator/
|
||||||
|
systemd.services.bananasimulator = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Bananasimulator FastAPI server";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
BS_DB_PATH = "/home/danny/.local/share/bananasimulator/bananasimulator.db";
|
||||||
|
BS_RIPE_MIN_PER_STAGE = "2"; # 2 min/stage → 30 min to compost in production
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/bananasimulator";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8083";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Bananasimulator BETA — cheat-instance for testing the full progression
|
||||||
|
# end-to-end. Separate DB, exposes /api/cheat/* (gated by BS_BETA_MODE=1)
|
||||||
|
# so the frontend cheat menu can seed canonical states and reset.
|
||||||
|
# Faster ripening (0.2 min/stage = ~3 min to compost) so cycles are
|
||||||
|
# testable in real time. Same code base; deploy to a sibling dir.
|
||||||
|
# vhost in vps-relay.nix → bananasimulator-beta.dannydannydanny.me.
|
||||||
|
systemd.services.bananasimulator-beta = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Bananasimulator BETA (cheat instance) FastAPI server";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
BS_DB_PATH = "/home/danny/.local/share/bananasimulator-beta/bananasimulator.db";
|
||||||
|
BS_RIPE_MIN_PER_STAGE = "0.2"; # ~3 min to compost — testable in real time
|
||||||
|
BS_BETA_MODE = "1"; # exposes /api/cheat/* + flips beta=true in /api/me
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/bananasimulator-beta";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8084";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Escape Hormuz — turn-based boat-race Mini App (Hara's first build).
|
||||||
|
# Code lives at /home/danny/escape_hormuz/. Same vps-relay-fronted ZT path
|
||||||
|
# as the others; binds :: so the ZeroTier IPv6 address is reachable.
|
||||||
|
systemd.services.escape-hormuz = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Escape Hormuz FastAPI server (turn-based boat race)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
DB_PATH = "/home/danny/.local/share/escape_hormuz/escape_hormuz.db";
|
||||||
|
MINIAPP_URL = "https://escapehormuz.dannydannydanny.me";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/escape_hormuz";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8090";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ollama — local LLM runtime, used by bon's structured-data extraction
|
||||||
|
# step. Listens on 127.0.0.1:11434 only (not exposed over ZT).
|
||||||
|
# 3B is bon's default — 7B was tested but ran ~3.6 min/receipt vs ~30s
|
||||||
|
# for 3B on phantom-ship CPU, with no real accuracy gain (still picked
|
||||||
|
# line items as merchant on header-less OCR; that's an OCR problem,
|
||||||
|
# not a model problem). Both kept loaded so we can A/B without a pull.
|
||||||
|
services.ollama = {
|
||||||
|
enable = true;
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = 11434;
|
||||||
|
loadModels = [
|
||||||
|
"qwen2.5:3b-instruct" # ~2.5 GB — current default
|
||||||
|
"qwen2.5:7b-instruct" # ~4.7 GB — A/B testing only
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# bon — receipt scanner Mini App (camera capture + gallery + OCR + extract).
|
||||||
|
# Code rsync'd from ~/python-projects/26_bon/ to /home/danny/bon/
|
||||||
|
# Images on disk under /home/danny/.local/share/bon/images/<user_id>/
|
||||||
|
# OCR via tesseract (binary on PATH; server uses subprocess directly).
|
||||||
|
# Structured extraction via local Ollama (qwen2.5:3b-instruct).
|
||||||
|
systemd.services.bon = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
python-telegram-bot
|
||||||
|
python-multipart
|
||||||
|
pillow
|
||||||
|
httpx # for the Ollama HTTP call from extract.py
|
||||||
|
]);
|
||||||
|
# English-only for now — Danish receipts in DK are mostly English chars
|
||||||
|
# plus prices, which `eng` handles fine. Add more languages later if
|
||||||
|
# vyscul or other testers report missed text.
|
||||||
|
tesseractEng = pkgs.tesseract.override {
|
||||||
|
enableLanguages = [ "eng" ];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
description = "bon FastAPI server (receipt scanner)";
|
||||||
|
after = [ "network-online.target" "ollama.service" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv tesseractEng ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
BON_DB_PATH = "/home/danny/.local/share/bon/bon.db";
|
||||||
|
BON_IMAGES_DIR = "/home/danny/.local/share/bon/images";
|
||||||
|
BON_OLLAMA_URL = "http://127.0.0.1:11434";
|
||||||
|
BON_OLLAMA_MODEL = "qwen2.5:3b-instruct";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/bon";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8091";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# KomTolk (formerly translate-platform) — Copenhagen translation gigs Mini App.
|
||||||
|
# Code rsync'd from ~/python-projects/26_komtolk/ to /home/danny/komtolk/
|
||||||
|
systemd.services.komtolk = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
|
python-telegram-bot
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "KomTolk FastAPI server (Copenhagen translation gigs)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment = {
|
||||||
|
SHIPYARD_BOT_TOKEN_FILE = "/home/danny/.secrets/telegram-bot-token-shipyard";
|
||||||
|
KT_DB_PATH = "/home/danny/.local/share/komtolk/komtolk.db";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/komtolk";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8080";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# notes — tiny markdown blog + apex landing page.
|
||||||
|
# One service serves two hostnames via Host-header switch:
|
||||||
|
# notes.dannydannydanny.me → blog
|
||||||
|
# dannydannydanny.me → landing
|
||||||
|
# Code rsync'd from ~/python-projects/26_notes/ to /home/danny/notes/
|
||||||
|
systemd.services.notes = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
markdown
|
||||||
|
jinja2
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "notes — markdown blog + landing page";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/notes";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8092";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# TDPixi — Idle Tower Defence Telegram Mini App by @plasmagoat.
|
||||||
|
# Pure static serve, no DB. Code rsync'd to /home/danny/tdpixi/.
|
||||||
|
# Upstream: https://github.com/plasmagoat/TDPixi
|
||||||
|
systemd.services.tdpixi = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "tdpixi — Idle Tower Defence Mini App";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/tdpixi";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python -m uvicorn server:app --host :: --port 8093";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Hara morning heartbeat — daily email check + Telegram good-morning ping.
|
||||||
|
# Runs claude in print mode with the Gmail MCP, then sends output via Bot API.
|
||||||
|
# Token lives in ~/.claude/channels/telegram/.env (managed by the telegram plugin).
|
||||||
|
systemd.services.hara-heartbeat = {
|
||||||
|
description = "Hara morning heartbeat (email check + Telegram ping)";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
path = [ pkgs.claude-code pkgs.curl pkgs.jq pkgs.gnused ];
|
||||||
|
environment = {
|
||||||
|
HOME = "/home/danny";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "danny";
|
||||||
|
Group = "users";
|
||||||
|
WorkingDirectory = "/home/danny";
|
||||||
|
EnvironmentFile = "/etc/claude-channels/env";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
CHAT_ID="66070351"
|
||||||
|
BOT_TOKEN=$(grep '^TELEGRAM_BOT_TOKEN=' /home/danny/.claude/channels/telegram/.env | cut -d= -f2-)
|
||||||
|
MSG=$(${pkgs.claude-code}/bin/claude -p \
|
||||||
|
"You are Hara, a concise cat-energy AI assistant. Read ~/.hara/HEARTBEAT.md. Check Gmail for all three accounts (danielth95, powerhouseplayer, wildstylewarrior) for urgent unread emails — security alerts, invoices, anything requiring a decision; skip newsletters and marketing. Compose a short message for Danny: flag urgent emails if any, otherwise just a brief check-in. One message, very short, cat energy." \
|
||||||
|
--mcp-config /etc/hara/mcp-servers.json \
|
||||||
|
2>/dev/null | ${pkgs.gnused}/bin/sed 's/\*\*//g; s/\*//g; s/__//g; s/_//g')
|
||||||
|
${pkgs.curl}/bin/curl -sf -X POST \
|
||||||
|
"https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"chat_id\": $CHAT_ID, \"text\": $(echo "$MSG" | ${pkgs.jq}/bin/jq -Rs .)}" \
|
||||||
|
> /dev/null
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.hara-heartbeat = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = "06:07";
|
||||||
|
Timezone = "Europe/Copenhagen";
|
||||||
|
Persistent = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Forgejo — self-hosted Git forge. Phase 1 of the de-platform-from-GitHub
|
||||||
|
# roadmap (vimwiki/diary/2026-05-03.md). Public URL git.dannydannydanny.me
|
||||||
|
# is fronted by Caddy on vps-relay reverse-proxying over ZT to :3000 here.
|
||||||
|
# Auth for now: HTTPS + PAT (osxkeychain credential helper on the Mac).
|
||||||
|
# SSH disabled in Phase 1; revisit if push-via-https gets annoying.
|
||||||
|
# Backups: TODO — snapshot /var/lib/forgejo/ once it's up.
|
||||||
|
services.forgejo = {
|
||||||
|
enable = true;
|
||||||
|
database.type = "sqlite3"; # personal scale; one user, plenty
|
||||||
|
lfs.enable = true;
|
||||||
|
settings = {
|
||||||
|
DEFAULT.APP_NAME = "git.dannydannydanny.me";
|
||||||
|
server = {
|
||||||
|
DOMAIN = "git.dannydannydanny.me";
|
||||||
|
ROOT_URL = "https://git.dannydannydanny.me/";
|
||||||
|
# Bind to all interfaces — firewall above scopes inbound to ZT.
|
||||||
|
HTTP_ADDR = "0.0.0.0";
|
||||||
|
HTTP_PORT = 3000;
|
||||||
|
DISABLE_SSH = true;
|
||||||
|
};
|
||||||
|
service = {
|
||||||
|
DISABLE_REGISTRATION = true; # admin-bootstrapped only
|
||||||
|
REQUIRE_SIGNIN_VIEW = true; # no anonymous browsing
|
||||||
|
};
|
||||||
|
session.COOKIE_SECURE = true;
|
||||||
|
log.LEVEL = "Info";
|
||||||
|
repository.DEFAULT_BRANCH = "main";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Deploys flow through clan dm-pull-deploy: the dm-pull-deploy.path
|
||||||
|
# watcher rebuilds when sunken-ship announces a new origin/main rev.
|
||||||
|
# The legacy pull-based dotfiles-rebuild module was retired 2026-05-19.
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,11 @@
|
||||||
#
|
#
|
||||||
# One-time on server: clone repo to /etc/dotfiles (root needs git access).
|
# One-time on server: clone repo to /etc/dotfiles (root needs git access).
|
||||||
# If private repo: use SSH (ssh:// or git@) and add root's key to GitHub, or use HTTPS + token.
|
# If private repo: use SSH (ssh:// or git@) and add root's key to GitHub, or use HTTPS + token.
|
||||||
# Then: sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship
|
# Then: sudo nixos-rebuild switch --flake /etc/dotfiles#sunken-ship
|
||||||
# If sudo git is not found: sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main
|
# If sudo git is not found: sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main
|
||||||
# Timer runs every 15 min: git fetch, pull if origin/main changed, rebuild.
|
# Timer runs every 15 min: git fetch, pull if origin/main changed, rebuild.
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
let
|
|
||||||
dotfilesDir = "/etc/dotfiles";
|
|
||||||
flakeRef = "${dotfilesDir}/nixos#sunken-ship";
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
imports = [ ./sunken-ship-hardware.nix ];
|
imports = [ ./sunken-ship-hardware.nix ];
|
||||||
|
|
||||||
|
|
@ -22,8 +18,7 @@ in
|
||||||
boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in
|
boot.kernelParams = [ "consoleblank=60" ]; # blank TTY after 60s to reduce burn-in
|
||||||
|
|
||||||
# Turn off panel backlight after boot so the screen actually dims (consoleblank only blanks framebuffer).
|
# Turn off panel backlight after boot so the screen actually dims (consoleblank only blanks framebuffer).
|
||||||
# At the console, run: light -S 100 (or any 0–100) to restore brightness.
|
# At the console, run: brightnessctl set 100% (or `brightnessctl max`) to restore brightness.
|
||||||
programs.light.enable = true;
|
|
||||||
systemd.services.server-backlight-off = {
|
systemd.services.server-backlight-off = {
|
||||||
description = "Turn off panel backlight after console idle (reduce burn-in)";
|
description = "Turn off panel backlight after console idle (reduce burn-in)";
|
||||||
after = [ "multi-user.target" ];
|
after = [ "multi-user.target" ];
|
||||||
|
|
@ -38,15 +33,28 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
programs.nix-ld.enable = true; # run dynamically linked binaries (e.g. Claude Code remote CLI)
|
||||||
system.stateVersion = "24.11";
|
system.stateVersion = "24.11";
|
||||||
|
|
||||||
users.users.danny = {
|
users.users.danny = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
extraGroups = [ "wheel" "video" ]; # video: backlight control via light(1)
|
extraGroups = [ "wheel" "video" "audio" ]; # video: backlight; audio: sound devices
|
||||||
# SSH keys: push via scp, don't commit. NixOS does not manage authorized_keys so scp'd keys persist.
|
openssh.authorizedKeys.keys = [
|
||||||
# Example: scp ~/.ssh/id_ed25519_sunken_ship.pub danny@server:/tmp/ then on server: mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys
|
# Mac admin (~/.ssh/id_ed25519_sunken_ship on Daniel-Macbook-Air).
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship"
|
||||||
|
# Self-loopback (used by clan ssh-ng:// during nix-copy-closure
|
||||||
|
# back to this same host on `clan machines update`). Pubkey of the
|
||||||
|
# /home/danny/.ssh/id_ed25519 that lives on this host.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship self"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# root needs the mac admin key so `clan machines update` can SSH to
|
||||||
|
# root@<host> to upload SOPS keys (sops-install-secrets bootstrap).
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@sunken-ship"
|
||||||
|
];
|
||||||
|
|
||||||
# Key-only auth; no password or keyboard-interactive.
|
# Key-only auth; no password or keyboard-interactive.
|
||||||
services.openssh = {
|
services.openssh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -59,28 +67,437 @@ in
|
||||||
|
|
||||||
# Passwordless sudo for wheel.
|
# Passwordless sudo for wheel.
|
||||||
security.sudo.wheelNeedsPassword = false;
|
security.sudo.wheelNeedsPassword = false;
|
||||||
environment.systemPackages = [ pkgs.git ]; # for clone/bootstrap and timer
|
|
||||||
|
|
||||||
# Pull dotfiles and rebuild if the repo has new commits.
|
# Trust `danny` for Nix remote builds (so the mac can delegate
|
||||||
systemd.services.dotfiles-rebuild = {
|
# x86_64-linux builds here via ssh-ng://danny@sunken-ship-zt).
|
||||||
description = "Pull dotfiles and run nixos-rebuild if repo changed";
|
nix.settings.trusted-users = [ "root" "danny" ];
|
||||||
path = with pkgs; [ git nix ];
|
environment.systemPackages = with pkgs; [
|
||||||
|
git # clone/bootstrap, repo-pull timers, dm-pull-deploy push
|
||||||
|
brightnessctl # manual backlight; replaces removed `light` from nixpkgs
|
||||||
|
uxplay # AirPlay mirroring receiver
|
||||||
|
alsa-utils # aplay, amixer, arecord for audio debugging
|
||||||
|
];
|
||||||
|
|
||||||
|
# Avahi (mDNS) — required for AirPlay discovery.
|
||||||
|
services.avahi = {
|
||||||
|
enable = true;
|
||||||
|
nssmdns4 = true;
|
||||||
|
publish = { enable = true; userServices = true; };
|
||||||
|
};
|
||||||
|
|
||||||
|
# Open firewall for AirPlay (mDNS + UxPlay default ports) + Navidrome.
|
||||||
|
# bbbot's HTTP backend (port 8080) is intentionally NOT in the global
|
||||||
|
# allowedTCPPorts — it's only allowed on the ZeroTier interface
|
||||||
|
# (clan-managed name; matches anything starting with `zt`) so the
|
||||||
|
# vps-relay Caddy can reach it via the ZT mesh. Same trick could lock
|
||||||
|
# 4533 down later but Navidrome stays globally accessible for now (LAN
|
||||||
|
# convenience).
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [ 7000 7001 7100 4533 ];
|
||||||
|
allowedUDPPorts = [ 5353 6000 6001 7011 ];
|
||||||
|
# 8080: bbbot HTTP backend. 8081: bbbot SHIPYARD STAGING (B3Bot beta).
|
||||||
|
# 8091: mulbo-server companion service. All ZT-only — see vps-relay.nix
|
||||||
|
# for the reverse proxies that expose them publicly.
|
||||||
|
interfaces."zt+".allowedTCPPorts = [ 8080 8081 8091 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Navidrome — self-hosted music streaming server (Subsonic API).
|
||||||
|
# Music library: /srv/music (bind-mounted from /home/danny/music).
|
||||||
|
# Web UI + Substreamer client on port 4533.
|
||||||
|
services.navidrome = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
Address = "0.0.0.0";
|
||||||
|
Port = 4533;
|
||||||
|
MusicFolder = "/srv/music";
|
||||||
|
# Auto-delete `missing=1` rows during scan so transient files
|
||||||
|
# (e.g. mulbo dedupe quarantine ones) don't accumulate as stale
|
||||||
|
# track IDs that Substreamer caches and then 500s on. Without
|
||||||
|
# this, Navidrome keeps missing rows forever (default behaviour
|
||||||
|
# preserves play history; we trade that for client-cache hygiene).
|
||||||
|
# Valid values: never | always | full. `always` purges on every
|
||||||
|
# scan (selective + full); risk on transient missing is fine
|
||||||
|
# here (stable local disk).
|
||||||
|
Scanner.PurgeMissing = "always";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Navidrome's Subsonic API path field is tag-virtual; only the internal
|
||||||
|
# SQLite has real fs paths. mulbo-server reads navidrome.db ro to
|
||||||
|
# power /folders + POST /tracks resolution. UMask=0027 makes new DB
|
||||||
|
# files (and WAL rotations) group-readable; the tmpfile rule fixes the
|
||||||
|
# existing files written under the previous 0600 umask.
|
||||||
|
systemd.services.navidrome.serviceConfig.UMask = lib.mkForce "0027";
|
||||||
|
|
||||||
|
# Persist the bind mount so navidrome can read music outside ProtectHome.
|
||||||
|
fileSystems."/srv/music" = {
|
||||||
|
device = "/home/danny/music";
|
||||||
|
fsType = "none";
|
||||||
|
options = [ "bind" "ro" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Navidrome is now reachable only over the ZeroTier mesh — see the
|
||||||
|
# sunken-ship-zt SSH alias on the mac, or hit http://[fdd5:53a2:de33:
|
||||||
|
# d269:6499:93d5:53a2:de33]:4533 directly from any ZT-joined device.
|
||||||
|
# The Cloudflare Tunnel + its clan vars generator were retired in 4d;
|
||||||
|
# delete the tunnel itself in the Cloudflare Zero Trust dashboard.
|
||||||
|
|
||||||
|
# UxPlay AirPlay receiver — audio-only, outputs directly to Scarlett Solo via ALSA.
|
||||||
|
# Runs as a system service (no PipeWire needed on a headless server).
|
||||||
|
systemd.services.uxplay = {
|
||||||
|
description = "UxPlay AirPlay receiver";
|
||||||
|
after = [ "network-online.target" "avahi-daemon.service" ];
|
||||||
|
wants = [ "network-online.target" "avahi-daemon.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = ''${pkgs.uxplay}/bin/uxplay -n sunken-ship -p -vs 0 -as "audioconvert ! audioresample ! alsasink device=plughw:USB,0 buffer-time=200000"'';
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
User = "danny";
|
||||||
|
SupplementaryGroups = [ "audio" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# BigBiggerBiggestBot — Mini App backend (no Telegram polling).
|
||||||
|
# Code: https://github.com/DannyDannyDanny/bigbiggerbiggestbot cloned at /home/danny/tg_fitness_bot
|
||||||
|
# Bot token (used only for validating Telegram WebApp initData HMACs):
|
||||||
|
# ~danny/.secrets/bigbiggerbiggestbot
|
||||||
|
# Deployment: fitness-bot-pull timer below runs every 15 min, git pulls, restarts service on changes.
|
||||||
|
#
|
||||||
|
# Mini App URL is fronted by Caddy on the vps-relay host at
|
||||||
|
# https://bbbot.dannydannydanny.me (VPS → ZeroTier → localhost:8080).
|
||||||
|
# start.py honors WEBAPP_URL to skip starting its own cloudflared
|
||||||
|
# Quick Tunnel when the stable URL from the VPS is already set.
|
||||||
|
#
|
||||||
|
# The slash-command bot (bot.py) was removed in May 2026 — the Mini App
|
||||||
|
# is now the only interface. No python-telegram-bot dependency required.
|
||||||
|
# ExecStartPost re-publishes the bot's chat-side presence (menu button,
|
||||||
|
# description, cleared command list) every time the service starts.
|
||||||
|
# Idempotent against the Telegram API. Errors are non-fatal (`-` prefix).
|
||||||
|
systemd.services.fitness-bot = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
python-dotenv
|
||||||
|
aiohttp
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "BigBiggerBiggestBot Mini App backend";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment.WEBAPP_URL = "https://bbbot.dannydannydanny.me";
|
||||||
|
# Bind dual-stack so the VPS Caddy can reach us over ZT IPv6.
|
||||||
|
environment.API_HOST = "::";
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/tg_fitness_bot";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python start.py";
|
||||||
|
ExecStartPost = "-${pythonEnv}/bin/python scripts/set-bot-presence.py";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Pull fitness bot from GitHub and restart the service if the repo has new commits.
|
||||||
|
# Code lives at /home/danny/tg_fitness_bot (git clone of DannyDannyDanny/bigbiggerbiggestbot).
|
||||||
|
# workouts.db is gitignored — preserved across pulls.
|
||||||
|
systemd.services.fitness-bot-pull = {
|
||||||
|
description = "Pull fitness bot and restart service if repo changed";
|
||||||
|
path = with pkgs; [ git systemd ];
|
||||||
|
environment.GIT_CONFIG_COUNT = "1";
|
||||||
|
environment.GIT_CONFIG_KEY_0 = "safe.directory";
|
||||||
|
environment.GIT_CONFIG_VALUE_0 = "/home/danny/tg_fitness_bot";
|
||||||
script = ''
|
script = ''
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd ${dotfilesDir}
|
cd /home/danny/tg_fitness_bot
|
||||||
git fetch origin
|
git fetch origin
|
||||||
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then
|
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
git pull origin main
|
git pull origin main
|
||||||
exec nixos-rebuild switch --flake ${flakeRef}
|
systemctl restart fitness-bot
|
||||||
'';
|
'';
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.timers.dotfiles-rebuild = {
|
systemd.timers.fitness-bot-pull = {
|
||||||
wantedBy = [ "timers.target" ];
|
wantedBy = [ "timers.target" ];
|
||||||
timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes
|
timerConfig.OnCalendar = "*-*-* *:07/15:00"; # every 15 minutes, offset from dotfiles-rebuild
|
||||||
timerConfig.RandomizedDelaySec = "2min";
|
timerConfig.RandomizedDelaySec = "2min";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ── Shipyard staging — B3Bot beta tenant under shipyard_poc_bot ──────
|
||||||
|
# Mini-App-only HTTP server (no Telegram polling — shipyard_poc_bot on
|
||||||
|
# phantom-ship owns the polling loop; this service only validates Telegram
|
||||||
|
# WebApp initData HMACs against the shared bot token).
|
||||||
|
#
|
||||||
|
# Working dir: /home/danny/tg_fitness_bot_shipyard (separate clone of the
|
||||||
|
# same repo, gitignored workouts.db kept across pulls).
|
||||||
|
# Branch: origin/staging (push there to deploy here; push to origin/main for prod).
|
||||||
|
# Token file: /home/danny/.secrets/shipyard_poc_bot.env
|
||||||
|
# File contents: BOT_TOKEN=<shipyard_poc_bot token>
|
||||||
|
# Service won't start until this file exists (ConditionPathExists).
|
||||||
|
# Mini App URL: https://b3.dannydannydanny.me (vps-relay Caddy →
|
||||||
|
# ZT IPv6 → here:8081). Stable across restarts — listed in
|
||||||
|
# ~/python-projects/26_shipyard/apps.json.
|
||||||
|
# Workflow: git push origin <branch>:staging → wait ~15 min → tap B3Bot
|
||||||
|
# beta in shipyard_poc_bot's launcher → test → git push <branch>:main.
|
||||||
|
systemd.services.fitness-bot-shipyard = let
|
||||||
|
pythonEnv = pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
python-dotenv
|
||||||
|
aiohttp
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "BigBiggerBiggestBot — SHIPYARD STAGING instance";
|
||||||
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pythonEnv ];
|
||||||
|
environment.API_HOST = "::";
|
||||||
|
environment.API_PORT = "8081";
|
||||||
|
# Stable URL fronted by vps-relay's Caddy → ZT → here:8081.
|
||||||
|
# WEBAPP_URL set tells start.py to skip cloudflared entirely.
|
||||||
|
environment.WEBAPP_URL = "https://b3.dannydannydanny.me";
|
||||||
|
unitConfig.ConditionPathExists = "/home/danny/.secrets/shipyard_poc_bot.env";
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/tg_fitness_bot_shipyard";
|
||||||
|
EnvironmentFile = "/home/danny/.secrets/shipyard_poc_bot.env";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python start.py";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 10;
|
||||||
|
User = "danny";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.fitness-bot-shipyard-pull = {
|
||||||
|
description = "Pull shipyard fitness bot from origin/staging and restart if changed";
|
||||||
|
path = with pkgs; [ git systemd ];
|
||||||
|
environment.GIT_CONFIG_COUNT = "1";
|
||||||
|
environment.GIT_CONFIG_KEY_0 = "safe.directory";
|
||||||
|
environment.GIT_CONFIG_VALUE_0 = "/home/danny/tg_fitness_bot_shipyard";
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
if [ ! -d /home/danny/tg_fitness_bot_shipyard/.git ]; then
|
||||||
|
echo "Shipyard working dir not bootstrapped yet — skipping pull."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
cd /home/danny/tg_fitness_bot_shipyard
|
||||||
|
git fetch origin
|
||||||
|
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/staging)" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git pull origin staging
|
||||||
|
systemctl restart fitness-bot-shipyard
|
||||||
|
'';
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.fitness-bot-shipyard-pull = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
# Offset from prod (07/15), mulbo (11/15), and dotfiles-rebuild.
|
||||||
|
timerConfig.OnCalendar = "*-*-* *:13/15:00";
|
||||||
|
timerConfig.RandomizedDelaySec = "2min";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Mulbo companion service (Phase 5: uploads + dedup index + folders).
|
||||||
|
# Wire spec: ~danny/python-projects/20_mulbo/SERVER_API.md.
|
||||||
|
# Bootstrap (one-time): git clone git@github.com:DannyDannyDanny/python-projects.git /home/danny/python-projects
|
||||||
|
# (uses sunken-ship's id_ed25519 as a read-only deploy key on the repo)
|
||||||
|
# ZT-only via the firewall rule above (port 8091). Runs as `danny` so
|
||||||
|
# writes go through to /home/danny/music/mulbo-uploads, which Navidrome
|
||||||
|
# reads via the existing /srv/music ro bind-mount with no mount changes.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /home/danny/music/mulbo-uploads 0755 danny users -"
|
||||||
|
# One-time fix for the existing navidrome.db (+ WAL/SHM) created
|
||||||
|
# under the old 0600 umask. UMask=0027 above keeps future writes
|
||||||
|
# group-readable.
|
||||||
|
"z /var/lib/navidrome/navidrome.db 0640 navidrome navidrome -"
|
||||||
|
"z /var/lib/navidrome/navidrome.db-wal 0640 navidrome navidrome -"
|
||||||
|
"z /var/lib/navidrome/navidrome.db-shm 0640 navidrome navidrome -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.mulbo-server = let
|
||||||
|
pythonEnv = pkgs.python312.withPackages (ps: with ps; [
|
||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
python-multipart
|
||||||
|
mutagen # tag writeback (enrich.write_tags); needed by the
|
||||||
|
# /enrich/revert endpoint which reuses enrich.py.
|
||||||
|
numpy # FFT for spectral-rolloff analysis (quality.py); used
|
||||||
|
# by chromaprint-dupe winner picker in --spectral mode.
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Mulbo companion service (uploads, dedup, folders)";
|
||||||
|
after = [ "network-online.target" "navidrome.service" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
# ffmpeg: PCM extraction for quality.py's spectral-rolloff probe
|
||||||
|
# (chromaprint-dupe winner picker in --spectral mode). Without it,
|
||||||
|
# the subprocess silently fails and rolloff returns 0Hz.
|
||||||
|
path = with pkgs; [ ffmpeg ];
|
||||||
|
environment = {
|
||||||
|
MULBO_UPLOADS_DIR = "/home/danny/music/mulbo-uploads";
|
||||||
|
MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db";
|
||||||
|
MULBO_MUSIC_ROOT = "/srv/music"; # ro view via bind-mount; reads + hashing
|
||||||
|
MULBO_MUSIC_WRITE_ROOT = "/home/danny/music"; # underlying rw path; deletes + quarantines
|
||||||
|
MULBO_NAVIDROME_URL = "http://localhost:4533";
|
||||||
|
MULBO_BIND_HOST = "::";
|
||||||
|
MULBO_BIND_PORT = "8091";
|
||||||
|
PYTHONUNBUFFERED = "1"; # immediate journal output
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "/home/danny/python-projects/20_mulbo";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python mulbo_server/app.py";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
User = "danny";
|
||||||
|
# Read-only access to navidrome.db (+WAL/SHM) — see UMask override
|
||||||
|
# on the navidrome service above.
|
||||||
|
SupplementaryGroups = [ "navidrome" ];
|
||||||
|
StateDirectory = "mulbo-server"; # /var/lib/mulbo-server, owned by danny
|
||||||
|
# Navidrome credentials — file format: KEY=value lines.
|
||||||
|
# Required keys: MULBO_NAVIDROME_USER, MULBO_NAVIDROME_PASS.
|
||||||
|
# Created manually on sunken-ship (mode 600, owned by danny):
|
||||||
|
# echo -e "MULBO_NAVIDROME_USER=DannyDannyDanny\nMULBO_NAVIDROME_PASS=..." > ~/.secrets/mulbo-server-navidrome
|
||||||
|
# chmod 600 ~/.secrets/mulbo-server-navidrome
|
||||||
|
EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Pull mulbo (python-projects repo) and restart service if repo changed.
|
||||||
|
# Repo lives at /home/danny/python-projects (must be cloned manually first
|
||||||
|
# — see bootstrap note above). DBs/state live in /var/lib/mulbo-server,
|
||||||
|
# not in the repo, so they survive pulls.
|
||||||
|
systemd.services.mulbo-pull = {
|
||||||
|
description = "Pull mulbo repo and restart mulbo-server if changed";
|
||||||
|
# openssh: `git fetch origin` over an SSH remote forks `ssh`; without
|
||||||
|
# it git dies with "cannot run ssh: No such file or directory" and the
|
||||||
|
# unit fails (shows up as system `degraded`).
|
||||||
|
path = with pkgs; [ git openssh systemd ];
|
||||||
|
environment = {
|
||||||
|
GIT_CONFIG_COUNT = "1";
|
||||||
|
GIT_CONFIG_KEY_0 = "safe.directory";
|
||||||
|
GIT_CONFIG_VALUE_0 = "/home/danny/python-projects";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -euo pipefail
|
||||||
|
cd /home/danny/python-projects
|
||||||
|
git fetch origin
|
||||||
|
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git pull origin main
|
||||||
|
systemctl restart mulbo-server
|
||||||
|
'';
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.mulbo-pull = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig.OnCalendar = "*-*-* *:11/15:00"; # every 15 min, offset from fitness-bot-pull and dotfiles-rebuild
|
||||||
|
timerConfig.RandomizedDelaySec = "2min";
|
||||||
|
};
|
||||||
|
|
||||||
|
# dm-pull-deploy push automation. sunken-ship is the push node for the
|
||||||
|
# clan dm-pull-deploy instance (wired in flake-modules/clan.nix), but
|
||||||
|
# the upstream module only ships a manual `dm-send-deploy` binary — no
|
||||||
|
# scheduler. This timer announces the latest origin/main rev over
|
||||||
|
# data-mesher gossip; the watchers (dm-pull-deploy.path on sunken +
|
||||||
|
# phantom) compare and only rebuild when the rev actually changes, so
|
||||||
|
# re-announcing the same rev is a cheap no-op. This is the replacement
|
||||||
|
# for the legacy dotfiles-rebuild pull timer (being retired).
|
||||||
|
#
|
||||||
|
# dm-send-deploy self-discovers the rev via `git ls-remote` and signs
|
||||||
|
# with /run/secrets/vars/dm-pull-deploy-signing-key — needs root.
|
||||||
|
systemd.services.dm-pull-deploy-push = {
|
||||||
|
description = "Announce latest origin/main rev via data-mesher (dm-pull-deploy push)";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "/run/current-system/sw/bin/dm-send-deploy";
|
||||||
|
User = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.dm-pull-deploy-push = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig.OnCalendar = "*-*-* *:04/15:00"; # every 15 min, offset from the other pull timers
|
||||||
|
timerConfig.RandomizedDelaySec = "2min";
|
||||||
|
timerConfig.Persistent = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# One-shot backfill: walks Navidrome's media_file, computes
|
||||||
|
# (sha256, chromaprint) per file, populates mulbo-server's tracks_index
|
||||||
|
# with the corresponding navidrome_track_id. Idempotent — existing rows
|
||||||
|
# left alone. Without this, /tracks/by-hash misses for every existing
|
||||||
|
# offshore track and `mulbo reconcile-local` duplicates content.
|
||||||
|
#
|
||||||
|
# Trigger manually: sudo systemctl start mulbo-server-backfill
|
||||||
|
# Follow progress: journalctl -fu mulbo-server-backfill
|
||||||
|
systemd.services.mulbo-server-backfill = let
|
||||||
|
pythonEnv = pkgs.python312.withPackages (ps: with ps; [ ]);
|
||||||
|
in {
|
||||||
|
description = "Backfill mulbo-server tracks_index from Navidrome catalog";
|
||||||
|
after = [ "mulbo-server.service" ];
|
||||||
|
requires = [ "mulbo-server.service" ];
|
||||||
|
path = [ pkgs.chromaprint ]; # provides fpcalc
|
||||||
|
environment = {
|
||||||
|
MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db";
|
||||||
|
MULBO_NAVIDROME_DB = "/var/lib/navidrome/navidrome.db";
|
||||||
|
MULBO_MUSIC_ROOT = "/srv/music";
|
||||||
|
PYTHONUNBUFFERED = "1";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
WorkingDirectory = "/home/danny/python-projects/20_mulbo";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python mulbo_server/backfill.py";
|
||||||
|
User = "danny";
|
||||||
|
SupplementaryGroups = [ "navidrome" ]; # ro access to navidrome.db
|
||||||
|
StateDirectory = "mulbo-server"; # so /var/lib/mulbo-server/index.db stays writable
|
||||||
|
TimeoutSec = "8h"; # full backfill on 274 GB ≈ 1h, leave headroom
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Phase 7.5 enrichment one-shot. For tracks where Navidrome's tags
|
||||||
|
# are empty/Unknown, runs three sources (filename heuristics, yt-dlp
|
||||||
|
# for SoundCloud `[<id>]` patterns, AcoustID+MusicBrainz), votes the
|
||||||
|
# results, and writes back via mutagen with strict-replacement
|
||||||
|
# (never touches user-set tags).
|
||||||
|
#
|
||||||
|
# Trigger: sudo systemctl start mulbo-server-enrich
|
||||||
|
# Follow progress: journalctl -fu mulbo-server-enrich
|
||||||
|
systemd.services.mulbo-server-enrich = let
|
||||||
|
pythonEnv = pkgs.python312.withPackages (ps: with ps; [
|
||||||
|
mutagen # tag writeback
|
||||||
|
]);
|
||||||
|
in {
|
||||||
|
description = "Enrich Navidrome tracks with empty/Unknown metadata";
|
||||||
|
after = [ "mulbo-server.service" ];
|
||||||
|
requires = [ "mulbo-server.service" ];
|
||||||
|
path = with pkgs; [ yt-dlp chromaprint ]; # yt-dlp for SC/YT lookups, chromaprint for AcoustID's -plain fingerprint
|
||||||
|
environment = {
|
||||||
|
MULBO_INDEX_DB = "/var/lib/mulbo-server/index.db";
|
||||||
|
MULBO_NAVIDROME_DB = "/var/lib/navidrome/navidrome.db";
|
||||||
|
MULBO_MUSIC_ROOT = "/srv/music";
|
||||||
|
MULBO_MUSIC_WRITE_ROOT = "/home/danny/music";
|
||||||
|
PYTHONUNBUFFERED = "1";
|
||||||
|
};
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
WorkingDirectory = "/home/danny/python-projects/20_mulbo";
|
||||||
|
ExecStart = "${pythonEnv}/bin/python mulbo_server/enrich.py";
|
||||||
|
User = "danny";
|
||||||
|
SupplementaryGroups = [ "navidrome" ];
|
||||||
|
StateDirectory = "mulbo-server";
|
||||||
|
# Add MULBO_ACOUSTID_KEY to the secrets file to enable the
|
||||||
|
# AcoustID source. yt-dlp source needs no key. Filename source
|
||||||
|
# needs nothing.
|
||||||
|
EnvironmentFile = "/home/danny/.secrets/mulbo-server-navidrome";
|
||||||
|
TimeoutSec = "8h";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Deploys now flow through clan dm-pull-deploy: the dm-pull-deploy-push
|
||||||
|
# timer above announces origin/main, and the dm-pull-deploy.path watcher
|
||||||
|
# rebuilds on change. The legacy pull-based dotfiles-rebuild module was
|
||||||
|
# retired 2026-05-19.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
189
nixos/hosts/vps-relay.nix
Normal file
189
nixos/hosts/vps-relay.nix
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
# Hetzner Cloud VPS — public reverse proxy into the clan.
|
||||||
|
#
|
||||||
|
# Role: terminates public TLS via Caddy + Let's Encrypt, reverse-proxies
|
||||||
|
# each declared subdomain over ZeroTier to the appropriate homelab host.
|
||||||
|
# No navidrome/bbbot data ever hits disk here; this box is a relay.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [ ../disko-cloud.nix ];
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
|
||||||
|
# Hetzner Cloud vServers boot in BIOS mode (confirmed via rescue:
|
||||||
|
# /sys/firmware/efi doesn't exist, product_name=vServer). systemd-boot
|
||||||
|
# is UEFI-only, so use GRUB/BIOS. disko's EF02 BIOS boot partition
|
||||||
|
# already tells GRUB where to embed stage-1.5; we just enable grub +
|
||||||
|
# set the install device list.
|
||||||
|
boot.loader.systemd-boot.enable = lib.mkForce false;
|
||||||
|
boot.loader.grub.enable = lib.mkForce true;
|
||||||
|
boot.loader.grub.efiSupport = lib.mkForce false;
|
||||||
|
boot.loader.grub.devices = lib.mkForce [ "/dev/sda" ];
|
||||||
|
# Ensure no default-set .device slips through and duplicates mirroredBoots.
|
||||||
|
boot.loader.grub.device = lib.mkForce "nodev";
|
||||||
|
|
||||||
|
# Hetzner Cloud cx23 uses QEMU virtio-scsi for the disk and virtio-net
|
||||||
|
# for the NIC. Without these modules in initrd, the kernel can't find
|
||||||
|
# the root partition and hangs during boot.
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"virtio_pci"
|
||||||
|
"virtio_scsi"
|
||||||
|
"virtio_net"
|
||||||
|
"virtio_blk"
|
||||||
|
"ata_piix"
|
||||||
|
"sd_mod"
|
||||||
|
"sr_mod"
|
||||||
|
];
|
||||||
|
boot.kernelModules = [ "virtio_pci" "virtio_scsi" "virtio_net" ];
|
||||||
|
|
||||||
|
# Cloud provisioners add the initial root SSH key via cloud-init or
|
||||||
|
# equivalent; we don't run cloud-init. All config is baked at install.
|
||||||
|
networking.hostName = "vps-relay";
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
time.timeZone = "Europe/Copenhagen";
|
||||||
|
|
||||||
|
# --- User + SSH ------------------------------------------------------
|
||||||
|
users.users.danny = {
|
||||||
|
isNormalUser = true;
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
# Mac admin key (~/.ssh/id_ed25519_sunken_ship on the laptop — the
|
||||||
|
# key the Mac uses to reach the fleet). Used for `clan machines
|
||||||
|
# update vps-relay` from the Mac and at install via clan.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKW/akfIiVU5o63YrTAJVZhMj7kXfYHOnXDtlpVFW7pf danny@mac-admin"
|
||||||
|
# sunken-ship's own key, so the push node can SSH into vps-relay
|
||||||
|
# over ZeroTier for mesh introspection / debugging.
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9t4YAaoHvVouqp+qyFOq8o3SAtXMiAmjF6J0ldyx4g danny@sunken-ship"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
users.users.root.openssh.authorizedKeys.keys =
|
||||||
|
config.users.users.danny.openssh.authorizedKeys.keys;
|
||||||
|
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
KbdInteractiveAuthentication = false;
|
||||||
|
PermitRootLogin = "prohibit-password";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# --- Firewall --------------------------------------------------------
|
||||||
|
# Public: 22 (SSH), 80 + 443 (Caddy).
|
||||||
|
# ZT interface: trusted (set in the clan ZT module).
|
||||||
|
networking.firewall.enable = true;
|
||||||
|
networking.firewall.allowedTCPPorts = [ 22 80 443 ];
|
||||||
|
|
||||||
|
# fail2ban — public SSH gets brute-force probed within minutes of any
|
||||||
|
# cloud VM being created. Ban offending IPs after a few failures.
|
||||||
|
services.fail2ban = {
|
||||||
|
enable = true;
|
||||||
|
bantime = "1h";
|
||||||
|
bantime-increment = {
|
||||||
|
enable = true;
|
||||||
|
multipliers = "1 4 16 64 256"; # 1h, 4h, 16h, ~2.7d, ~10.7d
|
||||||
|
maxtime = "30d";
|
||||||
|
};
|
||||||
|
jails.sshd.settings = {
|
||||||
|
enabled = true;
|
||||||
|
maxretry = 5;
|
||||||
|
findtime = "10m";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# --- Caddy reverse proxy --------------------------------------------
|
||||||
|
# Subdomains → clan backends over ZeroTier. IPs are sunken-ship's /
|
||||||
|
# phantom-ship's ZT IPv6; brackets required in URLs.
|
||||||
|
services.caddy = {
|
||||||
|
enable = true;
|
||||||
|
email = "powerhouseplayer@gmail.com";
|
||||||
|
# Tell ACME to use Let's Encrypt's production endpoint (Caddy default).
|
||||||
|
virtualHosts = {
|
||||||
|
"navidrome.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:4533
|
||||||
|
'';
|
||||||
|
"bbbot.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8080
|
||||||
|
'';
|
||||||
|
# B3Bot beta — bbbot's staging tenant under shipyard_poc_bot.
|
||||||
|
# Same backend host as bbbot prod, port 8081.
|
||||||
|
"b3.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:93d5:53a2:de33]:8081
|
||||||
|
'';
|
||||||
|
# Shelfish — phantom-ship's ZT IPv6.
|
||||||
|
"shelfish.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8081
|
||||||
|
'';
|
||||||
|
# Scuttle — same backend, different port. WebSocket upgrade is
|
||||||
|
# transparent under reverse_proxy.
|
||||||
|
"scuttle.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8082
|
||||||
|
'';
|
||||||
|
# Bananasimulator — same backend, port 8083.
|
||||||
|
"bananasimulator.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8083
|
||||||
|
'';
|
||||||
|
# Bananasimulator BETA — separate service on port 8084 with
|
||||||
|
# BS_BETA_MODE=1 (cheat menu + faster ripening for testing).
|
||||||
|
"bananasimulator-beta.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8084
|
||||||
|
'';
|
||||||
|
# KomTolk (formerly translate-platform) — same backend, port 8080.
|
||||||
|
"komtolk.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8080
|
||||||
|
'';
|
||||||
|
# Forgejo on phantom-ship — Phase 1 of the de-platform-from-GitHub
|
||||||
|
# roadmap (vimwiki/diary/2026-05-03.md).
|
||||||
|
"git.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:3000
|
||||||
|
'';
|
||||||
|
# Escape Hormuz — turn-based boat-race Mini App, port 8090.
|
||||||
|
"escapehormuz.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8090
|
||||||
|
'';
|
||||||
|
# bon — receipt scanner Mini App, port 8091. Camera capture in
|
||||||
|
# the WebView needs HTTPS, which Caddy terminates here.
|
||||||
|
"bon.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8091
|
||||||
|
'';
|
||||||
|
# TDPixi — Idle Tower Defence Mini App by @plasmagoat, port 8093.
|
||||||
|
"tdpixi.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8093
|
||||||
|
'';
|
||||||
|
# notes — markdown blog (notes.X) + apex landing (X). Same backend
|
||||||
|
# service on phantom :8092 routes by Host header.
|
||||||
|
"notes.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
|
||||||
|
'';
|
||||||
|
"dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
|
||||||
|
'';
|
||||||
|
# kf — Kyranna Fardi architecture portfolio. Same notes service on
|
||||||
|
# phantom :8092, routed by Host header (PORTFOLIO_HOST).
|
||||||
|
"kf.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
|
||||||
|
'';
|
||||||
|
# map — curated-architecture world map by Kyranna. Same notes
|
||||||
|
# service on phantom :8092, routed by Host header (MAP_HOST).
|
||||||
|
"map.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
|
||||||
|
'';
|
||||||
|
# studio — Kyranna's private art-learning archive. Same notes
|
||||||
|
# service on phantom :8092, routed by Host header (STUDIO_HOST).
|
||||||
|
"studio.dannydannydanny.me".extraConfig = ''
|
||||||
|
reverse_proxy http://[fdd5:53a2:de33:d269:6499:936c:48a:bbdc]:8092
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# --- Basic tooling ---------------------------------------------------
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
git
|
||||||
|
htop
|
||||||
|
tcpdump
|
||||||
|
];
|
||||||
|
|
||||||
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
}
|
||||||
|
|
@ -23,14 +23,7 @@
|
||||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||||
|
|
||||||
programs.nix-ld.enable = true;
|
programs.nix-ld.enable = true;
|
||||||
# TODO: move to home manager (?)
|
# direnv is now managed by home-manager (home/danny/home.nix)
|
||||||
programs = {
|
|
||||||
direnv = {
|
|
||||||
enable = true;
|
|
||||||
# enableFishIntegration = true;
|
|
||||||
nix-direnv.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
|
||||||
|
|
@ -47,42 +40,14 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
environment.variables = {
|
|
||||||
DBT_USER = "DNTH";
|
|
||||||
EDITOR = "nvim";
|
|
||||||
VISUAL = "nvim";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
# User-level packages (git, ripgrep, fzf, etc.) are managed by
|
||||||
|
# home-manager via home/danny/home.nix. Only system-wide deps here.
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
# tmux # activated in tmux.nix
|
wget # needed by vscode-server
|
||||||
# vim # using neovim in stead
|
busybox # useful system utilities (tree, unzip, etc.)
|
||||||
# neovim # activated in neovim.nix
|
xdg-utils # terminal desktop integrations
|
||||||
|
jq # parse json
|
||||||
git # version control
|
|
||||||
gh # github cli tool
|
|
||||||
|
|
||||||
ripgrep # faster grep
|
|
||||||
wget # for vscode-server
|
|
||||||
busybox # useful programs e.g. tree, unzip etc
|
|
||||||
openssl # cryptography swiss army knife
|
|
||||||
xdg-utils # terminal desktop intergrations (i.e. allow terminal to open browser)
|
|
||||||
|
|
||||||
# make default.nix in python project folders instead of using a top-level python environment manager
|
|
||||||
# pyenv
|
|
||||||
# poetry
|
|
||||||
|
|
||||||
fastfetch # system info
|
|
||||||
btop # resource monitor
|
|
||||||
tldr # community alternative to man
|
|
||||||
fzf # fuzzy finder
|
|
||||||
jq # parse json
|
|
||||||
|
|
||||||
# gimp # bloat
|
|
||||||
# blender # bloat
|
|
||||||
# inkscape # bloat
|
|
||||||
|
|
||||||
cowsay
|
|
||||||
lolcat
|
|
||||||
];
|
];
|
||||||
|
|
||||||
services.ollama.enable = true;
|
services.ollama.enable = true;
|
||||||
|
|
|
||||||
131
nixos/neovim.nix
131
nixos/neovim.nix
|
|
@ -4,71 +4,110 @@
|
||||||
programs.neovim = {
|
programs.neovim = {
|
||||||
enable = true;
|
enable = true;
|
||||||
defaultEditor = true;
|
defaultEditor = true;
|
||||||
# TODO: refactor (some parts) to extraLuaConfig
|
withRuby = false;
|
||||||
|
withPython3 = false;
|
||||||
|
|
||||||
|
# VimScript settings (options that have no Lua equivalent or are simpler in vim)
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
set title
|
set title
|
||||||
set mouse=a
|
|
||||||
set nohlsearch
|
set nohlsearch
|
||||||
set number
|
set number
|
||||||
let mapleader=","
|
let mapleader=","
|
||||||
|
|
||||||
lua << EOF
|
colorscheme catppuccin
|
||||||
local config_file = os.getenv("HOME")..'/.local/share/nvim_color_scheme'
|
|
||||||
local f = io.open(config_file, "r")
|
|
||||||
if f ~= nil then
|
|
||||||
local system_theme = f:read()
|
|
||||||
io.close(f)
|
|
||||||
if system_theme == 'dark' then
|
|
||||||
vim.cmd("set bg=dark")
|
|
||||||
elseif system_theme == 'light' then
|
|
||||||
vim.cmd("set bg=light")
|
|
||||||
else
|
|
||||||
print('warning: expected value "light" or "dark"')
|
|
||||||
print(' got:', system_theme)
|
|
||||||
print(' expected path:', config_file)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
print('warning: nvim color scheme not found')
|
|
||||||
print(' expected path:', config_file)
|
|
||||||
end
|
|
||||||
EOF
|
|
||||||
|
|
||||||
colorscheme catppuccin " catppuccin-latte, catppuccin-frappe, catppuccin-macchiato, catppuccin-mocha
|
|
||||||
|
|
||||||
" netrw (dir listing) settings
|
" netrw (dir listing) settings
|
||||||
let g:netrw_liststyle = 3
|
let g:netrw_liststyle = 3
|
||||||
let g:netrw_banner = 0
|
let g:netrw_banner = 0
|
||||||
let g:netrw_browse_split = 3
|
let g:netrw_browse_split = 3
|
||||||
let g:netrw_winsize = 25 " % of page
|
let g:netrw_winsize = 25
|
||||||
|
'';
|
||||||
|
|
||||||
set listchars=tab:→\ ,space:·,nbsp:␣,trail:•,eol:¶,precedes:«,extends:»
|
initLua = ''
|
||||||
set clipboard+=unnamedplus
|
-- Auto-detect system theme (dark/light) from marker file
|
||||||
|
local config_file = os.getenv("HOME") .. "/.local/share/nvim_color_scheme"
|
||||||
|
local f = io.open(config_file, "r")
|
||||||
|
if f then
|
||||||
|
local theme = f:read("*l")
|
||||||
|
f:close()
|
||||||
|
if theme then
|
||||||
|
theme = theme:gsub("^%s+", ""):gsub("%s+$", "")
|
||||||
|
end
|
||||||
|
if theme == "dark" or theme == "light" then
|
||||||
|
vim.opt.background = theme
|
||||||
|
else
|
||||||
|
vim.notify("nvim_color_scheme: expected 'light' or 'dark', got: " .. tostring(theme), vim.log.levels.WARN)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
" Replace-all is aliased to S.
|
-- General options
|
||||||
nnoremap S :%s//g<Left><Left>
|
vim.opt.cursorline = true
|
||||||
|
vim.opt.mouse = "a"
|
||||||
|
vim.opt.listchars = { tab = "→ ", space = "·", nbsp = "␣", trail = "•", eol = "¶", precedes = "«", extends = "»" }
|
||||||
|
vim.opt.clipboard:append("unnamedplus")
|
||||||
|
vim.opt.spell = true
|
||||||
|
vim.opt.spelllang = "en_us"
|
||||||
|
|
||||||
" save file with ,w
|
-- Markdown: fold by heading/section using Treesitter
|
||||||
map <leader>w :w<cr><Space>
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
pattern = "markdown",
|
||||||
|
callback = function()
|
||||||
|
vim.opt_local.foldmethod = "expr"
|
||||||
|
vim.opt_local.foldexpr = "v:lua.vim.treesitter.foldexpr()"
|
||||||
|
vim.opt_local.foldenable = true
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
" spellcheck
|
-- Treesitter highlighting: parser-driven syntax highlighting (richer
|
||||||
set spell spelllang=en_us
|
-- than the regex-based default). Leaving `indent` off — it's still
|
||||||
setlocal spell! spelllang=en_us
|
-- buggy in several languages (python, yaml).
|
||||||
|
require'nvim-treesitter.configs'.setup {
|
||||||
|
highlight = { enable = true },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Sticky scroll: pin enclosing scopes (functions, classes, YAML keys,
|
||||||
|
-- etc.) to the top of the window as you scroll deeper. Same idea as
|
||||||
|
-- Zed/VS Code's "Sticky Scroll". `mode = 'topline'` matches Zed's
|
||||||
|
-- "scrolled past" feel; switch to 'cursor' if you'd rather it track
|
||||||
|
-- the cursor instead of the viewport.
|
||||||
|
require'treesitter-context'.setup {
|
||||||
|
enable = true,
|
||||||
|
max_lines = 5,
|
||||||
|
mode = 'topline',
|
||||||
|
trim_scope = 'outer',
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Fish: expand tabs to spaces. Fish renders raw \t in the commandline
|
||||||
|
-- as the Unicode glyph ␉ (U+2409) and wrap-indents each line to the
|
||||||
|
-- column of the opening quote, which mangles Alt-E multiline edits.
|
||||||
|
-- Using spaces sidesteps the issue entirely.
|
||||||
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
pattern = "fish",
|
||||||
|
callback = function()
|
||||||
|
vim.opt_local.expandtab = true
|
||||||
|
vim.opt_local.tabstop = 2
|
||||||
|
vim.opt_local.shiftwidth = 2
|
||||||
|
vim.opt_local.softtabstop = 2
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Keymaps
|
||||||
|
vim.keymap.set("n", "S", ":%s//g<Left><Left>", { desc = "Replace all" })
|
||||||
|
vim.keymap.set("n", "<leader>w", ":w<CR>", { desc = "Save file" })
|
||||||
'';
|
'';
|
||||||
|
|
||||||
plugins = with pkgs.vimPlugins; [
|
plugins = with pkgs.vimPlugins; [
|
||||||
vim-surround # shortcuts for setting () {} etc.
|
vim-surround # shortcuts for setting () {} etc.
|
||||||
vim-gitgutter # git diff in sign column
|
vim-gitgutter # git diff in sign column
|
||||||
# vim-airline # nice and light status bar # doesn't work nicely with tmux
|
vim-nix # nix highlight
|
||||||
# coc-nvim coc-git coc-highlight coc-python coc-rls coc-vetur coc-vimtex coc-yaml coc-html coc-json # auto completion
|
fzf-lua # fuzzy finder through lua
|
||||||
vim-nix # nix highlight
|
nerdtree # file structure inside nvim
|
||||||
# vimtex # latex stuff - disabled due to build check issue
|
rainbow # color parenthesis
|
||||||
fzf-lua # fuzzy finder through lua
|
|
||||||
nerdtree # file structure inside nvim
|
|
||||||
rainbow # color parenthesis
|
|
||||||
# gruvbox-nvim # theme
|
|
||||||
catppuccin-nvim # theme
|
catppuccin-nvim # theme
|
||||||
goyo-vim # write prose
|
goyo-vim # write prose
|
||||||
limelight-vim # prose paragraph highlighter
|
limelight-vim # prose paragraph highlighter
|
||||||
|
nvim-treesitter.withAllGrammars # parsers (also makes vim.treesitter.foldexpr work for markdown)
|
||||||
|
nvim-treesitter-context # sticky scroll: pin parent scopes at top of window
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
nixos/pkgs/hara-gmail-mcp/default.nix
Normal file
22
nixos/pkgs/hara-gmail-mcp/default.nix
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Gmail MCP server for Hara.
|
||||||
|
#
|
||||||
|
# Path 1 implementation: IMAP for read/sort, SMTP for reply.
|
||||||
|
# Slated for replacement by an OAuth2 + Gmail API + Calendar API server later.
|
||||||
|
{ python3Packages }:
|
||||||
|
|
||||||
|
python3Packages.buildPythonApplication {
|
||||||
|
pname = "hara-gmail-mcp";
|
||||||
|
version = "0.2.0";
|
||||||
|
pyproject = true;
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = [ python3Packages.setuptools ];
|
||||||
|
propagatedBuildInputs = [ python3Packages.mcp ];
|
||||||
|
|
||||||
|
# The server is launched via stdio by Claude Code; no tests yet.
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)";
|
||||||
|
mainProgram = "hara-gmail-mcp";
|
||||||
|
};
|
||||||
|
}
|
||||||
71
nixos/pkgs/hara-gmail-mcp/module.nix
Normal file
71
nixos/pkgs/hara-gmail-mcp/module.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# NixOS module for the Hara Gmail MCP server.
|
||||||
|
#
|
||||||
|
# Generates /etc/hara/gmail-accounts.json from declarative options and
|
||||||
|
# exposes the server binary through the dotfiles flake's pkgs set. Wiring
|
||||||
|
# the server into the claude-channels systemd service ExecStart is done
|
||||||
|
# by the host (phantom-ship.nix) so this module stays composable.
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.hara-gmail-mcp;
|
||||||
|
package = pkgs.callPackage ./. { };
|
||||||
|
accountsJson = builtins.toJSON {
|
||||||
|
accounts = map (a: {
|
||||||
|
inherit (a) email password_file;
|
||||||
|
imap_host = a.imapHost;
|
||||||
|
imap_port = a.imapPort;
|
||||||
|
smtp_host = a.smtpHost;
|
||||||
|
smtp_port = a.smtpPort;
|
||||||
|
}) cfg.accounts;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.hara-gmail-mcp = {
|
||||||
|
enable = lib.mkEnableOption "Hara Gmail MCP server (IMAP+SMTP)";
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = package;
|
||||||
|
description = "The hara-gmail-mcp package to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
accounts = lib.mkOption {
|
||||||
|
description = "Gmail accounts the MCP server should expose.";
|
||||||
|
type = lib.types.listOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
email = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "user@example.com";
|
||||||
|
};
|
||||||
|
password_file = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "Path to the file containing the IMAP/SMTP app password.";
|
||||||
|
};
|
||||||
|
imapHost = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "imap.gmail.com";
|
||||||
|
};
|
||||||
|
imapPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 993;
|
||||||
|
};
|
||||||
|
smtpHost = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "smtp.gmail.com";
|
||||||
|
};
|
||||||
|
smtpPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 465;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
environment.etc."hara/gmail-accounts.json" = {
|
||||||
|
text = accountsJson;
|
||||||
|
mode = "0644";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
18
nixos/pkgs/hara-gmail-mcp/pyproject.toml
Normal file
18
nixos/pkgs/hara-gmail-mcp/pyproject.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "hara-gmail-mcp"
|
||||||
|
version = "0.2.0"
|
||||||
|
description = "Gmail MCP server for Hara (IMAP+SMTP, throwaway pre-OAuth2)"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"mcp>=1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
hara-gmail-mcp = "hara_gmail_mcp.__main__:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["src"]
|
||||||
0
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__init__.py
Normal file
0
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__init__.py
Normal file
6
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py
Normal file
6
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/__main__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
"""Entry point for `python -m hara_gmail_mcp` and the `hara-gmail-mcp` script."""
|
||||||
|
from .server import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
112
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py
Normal file
112
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/accounts.py
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
"""Account config loader.
|
||||||
|
|
||||||
|
Reads a JSON file (default: /etc/hara/gmail-accounts.json) listing the Gmail
|
||||||
|
accounts Hara can act on, and the path to each account's IMAP/SMTP app
|
||||||
|
password. Passwords are loaded once via `sudo -n cat` because the password
|
||||||
|
files are root:991 0640 and the MCP server process runs as `danny`. The
|
||||||
|
result is cached in memory for the process lifetime.
|
||||||
|
|
||||||
|
Schema:
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"email": "user@example.com",
|
||||||
|
"password_file": "/etc/openclaw/gmail-user-app-password",
|
||||||
|
"imap_host": "imap.gmail.com",
|
||||||
|
"imap_port": 993,
|
||||||
|
"smtp_host": "smtp.gmail.com",
|
||||||
|
"smtp_port": 465
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_PATH = "/etc/hara/gmail-accounts.json"
|
||||||
|
# NixOS keeps the setuid sudo wrapper at /run/wrappers/bin; non-NixOS distros
|
||||||
|
# put it in /usr/bin or /bin. We try $PATH first, then fall back to these.
|
||||||
|
_SUDO_FALLBACKS = ["/run/wrappers/bin/sudo", "/usr/bin/sudo", "/bin/sudo"]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_sudo() -> str:
|
||||||
|
found = shutil.which("sudo")
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
for candidate in _SUDO_FALLBACKS:
|
||||||
|
if Path(candidate).exists():
|
||||||
|
return candidate
|
||||||
|
raise RuntimeError(
|
||||||
|
"sudo not found on PATH or in known locations; "
|
||||||
|
"cannot read group-restricted password files"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Account:
|
||||||
|
email: str
|
||||||
|
password_file: str
|
||||||
|
imap_host: str
|
||||||
|
imap_port: int
|
||||||
|
smtp_host: str
|
||||||
|
smtp_port: int
|
||||||
|
|
||||||
|
|
||||||
|
class AccountStore:
|
||||||
|
"""Holds account metadata and lazily resolves passwords on first use."""
|
||||||
|
|
||||||
|
def __init__(self, accounts: list[Account]) -> None:
|
||||||
|
self._accounts = {a.email: a for a in accounts}
|
||||||
|
self._password_cache: dict[str, str] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_config_file(cls, path: str | os.PathLike[str] | None = None) -> "AccountStore":
|
||||||
|
config_path = Path(path or os.environ.get("HARA_GMAIL_CONFIG", DEFAULT_CONFIG_PATH))
|
||||||
|
with config_path.open() as f:
|
||||||
|
data = json.load(f)
|
||||||
|
accounts = [
|
||||||
|
Account(
|
||||||
|
email=a["email"],
|
||||||
|
password_file=a["password_file"],
|
||||||
|
imap_host=a.get("imap_host", "imap.gmail.com"),
|
||||||
|
imap_port=int(a.get("imap_port", 993)),
|
||||||
|
smtp_host=a.get("smtp_host", "smtp.gmail.com"),
|
||||||
|
smtp_port=int(a.get("smtp_port", 465)),
|
||||||
|
)
|
||||||
|
for a in data.get("accounts", [])
|
||||||
|
]
|
||||||
|
return cls(accounts)
|
||||||
|
|
||||||
|
def emails(self) -> list[str]:
|
||||||
|
return list(self._accounts.keys())
|
||||||
|
|
||||||
|
def get(self, email: str) -> Account:
|
||||||
|
try:
|
||||||
|
return self._accounts[email]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError(f"Unknown account: {email!r}. Configured: {self.emails()}")
|
||||||
|
|
||||||
|
def password_for(self, email: str) -> str:
|
||||||
|
if email in self._password_cache:
|
||||||
|
return self._password_cache[email]
|
||||||
|
account = self.get(email)
|
||||||
|
# Prefer direct read if the file is reachable (e.g. after path 2
|
||||||
|
# migration where the daemon owns its own creds), fall back to
|
||||||
|
# `sudo -n cat` for the current /etc/openclaw/ layout.
|
||||||
|
try:
|
||||||
|
value = Path(account.password_file).read_text().strip()
|
||||||
|
except PermissionError:
|
||||||
|
value = subprocess.check_output(
|
||||||
|
[_find_sudo(), "-n", "cat", account.password_file],
|
||||||
|
text=True,
|
||||||
|
).strip()
|
||||||
|
if not value:
|
||||||
|
raise RuntimeError(f"Empty password file for {email}: {account.password_file}")
|
||||||
|
self._password_cache[email] = value
|
||||||
|
return value
|
||||||
212
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py
Normal file
212
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/imap_client.py
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
"""Minimal IMAP wrapper over stdlib imaplib.
|
||||||
|
|
||||||
|
One short-lived IMAP connection per call. Good enough for v1; if latency
|
||||||
|
hurts when Hara fans out across three accounts in a summary, swap to a
|
||||||
|
connection pool keyed by account email.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import email
|
||||||
|
import email.policy
|
||||||
|
import imaplib
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from email.message import EmailMessage
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
from .accounts import Account, AccountStore
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MessageSummary:
|
||||||
|
uid: str
|
||||||
|
subject: str
|
||||||
|
sender: str
|
||||||
|
date: str
|
||||||
|
snippet: str = ""
|
||||||
|
flags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FullMessage:
|
||||||
|
uid: str
|
||||||
|
subject: str
|
||||||
|
sender: str
|
||||||
|
to: str
|
||||||
|
date: str
|
||||||
|
body_text: str
|
||||||
|
body_html: str
|
||||||
|
flags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _open(account: Account, password: str, mailbox: str = "INBOX") -> Iterator[imaplib.IMAP4_SSL]:
|
||||||
|
conn = imaplib.IMAP4_SSL(account.imap_host, account.imap_port)
|
||||||
|
try:
|
||||||
|
conn.login(account.email, password)
|
||||||
|
# SELECT for read-write, EXAMINE for read-only. Use SELECT so we can
|
||||||
|
# add/remove flags later (label/archive). Most reads still tolerate
|
||||||
|
# the implicit \Seen behaviour Gmail applies; we set PEEK below.
|
||||||
|
typ, _ = conn.select(mailbox)
|
||||||
|
if typ != "OK":
|
||||||
|
raise RuntimeError(f"SELECT {mailbox} failed for {account.email}")
|
||||||
|
yield conn
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
conn.logout()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _decode_header(raw: str | None) -> str:
|
||||||
|
if not raw:
|
||||||
|
return ""
|
||||||
|
parts = email.header.decode_header(raw)
|
||||||
|
out = []
|
||||||
|
for chunk, enc in parts:
|
||||||
|
if isinstance(chunk, bytes):
|
||||||
|
try:
|
||||||
|
out.append(chunk.decode(enc or "utf-8", errors="replace"))
|
||||||
|
except LookupError:
|
||||||
|
out.append(chunk.decode("utf-8", errors="replace"))
|
||||||
|
else:
|
||||||
|
out.append(chunk)
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def list_inbox(
|
||||||
|
store: AccountStore,
|
||||||
|
email_addr: str,
|
||||||
|
limit: int = 20,
|
||||||
|
mailbox: str = "INBOX",
|
||||||
|
) -> list[MessageSummary]:
|
||||||
|
account = store.get(email_addr)
|
||||||
|
password = store.password_for(email_addr)
|
||||||
|
with _open(account, password, mailbox) as conn:
|
||||||
|
typ, data = conn.uid("search", None, "ALL")
|
||||||
|
if typ != "OK":
|
||||||
|
raise RuntimeError(f"SEARCH ALL failed for {email_addr}")
|
||||||
|
uids = data[0].split()[-limit:][::-1] # most recent first
|
||||||
|
return [_fetch_summary(conn, uid.decode()) for uid in uids]
|
||||||
|
|
||||||
|
|
||||||
|
def search(
|
||||||
|
store: AccountStore,
|
||||||
|
email_addr: str,
|
||||||
|
query: str,
|
||||||
|
limit: int = 20,
|
||||||
|
mailbox: str = "INBOX",
|
||||||
|
) -> list[MessageSummary]:
|
||||||
|
"""Run an IMAP SEARCH. `query` is a raw IMAP search expression, e.g.
|
||||||
|
`FROM alice@example.com`, `UNSEEN`, `SUBJECT "invoice"`, `SINCE 1-Jan-2026`.
|
||||||
|
"""
|
||||||
|
account = store.get(email_addr)
|
||||||
|
password = store.password_for(email_addr)
|
||||||
|
with _open(account, password, mailbox) as conn:
|
||||||
|
typ, data = conn.uid("search", None, query)
|
||||||
|
if typ != "OK":
|
||||||
|
raise RuntimeError(f"SEARCH {query!r} failed for {email_addr}")
|
||||||
|
uids = data[0].split()[-limit:][::-1]
|
||||||
|
return [_fetch_summary(conn, uid.decode()) for uid in uids]
|
||||||
|
|
||||||
|
|
||||||
|
def read_email(
|
||||||
|
store: AccountStore,
|
||||||
|
email_addr: str,
|
||||||
|
uid: str,
|
||||||
|
mailbox: str = "INBOX",
|
||||||
|
) -> FullMessage:
|
||||||
|
account = store.get(email_addr)
|
||||||
|
password = store.password_for(email_addr)
|
||||||
|
with _open(account, password, mailbox) as conn:
|
||||||
|
# BODY.PEEK[] avoids setting \Seen automatically.
|
||||||
|
typ, data = conn.uid("fetch", uid, "(FLAGS BODY.PEEK[])")
|
||||||
|
if typ != "OK" or not data or data[0] is None:
|
||||||
|
raise RuntimeError(f"FETCH uid={uid} failed for {email_addr}")
|
||||||
|
meta, raw = data[0]
|
||||||
|
flags = _parse_flags(meta.decode() if isinstance(meta, bytes) else meta)
|
||||||
|
msg: EmailMessage = email.message_from_bytes(raw, policy=email.policy.default)
|
||||||
|
body_text = ""
|
||||||
|
body_html = ""
|
||||||
|
if msg.is_multipart():
|
||||||
|
for part in msg.walk():
|
||||||
|
ctype = part.get_content_type()
|
||||||
|
if ctype == "text/plain" and not body_text:
|
||||||
|
body_text = part.get_content()
|
||||||
|
elif ctype == "text/html" and not body_html:
|
||||||
|
body_html = part.get_content()
|
||||||
|
else:
|
||||||
|
ctype = msg.get_content_type()
|
||||||
|
if ctype == "text/html":
|
||||||
|
body_html = msg.get_content()
|
||||||
|
else:
|
||||||
|
body_text = msg.get_content()
|
||||||
|
return FullMessage(
|
||||||
|
uid=uid,
|
||||||
|
subject=_decode_header(msg["Subject"]),
|
||||||
|
sender=_decode_header(msg["From"]),
|
||||||
|
to=_decode_header(msg["To"]),
|
||||||
|
date=_decode_header(msg["Date"]),
|
||||||
|
body_text=body_text,
|
||||||
|
body_html=body_html,
|
||||||
|
flags=flags,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mark_read(
|
||||||
|
store: AccountStore,
|
||||||
|
email_addr: str,
|
||||||
|
uid: str,
|
||||||
|
mailbox: str = "INBOX",
|
||||||
|
) -> None:
|
||||||
|
"""Mark a message as read by adding the \\Seen flag."""
|
||||||
|
account = store.get(email_addr)
|
||||||
|
password = store.password_for(email_addr)
|
||||||
|
with _open(account, password, mailbox) as conn:
|
||||||
|
conn.uid("store", uid, "+FLAGS", r"(\Seen)")
|
||||||
|
|
||||||
|
|
||||||
|
def archive(
|
||||||
|
store: AccountStore,
|
||||||
|
email_addr: str,
|
||||||
|
uid: str,
|
||||||
|
mailbox: str = "INBOX",
|
||||||
|
) -> None:
|
||||||
|
"""Archive a message: copy to All Mail then delete from INBOX."""
|
||||||
|
account = store.get(email_addr)
|
||||||
|
password = store.password_for(email_addr)
|
||||||
|
with _open(account, password, mailbox) as conn:
|
||||||
|
conn.uid("copy", uid, "[Gmail]/All Mail")
|
||||||
|
conn.uid("store", uid, "+FLAGS", r"(\Deleted)")
|
||||||
|
conn.expunge()
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_summary(conn: imaplib.IMAP4_SSL, uid: str) -> MessageSummary:
|
||||||
|
typ, data = conn.uid(
|
||||||
|
"fetch",
|
||||||
|
uid,
|
||||||
|
"(FLAGS BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)])",
|
||||||
|
)
|
||||||
|
if typ != "OK" or not data or data[0] is None:
|
||||||
|
return MessageSummary(uid=uid, subject="(fetch failed)", sender="", date="")
|
||||||
|
meta, raw = data[0]
|
||||||
|
flags = _parse_flags(meta.decode() if isinstance(meta, bytes) else meta)
|
||||||
|
headers = email.message_from_bytes(raw, policy=email.policy.default)
|
||||||
|
return MessageSummary(
|
||||||
|
uid=uid,
|
||||||
|
subject=_decode_header(headers["Subject"]),
|
||||||
|
sender=_decode_header(headers["From"]),
|
||||||
|
date=_decode_header(headers["Date"]),
|
||||||
|
flags=flags,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_flags(meta: str) -> list[str]:
|
||||||
|
# meta looks like: b'<uid> (FLAGS (\\Seen \\Answered) BODY[...] {1234}'
|
||||||
|
start = meta.find("FLAGS (")
|
||||||
|
if start < 0:
|
||||||
|
return []
|
||||||
|
end = meta.find(")", start)
|
||||||
|
if end < 0:
|
||||||
|
return []
|
||||||
|
return meta[start + len("FLAGS (") : end].split()
|
||||||
133
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py
Normal file
133
nixos/pkgs/hara-gmail-mcp/src/hara_gmail_mcp/server.py
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""Hara Gmail MCP server.
|
||||||
|
|
||||||
|
Exposes a small toolset for reading and writing mail across the configured
|
||||||
|
Gmail accounts.
|
||||||
|
|
||||||
|
Tools:
|
||||||
|
list_accounts() list configured accounts
|
||||||
|
list_inbox(email, limit) recent messages from an account
|
||||||
|
search(email, query, limit) IMAP SEARCH wrapper
|
||||||
|
read_email(email, uid) full body of one message
|
||||||
|
mark_read(email, uid) mark a message as read
|
||||||
|
archive(email, uid) archive a message (remove from INBOX)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from dataclasses import asdict
|
||||||
|
|
||||||
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
from .accounts import AccountStore
|
||||||
|
from .imap_client import archive, list_inbox, mark_read, read_email, search
|
||||||
|
|
||||||
|
logger = logging.getLogger("hara_gmail_mcp")
|
||||||
|
|
||||||
|
mcp = FastMCP("hara-gmail-mcp")
|
||||||
|
_store: AccountStore | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_store() -> AccountStore:
|
||||||
|
global _store
|
||||||
|
if _store is None:
|
||||||
|
_store = AccountStore.from_config_file()
|
||||||
|
return _store
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def list_accounts() -> list[str]:
|
||||||
|
"""Return the email addresses of all Gmail accounts Hara can access."""
|
||||||
|
return _get_store().emails()
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def gmail_list_inbox(email: str, limit: int = 20) -> str:
|
||||||
|
"""List the most recent messages in INBOX for the given account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: which configured account to read (use list_accounts to see options)
|
||||||
|
limit: max number of messages to return, newest first (default 20, cap 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON list of {uid, subject, sender, date, flags}.
|
||||||
|
"""
|
||||||
|
limit = max(1, min(int(limit), 100))
|
||||||
|
msgs = list_inbox(_get_store(), email, limit=limit)
|
||||||
|
return json.dumps([asdict(m) for m in msgs], ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def gmail_search(email: str, query: str, limit: int = 20) -> str:
|
||||||
|
"""Run an IMAP SEARCH against the given account's INBOX.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: which configured account to search
|
||||||
|
query: raw IMAP search expression, e.g. 'UNSEEN', 'FROM alice@x.com',
|
||||||
|
'SUBJECT "invoice"', 'SINCE 1-Jan-2026'. Quote arguments as needed.
|
||||||
|
limit: max results (default 20, cap 100)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON list of {uid, subject, sender, date, flags}.
|
||||||
|
"""
|
||||||
|
limit = max(1, min(int(limit), 100))
|
||||||
|
msgs = search(_get_store(), email, query=query, limit=limit)
|
||||||
|
return json.dumps([asdict(m) for m in msgs], ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def gmail_read_email(email: str, uid: str) -> str:
|
||||||
|
"""Fetch the full body of one message by IMAP UID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: which configured account
|
||||||
|
uid: the message UID (returned by gmail_list_inbox or gmail_search)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON object with subject, sender, to, date, body_text, body_html, flags.
|
||||||
|
BODY.PEEK is used so reading does not auto-mark the message as seen.
|
||||||
|
"""
|
||||||
|
msg = read_email(_get_store(), email, uid=uid)
|
||||||
|
return json.dumps(asdict(msg), ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def gmail_mark_read(email: str, uid: str) -> str:
|
||||||
|
"""Mark a message as read (sets the \\Seen flag).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: which configured account
|
||||||
|
uid: the message UID (returned by gmail_list_inbox or gmail_search)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON object with ok and uid.
|
||||||
|
"""
|
||||||
|
mark_read(_get_store(), email, uid=uid)
|
||||||
|
return json.dumps({"ok": True, "uid": uid})
|
||||||
|
|
||||||
|
|
||||||
|
@mcp.tool()
|
||||||
|
def gmail_archive(email: str, uid: str) -> str:
|
||||||
|
"""Archive a message (copies to All Mail, removes from INBOX).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: which configured account
|
||||||
|
uid: the message UID (returned by gmail_list_inbox or gmail_search)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON object with ok and uid.
|
||||||
|
"""
|
||||||
|
archive(_get_store(), email, uid=uid)
|
||||||
|
return json.dumps({"ok": True, "uid": uid})
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
logging.basicConfig(
|
||||||
|
level=os.environ.get("HARA_GMAIL_LOG_LEVEL", "INFO"),
|
||||||
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||||
|
stream=sys.stderr,
|
||||||
|
)
|
||||||
|
logger.info("hara-gmail-mcp starting")
|
||||||
|
mcp.run()
|
||||||
|
|
@ -1,27 +1,39 @@
|
||||||
# NixOS flake
|
# NixOS modules
|
||||||
|
|
||||||
Rebuild from dotfiles dir:
|
Host-specific NixOS and home-manager modules live under this dir:
|
||||||
|
|
||||||
|
- `hosts/<machine>.nix` + `hosts/<machine>-hardware.nix`
|
||||||
|
- `home/danny/home.nix` (home-manager)
|
||||||
|
- `fish.nix`, `neovim.nix`, `ollama.nix`, `installer-iso.nix`, `disko-server.nix`
|
||||||
|
|
||||||
|
The flake itself (`flake.nix`, `flake.lock`, `flake-modules/`, `lib/`, `modules/`, `sops/`, `vars/`) lives at the **repo root**, not here. See [CLAUDE.md](../CLAUDE.md) at the repo root for rebuild commands, clan.lol operations, and the `dotfiles-rebuild` timer.
|
||||||
|
|
||||||
|
## Quick rebuild reference
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#macbookair
|
# macOS
|
||||||
# or #wsl
|
cd ~/dotfiles && darwin-rebuild switch --flake .
|
||||||
# macOS: cd ~/dotfiles/nixos && darwin-rebuild switch --flake .
|
|
||||||
|
# WSL
|
||||||
|
sudo nixos-rebuild switch --flake ~/dotfiles#wsl
|
||||||
|
|
||||||
|
# Servers (via clan from mac)
|
||||||
|
nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \
|
||||||
|
machines update sunken-ship --flake ~/dotfiles
|
||||||
```
|
```
|
||||||
|
|
||||||
## Server (sunken-ship)
|
## Server bootstrap (one-time)
|
||||||
|
|
||||||
One-time bootstrap (no git until first rebuild):
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- clone https://github.com/DannyDannyDanny/dotfiles.git /tmp/dotfiles
|
nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \
|
||||||
|
clone https://github.com/DannyDannyDanny/dotfiles.git /tmp/dotfiles
|
||||||
sudo mv /tmp/dotfiles /etc/dotfiles
|
sudo mv /tmp/dotfiles /etc/dotfiles
|
||||||
sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship --option accept-flake-config true
|
sudo nixos-rebuild switch --flake /etc/dotfiles#sunken-ship \
|
||||||
|
--option accept-flake-config true
|
||||||
```
|
```
|
||||||
|
|
||||||
If the daemon doesn’t have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, run `sudo nixos-rebuild switch`, then build and switch to the flake (see [server-quickstart.md](../server-quickstart.md) for SSH keys).
|
If the daemon doesn't have flakes: copy [server-configuration-with-flakes.nix](server-configuration-with-flakes.nix) to `/etc/nixos/configuration.nix`, `sudo nixos-rebuild switch`, then build the flake.
|
||||||
|
|
||||||
SSH keys (not in repo): `scp ~/.ssh/*.pub danny@server:/tmp/`, then on server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. See [docs/ssh-and-secrets.md](../docs/ssh-and-secrets.md).
|
SSH keys (not in repo): `scp ~/.ssh/*.pub danny@server:/tmp/`, then on server `mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys`. See [docs/ssh-and-secrets.md](../docs/ssh-and-secrets.md).
|
||||||
|
|
||||||
Timer: every 15 min the server pulls and rebuilds when `main` changes. Config: `hosts/sunken-ship.nix`, `hosts/sunken-ship-hardware.nix`.
|
|
||||||
|
|
||||||
No git in PATH: `sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main`.
|
No git in PATH: `sudo nix run nixpkgs#git -- -C /etc/dotfiles pull origin main`.
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
programs.tmux = {
|
|
||||||
enable = true;
|
|
||||||
clock24 = true;
|
|
||||||
escapeTime = 20;
|
|
||||||
keyMode = "vi";
|
|
||||||
historyLimit = 100000;
|
|
||||||
baseIndex = 1;
|
|
||||||
|
|
||||||
extraConfig = ''
|
|
||||||
# remap prefix from ^+B to alt-f
|
|
||||||
unbind C-b
|
|
||||||
set -g prefix M-f
|
|
||||||
bind M-f send-prefix
|
|
||||||
|
|
||||||
# nvim 'checkhealth' advice
|
|
||||||
set-option -g focus-events on
|
|
||||||
set-option -sa terminal-overrides ',xterm-256color:RGB'
|
|
||||||
set-option -g default-terminal "screen-256color"
|
|
||||||
|
|
||||||
# enable mouse support for switching panes/windows
|
|
||||||
set -g mouse on
|
|
||||||
|
|
||||||
# pane movement shortcuts
|
|
||||||
bind h select-pane -L
|
|
||||||
bind j select-pane -D
|
|
||||||
bind k select-pane -U
|
|
||||||
bind l select-pane -R
|
|
||||||
|
|
||||||
# window selection
|
|
||||||
bind -r C-h select-window -t :-
|
|
||||||
bind -r C-l select-window -t :+
|
|
||||||
|
|
||||||
# Resize pane shortcuts
|
|
||||||
bind -r H resize-pane -L 10
|
|
||||||
bind -r J resize-pane -D 10
|
|
||||||
bind -r K resize-pane -U 10
|
|
||||||
bind -r L resize-pane -R 10
|
|
||||||
|
|
||||||
# split with dash and vbar
|
|
||||||
bind | split-window -h -c "#{pane_current_path}"
|
|
||||||
bind - split-window -v -c "#{pane_current_path}"
|
|
||||||
|
|
||||||
# server-tmux only:
|
|
||||||
# fix ssh agent when tmux is detached
|
|
||||||
# setenv -g SSH_AUTH_SOCK $HOME/.ssh/ssh_auth_sock
|
|
||||||
'';
|
|
||||||
plugins = [
|
|
||||||
# pkgs.tmuxPlugins.tmux-powerline # status bar
|
|
||||||
pkgs.tmuxPlugins.catppuccin
|
|
||||||
pkgs.tmuxPlugins.tmux-fzf # search tmux commands (prefix + F)
|
|
||||||
pkgs.tmuxPlugins.extrakto # fuzzyfind text history (prefix + tab)
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
# article / guide:
|
|
||||||
# https://taoa.io/posts/Setting-up-ipad-screen-mirroring-on-nixos
|
|
||||||
# https://gist.github.com/cmrfrd/fe8f61da076f8a4a751bf8fc8cb579a5
|
|
||||||
# also see: 24_nix_uxplay for script
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
services.avahi = {
|
|
||||||
nssmdns4 = true;
|
|
||||||
enable = true;
|
|
||||||
publish = {
|
|
||||||
enable = true;
|
|
||||||
userServices = true;
|
|
||||||
domain = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
# Required parameters:
|
# Required parameters:
|
||||||
# @raycast.schemaVersion 1
|
# @raycast.schemaVersion 1
|
||||||
|
|
@ -7,11 +8,11 @@
|
||||||
|
|
||||||
# Optional parameters:
|
# Optional parameters:
|
||||||
# @raycast.icon 🤖
|
# @raycast.icon 🤖
|
||||||
# @raycast.argument1 { "type": "text", "placeholder": "Placeholder" }
|
# @raycast.argument1 { "type": "text", "placeholder": "Text to count" }
|
||||||
|
|
||||||
# Documentation:
|
# Documentation:
|
||||||
# @raycast.description counts chars in selected text
|
# @raycast.description counts chars in selected text
|
||||||
# @raycast.author DannyDannyDanny
|
# @raycast.author DannyDannyDanny
|
||||||
# @raycast.authorURL https://raycast.com/DannyDannyDanny
|
# @raycast.authorURL https://raycast.com/DannyDannyDanny
|
||||||
|
|
||||||
echo -n "$1" | wc -c
|
printf '%s' "${1:-}" | wc -c | awk '{ print $1 }'
|
||||||
|
|
|
||||||
1
result
1
result
|
|
@ -1 +0,0 @@
|
||||||
/nix/store/x8ain9193yl3k10mk0bi667qp5iwk03w-lua-5.2.4
|
|
||||||
46
scripts/alacritty-sync-system-theme.sh
Executable file
46
scripts/alacritty-sync-system-theme.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Keep Alacritty in sync with macOS light/dark appearance.
|
||||||
|
# No Nix rebuild: copies a palette into active-colors.toml; Alacritty reloads via live_config_reload.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
[[ "$(uname -s)" == "Darwin" ]] || exit 0
|
||||||
|
|
||||||
|
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
||||||
|
ALACRITTY_DIR="$XDG_CONFIG_HOME/alacritty"
|
||||||
|
ACTIVE="$ALACRITTY_DIR/active-colors.toml"
|
||||||
|
MARKER="$ALACRITTY_DIR/.last-system-theme"
|
||||||
|
|
||||||
|
LIGHT="$ALACRITTY_DIR/catppuccin-latte-colors.toml"
|
||||||
|
DARK="$ALACRITTY_DIR/catppuccin-mocha-colors.toml"
|
||||||
|
|
||||||
|
if [[ ! -f "$LIGHT" || ! -f "$DARK" ]]; then
|
||||||
|
echo "alacritty-sync-system-theme: missing $LIGHT or $DARK (run home-manager switch first)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
appearance="$(defaults read -g AppleInterfaceStyle 2>/dev/null || true)"
|
||||||
|
if [[ "$appearance" == "Dark" ]]; then
|
||||||
|
want="dark"
|
||||||
|
else
|
||||||
|
want="light"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$ALACRITTY_DIR"
|
||||||
|
printf '%s' "$want" >"$MARKER"
|
||||||
|
|
||||||
|
# Neovim (see nixos/neovim.nix): same file as `theme` on WSL; keep in sync with Appearance.
|
||||||
|
NVIM_THEME="${XDG_DATA_HOME:-$HOME/.local/share}/nvim_color_scheme"
|
||||||
|
mkdir -p "$(dirname "$NVIM_THEME")"
|
||||||
|
printf '%s\n' "$want" >"$NVIM_THEME"
|
||||||
|
|
||||||
|
if [[ "$want" == "light" ]]; then
|
||||||
|
tmp="$(mktemp "$ALACRITTY_DIR/active-colors.toml.XXXXXX")"
|
||||||
|
cp "$LIGHT" "$tmp"
|
||||||
|
else
|
||||||
|
tmp="$(mktemp "$ALACRITTY_DIR/active-colors.toml.XXXXXX")"
|
||||||
|
cp "$DARK" "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod 0644 "$tmp"
|
||||||
|
mv -f "$tmp" "$ACTIVE"
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Fetch with curl and run to install NixOS (clone + run nixos-server-install.sh).
|
# Fetch with curl and run to install NixOS (clone + run nixos-server-install.sh).
|
||||||
# On the live system, run only:
|
# On the live system, run only:
|
||||||
# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/bootstrap-install.sh | sudo bash
|
||||||
#
|
#
|
||||||
# Optional: REPO_URL=... BRANCH=... (default repo and server-installer-usb)
|
# Optional: REPO_URL=... BRANCH=... (default repo and server-installer-usb)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
REPO_URL="${REPO_URL:-https://github.com/DannyDannyDanny/dotfiles.git}"
|
REPO_URL="${REPO_URL:-https://github.com/DannyDannyDanny/dotfiles.git}"
|
||||||
BRANCH="${BRANCH:-server-installer-usb}"
|
BRANCH="${BRANCH:-main}"
|
||||||
DEST="/tmp/dotfiles"
|
DEST="/tmp/dotfiles"
|
||||||
INSTALL_SCRIPT="$DEST/scripts/nixos-server-install.sh"
|
INSTALL_SCRIPT="$DEST/scripts/nixos-server-install.sh"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,17 @@
|
||||||
# host: SSH host (default: sunken-ship)
|
# host: SSH host (default: sunken-ship)
|
||||||
# output_dir: where to save the ISO on your Mac (default: .)
|
# output_dir: where to save the ISO on your Mac (default: .)
|
||||||
# Override SSH key: SSH_KEY=~/.ssh/my_key ./scripts/build-installer-iso-on-server.sh
|
# Override SSH key: SSH_KEY=~/.ssh/my_key ./scripts/build-installer-iso-on-server.sh
|
||||||
|
#
|
||||||
|
# If nixos/installer-wifi.nix exists locally (gitignored), it is copied into
|
||||||
|
# the build and the ISO gets preconfigured live-system WiFi. flake-modules/
|
||||||
|
# installer-iso.nix auto-includes it via a builtins.pathExists check.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
HOST="${1:-sunken-ship}"
|
HOST="${1:-sunken-ship}"
|
||||||
OUT="${2:-.}"
|
OUT="${2:-.}"
|
||||||
|
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
|
||||||
# Use sunken-ship key if not set (AGENTS.md)
|
# Default to the sunken-ship SSH key when targeting that host.
|
||||||
if [[ -n "${SSH_KEY:-}" ]]; then
|
if [[ -n "${SSH_KEY:-}" ]]; then
|
||||||
SSH_OPTS=(-i "$SSH_KEY")
|
SSH_OPTS=(-i "$SSH_KEY")
|
||||||
elif [[ "$HOST" == "sunken-ship" ]] && [[ -f ~/.ssh/id_ed25519_sunken_ship ]]; then
|
elif [[ "$HOST" == "sunken-ship" ]] && [[ -f ~/.ssh/id_ed25519_sunken_ship ]]; then
|
||||||
|
|
@ -19,23 +24,37 @@ else
|
||||||
SSH_OPTS=()
|
SSH_OPTS=()
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Pushing branch so server can pull..."
|
echo "Pushing main so the server can clone the latest..."
|
||||||
git push origin server-installer-usb 2>/dev/null || true
|
git -C "$REPO_ROOT" push origin main 2>/dev/null || true
|
||||||
|
|
||||||
echo "On $HOST: clone branch, build ISO..."
|
echo "On $HOST: clone main into ~/dotfiles-iso-build..."
|
||||||
ssh "${SSH_OPTS[@]}" "$HOST" 'set -e
|
ssh "${SSH_OPTS[@]}" "$HOST" 'set -e
|
||||||
BUILD_DIR=~/dotfiles-iso-build
|
BUILD_DIR=~/dotfiles-iso-build
|
||||||
rm -rf "$BUILD_DIR"
|
rm -rf "$BUILD_DIR"
|
||||||
git clone --branch server-installer-usb https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR"
|
git clone --branch main https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR"
|
||||||
cd "$BUILD_DIR/nixos"
|
'
|
||||||
|
|
||||||
|
# Optional live-system WiFi: the module is gitignored, so a fresh clone never
|
||||||
|
# has it. Copy it in and stage it (git add -f) so the flake sees it -- a flake
|
||||||
|
# build only includes git-tracked files.
|
||||||
|
if [[ -f "$REPO_ROOT/nixos/installer-wifi.nix" ]]; then
|
||||||
|
echo "Found nixos/installer-wifi.nix - including live-system WiFi in the ISO."
|
||||||
|
scp "${SSH_OPTS[@]}" "$REPO_ROOT/nixos/installer-wifi.nix" \
|
||||||
|
"$HOST:dotfiles-iso-build/nixos/installer-wifi.nix"
|
||||||
|
ssh "${SSH_OPTS[@]}" "$HOST" 'cd ~/dotfiles-iso-build && git add -f nixos/installer-wifi.nix'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "On $HOST: build ISO (flake is at the repo root)..."
|
||||||
|
ssh "${SSH_OPTS[@]}" "$HOST" 'set -e
|
||||||
|
cd ~/dotfiles-iso-build
|
||||||
nix build .#installer-iso
|
nix build .#installer-iso
|
||||||
ls -la result/iso/
|
ls -la result/iso/
|
||||||
'
|
'
|
||||||
|
|
||||||
ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/nixos/result/iso/*.iso 2>/dev/null | head -1')
|
ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/result/iso/*.iso 2>/dev/null | head -1')
|
||||||
ISO_NAME=$(basename "$ISO_NAME")
|
ISO_NAME=$(basename "$ISO_NAME")
|
||||||
|
|
||||||
echo "Copying $ISO_NAME to $OUT ..."
|
echo "Copying $ISO_NAME to $OUT ..."
|
||||||
scp "${SSH_OPTS[@]}" "$HOST:~/dotfiles-iso-build/nixos/result/iso/$ISO_NAME" "$OUT/"
|
scp "${SSH_OPTS[@]}" "$HOST:dotfiles-iso-build/result/iso/$ISO_NAME" "$OUT/"
|
||||||
echo "Done. ISO at $OUT/$ISO_NAME"
|
echo "Done. ISO at $OUT/$ISO_NAME"
|
||||||
echo "Write to USB: diskutil unmountDisk diskN && sudo dd if=$OUT/$ISO_NAME of=/dev/rdiskN bs=4m"
|
echo "Write to USB: diskutil unmountDisk diskN && sudo dd if=$OUT/$ISO_NAME of=/dev/rdiskN bs=4m"
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Detect macOS system theme (light/dark mode)
|
|
||||||
# Returns "light" or "dark"
|
|
||||||
|
|
||||||
# Get the current appearance setting
|
|
||||||
appearance=$(defaults read -g AppleInterfaceStyle 2>/dev/null)
|
|
||||||
|
|
||||||
if [ "$appearance" = "Dark" ]; then
|
|
||||||
echo "dark"
|
|
||||||
else
|
|
||||||
echo "light"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,26 +1,27 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Run on a NixOS minimal live system (or installer ISO) to install NixOS with
|
# Install NixOS with disko (LUKS + root) on a live system.
|
||||||
# disko (LUKS + root). Prompts for hostname and target disk; optionally use
|
# Prompts for hostname and target disk, then provisions the installed system
|
||||||
# INSTALLER_SYSTEM_CONFIG_FILE for WiFi etc.
|
# (clones dotfiles, installs SSH key, generates hardware config).
|
||||||
#
|
#
|
||||||
# Usage (from repo root, e.g. /tmp/dotfiles):
|
# Usage (from repo root, e.g. /tmp/dotfiles):
|
||||||
# sudo ./scripts/nixos-server-install.sh
|
# sudo ./scripts/nixos-server-install.sh
|
||||||
# If you see "command not found", use: sudo bash ./scripts/nixos-server-install.sh
|
|
||||||
#
|
#
|
||||||
# Optional: FLAKE_REF=github:User/dotfiles or path:/path/to/dotfiles/nixos
|
# Environment variables (all optional):
|
||||||
#
|
# INSTALLER_HOSTNAME — skip hostname prompt
|
||||||
# Optional: INSTALLER_SYSTEM_CONFIG_FILE=/path/to/json with full --system-config
|
# INSTALLER_DISK — skip disk prompt (validated as block device)
|
||||||
# (e.g. hostName + networking.wireless.networks). If unset, only hostname is passed.
|
# SSH_PUBKEY_FILE — path to .pub file; installed to danny's authorized_keys
|
||||||
|
# FLAKE_REF — override flake reference (default: auto-detect from repo)
|
||||||
|
# INSTALLER_SYSTEM_CONFIG_FILE — JSON file merged into --system-config
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
FLAKE_REF="${FLAKE_REF:-}"
|
FLAKE_REF="${FLAKE_REF:-}"
|
||||||
if [[ -z "$FLAKE_REF" ]]; then
|
if [[ -z "$FLAKE_REF" ]]; then
|
||||||
if [[ -d "$(dirname "$0")/../nixos" ]] && [[ -f "$(dirname "$0")/../nixos/flake.nix" ]]; then
|
if [[ -f "$(dirname "$0")/../flake.nix" ]]; then
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
FLAKE_REF="path:${REPO_ROOT}/nixos"
|
FLAKE_REF="path:${REPO_ROOT}"
|
||||||
else
|
else
|
||||||
echo "FLAKE_REF not set and not running from dotfiles repo. Example:"
|
echo "FLAKE_REF not set and not running from dotfiles repo. Example:"
|
||||||
echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles/nixos"
|
echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -30,21 +31,29 @@ if [[ "$EUID" -ne 0 ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read -r -p "Hostname (e.g. my-server): " hostname
|
# --- Hostname ---
|
||||||
|
hostname="${INSTALLER_HOSTNAME:-}"
|
||||||
|
if [[ -z "$hostname" ]]; then
|
||||||
|
read -r -p "Hostname (e.g. phantom-ship): " hostname
|
||||||
|
fi
|
||||||
if [[ -z "$hostname" ]]; then
|
if [[ -z "$hostname" ]]; then
|
||||||
echo "Hostname cannot be empty."
|
echo "Hostname cannot be empty."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read -r -p "Target disk [default: /dev/sda]: " disk
|
# --- Target disk ---
|
||||||
disk="${disk:-/dev/sda}"
|
disk="${INSTALLER_DISK:-}"
|
||||||
|
if [[ -z "$disk" ]]; then
|
||||||
|
read -r -p "Target disk [default: /dev/sda]: " disk
|
||||||
|
disk="${disk:-/dev/sda}"
|
||||||
|
fi
|
||||||
if [[ ! -b "$disk" ]]; then
|
if [[ ! -b "$disk" ]]; then
|
||||||
echo "Not a block device: $disk"
|
echo "Not a block device: $disk"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- System config (hostname + optional extras) ---
|
||||||
if [[ -n "${INSTALLER_SYSTEM_CONFIG_FILE:-}" ]] && [[ -f "$INSTALLER_SYSTEM_CONFIG_FILE" ]]; then
|
if [[ -n "${INSTALLER_SYSTEM_CONFIG_FILE:-}" ]] && [[ -f "$INSTALLER_SYSTEM_CONFIG_FILE" ]]; then
|
||||||
# Use provided JSON; ensure hostname is set
|
|
||||||
if command -v jq &>/dev/null; then
|
if command -v jq &>/dev/null; then
|
||||||
SYSTEM_CONFIG=$(jq --arg h "$hostname" '.networking.hostName = $h' "$INSTALLER_SYSTEM_CONFIG_FILE")
|
SYSTEM_CONFIG=$(jq --arg h "$hostname" '.networking.hostName = $h' "$INSTALLER_SYSTEM_CONFIG_FILE")
|
||||||
else
|
else
|
||||||
|
|
@ -55,8 +64,9 @@ else
|
||||||
SYSTEM_CONFIG='{"networking":{"hostName":"'"$hostname"'"}}'
|
SYSTEM_CONFIG='{"networking":{"hostName":"'"$hostname"'"}}'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Prompt for password for danny so you can log in at console after reboot (no rescue needed)
|
# --- Optional: danny password ---
|
||||||
read -r -p "Set a password for user danny (console/SSH login)? [y/N] " set_pass
|
danny_pass=""
|
||||||
|
read -r -p "Set a password for user danny? [y/N] " set_pass
|
||||||
if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then
|
if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then
|
||||||
read -s -r -p "Password for danny: " danny_pass
|
read -s -r -p "Password for danny: " danny_pass
|
||||||
echo
|
echo
|
||||||
|
|
@ -70,23 +80,27 @@ if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then
|
||||||
echo "Password cannot be empty. Aborted."
|
echo "Password cannot be empty. Aborted."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
HASH=$(echo -n "$danny_pass" | openssl passwd -6 -stdin 2>/dev/null) || HASH=$(mkpasswd -6 -m sha-512 "$danny_pass" 2>/dev/null)
|
HASH=$(echo -n "$danny_pass" | openssl passwd -6 -stdin 2>/dev/null) || HASH=$(mkpasswd -6 -m sha-512 "$danny_pass" 2>/dev/null) || true
|
||||||
if [[ -z "$HASH" ]]; then
|
if [[ -n "${HASH:-}" ]]; then
|
||||||
echo "Could not hash password (need openssl or mkpasswd). Skipping password."
|
|
||||||
else
|
|
||||||
if command -v jq &>/dev/null; then
|
if command -v jq &>/dev/null; then
|
||||||
SYSTEM_CONFIG=$(echo "$SYSTEM_CONFIG" | jq --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}')
|
SYSTEM_CONFIG=$(echo "$SYSTEM_CONFIG" | jq --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}')
|
||||||
else
|
else
|
||||||
NEW_CONFIG=$(echo "$SYSTEM_CONFIG" | nix run nixpkgs#jq -- --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}' 2>/dev/null)
|
NEW_CONFIG=$(echo "$SYSTEM_CONFIG" | nix run nixpkgs#jq -- --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}' 2>/dev/null)
|
||||||
[[ -n "$NEW_CONFIG" ]] && SYSTEM_CONFIG="$NEW_CONFIG" || echo "Could not merge password (jq not found). Set after boot: passwd danny"
|
[[ -n "$NEW_CONFIG" ]] && SYSTEM_CONFIG="$NEW_CONFIG" || echo "Could not merge password. Set after boot: passwd danny"
|
||||||
fi
|
fi
|
||||||
[[ -n "$SYSTEM_CONFIG" ]] && echo "Password will be set for danny."
|
echo "Password will be set for danny."
|
||||||
|
else
|
||||||
|
echo "Could not hash password (need openssl or mkpasswd). Set after boot: passwd danny"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- Confirm and install ---
|
||||||
|
echo ""
|
||||||
|
echo "=== Install Summary ==="
|
||||||
echo "Flake: ${FLAKE_REF}#server-install"
|
echo "Flake: ${FLAKE_REF}#server-install"
|
||||||
echo "Disk: $disk"
|
echo "Disk: $disk"
|
||||||
echo "Hostname: $hostname"
|
echo "Hostname: $hostname"
|
||||||
|
echo "SSH pubkey: ${SSH_PUBKEY_FILE:-none}"
|
||||||
echo "System config: $SYSTEM_CONFIG"
|
echo "System config: $SYSTEM_CONFIG"
|
||||||
read -r -p "Proceed? [y/N] " confirm
|
read -r -p "Proceed? [y/N] " confirm
|
||||||
if [[ "${confirm,,}" != "y" && "${confirm,,}" != "yes" ]]; then
|
if [[ "${confirm,,}" != "y" && "${confirm,,}" != "yes" ]]; then
|
||||||
|
|
@ -100,33 +114,88 @@ nix run --extra-experimental-features "nix-command flakes" \
|
||||||
--disk main "$disk" \
|
--disk main "$disk" \
|
||||||
--system-config "$SYSTEM_CONFIG"
|
--system-config "$SYSTEM_CONFIG"
|
||||||
|
|
||||||
# Set danny password directly on disk (Nix merge can fail); re-open LUKS and chroot
|
echo ""
|
||||||
if [[ -n "${danny_pass:-}" ]]; then
|
echo "=== Post-install provisioning ==="
|
||||||
echo "Setting password for danny on installed system (re-enter LUKS passphrase once)..."
|
echo "Re-opening LUKS to provision the installed system..."
|
||||||
read -s -r -p "LUKS passphrase: " luks_pass
|
read -s -r -p "LUKS passphrase: " luks_pass
|
||||||
echo
|
echo
|
||||||
LUKS_DEV="/dev/disk/by-partlabel/disk-main-luks"
|
|
||||||
ESP_DEV="/dev/disk/by-partlabel/disk-main-ESP"
|
LUKS_DEV="/dev/disk/by-partlabel/disk-main-luks"
|
||||||
if [[ ! -b "$LUKS_DEV" ]]; then
|
ESP_DEV="/dev/disk/by-partlabel/disk-main-ESP"
|
||||||
LUKS_DEV="${disk}2"
|
if [[ ! -b "$LUKS_DEV" ]]; then
|
||||||
ESP_DEV="${disk}1"
|
LUKS_DEV="${disk}2"
|
||||||
fi
|
ESP_DEV="${disk}1"
|
||||||
if [[ -b "$LUKS_DEV" ]]; then
|
|
||||||
if ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then
|
|
||||||
echo "Wrong LUKS passphrase; set danny password after boot: passwd danny"
|
|
||||||
else
|
|
||||||
mount /dev/mapper/crypted /mnt
|
|
||||||
[[ -b "$ESP_DEV" ]] && mount "$ESP_DEV" /mnt/boot
|
|
||||||
mount --bind /dev /mnt/dev
|
|
||||||
mount --bind /proc /mnt/proc
|
|
||||||
mount --bind /sys /mnt/sys
|
|
||||||
echo "danny:${danny_pass}" | chroot /mnt chpasswd
|
|
||||||
umount -R /mnt
|
|
||||||
cryptsetup close crypted
|
|
||||||
echo "Password for danny set. Reboot and log in."
|
|
||||||
fi
|
|
||||||
unset luks_pass
|
|
||||||
else
|
|
||||||
echo "Could not find LUKS partition; set password after boot: passwd danny"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! -b "$LUKS_DEV" ]]; then
|
||||||
|
echo "Could not find LUKS partition. Complete these steps manually after boot:"
|
||||||
|
echo " 1. Clone dotfiles: sudo git clone ... /etc/dotfiles"
|
||||||
|
echo " 2. Add SSH key: mkdir -p ~/.ssh && cat /tmp/key.pub >> ~/.ssh/authorized_keys"
|
||||||
|
echo " 3. Generate hardware config: nixos-generate-config --show-hardware-config > /etc/dotfiles/nixos/hosts/${hostname}-hardware.nix"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -e /dev/mapper/crypted ]]; then
|
||||||
|
echo " [ok] LUKS device already open (left open by disko-install)"
|
||||||
|
unset luks_pass
|
||||||
|
elif ! echo -n "$luks_pass" | cryptsetup open "$LUKS_DEV" crypted --key-file -; then
|
||||||
|
echo "Wrong LUKS passphrase. Complete provisioning manually after boot."
|
||||||
|
unset luks_pass
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
unset luks_pass
|
||||||
|
fi
|
||||||
|
|
||||||
|
mount /dev/mapper/crypted /mnt
|
||||||
|
[[ -b "$ESP_DEV" ]] && mount "$ESP_DEV" /mnt/boot
|
||||||
|
mount --bind /dev /mnt/dev
|
||||||
|
mount --bind /proc /mnt/proc
|
||||||
|
mount --bind /sys /mnt/sys
|
||||||
|
|
||||||
|
# 1. Set danny password (belt-and-suspenders; Nix merge can fail)
|
||||||
|
if [[ -n "$danny_pass" ]]; then
|
||||||
|
echo "danny:${danny_pass}" | chroot /mnt chpasswd
|
||||||
|
echo " [ok] danny password set"
|
||||||
|
fi
|
||||||
|
unset danny_pass
|
||||||
|
|
||||||
|
# 2. Clone dotfiles
|
||||||
|
if [[ ! -d /mnt/etc/dotfiles ]]; then
|
||||||
|
chroot /mnt nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \
|
||||||
|
clone https://github.com/DannyDannyDanny/dotfiles.git /etc/dotfiles
|
||||||
|
echo " [ok] dotfiles cloned to /etc/dotfiles"
|
||||||
|
else
|
||||||
|
echo " [skip] /etc/dotfiles already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Install SSH public key
|
||||||
|
if [[ -n "${SSH_PUBKEY_FILE:-}" ]] && [[ -f "$SSH_PUBKEY_FILE" ]]; then
|
||||||
|
mkdir -p /mnt/home/danny/.ssh
|
||||||
|
cat "$SSH_PUBKEY_FILE" >> /mnt/home/danny/.ssh/authorized_keys
|
||||||
|
chmod 700 /mnt/home/danny/.ssh
|
||||||
|
chmod 600 /mnt/home/danny/.ssh/authorized_keys
|
||||||
|
chroot /mnt chown -R danny:users /home/danny/.ssh
|
||||||
|
echo " [ok] SSH public key installed"
|
||||||
|
elif [[ -n "${SSH_PUBKEY_FILE:-}" ]]; then
|
||||||
|
echo " [warn] SSH_PUBKEY_FILE set but file not found: $SSH_PUBKEY_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Generate hardware config
|
||||||
|
HW_CONFIG="/mnt/etc/dotfiles/nixos/hosts/${hostname}-hardware.nix"
|
||||||
|
if nixos-generate-config --show-hardware-config --root /mnt > "$HW_CONFIG" 2>/dev/null; then
|
||||||
|
echo " [ok] hardware config saved to hosts/${hostname}-hardware.nix"
|
||||||
|
echo " NOTE: Commit this file to the repo after first boot."
|
||||||
|
else
|
||||||
|
echo " [warn] nixos-generate-config failed; run manually after boot:"
|
||||||
|
echo " nixos-generate-config --show-hardware-config > /etc/dotfiles/nixos/hosts/${hostname}-hardware.nix"
|
||||||
|
fi
|
||||||
|
|
||||||
|
umount -R /mnt
|
||||||
|
cryptsetup close crypted
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Done! ==="
|
||||||
|
echo "Remove the USB and reboot. After unlocking LUKS:"
|
||||||
|
echo " 1. SSH in: ssh danny@${hostname}"
|
||||||
|
echo " 2. First rebuild: cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#${hostname}"
|
||||||
|
echo " 3. Commit ${hostname}-hardware.nix back to the repo"
|
||||||
|
|
|
||||||
61
scripts/post-install-provision.sh
Executable file
61
scripts/post-install-provision.sh
Executable file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Run after disko-install when LUKS is already open.
|
||||||
|
# Usage: curl -fsSL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/post-install-provision.sh | sudo bash -s -- phantom-ship
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOSTNAME="${1:-phantom-ship}"
|
||||||
|
USB_DATA="/tmp/usb-data"
|
||||||
|
REPO="https://github.com/DannyDannyDanny/dotfiles.git"
|
||||||
|
|
||||||
|
echo "=== Post-install provisioning for ${HOSTNAME} ==="
|
||||||
|
|
||||||
|
# Mount installed system (LUKS already open from disko-install)
|
||||||
|
mount /dev/mapper/crypted /mnt
|
||||||
|
mount /dev/disk/by-partlabel/disk-main-ESP /mnt/boot 2>/dev/null || true
|
||||||
|
for d in dev proc sys; do mount --bind /$d /mnt/$d; done
|
||||||
|
|
||||||
|
# Clone dotfiles — find git or nix, clone directly into /mnt (no chroot)
|
||||||
|
if [[ ! -d /mnt/etc/dotfiles ]]; then
|
||||||
|
# Ensure nix is in PATH (live installer may strip it under sudo)
|
||||||
|
export PATH=$PATH:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin
|
||||||
|
if command -v git &>/dev/null; then
|
||||||
|
git clone "$REPO" /mnt/etc/dotfiles
|
||||||
|
else
|
||||||
|
nix run --extra-experimental-features "nix-command flakes" nixpkgs#git -- \
|
||||||
|
clone "$REPO" /mnt/etc/dotfiles
|
||||||
|
fi
|
||||||
|
echo "[ok] dotfiles cloned"
|
||||||
|
else
|
||||||
|
echo "[skip] dotfiles already present"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install SSH key
|
||||||
|
if [[ -f "$USB_DATA/authorized_keys" ]]; then
|
||||||
|
mkdir -p /mnt/home/danny/.ssh
|
||||||
|
cp "$USB_DATA/authorized_keys" /mnt/home/danny/.ssh/authorized_keys
|
||||||
|
chmod 700 /mnt/home/danny/.ssh
|
||||||
|
chmod 600 /mnt/home/danny/.ssh/authorized_keys
|
||||||
|
chroot /mnt chown -R danny:users /home/danny/.ssh
|
||||||
|
echo "[ok] SSH key installed"
|
||||||
|
else
|
||||||
|
echo "[warn] no authorized_keys on USB — add SSH key manually after boot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate hardware config
|
||||||
|
nixos-generate-config --show-hardware-config --root /mnt \
|
||||||
|
> /mnt/etc/dotfiles/nixos/hosts/${HOSTNAME}-hardware.nix
|
||||||
|
echo "[ok] hardware config saved to hosts/${HOSTNAME}-hardware.nix"
|
||||||
|
|
||||||
|
# Copy hardware config to USB for committing from Mac
|
||||||
|
mkdir -p "$USB_DATA"
|
||||||
|
cp /mnt/etc/dotfiles/nixos/hosts/${HOSTNAME}-hardware.nix "$USB_DATA/"
|
||||||
|
echo "[ok] hardware config also copied to USB ($USB_DATA/)"
|
||||||
|
|
||||||
|
umount -R /mnt
|
||||||
|
cryptsetup close crypted 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Done! Remove USB and reboot. ==="
|
||||||
|
echo "After unlocking LUKS, SSH in: ssh danny@${HOSTNAME}"
|
||||||
|
echo "Then: cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#${HOSTNAME}"
|
||||||
|
echo "Commit ${HOSTNAME}-hardware.nix from the USB back to the repo."
|
||||||
|
|
@ -1,27 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# One-shot sync of Alacritty palette + nvim marker from current macOS appearance.
|
||||||
# Simple setup for Alacritty theme synchronization
|
|
||||||
# This creates the theme file and rebuilds the Nix configuration
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
echo "Syncing from system appearance..."
|
||||||
echo "Setting up simple Alacritty theme synchronization..."
|
"$SCRIPT_DIR/alacritty-sync-system-theme.sh"
|
||||||
|
|
||||||
# Run the theme sync script to create the initial theme file
|
|
||||||
echo "Detecting current system theme..."
|
|
||||||
"$SCRIPT_DIR/sync-alacritty-theme.sh"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Setup complete!"
|
echo "Done. Alacritty reloads colors automatically if live_config_reload is enabled."
|
||||||
echo ""
|
echo "A LaunchAgent (nix-darwin: launchd.user.agents.alacritty-system-theme) runs this every 30s."
|
||||||
echo "To apply the theme to Alacritty, run:"
|
|
||||||
echo " cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air"
|
|
||||||
echo ""
|
|
||||||
echo "To sync themes when your system theme changes:"
|
|
||||||
echo " $SCRIPT_DIR/sync-alacritty-theme.sh && cd nixos && sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air"
|
|
||||||
echo ""
|
|
||||||
echo "For automatic theme switching, you can set up a LaunchAgent or"
|
|
||||||
echo "run the sync script manually when needed."
|
|
||||||
|
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Switch Alacritty theme by updating the Nix configuration
|
|
||||||
# This script changes the isLightTheme variable in home.nix and rebuilds
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
|
||||||
HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix"
|
|
||||||
|
|
||||||
# Check if home.nix exists
|
|
||||||
if [ ! -f "$HOME_NIX" ]; then
|
|
||||||
echo "Error: home.nix not found at $HOME_NIX"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Function to switch to light theme
|
|
||||||
switch_to_light() {
|
|
||||||
echo "Switching to light theme (Catppuccin Latte)..."
|
|
||||||
sed -i '' 's/isLightTheme = false;/isLightTheme = true;/' "$HOME_NIX"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to switch to dark theme
|
|
||||||
switch_to_dark() {
|
|
||||||
echo "Switching to dark theme (Catppuccin Mocha)..."
|
|
||||||
sed -i '' 's/isLightTheme = true;/isLightTheme = false;/' "$HOME_NIX"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to show current theme
|
|
||||||
show_current() {
|
|
||||||
if grep -q "isLightTheme = true" "$HOME_NIX"; then
|
|
||||||
echo "Current theme: Light (Catppuccin Latte)"
|
|
||||||
else
|
|
||||||
echo "Current theme: Dark (Catppuccin Mocha)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function to rebuild the configuration
|
|
||||||
rebuild() {
|
|
||||||
echo "Rebuilding configuration..."
|
|
||||||
cd "$DOTFILES_DIR/nixos"
|
|
||||||
sudo darwin-rebuild switch --flake .#Daniel-Macbook-Air
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main logic
|
|
||||||
case "${1:-}" in
|
|
||||||
"light")
|
|
||||||
switch_to_light
|
|
||||||
rebuild
|
|
||||||
;;
|
|
||||||
"dark")
|
|
||||||
switch_to_dark
|
|
||||||
rebuild
|
|
||||||
;;
|
|
||||||
"toggle")
|
|
||||||
if grep -q "isLightTheme = true" "$HOME_NIX"; then
|
|
||||||
switch_to_dark
|
|
||||||
else
|
|
||||||
switch_to_light
|
|
||||||
fi
|
|
||||||
rebuild
|
|
||||||
;;
|
|
||||||
"status"|"current")
|
|
||||||
show_current
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 {light|dark|toggle|status}"
|
|
||||||
echo ""
|
|
||||||
echo "Commands:"
|
|
||||||
echo " light - Switch to light theme (Catppuccin Latte)"
|
|
||||||
echo " dark - Switch to dark theme (Catppuccin Mocha)"
|
|
||||||
echo " toggle - Toggle between light and dark themes"
|
|
||||||
echo " status - Show current theme"
|
|
||||||
echo ""
|
|
||||||
show_current
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
@ -1,31 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Back-compat wrapper: sync Alacritty + nvim marker from macOS appearance.
|
||||||
# Sync Alacritty theme with system theme
|
|
||||||
# This script detects the current system theme and updates the theme file that Nix reads
|
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Get the directory where this script is located
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
exec "$SCRIPT_DIR/alacritty-sync-system-theme.sh"
|
||||||
# Paths
|
|
||||||
THEME_DETECTION_SCRIPT="$SCRIPT_DIR/detect-system-theme.sh"
|
|
||||||
THEME_FILE="/Users/danny/.local/share/nvim_color_scheme"
|
|
||||||
|
|
||||||
# Create the directory if it doesn't exist
|
|
||||||
mkdir -p "$(dirname "$THEME_FILE")"
|
|
||||||
|
|
||||||
# Detect current system theme
|
|
||||||
if [ ! -f "$THEME_DETECTION_SCRIPT" ]; then
|
|
||||||
echo "Error: Theme detection script not found at $THEME_DETECTION_SCRIPT"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CURRENT_THEME=$("$THEME_DETECTION_SCRIPT")
|
|
||||||
echo "Current system theme: $CURRENT_THEME"
|
|
||||||
|
|
||||||
# Write the theme to the file that Nix reads
|
|
||||||
echo "$CURRENT_THEME" > "$THEME_FILE"
|
|
||||||
|
|
||||||
echo "Theme file updated: $THEME_FILE"
|
|
||||||
echo "Run 'home-manager switch' to apply the new theme to Alacritty"
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ show_usage() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "This command switches themes for:"
|
echo "This command switches themes for:"
|
||||||
echo " - Neovim (via nvim_color_scheme file)"
|
echo " - Neovim (via nvim_color_scheme file)"
|
||||||
echo " - Alacritty (via Nix configuration on macOS)"
|
echo " - Alacritty on macOS follows System Settings (LaunchAgent sync)"
|
||||||
echo " - Windows Terminal (via settings.json on WSL)"
|
echo " - Windows Terminal (via settings.json on WSL)"
|
||||||
echo " - Windows system theme (on WSL)"
|
echo " - Windows system theme (on WSL)"
|
||||||
}
|
}
|
||||||
|
|
@ -43,19 +43,11 @@ show_status() {
|
||||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
echo " Platform: macOS"
|
echo " Platform: macOS"
|
||||||
|
|
||||||
# Check Alacritty theme from Nix config
|
marker="$HOME/.config/alacritty/.last-system-theme"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
if [ -f "$marker" ]; then
|
||||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
echo " Alacritty: follows system (active palette: $(tr -d '\n' <"$marker"))"
|
||||||
HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix"
|
|
||||||
|
|
||||||
if [ -f "$HOME_NIX" ]; then
|
|
||||||
if grep -q "isLightTheme = true" "$HOME_NIX"; then
|
|
||||||
echo " Alacritty: light (Catppuccin Latte)"
|
|
||||||
else
|
|
||||||
echo " Alacritty: dark (Catppuccin Mocha)"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo " Alacritty: config file not found"
|
echo " Alacritty: follows system (sync after next login or run alacritty-sync-system-theme)"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " Platform: other"
|
echo " Platform: other"
|
||||||
|
|
@ -67,17 +59,10 @@ toggle_theme() {
|
||||||
current_theme=""
|
current_theme=""
|
||||||
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
# On macOS, check the Nix config for current theme
|
if [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" == "Dark" ]]; then
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
current_theme="dark"
|
||||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
else
|
||||||
HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix"
|
current_theme="light"
|
||||||
|
|
||||||
if [ -f "$HOME_NIX" ]; then
|
|
||||||
if grep -q "isLightTheme = true" "$HOME_NIX"; then
|
|
||||||
current_theme="light"
|
|
||||||
else
|
|
||||||
current_theme="dark"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -183,18 +168,8 @@ if [[ -n "$WSL_DISTRO_NAME" ]]; then
|
||||||
powershell.exe -Command "Set-ItemProperty -Path HKCU:\AppEvents\Schemes -Name '(Default)' -Value '.None'"
|
powershell.exe -Command "Set-ItemProperty -Path HKCU:\AppEvents\Schemes -Name '(Default)' -Value '.None'"
|
||||||
|
|
||||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
# macOS platform - handle Alacritty theme
|
|
||||||
echo "Detected macOS platform"
|
echo "Detected macOS platform"
|
||||||
|
echo "Alacritty follows System Settings → Appearance (no rebuild). Neovim theme file updated above."
|
||||||
# Use the existing Alacritty theme switching script
|
|
||||||
alacritty_script="$DOTFILES_DIR/scripts/switch-alacritty-theme.sh"
|
|
||||||
if [ -f "$alacritty_script" ]; then
|
|
||||||
echo "Switching Alacritty theme to: $color_scheme"
|
|
||||||
"$alacritty_script" "$color_scheme"
|
|
||||||
else
|
|
||||||
echo "Warning: Alacritty theme script not found at $alacritty_script"
|
|
||||||
echo "Theme file updated, but Alacritty theme not switched"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
else
|
||||||
# Other platforms - just update the theme file
|
# Other platforms - just update the theme file
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ Optional: `services.openssh.settings = { PasswordAuthentication = false; PermitR
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo nixos-rebuild switch
|
sudo nixos-rebuild switch
|
||||||
# or: sudo nixos-rebuild switch --flake /path/to/dotfiles/nixos#hostname
|
# or: sudo nixos-rebuild switch --flake /path/to/dotfiles#hostname
|
||||||
```
|
```
|
||||||
|
|
||||||
Then from your main machine: `ssh danny@myserver`
|
Then from your main machine: `ssh danny@myserver`
|
||||||
|
|
|
||||||
6
sops/machines/distant-shore/key.json
Executable file
6
sops/machines/distant-shore/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
6
sops/machines/foreign-port/key.json
Executable file
6
sops/machines/foreign-port/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
6
sops/machines/phantom-ship/key.json
Executable file
6
sops/machines/phantom-ship/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
6
sops/machines/sunken-ship/key.json
Executable file
6
sops/machines/sunken-ship/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
6
sops/machines/vps-relay/key.json
Executable file
6
sops/machines/vps-relay/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1mlljsdpqf054p4nav9s855rtd5szwyl9av8w2lvg86j59cdtugxqylcn6k",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
14
sops/secrets/distant-shore-age.key/secret
Normal file
14
sops/secrets/distant-shore-age.key/secret
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:WTerGWNmve9/q+TLYi8HoGUQI0UgYMZN2zuC/FABX0MC6VuUsz9Doz36X8lsy+MRJzcHNPqdaHmAHopY/hODHLBirfUPLVZjEKI=,iv:ilp+cJivxY2us1jO85dWUHAqLJSsJ7ZKpmYMyi2476I=,tag:H0k2CZDhcH9lYSxz6BAPrg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZS2ZhQlJacmR4R3JMek5l\nZlFRajM4VllmK2R6NFlRMEkwNUJOL05OUUhzCmpWQ0gxQ1BHUkZOVm80QzRUc1BY\nTDNRZDZOL3EyS1FWK1A4UUd6MTFaTjAKLS0tIEUxU3hBSkZqRmc5d0dXZm0rNTYw\nQ0hrZUF5dDJLN0MvM2RQZlVFZkNPc28Kvq8yV+VwqQIuG1SPI/mMYbGwuD7oUOeR\nCzAuZvqGtludjW7+wX5uIwRzHMudU/yP/iME8vsDC3dL6sf75+arHg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-06-07T16:36:36Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:g35f5YmoneVewxmTh3E8ECDGAl0OwUj4v/2bjFs9Dd7MaT3in7PHvu30jJ4WHalYC8pkT5IlpBwsp1nVUnKsgh+2V+jN4JiGizlvTwByaYoalOoGZStIyQa+k8XRQqoUDbV3ESdI5q+dwS5PCWYIOH3MoA0o5b42iQPghrViaeY=,iv:v0UUy4LtQ5SRLB01vbcfNpcm8zgs1Vp3KCK552peXlA=,tag:45b8czXYtNh02q7P42FJmQ==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
sops/secrets/distant-shore-age.key/users/danny
Symbolic link
1
sops/secrets/distant-shore-age.key/users/danny
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../users/danny
|
||||||
14
sops/secrets/foreign-port-age.key/secret
Normal file
14
sops/secrets/foreign-port-age.key/secret
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:MH/ib8WAbzucbm2dhhoo6ESSSLKtKMWmjUwtpAOZhU7KyhOoechpJRSkBBmFV4LzbSP1qeaFbid6USJBnRsxkoz6XvhMzP0kzS0=,iv:9sPwc/JIlo5mzxelNzLCB26k2f+n2C9tB8Y/HEdPvHw=,tag:hJBhzTMsTWd9PDydS4aosg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6L2JGejlzQlhiNFhES1lT\nNTZsNFFMT1NzZEV0T28rNDA3STJ1UXNRcUZFCkxoenpFYWJicHpGVDhtMUdwNXBo\nS29EazVsRGFST2ZodDJMTkxQN2I1RjAKLS0tIFpib0RoOTJ6bkU0b1F6NnRaV3lF\nVHhvYjNOUUtMbGF5ejdaVk5WdGt2d1kKNU5JR1nIYPQLALUM3wRO945Sk6GLxJpn\nTVmVUEgXcwHcSij10a/cQOyPXNNnsfIC+WJFMJcjHfsjBnwS5W/Bgw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-06-07T19:41:18Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:mVobAhXUhbs49+g0bXfi4TjPG667F7pM8Kk518a7kRZ/HtN2kLYcSyl3XpspTosAs4x3QbFQUbFCgsBgqx+gS6xlw3OAJXM3iG2fNu2qoj9Q7viAEHoWVHwT+ftjA0qVTUf0BDD1r4ow6BNhe6kQy5bQqVu0MhjDfsK9BNTXAu4=,iv:aFHo3bQKgr1XSnwGUajkSFa4UftTWdZbPtXY05N7qOM=,tag:VymYJf4XFLaEGvxQmvF6rA==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
sops/secrets/foreign-port-age.key/users/danny
Symbolic link
1
sops/secrets/foreign-port-age.key/users/danny
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../users/danny
|
||||||
14
sops/secrets/phantom-ship-age.key/secret
Normal file
14
sops/secrets/phantom-ship-age.key/secret
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:43IKkW3YpbpEtECD3kXV4zWF6hB39knoWwqy5BGCqvYWSPccKIwwLD3ctCy3SeH806AatvE8Bl2dvHFvP++xtvFtw5PaHdnenn8=,iv:j7ODs5O0rbwD0LWkkv9BEk6O9ySl+uhCiEVa+GkRE3k=,tag:Bk/PkQjOvul8pP7hoh2cwQ==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaVlVQ3pvRmpLaVB3WWtl\nYjBIdDBJK0VKeS90eE5YeFhFRnBPak5YckFZCkl5RkVMV3JxL0pSVkM4cjhRaUE3\nK24vSWM0YnFWeXNjc3ZSWDRBb1ZDeWsKLS0tIENabmsxVUl0UGZzN1pncWswTVdM\nWDBVTVMrYzJHUklKSVVjYXBBM2RuajgKCvrGjfjujmqq2lsbNAb8d1xUhv+es2uX\nydcfnqbFRF4pjrku41iRaOolWrZHDvl+PnMslk8bclZG23UKYbSkbA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-04-19T12:31:43Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:7/Z1Up1DZUgNMCuuBh2pnfTH3Ih6824yJqD1+w9clqgkSrFtKL6v5oo5EV4TF2FDJcrYQtbbAWQoEgJXfCKXfIYOPBIChfoQEG5N5XxNe57bklkipOMWJBm7448qBhLgy3yJQqAVFkQw6uHTuDrcngRFW5D3xHkCSilHC/xau9U=,iv:WL98Dcuxojg6BQ5tLOuhXYCfFHVXqpIBr680uriPXz0=,tag:FCl6wkBiLJUyMu1RnOqeIw==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
sops/secrets/phantom-ship-age.key/users/danny
Symbolic link
1
sops/secrets/phantom-ship-age.key/users/danny
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../users/danny
|
||||||
14
sops/secrets/sunken-ship-age.key/secret
Normal file
14
sops/secrets/sunken-ship-age.key/secret
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:Mk4Vfs0PvKI4Ynwmz+8myrFtPW1swn9PdtQoeZw0xh9aCT+o6IWstAUypuCfwSgPYkj8PFPi2yq7ysTzglBkhrThV9Zto48U2dA=,iv:jL1WHTpN3mVNQJ/ltHBFd7zMtVtRmh9RIJAnh1SiGZc=,tag:zmRAQvcg6FW1+bEvZd8D6g==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6NzV5c2FDVndUSWRnZndR\ndXI2bEY2VGRiRndNbjZscHdjL0N0eHUrV1hZCmJMRllSdjNLWS8rcnlYLy94VUcy\ndDlXeUptaGdwb2ZsMW1UZHJoeW5CZzgKLS0tIDBkeUozUDd2YWpIRTFlK3M3K2RH\naW9CMnc1ZXRmM0x4MDYwVHVLZnVpR0UKZSowubfXrUemRSFNYo8hxSaeV6/egOi6\nmtmxPICosAV5VRbf8c5Hn3XGNGfOGVwwox+GmLjzqfpVsM9f2Qm9IQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-04-19T12:31:44Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:SaRWT7Q7joTgG7+LBL2icBQ4k2SJdFfDcPzV3IsBIMgVFC4kQNbkVr0BlTM4mgtfH+IxE8PBQu1v/JFo6kf43njnF3mD/Yzr/EsLxwVmD9U1DTpW+mr1EBUVLfiGqnVrTj2DhMdatKB1g8jRwAlpIcsmrlnsHIKjuSj5HKRIi7Q=,iv:YVV3BMhfh1ThIiYwW4uHUmUKqkHUtCy0i0owiAngKyg=,tag:f4UaL5ZjEp3Gkd6LGiq+uw==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
sops/secrets/sunken-ship-age.key/users/danny
Symbolic link
1
sops/secrets/sunken-ship-age.key/users/danny
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../users/danny
|
||||||
14
sops/secrets/vps-relay-age.key/secret
Normal file
14
sops/secrets/vps-relay-age.key/secret
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:+Cd3Hxr5KzX6J/74M2IZ6VOE6KEDsK8NoVyTleSB7UdsDWWGAS+mgdNZTiVBJEIBx+cmMKMcNj2rNu6T4Z2OCvqH/o6GBAhKBmM=,iv:RllA6vH/qWsx08gTEi5Nl4VkvoeI00Bw56IwPp1TOLk=,tag:PdQJpm0oaYZUZvc1y9Cmcw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxSmVLKzl3akVZNlE0QWJr\namc0T3NPb0pzM2hvR3VlSEo2TDJ6VDJOQmhBCnJOTkZaOVM2RXpTOEdYUEtWTUht\nS0ZmNDVoVDJzajYxRDVWVFVkTkJLbkUKLS0tICsrUGx0Q2FmZk04NHBVb2wvaU1p\nSktZNVl5bUtKZEJLWm1kYm9wSFl5ZXMKEb+0fq1idxA4mpJAxt3DUWX8kYp8HwYN\nwUQ7SFAlj3k611jfVFwRYdqJZQLYQ0iVbEwy5BfJw4tnqZFeaEBueA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-04-24T11:41:47Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:AjAgVpuV7QvCh1E4AvTSP+Oxg/M1at8X08s76C9OxmdCR0Evd67Hb5TaPkujhtX87Qs9IHoOK6yY+aQv2exLXWt6U4uRzapsVIpcofdyA7EUF2q0UaykrqtKLGYW3IY8fXL4XwMMFJ+wmThmwKnJVJO8hUug8AceA83/QVYNccM=,iv:JuxpYvmTROZPv7zawPQ/NpfbWAQqwRfBRp+zhNQnm5I=,tag:v6IMgQTbPP+XEeCSrpVTxg==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
sops/secrets/vps-relay-age.key/users/danny
Symbolic link
1
sops/secrets/vps-relay-age.key/users/danny
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../../users/danny
|
||||||
6
sops/users/danny/key.json
Executable file
6
sops/users/danny/key.json
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"publickey": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"type": "age"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/machines/distant-shore
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:esTlopK7VkLLnWvxsLoZtAGgbYKWKfu0XJde2fzxDuOaf9yUCU6NHpnyRAZnChceEZ3frwS7Lh/LWqX9CTKQ1LHTV8HrJERSERDzrQDHbIXFLtDbeF+qN7M1wYFEwCUa8PVAg4XHMN/ZGy6H71+J8UrktcbxcHUr+8L3pj4DZb5930kT3U02rzSoan8zb4zMhGqA0keq9QJ04uNJEN2Bly1kCBvdgc7kVUBNHwS78s+jfsa3PyOiLy5AI4CEbQ5r/xBjNgY/aSEOzRMoZtVWUFlh5Kxc47gz7MlK2x/2iXyCIAw3qeTIxor30GIL,iv:QbSPukR5aMrhBfYOM6lOb0qSEPm4oEqqQp59WDv0p6Y=,tag:KrMyGleLjIhT1LTlS3S63g==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyWlpRS1hhQThqaFUyNE4y\nYU1YVDFtazZnSHpTOWRFQkZYVThsRk9RQ1RZCnI4ZlFacTRRSHlub2hWVTNSSkhN\ndWExR202RG1nZ2dQTzQ5LzBNNW1kc2sKLS0tIHZlZXNhSm9wdElTZzRXZjQxaDAx\nRXpvcEkwK3dMNHZ2M21OSFluWnhDOFEKv0/yC/Llmhsm3+kV3AUJ2PPF817rOyL5\n6GkqTrb/gB8q8jnQabDr2sHUz7AB4w7zlQaNLRSo3Ba8KFbW7GZNRg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvcEdVdTBsUThiOE1EVWZW\nYXh2NUNCZTVieUZKbjByY2dSZVU2c0Q4L2hBCm1mNzVrcTRTTFpUTkJDZlArYTBZ\nWXhEMERmd1J3VTYxa2dWTlFxOW45N00KLS0tIHNLVzRCdDJGdWk2K0JoY1dJbzIz\nQU9DR2tXU3l2aU9YMGd1RjBGbUJYM0UKYmdAj535wvaGxN6m2VBBVtWRAD5RzQ7K\nbiJjvf8NH4A0aO9RVTFCevqRXUOKBu7jNIpFFfEyUEGHEUCWOuVSlA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-06-07T16:36:36Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:QVkNUsAO6BsVoPAL5GG1/DProapF8ryaUGDr8Y8mYPpD1Y2YXAF2sBRJ4FWkFZkWl4L2sp5DLXfqs+z0tpvi6rpG0jfpgJzy3Du2QKnk5W78WENlK+M74tSzAUfCUPn6RodykLJ8ik+EvxR+yxRmfjStAWsS6eqoTYowa4TGeJ0=,iv:qousMcaNKMtl8hGcfiS1WYBe0ftyb9ohHdBG+gqTio0=,tag:j64zgZpB7cmDfPcq4csjMQ==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/users/danny
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/machines/distant-shore
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:kAzaF+nxyux0zwjoqC5QYrx5UyEhMPW0v9hGcYUXExZl6ShMMgCWhKN82al2jY6OnU/CQ7UT9USH6PC+eecimyM6A5YXQ0GvvU3uus0t46GKqXqcGVl4BdgVO6tm8ienIcfjF6ml3LyvMXirjDdIluVkrH/P0vM=,iv:BSQrtg9kgBHRkCV8+nODNyPX3PchkTEjPPTYy5JZrfo=,tag:dPtjxWqDh1Bce9rlW6czyw==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMNkR1YTZNU1FyU2VWaUJR\nbjBOc0RSMW1SL1ZkZ1ozVHRmcVdkS01sdkZnCndTbGJlOVFrdDJHVDUxS1JFUmcy\nZS9jWGhRbWFCeGZOMHQwdzYxTFlrSjgKLS0tIEFaZmFzOXdXVjVOeUJuMDdpQ3hK\ndnRkUytmZk1zaXhUTSt1OTljUkNYK2MKpe6f3GHGCfduiidzYh0qaKEBaKyBZY4s\ne/f5QvZVApMiI4HFkOwFmNITOv6JdjGMQOw+OI6po0nqg0mqVnNIVA==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpa0p1NCtUeGFLRzJFOFQv\nVXh4MThqOVR4TU9SK3Mrc21Ga1BPdUZrM1c0CmJxQzNyam56aTdUVVB5NVhEenlV\nOTkwb2YyRWdoVXc4K2VEaXhwZXM3TEUKLS0tIEFBajAycEQzelNoR2tCU3l6cVJo\nc3lHbWJZQWFQTkVxd0lBamxlQStZWlkKopG1Z2E0Smt/z/y1+cQeTKUKyJKBXzZr\nCQNkGfi1Dk/7n/WeKwePHWVF/19WqVfOIZW0E3tOKOIqDQZa0Io1Nw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-06-07T16:36:36Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:B6UAFOrK0QIngkf5OA3+BnLAouBvsr0AbW8lKI8RH7ylGQNOyXfnN06fYshi+jQyu5EAZBqovfSZzgcTDm7MDRAjzzmTToT5ekHPZnquleU/F7pF/D7JF78M6rQyw3uG0nwhnJcRqlCAXy+56++kTJhoKEW+B5fUsbvlHTmxwLk=,iv:BXDbLObPBXsL3Uj+TRwIFtNDRzWYJeM0mJyDDluz70s=,tag:eTANaLmNaUUSYBNcIhuIFQ==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/users/danny
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEAABhcRTNvFEyWkyRBX17KkM5nDuqOvR1xTY5vDqTygvk=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
12D3KooW9pjiKnqmnHSwGRhgyUqKeFydDUE8RvYJDAqHb5PZvzue
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/machines/distant-shore
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"data": "ENC[AES256_GCM,data:tLR5iZ7Iro3BuBJlpvkKO7RrA9X2pO2H9Isi6jc8y8krh+a89Eug0PCNb4U/aSASjQgDfZgwg9+SU1y4iIoc3qC4sxw3f4uTdjCWRDEgfAvY3DVWiWI/EbWcfX7bVvl/GCQtHSwBW5z3KwhJV2McLK6Fpblx6fM=,iv:exFXncN3SA9zqSTFxX6o3kstwMGL9y8x0IOqJVNqK+I=,tag:dEkDG3meaWoq74hkRHbplg==,type:str]",
|
||||||
|
"sops": {
|
||||||
|
"age": [
|
||||||
|
{
|
||||||
|
"recipient": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3YWliNzFKWnZqdnZYeHRs\nVy8wdk1ZZWpJTlg2WWc4eEpUM2diZEFoamg0CnJnVDZJT3lWaUZlV3NHY1NpN0tW\nMXdRTnNGSjBhSFpLY0xvaER6UDI5RlEKLS0tIDloRHJFV2I1RVN6TXh6dmd5dzV0\nZ1AvZmpOM0VkaVZjNHlFdFBNd0FhTVkKEVFjtN66i+8f7P03ODYgoWZsTUiEcPiL\nYaV4UZKbjnp3SKTAeWk1P/lEj5DkicW3hq0ONQf2xrYriCpAc3/gKw==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipient": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z",
|
||||||
|
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0VG1wZE52RFQ2ekl1R1ZY\nN2pmYWd1cStGZzU0Tk1LTmNuMnc5c1UwMnlVCmJrMjB6Qzc5ZUE3aXhmUmVuTTN1\neVFWbGhOUUNYUFJJVHF0OGtaR1FvVG8KLS0tIFp4aHgwN1QvWVVnaTNDVG42SXVB\nYVlLRndmQ1ovQjFMcHZYU0dqNS9ML2sKtHjmgLODafDcmrpYQyXRc/ajAR2saGs8\nlh4NVYYYwoXE6sNKSXwzgXXSjGEXGTVLxVvp9OKnSloI5/LsbrxANQ==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"lastmodified": "2026-06-07T16:36:37Z",
|
||||||
|
"mac": "ENC[AES256_GCM,data:20RiSc6b3o3xy23NDQRw4pcSf/akdcUMO6ciSFSZMQrhreYPBEa+Tb85qqqZ0dqQHRQFanzE3Usomp+Ux4FhFfSsCxljxdOjkQCAfkQKrg+GQ7/NOUhgdVQtep2+gT7MFrEzo5Jv8kctNuT18kqUjv5CwCOR35QJ98yHeAUULoo=,iv:ocUDNN4vhOX9pCUJKqQiBRhjTHbdRdw96csN6EWFdUg=,tag:Lps0aJy0ctWU5ilCUn9Uww==,type:str]",
|
||||||
|
"version": "3.12.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../../../../../../sops/users/danny
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MCowBQYDK2VwAyEAPVF7m/+s1YroGdvSMxPwKmenJjk4yNrP8tNtZGHEhJI=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue