Compare commits
No commits in common. "main" and "server-installer-usb" have entirely different histories.
main
...
server-ins
168 changed files with 1499 additions and 4999 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -11,12 +11,3 @@ env/
|
|||
|
||||
# Installer ISO live WiFi (SSID/PSK); see docs/server-installer-usb.md
|
||||
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/
|
||||
|
|
|
|||
45
AGENTS.md
45
AGENTS.md
|
|
@ -1,24 +1,53 @@
|
|||
# Agent Instructions
|
||||
|
||||
See **CLAUDE.md** for build commands, rebuild protocol, flake architecture, repo rules, and SSH key strategy. This file covers agent-specific operational details.
|
||||
## Nix/Darwin Rebuilds
|
||||
|
||||
## Running commands on sunken-ship
|
||||
|
||||
From the Mac, agents can SSH to sunken-ship:
|
||||
**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.
|
||||
|
||||
To rebuild:
|
||||
```bash
|
||||
ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship 'hostname; ip addr'
|
||||
cd ~/dotfiles/nixos
|
||||
darwin-rebuild switch --flake .
|
||||
```
|
||||
|
||||
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`.
|
||||
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 && 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.
|
||||
- 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.
|
||||
- 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
|
||||
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.
|
||||
|
||||
|
|
|
|||
63
CLAUDE.md
63
CLAUDE.md
|
|
@ -2,28 +2,21 @@
|
|||
|
||||
## Build commands
|
||||
|
||||
The flake lives at the repo root (`~/dotfiles/flake.nix`) — clan-cli doesn't handle flakes in subdirs.
|
||||
|
||||
```bash
|
||||
# macOS (from ~/dotfiles)
|
||||
# macOS (from ~/dotfiles/nixos)
|
||||
darwin-rebuild switch --flake .
|
||||
|
||||
# NixOS servers (SSH from mac, or on server)
|
||||
# NixOS server (SSH from mac, or on server)
|
||||
sudo nixos-rebuild switch --flake .#sunken-ship
|
||||
sudo nixos-rebuild switch --flake .#phantom-ship
|
||||
|
||||
# WSL
|
||||
sudo nixos-rebuild switch --flake ~/dotfiles#wsl
|
||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl
|
||||
|
||||
# Update flake + rebuild (fish alias: nixupdate)
|
||||
cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air
|
||||
cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air
|
||||
|
||||
# Installer ISO (Linux only, cannot build on macOS)
|
||||
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
|
||||
cd ~/dotfiles/nixos && nix build .#installer-iso
|
||||
```
|
||||
|
||||
## Rebuild protocol
|
||||
|
|
@ -35,13 +28,13 @@ nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \
|
|||
- **Flake:** `nixos/flake.nix` — single flake for all hosts
|
||||
- **Inputs:** nixpkgs-unstable, nix-darwin, home-manager, nixos-wsl, disko, zen-browser
|
||||
- **Host configs** in `nixos/hosts/`:
|
||||
- `daniel-macbook-air.nix` — hostname `Daniel-Macbook-Air` (aarch64-darwin, nix-darwin)
|
||||
- `sunken-ship.nix` — NixOS home server (x86_64-linux, WiFi + AirPlay)
|
||||
- `phantom-ship.nix` — NixOS home server (x86_64-linux, Ethernet)
|
||||
- `macos.nix` — Apple Silicon MacBook Air (aarch64-darwin, nix-darwin)
|
||||
- `sunken-ship.nix` — NixOS home server (x86_64-linux)
|
||||
- `wsl.nix` — WSL (x86_64-linux)
|
||||
- `server-install.nix` — disko-install target (LUKS)
|
||||
- **Home Manager:** integrated on macOS, WSL, and sunken-ship; user config in `nixos/home/danny/home.nix`
|
||||
- **Shared modules:** `nixos/fish.nix` (fish + bash), `nixos/ollama.nix`
|
||||
- `macbookair.nix` — old MacBook Air NixOS/WSL config
|
||||
- `server-install.nix` — disko-install target (LUKS + WiFi)
|
||||
- **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/tmux.nix`, `nixos/ollama.nix`
|
||||
- **Darwin config name:** `Daniel-Macbook-Air` (must match in rebuild commands)
|
||||
|
||||
## Repo rules
|
||||
|
|
@ -53,39 +46,13 @@ nix run git+https://git.clan.lol/clan/clan-core#clan-cli -- \
|
|||
## Server (sunken-ship)
|
||||
|
||||
- SSH: `ssh -i ~/.ssh/id_ed25519_sunken_ship danny@sunken-ship`
|
||||
- Remote rebuild: `ssh ... 'cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship'`
|
||||
- Auto-rebuild timer: `dotfiles-rebuild` — every 15 min. Check with `systemctl is-active dotfiles-rebuild.timer`.
|
||||
- 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).
|
||||
- Remote rebuild: `ssh ... 'cd /etc/dotfiles/nixos && 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`.
|
||||
- Server has WiFi; stays reachable when ethernet is unplugged.
|
||||
|
||||
## Ollama
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
||||
## Shell
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Extension of [dannydannydanny/methodology](https://github.com/DannyDannyDanny/me
|
|||
## Roadmap
|
||||
|
||||
- [firefox-scrolling](firefox-scrolling.md) via terminal
|
||||
- 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).
|
||||
- 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).
|
||||
- nvim checkhealth; tmux setup; [fonts](https://www.programmingfonts.org/) / nerdfonts; [HN: home server](https://news.ycombinator.com/item?id=34271167)
|
||||
|
||||
## Windows
|
||||
|
|
@ -25,7 +25,7 @@ nix-shell -p gh git
|
|||
gh auth login
|
||||
gh repo clone dannydannydanny/dotfiles && cd dotfiles
|
||||
# git checkout <branch> # if needed
|
||||
sudo nixos-rebuild switch --flake ~/dotfiles#wsl
|
||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#wsl
|
||||
```
|
||||
|
||||
### Clone via SSH
|
||||
|
|
@ -40,10 +40,9 @@ ssh-add ~/.ssh/id_ed25519_github
|
|||
git clone git@github.com:DannyDannyDanny/dotfiles.git && cd dotfiles
|
||||
git config user.name "DannyDannyDanny"
|
||||
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
|
||||
|
||||
- [TODOs aren't for doing](https://sophiebits.com/2025/07/21/todos-arent-for-doing)
|
||||
|
|
|
|||
9
TODO.md
9
TODO.md
|
|
@ -1,6 +1,7 @@
|
|||
# TODO
|
||||
|
||||
- [ ] **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.
|
||||
- [ ] **Encrypt sunken-ship**: Currently running on plain ext4. Needs reinstall with LUKS via disko, or in-place migration (backup, reformat, restore).
|
||||
- [ ] **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).
|
||||
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).
|
||||
* I have a set wifi SSID/PSK, assume servers will start up and be able to reach this wifi.
|
||||
* I don't know how to go about the rest of this.
|
||||
|
||||
2. Encrypt sunken-ship (LUKS); update hardware/config for encrypted root and boot.
|
||||
|
|
|
|||
|
|
@ -1,54 +1,102 @@
|
|||
# Alacritty + system appearance (macOS)
|
||||
# Unified Theme Switching
|
||||
|
||||
Alacritty follows **System Settings → Appearance** automatically. No `darwin-rebuild` when you change light/dark.
|
||||
Unified theme switching that works across platforms (WSL and macOS) for Neovim, Alacritty, and Windows Terminal.
|
||||
|
||||
## How it works
|
||||
**This solution uses a single `theme` command that detects the platform and switches themes appropriately.**
|
||||
|
||||
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.
|
||||
## How It Works
|
||||
|
||||
## Optional manual LaunchAgent
|
||||
1. The `theme` command detects the platform (WSL vs macOS)
|
||||
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
|
||||
|
||||
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.
|
||||
## Setup
|
||||
|
||||
If you previously used the old plist label `com.user.alacritty-theme-sync` and switch to nix-darwin only:
|
||||
1. **The configuration is already set up!** The `theme` command is available as a fish alias.
|
||||
|
||||
2. **To switch themes, use the unified command:**
|
||||
```bash
|
||||
launchctl bootout "gui/$(id -u)" ~/Library/LaunchAgents/com.user.alacritty-theme-sync.plist 2>/dev/null || true
|
||||
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
|
||||
```
|
||||
|
||||
## `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.
|
||||
## Usage
|
||||
|
||||
### Unified Theme Command
|
||||
```bash
|
||||
theme light # Neovim (+ WSL terminal); macOS Alacritty unchanged (uses Appearance)
|
||||
# Switch to light theme (works on WSL and macOS)
|
||||
theme light
|
||||
|
||||
# Switch to dark theme (works on WSL and macOS)
|
||||
theme dark
|
||||
|
||||
# Toggle between light and dark themes
|
||||
theme toggle
|
||||
|
||||
# Show current 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
|
||||
|
||||
- `assets/alacritty/catppuccin-latte-colors.toml` / `catppuccin-mocha-colors.toml` — palette fragments
|
||||
- `scripts/alacritty-sync-system-theme.sh` — detect macOS appearance, copy palette, refresh nvim marker
|
||||
- `scripts/sync-alacritty-theme.sh` — thin wrapper (backwards compatible)
|
||||
- `nixos/home/danny/home.nix` — `programs.alacritty` + `xdg.configFile` for palettes
|
||||
- `nixos/hosts/daniel-macbook-air.nix` — LaunchAgent + `alacritty-sync-system-theme` in `environment.systemPackages`
|
||||
- `nixos/fish.nix` — optional shell-open sync on Darwin
|
||||
- `scripts/theme.sh` - **Main unified theme switching script**
|
||||
- `scripts/switch-alacritty-theme.sh` - Alacritty-specific theme switching (used by theme.sh)
|
||||
- `scripts/detect-system-theme.sh` - Detects current macOS system theme (for reference)
|
||||
- `nixos/fish.nix` - Contains the `theme` fish alias
|
||||
- `nixos/home/danny/home.nix` - Contains the conditional Alacritty configuration
|
||||
- `bashscripts/wsl_theme.sh` - Legacy WSL script (replaced by theme.sh)
|
||||
|
||||
After changing Nix config, run `darwin-rebuild switch` once (see repo `AGENTS.md`).
|
||||
|
||||
## Theme colors
|
||||
## Theme Colors
|
||||
|
||||
### Catppuccin Latte (Light)
|
||||
|
||||
- Background: `#eff1f5` (base)
|
||||
- Foreground: `#4c4f69` (text)
|
||||
- Accent colors optimized for light backgrounds
|
||||
|
||||
### Catppuccin Mocha (Dark)
|
||||
|
||||
- Background: `#1e1e2e` (base)
|
||||
- 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
|
||||
28
assets/alacritty/catppuccin-dark.yml
Normal file
28
assets/alacritty/catppuccin-dark.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 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
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# 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"
|
||||
28
assets/alacritty/catppuccin-light.yml
Normal file
28
assets/alacritty/catppuccin-light.yml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# 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
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
# 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,18 +4,28 @@
|
|||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.user.alacritty-theme-sync</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/bash</string>
|
||||
<string>/Users/danny/dotfiles/scripts/alacritty-sync-system-theme.sh</string>
|
||||
<string>/Users/danny/dotfiles/scripts/sync-alacritty-theme.sh</string>
|
||||
</array>
|
||||
|
||||
<key>StartInterval</key>
|
||||
<integer>30</integer>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/tmp/alacritty-theme-sync.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<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>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
// 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.
|
||||
|
||||
- **Ships / sea:** sunken-ship ✓, phantom-ship ✓, **rusty-anchor** (next), salty-wind, stormy-wave, calm-harbor, distant-shore, foreign-port, wooden-hull, anchor-chain
|
||||
- **Ships / sea:** sunken-ship, phantom-ship, rusty-anchor, 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
|
||||
- **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
|
||||
|
|
|
|||
|
|
@ -1,163 +1,180 @@
|
|||
# Server installer USB (NixOS + LUKS)
|
||||
# Server installer USB (NixOS + LUKS + WiFi)
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Quick path (Ethernet server like phantom-ship)
|
||||
## Quick path: boot USB → WiFi → SSH in → run bootstrap
|
||||
|
||||
### Prep (on sunken-ship or any Linux box)
|
||||
|
||||
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:
|
||||
1. Boot the target machine from the NixOS installer USB.
|
||||
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:
|
||||
```bash
|
||||
sudo systemctl start sshd
|
||||
sudo passwd nixos
|
||||
hostname -I # note the IP
|
||||
hostname -I
|
||||
```
|
||||
5. From your **Mac**, scp your SSH public key and SSH in:
|
||||
Note the IP from `hostname -I`.
|
||||
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
|
||||
scp ~/.ssh/id_ed25519_phantom_ship.pub nixos@<IP>:/tmp/key.pub
|
||||
ssh nixos@<IP>
|
||||
curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
||||
```
|
||||
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).
|
||||
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.
|
||||
|
||||
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`
|
||||
## Option A: Official NixOS ISO (works from macOS)
|
||||
|
||||
7. Reboot, remove USB, unlock LUKS.
|
||||
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:
|
||||
|
||||
### After first boot
|
||||
|
||||
8. SSH in: `ssh danny@phantom-ship`
|
||||
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.
|
||||
1. Download the [minimal ISO](https://nixos.org/download.html#nixos-iso) (e.g. `nixos-minimal-*-x86_64-linux.iso`).
|
||||
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.
|
||||
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.
|
||||
|
||||
## Option B: Custom ISO (build on Linux only)
|
||||
|
||||
Adds WiFi kernel modules for servers that need WiFi on the live system.
|
||||
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.
|
||||
|
||||
### Build from sunken-ship
|
||||
### Build from sunken-ship (one command from your Mac)
|
||||
|
||||
When the server is on the same network, run from the dotfiles repo:
|
||||
|
||||
```bash
|
||||
./scripts/build-installer-iso-on-server.sh
|
||||
```
|
||||
|
||||
### Build directly on Linux
|
||||
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 a Linux machine
|
||||
|
||||
From a Linux box (or on sunken-ship after SSH in):
|
||||
|
||||
```bash
|
||||
cd ~/dotfiles && nix build .#installer-iso
|
||||
# Write to USB:
|
||||
sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress bs=4M
|
||||
cd ~/dotfiles/nixos
|
||||
nix build .#installer-iso
|
||||
```
|
||||
|
||||
## Live-system WiFi (optional, custom ISO only)
|
||||
The image is at `result/iso/nixos-minimal-*.iso`. Write it to a USB stick (replace `sdX` with your device, e.g. `sda`):
|
||||
|
||||
The minimal installer ISO runs NetworkManager, so live-system WiFi must be a
|
||||
declarative NetworkManager profile. `networking.wireless` / wpa_supplicant does
|
||||
**not** work here — NixOS asserts you cannot combine `networking.networkmanager`
|
||||
with `networking.wireless.networks`.
|
||||
```bash
|
||||
# Linux
|
||||
sudo dd if=result/iso/nixos-minimal-*.iso of=/dev/sdX status=progress
|
||||
sync
|
||||
```
|
||||
|
||||
Create `nixos/installer-wifi.nix` (gitignored — it holds the PSK):
|
||||
On macOS, use the disk number (e.g. `4` for `disk4`):
|
||||
|
||||
```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
|
||||
{
|
||||
networking.networkmanager.ensureProfiles.profiles.installer-wifi = {
|
||||
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";
|
||||
};
|
||||
networking.wireless.enable = true;
|
||||
networking.wireless.networks."YourSSID".psk = "your-password";
|
||||
}
|
||||
```
|
||||
|
||||
`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:
|
||||
2. Add it to the flake for the installer ISO only. In `nixos/flake.nix`, change the `installer-iso` modules to:
|
||||
|
||||
- **`build-installer-iso-on-server.sh`** copies the file to the build host and
|
||||
runs `git add -f` automatically.
|
||||
- For a **direct `nix build`**, run `git add -f nixos/installer-wifi.nix` first
|
||||
(staging is enough — never commit it; it contains the PSK).
|
||||
```nix
|
||||
installer-iso = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [ ./installer-iso.nix ./installer-wifi.nix ]; # add installer-wifi.nix
|
||||
};
|
||||
```
|
||||
|
||||
Then rebuild the ISO on Linux.
|
||||
3. Ensure `nixos/installer-wifi.nix` is in `.gitignore`, then rebuild the ISO.
|
||||
|
||||
## Installed-system WiFi (optional)
|
||||
If you skip this, use Ethernet on the live system or the graphical NixOS installer to join Wi‑Fi, then run the install script.
|
||||
|
||||
Pass a JSON file with wireless config:
|
||||
## Install on the server
|
||||
|
||||
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
|
||||
sudo INSTALLER_SYSTEM_CONFIG_FILE=/path/to/wifi.json INSTALLER_HOSTNAME=my-server ./scripts/nixos-server-install.sh
|
||||
curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
You can run disko-install yourself:
|
||||
|
||||
```bash
|
||||
sudo nix run github:nix-community/disko/latest#disko-install -- \
|
||||
--flake 'path:/tmp/dotfiles#server-install' \
|
||||
--flake 'path:/tmp/dotfiles/nixos#server-install' \
|
||||
--disk main /dev/sda \
|
||||
--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
|
||||
|
||||
| Step | Action |
|
||||
|------|--------|
|
||||
| **Prep** | Download NixOS minimal ISO on sunken-ship, write to USB |
|
||||
| **Boot** | Boot new server from USB, plug Ethernet |
|
||||
| **Install** | `curl ... \| INSTALLER_HOSTNAME=phantom-ship SSH_PUBKEY_FILE=/tmp/key.pub sudo -E bash` |
|
||||
| **Reboot** | Remove USB, unlock LUKS |
|
||||
| **First rebuild** | `sudo nixos-rebuild switch --flake /etc/dotfiles#phantom-ship` |
|
||||
| **Commit** | Push generated `phantom-ship-hardware.nix` to repo |
|
||||
| **From macOS** | Use Option A: download official NixOS minimal ISO, write to USB, boot server, clone repo, run install script. |
|
||||
| **From Linux** | Option B: `nix build .#installer-iso` in `nixos/`, then write `result/iso/*.iso` to USB. |
|
||||
| Optional live WiFi | (Custom ISO only) Add `installer-wifi.nix` (gitignored), include in flake, rebuild on Linux. |
|
||||
| Boot | Boot server from USB |
|
||||
| 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`) |
|
||||
| Optional installed WiFi | Set `INSTALLER_SYSTEM_CONFIG_FILE` to a JSON file with wireless config |
|
||||
| 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)
|
||||
|
||||
From the server (flake is at the repo root):
|
||||
From the server (flake is in `nixos/`):
|
||||
|
||||
```bash
|
||||
cd /etc/dotfiles && sudo nixos-rebuild switch --flake .#sunken-ship
|
||||
cd /etc/dotfiles/nixos && sudo nixos-rebuild switch --flake .#sunken-ship
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
|
|
|||
|
|
@ -1,254 +0,0 @@
|
|||
# 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";
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{ 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 ];
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{ 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;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# 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;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{ 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
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{ 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
665
flake.lock
generated
|
|
@ -1,665 +0,0 @@
|
|||
{
|
||||
"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
44
flake.nix
|
|
@ -1,44 +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";
|
||||
|
||||
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) ];
|
||||
};
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# 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 ];
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
# 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 ];
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# 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
|
||||
];
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# 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 = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# 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 = "/";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# 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";
|
||||
# TODO: rename and move 25_flakes into dotfiles
|
||||
nide = "nix develop ~/python-projects/25_flakes/$(basename (pwd)) -c $(which fish)";
|
||||
nixupdate = "cd ~/dotfiles && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles#Daniel-Macbook-Air";
|
||||
nixupdate = "cd ~/dotfiles/nixos && sudo nix flake update && sudo darwin-rebuild switch --flake ~/dotfiles/nixos#Daniel-Macbook-Air";
|
||||
};
|
||||
interactiveShellInit = ''
|
||||
function fish_user_key_bindings
|
||||
|
|
@ -24,43 +24,6 @@
|
|||
|
||||
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
|
||||
# author: Lily Ballard
|
||||
# edits: DannyDannyDanny
|
||||
|
|
|
|||
254
nixos/flake.lock
generated
Normal file
254
nixos/flake.lock
generated
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
{
|
||||
"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
Normal file
134
nixos/flake.nix
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
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
|
||||
];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
42
nixos/hardware-configuration.nix
Normal file
42
nixos/hardware-configuration.nix
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# 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, config, ... }:
|
||||
{ pkgs, lib, zen-browser ? null, ... }:
|
||||
{
|
||||
# TODO: remove next two lines from here or from flake.nix
|
||||
# home.username = "danny";
|
||||
|
|
@ -9,24 +9,6 @@
|
|||
# Import neovim configuration
|
||||
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)
|
||||
programs.tmux = {
|
||||
enable = true;
|
||||
|
|
@ -61,12 +43,6 @@
|
|||
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}"
|
||||
|
|
@ -88,35 +64,6 @@
|
|||
catppuccin
|
||||
tmux-fzf
|
||||
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'
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
|
|
@ -165,31 +112,15 @@
|
|||
executable = true;
|
||||
};
|
||||
|
||||
# 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)
|
||||
# Alacritty terminal configuration with conditional theme switching
|
||||
programs.alacritty = {
|
||||
enable = true;
|
||||
settings = {
|
||||
general = {
|
||||
live_config_reload = true;
|
||||
import = [ "${config.xdg.configHome}/alacritty/active-colors.toml" ];
|
||||
};
|
||||
window = {
|
||||
padding = { x = 8; y = 8; };
|
||||
dynamic_padding = true;
|
||||
decorations = "buttonless";
|
||||
decorations_theme_variant = "None";
|
||||
opacity = 1.0;
|
||||
opacity = 0.95;
|
||||
startup_mode = "Maximized";
|
||||
option_as_alt = "Both";
|
||||
};
|
||||
|
|
@ -203,29 +134,51 @@
|
|||
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";
|
||||
};
|
||||
};
|
||||
|
||||
# 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
|
||||
'';
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
# TODO: Put user-installed binaries here if you want HM to own them (optional)
|
||||
# Fonts
|
||||
fonts.fontconfig.enable = true;
|
||||
home.packages = with pkgs; [
|
||||
# Zen Browser (Firefox fork; from flake overlay, supports aarch64-darwin)
|
||||
] ++ (lib.optionals (pkgs ? zen-browser) [
|
||||
pkgs.zen-browser
|
||||
# Zen Browser (Firefox fork; from flake, supports aarch64-darwin)
|
||||
] ++ (lib.optionals (zen-browser != null) [
|
||||
zen-browser.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||
]) ++ (with pkgs; [
|
||||
# Google Fonts (includes Michroma)
|
||||
google-fonts
|
||||
|
|
@ -262,15 +215,12 @@
|
|||
# alacritty # TODO: configured via programs.alacritty above, so not needed here
|
||||
# warp-terminal # TODO: Bloat
|
||||
# vscodium # TODO: Bloat
|
||||
zed-editor
|
||||
# zed-editor # TODO: Bloat
|
||||
code-cursor
|
||||
cursor-cli
|
||||
cinny-desktop # Matrix client (Tauri wrapper around the Cinny web app)
|
||||
dfu-util # USB DFU firmware flasher (Flipper Zero etc.)
|
||||
discord
|
||||
mapscii
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
{ 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;
|
||||
|
||||
}
|
||||
|
|
@ -1,18 +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 = [ "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;
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
# 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 ];
|
||||
}
|
||||
|
|
@ -1,18 +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 = [ "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;
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
# 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 ];
|
||||
}
|
||||
197
nixos/hosts/macbookair.nix
Normal file
197
nixos/hosts/macbookair.nix
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# 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?
|
||||
|
||||
}
|
||||
55
nixos/hosts/macos.nix
Normal file
55
nixos/hosts/macos.nix
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{ 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;
|
||||
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
# 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;
|
||||
}
|
||||
|
|
@ -1,664 +0,0 @@
|
|||
# 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,11 +2,15 @@
|
|||
#
|
||||
# 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.
|
||||
# Then: sudo nixos-rebuild switch --flake /etc/dotfiles#sunken-ship
|
||||
# Then: sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#sunken-ship
|
||||
# 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.
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
dotfilesDir = "/etc/dotfiles";
|
||||
flakeRef = "${dotfilesDir}/nixos#sunken-ship";
|
||||
in
|
||||
{
|
||||
imports = [ ./sunken-ship-hardware.nix ];
|
||||
|
||||
|
|
@ -18,7 +22,8 @@
|
|||
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).
|
||||
# At the console, run: brightnessctl set 100% (or `brightnessctl max`) to restore brightness.
|
||||
# At the console, run: light -S 100 (or any 0–100) to restore brightness.
|
||||
programs.light.enable = true;
|
||||
systemd.services.server-backlight-off = {
|
||||
description = "Turn off panel backlight after console idle (reduce burn-in)";
|
||||
after = [ "multi-user.target" ];
|
||||
|
|
@ -33,28 +38,15 @@
|
|||
};
|
||||
|
||||
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" "video" "audio" ]; # video: backlight; audio: sound devices
|
||||
openssh.authorizedKeys.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"
|
||||
];
|
||||
extraGroups = [ "wheel" "video" ]; # video: backlight control via light(1)
|
||||
# SSH keys: push via scp, don't commit. NixOS does not manage authorized_keys so scp'd keys persist.
|
||||
# Example: scp ~/.ssh/id_ed25519_sunken_ship.pub danny@server:/tmp/ then on server: mkdir -p ~/.ssh; cat /tmp/*.pub >> ~/.ssh/authorized_keys
|
||||
};
|
||||
|
||||
# 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.
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
|
|
@ -67,437 +59,28 @@
|
|||
|
||||
# Passwordless sudo for wheel.
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
environment.systemPackages = [ pkgs.git ]; # for clone/bootstrap and timer
|
||||
|
||||
# Trust `danny` for Nix remote builds (so the mac can delegate
|
||||
# x86_64-linux builds here via ssh-ng://danny@sunken-ship-zt).
|
||||
nix.settings.trusted-users = [ "root" "danny" ];
|
||||
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";
|
||||
# Pull dotfiles and rebuild if the repo has new commits.
|
||||
systemd.services.dotfiles-rebuild = {
|
||||
description = "Pull dotfiles and run nixos-rebuild if repo changed";
|
||||
path = with pkgs; [ git nix ];
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
cd /home/danny/tg_fitness_bot
|
||||
cd ${dotfilesDir}
|
||||
git fetch origin
|
||||
if [ "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)" ]; then
|
||||
exit 0
|
||||
fi
|
||||
git pull origin main
|
||||
systemctl restart fitness-bot
|
||||
exec nixos-rebuild switch --flake ${flakeRef}
|
||||
'';
|
||||
serviceConfig.Type = "oneshot";
|
||||
};
|
||||
|
||||
systemd.timers.fitness-bot-pull = {
|
||||
systemd.timers.dotfiles-rebuild = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig.OnCalendar = "*-*-* *:07/15:00"; # every 15 minutes, offset from dotfiles-rebuild
|
||||
timerConfig.OnCalendar = "*-*-* *:00/15:00"; # every 15 minutes
|
||||
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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,189 +0,0 @@
|
|||
# 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,7 +23,14 @@
|
|||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
programs.nix-ld.enable = true;
|
||||
# direnv is now managed by home-manager (home/danny/home.nix)
|
||||
# TODO: move to home manager (?)
|
||||
programs = {
|
||||
direnv = {
|
||||
enable = true;
|
||||
# enableFishIntegration = true;
|
||||
nix-direnv.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
# This value determines the NixOS release from which the default
|
||||
# settings for stateful data, like file locations and database versions
|
||||
|
|
@ -40,14 +47,42 @@
|
|||
};
|
||||
|
||||
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; [
|
||||
wget # needed by vscode-server
|
||||
busybox # useful system utilities (tree, unzip, etc.)
|
||||
xdg-utils # terminal desktop integrations
|
||||
# tmux # activated in tmux.nix
|
||||
# vim # using neovim in stead
|
||||
# neovim # activated in neovim.nix
|
||||
|
||||
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;
|
||||
|
|
|
|||
115
nixos/neovim.nix
115
nixos/neovim.nix
|
|
@ -4,110 +4,71 @@
|
|||
programs.neovim = {
|
||||
enable = true;
|
||||
defaultEditor = true;
|
||||
withRuby = false;
|
||||
withPython3 = false;
|
||||
|
||||
# VimScript settings (options that have no Lua equivalent or are simpler in vim)
|
||||
# TODO: refactor (some parts) to extraLuaConfig
|
||||
extraConfig = ''
|
||||
set title
|
||||
set mouse=a
|
||||
set nohlsearch
|
||||
set number
|
||||
let mapleader=","
|
||||
|
||||
colorscheme catppuccin
|
||||
lua << EOF
|
||||
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
|
||||
let g:netrw_liststyle = 3
|
||||
let g:netrw_banner = 0
|
||||
let g:netrw_browse_split = 3
|
||||
let g:netrw_winsize = 25
|
||||
'';
|
||||
let g:netrw_winsize = 25 " % of page
|
||||
|
||||
initLua = ''
|
||||
-- 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
|
||||
set listchars=tab:→\ ,space:·,nbsp:␣,trail:•,eol:¶,precedes:«,extends:»
|
||||
set clipboard+=unnamedplus
|
||||
|
||||
-- General options
|
||||
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"
|
||||
" Replace-all is aliased to S.
|
||||
nnoremap S :%s//g<Left><Left>
|
||||
|
||||
-- Markdown: fold by heading/section using Treesitter
|
||||
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,
|
||||
})
|
||||
" save file with ,w
|
||||
map <leader>w :w<cr><Space>
|
||||
|
||||
-- Treesitter highlighting: parser-driven syntax highlighting (richer
|
||||
-- than the regex-based default). Leaving `indent` off — it's still
|
||||
-- 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" })
|
||||
" spellcheck
|
||||
set spell spelllang=en_us
|
||||
setlocal spell! spelllang=en_us
|
||||
'';
|
||||
|
||||
plugins = with pkgs.vimPlugins; [
|
||||
vim-surround # shortcuts for setting () {} etc.
|
||||
vim-gitgutter # git diff in sign column
|
||||
# vim-airline # nice and light status bar # doesn't work nicely with tmux
|
||||
# coc-nvim coc-git coc-highlight coc-python coc-rls coc-vetur coc-vimtex coc-yaml coc-html coc-json # auto completion
|
||||
vim-nix # nix highlight
|
||||
# vimtex # latex stuff - disabled due to build check issue
|
||||
fzf-lua # fuzzy finder through lua
|
||||
nerdtree # file structure inside nvim
|
||||
rainbow # color parenthesis
|
||||
# gruvbox-nvim # theme
|
||||
catppuccin-nvim # theme
|
||||
goyo-vim # write prose
|
||||
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
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
# 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";
|
||||
};
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
# 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
[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"]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
"""Entry point for `python -m hara_gmail_mcp` and the `hara-gmail-mcp` script."""
|
||||
from .server import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
"""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
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
"""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()
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
"""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,39 +1,27 @@
|
|||
# NixOS modules
|
||||
# NixOS flake
|
||||
|
||||
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
|
||||
Rebuild from dotfiles dir:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
cd ~/dotfiles && 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
|
||||
sudo nixos-rebuild switch --flake ~/dotfiles/nixos#macbookair
|
||||
# or #wsl
|
||||
# macOS: cd ~/dotfiles/nixos && darwin-rebuild switch --flake .
|
||||
```
|
||||
|
||||
## Server bootstrap (one-time)
|
||||
## Server (sunken-ship)
|
||||
|
||||
One-time bootstrap (no git until first rebuild):
|
||||
|
||||
```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 nixos-rebuild switch --flake /etc/dotfiles#sunken-ship \
|
||||
--option accept-flake-config true
|
||||
sudo nixos-rebuild switch --flake /etc/dotfiles/nixos#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`, `sudo nixos-rebuild switch`, then build the flake.
|
||||
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).
|
||||
|
||||
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`.
|
||||
|
|
|
|||
57
nixos/tmux.nix
Normal file
57
nixos/tmux.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ 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)
|
||||
];
|
||||
};
|
||||
}
|
||||
17
nixos/uxplay.nix
Normal file
17
nixos/uxplay.nix
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# 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,5 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
#!/bin/bash
|
||||
|
||||
# Required parameters:
|
||||
# @raycast.schemaVersion 1
|
||||
|
|
@ -8,11 +7,11 @@ set -euo pipefail
|
|||
|
||||
# Optional parameters:
|
||||
# @raycast.icon 🤖
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Text to count" }
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Placeholder" }
|
||||
|
||||
# Documentation:
|
||||
# @raycast.description counts chars in selected text
|
||||
# @raycast.author DannyDannyDanny
|
||||
# @raycast.authorURL https://raycast.com/DannyDannyDanny
|
||||
|
||||
printf '%s' "${1:-}" | wc -c | awk '{ print $1 }'
|
||||
echo -n "$1" | wc -c
|
||||
|
|
|
|||
1
result
Symbolic link
1
result
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
/nix/store/x8ain9193yl3k10mk0bi667qp5iwk03w-lua-5.2.4
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
#!/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
|
||||
# Fetch with curl and run to install NixOS (clone + run nixos-server-install.sh).
|
||||
# On the live system, run only:
|
||||
# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/main/scripts/bootstrap-install.sh | sudo bash
|
||||
# curl -sL https://raw.githubusercontent.com/DannyDannyDanny/dotfiles/server-installer-usb/scripts/bootstrap-install.sh | sudo bash
|
||||
#
|
||||
# Optional: REPO_URL=... BRANCH=... (default repo and server-installer-usb)
|
||||
set -euo pipefail
|
||||
|
||||
REPO_URL="${REPO_URL:-https://github.com/DannyDannyDanny/dotfiles.git}"
|
||||
BRANCH="${BRANCH:-main}"
|
||||
BRANCH="${BRANCH:-server-installer-usb}"
|
||||
DEST="/tmp/dotfiles"
|
||||
INSTALL_SCRIPT="$DEST/scripts/nixos-server-install.sh"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@
|
|||
# host: SSH host (default: sunken-ship)
|
||||
# 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
|
||||
#
|
||||
# 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
|
||||
|
||||
HOST="${1:-sunken-ship}"
|
||||
OUT="${2:-.}"
|
||||
REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
|
||||
# Default to the sunken-ship SSH key when targeting that host.
|
||||
# Use sunken-ship key if not set (AGENTS.md)
|
||||
if [[ -n "${SSH_KEY:-}" ]]; then
|
||||
SSH_OPTS=(-i "$SSH_KEY")
|
||||
elif [[ "$HOST" == "sunken-ship" ]] && [[ -f ~/.ssh/id_ed25519_sunken_ship ]]; then
|
||||
|
|
@ -24,37 +19,23 @@ else
|
|||
SSH_OPTS=()
|
||||
fi
|
||||
|
||||
echo "Pushing main so the server can clone the latest..."
|
||||
git -C "$REPO_ROOT" push origin main 2>/dev/null || true
|
||||
echo "Pushing branch so server can pull..."
|
||||
git push origin server-installer-usb 2>/dev/null || true
|
||||
|
||||
echo "On $HOST: clone main into ~/dotfiles-iso-build..."
|
||||
echo "On $HOST: clone branch, build ISO..."
|
||||
ssh "${SSH_OPTS[@]}" "$HOST" 'set -e
|
||||
BUILD_DIR=~/dotfiles-iso-build
|
||||
rm -rf "$BUILD_DIR"
|
||||
git clone --branch main https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR"
|
||||
'
|
||||
|
||||
# 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
|
||||
git clone --branch server-installer-usb https://github.com/DannyDannyDanny/dotfiles.git "$BUILD_DIR"
|
||||
cd "$BUILD_DIR/nixos"
|
||||
nix build .#installer-iso
|
||||
ls -la result/iso/
|
||||
'
|
||||
|
||||
ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/result/iso/*.iso 2>/dev/null | head -1')
|
||||
ISO_NAME=$(ssh "${SSH_OPTS[@]}" "$HOST" 'ls ~/dotfiles-iso-build/nixos/result/iso/*.iso 2>/dev/null | head -1')
|
||||
ISO_NAME=$(basename "$ISO_NAME")
|
||||
|
||||
echo "Copying $ISO_NAME to $OUT ..."
|
||||
scp "${SSH_OPTS[@]}" "$HOST:dotfiles-iso-build/result/iso/$ISO_NAME" "$OUT/"
|
||||
scp "${SSH_OPTS[@]}" "$HOST:~/dotfiles-iso-build/nixos/result/iso/$ISO_NAME" "$OUT/"
|
||||
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"
|
||||
|
|
|
|||
13
scripts/detect-system-theme.sh
Executable file
13
scripts/detect-system-theme.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/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,27 +1,26 @@
|
|||
#!/bin/bash
|
||||
# Install NixOS with disko (LUKS + root) on a live system.
|
||||
# Prompts for hostname and target disk, then provisions the installed system
|
||||
# (clones dotfiles, installs SSH key, generates hardware config).
|
||||
# Run on a NixOS minimal live system (or installer ISO) to install NixOS with
|
||||
# disko (LUKS + root). Prompts for hostname and target disk; optionally use
|
||||
# INSTALLER_SYSTEM_CONFIG_FILE for WiFi etc.
|
||||
#
|
||||
# Usage (from repo root, e.g. /tmp/dotfiles):
|
||||
# sudo ./scripts/nixos-server-install.sh
|
||||
# If you see "command not found", use: sudo bash ./scripts/nixos-server-install.sh
|
||||
#
|
||||
# Environment variables (all optional):
|
||||
# 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
|
||||
# Optional: FLAKE_REF=github:User/dotfiles or path:/path/to/dotfiles/nixos
|
||||
#
|
||||
# Optional: INSTALLER_SYSTEM_CONFIG_FILE=/path/to/json with full --system-config
|
||||
# (e.g. hostName + networking.wireless.networks). If unset, only hostname is passed.
|
||||
set -euo pipefail
|
||||
|
||||
FLAKE_REF="${FLAKE_REF:-}"
|
||||
if [[ -z "$FLAKE_REF" ]]; then
|
||||
if [[ -f "$(dirname "$0")/../flake.nix" ]]; then
|
||||
if [[ -d "$(dirname "$0")/../nixos" ]] && [[ -f "$(dirname "$0")/../nixos/flake.nix" ]]; then
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
FLAKE_REF="path:${REPO_ROOT}"
|
||||
FLAKE_REF="path:${REPO_ROOT}/nixos"
|
||||
else
|
||||
echo "FLAKE_REF not set and not running from dotfiles repo. Example:"
|
||||
echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles"
|
||||
echo " export FLAKE_REF=github:USER/REPO # or path:/path/to/dotfiles/nixos"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
@ -31,29 +30,21 @@ if [[ "$EUID" -ne 0 ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# --- Hostname ---
|
||||
hostname="${INSTALLER_HOSTNAME:-}"
|
||||
if [[ -z "$hostname" ]]; then
|
||||
read -r -p "Hostname (e.g. phantom-ship): " hostname
|
||||
fi
|
||||
read -r -p "Hostname (e.g. my-server): " hostname
|
||||
if [[ -z "$hostname" ]]; then
|
||||
echo "Hostname cannot be empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Target disk ---
|
||||
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
|
||||
echo "Not a block device: $disk"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- System config (hostname + optional extras) ---
|
||||
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
|
||||
SYSTEM_CONFIG=$(jq --arg h "$hostname" '.networking.hostName = $h' "$INSTALLER_SYSTEM_CONFIG_FILE")
|
||||
else
|
||||
|
|
@ -64,9 +55,8 @@ else
|
|||
SYSTEM_CONFIG='{"networking":{"hostName":"'"$hostname"'"}}'
|
||||
fi
|
||||
|
||||
# --- Optional: danny password ---
|
||||
danny_pass=""
|
||||
read -r -p "Set a password for user danny? [y/N] " set_pass
|
||||
# Prompt for password for danny so you can log in at console after reboot (no rescue needed)
|
||||
read -r -p "Set a password for user danny (console/SSH login)? [y/N] " set_pass
|
||||
if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then
|
||||
read -s -r -p "Password for danny: " danny_pass
|
||||
echo
|
||||
|
|
@ -80,27 +70,23 @@ if [[ "${set_pass,,}" == "y" || "${set_pass,,}" == "yes" ]]; then
|
|||
echo "Password cannot be empty. Aborted."
|
||||
exit 1
|
||||
fi
|
||||
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 [[ -n "${HASH:-}" ]]; then
|
||||
HASH=$(echo -n "$danny_pass" | openssl passwd -6 -stdin 2>/dev/null) || HASH=$(mkpasswd -6 -m sha-512 "$danny_pass" 2>/dev/null)
|
||||
if [[ -z "$HASH" ]]; then
|
||||
echo "Could not hash password (need openssl or mkpasswd). Skipping password."
|
||||
else
|
||||
if command -v jq &>/dev/null; then
|
||||
SYSTEM_CONFIG=$(echo "$SYSTEM_CONFIG" | jq --arg h "$HASH" '. + {"users":{"users":{"danny":{"hashedPassword":$h}}}}')
|
||||
else
|
||||
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. Set after boot: passwd danny"
|
||||
[[ -n "$NEW_CONFIG" ]] && SYSTEM_CONFIG="$NEW_CONFIG" || echo "Could not merge password (jq not found). Set after boot: passwd danny"
|
||||
fi
|
||||
echo "Password will be set for danny."
|
||||
else
|
||||
echo "Could not hash password (need openssl or mkpasswd). Set after boot: passwd danny"
|
||||
[[ -n "$SYSTEM_CONFIG" ]] && echo "Password will be set for danny."
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Confirm and install ---
|
||||
echo ""
|
||||
echo "=== Install Summary ==="
|
||||
echo "Flake: ${FLAKE_REF}#server-install"
|
||||
echo "Disk: $disk"
|
||||
echo "Hostname: $hostname"
|
||||
echo "SSH pubkey: ${SSH_PUBKEY_FILE:-none}"
|
||||
echo "System config: $SYSTEM_CONFIG"
|
||||
read -r -p "Proceed? [y/N] " confirm
|
||||
if [[ "${confirm,,}" != "y" && "${confirm,,}" != "yes" ]]; then
|
||||
|
|
@ -114,88 +100,33 @@ nix run --extra-experimental-features "nix-command flakes" \
|
|||
--disk main "$disk" \
|
||||
--system-config "$SYSTEM_CONFIG"
|
||||
|
||||
echo ""
|
||||
echo "=== Post-install provisioning ==="
|
||||
echo "Re-opening LUKS to provision the installed system..."
|
||||
# Set danny password directly on disk (Nix merge can fail); re-open LUKS and chroot
|
||||
if [[ -n "${danny_pass:-}" ]]; then
|
||||
echo "Setting password for danny on installed system (re-enter LUKS passphrase once)..."
|
||||
read -s -r -p "LUKS passphrase: " luks_pass
|
||||
echo
|
||||
|
||||
LUKS_DEV="/dev/disk/by-partlabel/disk-main-luks"
|
||||
ESP_DEV="/dev/disk/by-partlabel/disk-main-ESP"
|
||||
if [[ ! -b "$LUKS_DEV" ]]; then
|
||||
LUKS_DEV="${disk}2"
|
||||
ESP_DEV="${disk}1"
|
||||
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
|
||||
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
|
||||
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"
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
#!/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,9 +1,27 @@
|
|||
#!/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
|
||||
|
||||
# Get the directory where this script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
echo "Syncing from system appearance..."
|
||||
"$SCRIPT_DIR/alacritty-sync-system-theme.sh"
|
||||
|
||||
echo "Setting up simple Alacritty theme synchronization..."
|
||||
|
||||
# Run the theme sync script to create the initial theme file
|
||||
echo "Detecting current system theme..."
|
||||
"$SCRIPT_DIR/sync-alacritty-theme.sh"
|
||||
|
||||
echo ""
|
||||
echo "Done. Alacritty reloads colors automatically if live_config_reload is enabled."
|
||||
echo "A LaunchAgent (nix-darwin: launchd.user.agents.alacritty-system-theme) runs this every 30s."
|
||||
echo "Setup complete!"
|
||||
echo ""
|
||||
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."
|
||||
|
|
|
|||
80
scripts/switch-alacritty-theme.sh
Executable file
80
scripts/switch-alacritty-theme.sh
Executable file
|
|
@ -0,0 +1,80 @@
|
|||
#!/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,5 +1,31 @@
|
|||
#!/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
|
||||
|
||||
# Get the directory where this script is located
|
||||
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 "This command switches themes for:"
|
||||
echo " - Neovim (via nvim_color_scheme file)"
|
||||
echo " - Alacritty on macOS follows System Settings (LaunchAgent sync)"
|
||||
echo " - Alacritty (via Nix configuration on macOS)"
|
||||
echo " - Windows Terminal (via settings.json on WSL)"
|
||||
echo " - Windows system theme (on WSL)"
|
||||
}
|
||||
|
|
@ -43,11 +43,19 @@ show_status() {
|
|||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
echo " Platform: macOS"
|
||||
|
||||
marker="$HOME/.config/alacritty/.last-system-theme"
|
||||
if [ -f "$marker" ]; then
|
||||
echo " Alacritty: follows system (active palette: $(tr -d '\n' <"$marker"))"
|
||||
# Check Alacritty theme from Nix config
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
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: follows system (sync after next login or run alacritty-sync-system-theme)"
|
||||
echo " Alacritty: dark (Catppuccin Mocha)"
|
||||
fi
|
||||
else
|
||||
echo " Alacritty: config file not found"
|
||||
fi
|
||||
else
|
||||
echo " Platform: other"
|
||||
|
|
@ -59,10 +67,17 @@ toggle_theme() {
|
|||
current_theme=""
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
if [[ "$(defaults read -g AppleInterfaceStyle 2>/dev/null)" == "Dark" ]]; then
|
||||
current_theme="dark"
|
||||
else
|
||||
# On macOS, check the Nix config for current theme
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOTFILES_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
HOME_NIX="$DOTFILES_DIR/nixos/home/danny/home.nix"
|
||||
|
||||
if [ -f "$HOME_NIX" ]; then
|
||||
if grep -q "isLightTheme = true" "$HOME_NIX"; then
|
||||
current_theme="light"
|
||||
else
|
||||
current_theme="dark"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
|
|
@ -168,8 +183,18 @@ if [[ -n "$WSL_DISTRO_NAME" ]]; then
|
|||
powershell.exe -Command "Set-ItemProperty -Path HKCU:\AppEvents\Schemes -Name '(Default)' -Value '.None'"
|
||||
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS platform - handle Alacritty theme
|
||||
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
|
||||
# Other platforms - just update the theme file
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Optional: `services.openssh.settings = { PasswordAuthentication = false; PermitR
|
|||
|
||||
```bash
|
||||
sudo nixos-rebuild switch
|
||||
# or: sudo nixos-rebuild switch --flake /path/to/dotfiles#hostname
|
||||
# or: sudo nixos-rebuild switch --flake /path/to/dotfiles/nixos#hostname
|
||||
```
|
||||
|
||||
Then from your main machine: `ssh danny@myserver`
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age1hjhqyuvcjuh62xh9m5ek3aa2rluaz8c28hgh2pm435jkqtpry9ssdn2l0z",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age1lwl2z6ymqjshknr79277qnr7hvffcc8n7qdqt98sz3t709a5yutq8d7gka",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age18gtjh28qxeltg2r2tzxwl096crkqkqk8tjhersyf7mzdsddady7qs34x0m",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age1zy3q73pujauyajgfqwu0pnyy8732lzwvw87tu7p2xg3xuzaujc2qh6ql77",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age1mlljsdpqf054p4nav9s855rtd5szwyl9av8w2lvg86j59cdtugxqylcn6k",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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 +0,0 @@
|
|||
../../../users/danny
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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 +0,0 @@
|
|||
../../../users/danny
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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 +0,0 @@
|
|||
../../../users/danny
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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 +0,0 @@
|
|||
../../../users/danny
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"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 +0,0 @@
|
|||
../../../users/danny
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
[
|
||||
{
|
||||
"publickey": "age1g6y8gvcampqj5y3yzdajke2h5n7k6ckdg6a424cghy5325px7cmqjmmd28",
|
||||
"type": "age"
|
||||
}
|
||||
]
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/machines/distant-shore
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/users/danny
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/machines/distant-shore
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/users/danny
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEAABhcRTNvFEyWkyRBX17KkM5nDuqOvR1xTY5vDqTygvk=
|
||||
-----END PUBLIC KEY-----
|
||||
|
|
@ -1 +0,0 @@
|
|||
12D3KooW9pjiKnqmnHSwGRhgyUqKeFydDUE8RvYJDAqHb5PZvzue
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/machines/distant-shore
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../../../../../../sops/users/danny
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
-----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