Compare commits

...

16 Commits

23 changed files with 1003 additions and 184 deletions

117
AGENTS.md Normal file
View File

@@ -0,0 +1,117 @@
# AGENTS.md
A guide for AI coding agents working in this Nix flake repository.
## Project Overview
Multi-host Nix flake managing NixOS desktops, macOS via nix-darwin, and a home server cluster — all sharing a common Home Manager configuration. Secrets are encrypted with sops-nix + age.
## Key Commands
```fish
# Apply config on current host (works on any machine)
nix-switch # alias for: sudo nixos-rebuild switch --flake ~/.config/nix#(hostname -s)
# or: sudo darwin-rebuild switch --flake .#(hostname -s)
# Check flake without building (NixOS) / eval toplevel (macOS)
nix-check
# Validate flake inputs and locks
nix flake check --no-build
# Format Nix files
nixfmt <file> # managed via nixvim, runs nixfmt
# Enter dev shell if defined
nix develop
```
## Repo Layout
```
flake.nix # Entry point — defines all hosts via mkSystem
hosts/<hostname>/ # Per-host configuration.nix + hardware-configuration.nix
nixos/ # Shared NixOS system modules (audio, fonts, sops, tailscale…)
nixos/roles/ # Optional services (Gitea, Matrix, Vaultwarden, AdGuard…)
darwin/ # macOS-only system modules (fonts, homebrew, yabai, sketchybar)
home/ # Shared Home Manager config (all hosts, both platforms)
home/desktop/ # Desktop-only home modules — Linux (hyprland/niri, waybar, rofi…)
home/desktop/sketchybar/ # macOS-only bar config
home/neovim/ # nixvim configuration split by plugin
assets/ # Wallpapers and avatar images — do not modify programmatically
secrets/ # age-encrypted secrets — never edit .age files directly
```
## Hosts
| Hostname | Platform | Type | Notes |
|---|---|---|---|
| cyper-desktop | NixOS x86_64 | Desktop | Primary Linux workstation |
| cyper-mac | macOS x86_64 | Desktop | nix-darwin + Homebrew |
| cyper-controller | NixOS x86_64 | Server | Runs all roles/services |
| cyper-node-1 | NixOS x86_64 | Server | `isServer = true` |
| cyper-node-2 | NixOS x86_64 | Server | `isServer = true` |
## mkSystem Convention
All hosts are built via `mkSystem` in `flake.nix`. Key flags:
- `isDarwin = true` → uses `darwin.lib.darwinSystem` + darwin modules
- `isServer = true` → skips desktop/GUI modules; both flags are passed as `specialArgs` to all modules via `sharedSpecialArgs`
Guard platform-specific code with:
```nix
if isDarwin then { ... } else { ... }
if isServer then { ... } else { ... }
```
## Home Manager
A single `home/` tree is shared by all hosts. Desktop-only modules live under `home/desktop/` and are conditionally included. The `isDarwin` and `isServer` flags are available as `specialArgs` inside Home Manager modules.
## Secrets
Managed with [sops-nix](https://github.com/Mic92/sops-nix) + age encryption.
- **Never edit `.age` files directly** — use `sops secrets/secrets.yaml`
- Age key must exist at `~/.config/sops/age/keys.txt` on every host
- Public keys are declared in `secrets/keys.txt.age` and `.sops.yaml` (if present)
- Secrets are referenced in Nix via `config.sops.secrets.<name>.path`
## Conventions
- **Formatter:** `nixfmt` (run via nixvim; apply before committing)
- **No `hardware-configuration.nix` edits** — these are machine-generated; regenerate with `nixos-generate-config` if needed
- **Homebrew** is managed declaratively via `darwin/homebrew.nix` — do not run `brew install` manually
- **Catppuccin** theming is applied system-wide via `home/catppuccin.nix` and `nixos/catppuccin.nix`; keep theme tokens consistent across modules
- **Shell is Fish** — shell aliases and functions live in `home/shell.nix`; use fish syntax
## Adding a New Host
1. Create `hosts/<hostname>/configuration.nix` (and `hardware-configuration.nix` for NixOS)
2. Add an entry to `nixosConfigurations` (or `darwinConfigurations`) in `flake.nix` via `mkSystem`
3. Add the host to the machines table in `README.md` and this file
## Adding a New Role/Service
1. Create `nixos/roles/<service>.nix`
2. Import it in the relevant host's `configuration.nix` or in `nixos/default.nix` behind an `isServer` guard
3. Add any required secrets to `secrets/secrets.yaml` via `sops`
## PR Checklist
- [ ] `nix flake check --no-build` passes
- [ ] `nixfmt` applied to changed `.nix` files
- [ ] No hardcoded paths or usernames — use `primaryUser` / `hostName` from `specialArgs`
- [ ] Secrets referenced via sops, not inlined
- [ ] `hardware-configuration.nix` untouched unless intentional
- [ ] README and AGENTS.md updated if hosts, roles, or structure changed
## Gotchas
- `primaryUser` is defined in `flake.nix` and injected everywhere via `sharedSpecialArgs` — never hardcode the username
- `home-manager.backupFileExtension = "backup"` is set globally; conflicts create `.backup` files rather than erroring
- The `l` fish function calls a Groq LLM (`llama-3.3-70b-versatile`) and pipes output through `glow` — it requires `$GROQ_API_KEY` to be set as a file path
- sketchybar lives under `home/desktop/sketchybar/` but is macOS-only; hyprland/niri are Linux-only
- `nix-switch` uses `hostname -s` at runtime — the hostname must match a key in `nixosConfigurations` / `darwinConfigurations`

116
README.md
View File

@@ -1,33 +1,41 @@
# DerGrumpfs Nix Configuration
A unified Nix configuration for both NixOS and macOS using flakes, nix-darwin, and Home Manager.
## About
A single repository managing both machines declaratively with Nix. Shared home-manager configuration across platforms with platform-specific modules where needed.
A unified Nix flake managing NixOS desktops, a macOS machine via nix-darwin, and a home server cluster — all sharing a common Home Manager configuration.
**Author:** Phil Keier
---
## Machines
| Hostname | Platform | Architecture |
|----------|----------|--------------|
| cyper-desktop | NixOS | x86_64-linux |
| cyper-mac | macOS | x86_64-darwin |
| Hostname | Platform | Architecture | Type |
|---|---|---|---|
| cyper-desktop | NixOS | x86_64-linux | Desktop workstation |
| cyper-mac | macOS | x86_64-darwin | nix-darwin + Homebrew |
| cyper-controller | NixOS | x86_64-linux | Home server (runs all services) |
| cyper-node-1 | NixOS | x86_64-linux | Server node |
| cyper-node-2 | NixOS | x86_64-linux | Server node |
---
## Prerequisites
### NixOS
Nix is available out of the box. Enable flakes in your configuration.
### macOS
Install Nix using the [Determinate Systems installer](https://docs.determinate.systems/#products).
**Note:** Homebrew is managed declaratively via nix-homebrew — if already installed it will auto-migrate, otherwise it is installed automatically.
> **Note:** Homebrew is managed declaratively via nix-homebrew — if already installed it will auto-migrate, otherwise it is installed automatically.
---
## Quick Start
### Clone
```bash
git clone https://github.com/DerGrumpf/nix ~/.config/nix
cd ~/.config/nix
@@ -39,7 +47,10 @@ Replace placeholders in `home/git.nix`:
- `DerGrumpf` → your Git username
- `phil.keier@hotmail.com` → your Git email
Update `secrets/keys.txt.age` and `.sops.yaml` with your age public key.
### Apply
```bash
# NixOS
sudo nixos-rebuild switch --flake .#cyper-desktop
@@ -47,64 +58,83 @@ sudo nixos-rebuild switch --flake .#cyper-desktop
# macOS
darwin-rebuild switch --flake .#cyper-mac
# Or after initial setup on either machine
# Or use the shell alias (auto-detects hostname and platform)
nix-switch
```
### Check (without building)
```bash
nix-check
# expands to: nix flake check --no-build (NixOS)
# or: nix eval ...darwinConfigurations.(hostname).config... (macOS)
```
---
## Project Structure
```mermaid
graph TD
flake[flake.nix] --> desktop[nixosConfigurations<br/>cyper-desktop]
flake --> servers[nixosConfigurations<br/>cyper-controller / node-1 / node-2]
flake --> mac[darwinConfigurations<br/>cyper-mac]
desktop --> nixos[nixos/<br/>NixOS system modules]
desktop --> hd[hosts/cyper-desktop/<br/>hardware + networking]
desktop --> home[home/<br/>shared home-manager]
desktop --> hd[hosts/cyper-desktop/<br/>hardware + config]
desktop --> home[home/<br/>shared Home Manager]
servers --> nixos
servers --> roles[nixos/roles/<br/>Gitea · Matrix · Vaultwarden<br/>AdGuard · Searxng · Unifi<br/>Monitoring · Filebrowser]
servers --> hc[hosts/cyper-*/]
mac --> darwin[darwin/<br/>macOS system modules]
mac --> hm[hosts/cyper-mac/<br/>host specific]
mac --> hm[hosts/cyper-mac/]
mac --> home
home --> shared[shared<br/>packages, git, shell, python<br/>nixvim, nixcord, spicetify<br/>floorp, obsidian]
home --> shared[shared<br/>packages · git · shell · python<br/>nixvim · nixcord · spicetify<br/>floorp · obsidian · ssh]
home --> deskmod[desktop/]
deskmod --> dlinux[Linux only<br/>hyprland, niri<br/>waybar, rofi<br/>gtk, qt, onlyoffice<br/>xdg, waypaper]
deskmod --> dlinux[Linux only<br/>hyprland · niri · waybar<br/>rofi · gtk · qt · xdg · waypaper]
deskmod --> dmac[macOS only<br/>sketchybar]
```
---
## Secrets
Secrets are managed with [sops-nix](https://github.com/Mic92/sops-nix) and age encryption. The age key must be present at:
Secrets are managed with [sops-nix](https://github.com/Mic92/sops-nix) and age encryption.
- **Linux:** `~/.config/sops/age/keys.txt`
- **macOS:** `~/.config/sops/age/keys.txt`
The age key must exist at `~/.config/sops/age/keys.txt` on every host. To edit secrets:
```bash
sops secrets/secrets.yaml
```
Never edit `.age` files directly.
---
## Shell Aliases (Fish)
| Alias | Expands to |
|---|---|
| `nix-switch` | `sudo nixos-rebuild switch --flake ~/.config/nix#(hostname -s)` |
| `nix-check` | `nix flake check --no-build` (or darwin eval equivalent) |
| `ls` | `eza --icons=always` |
| `la` | `eza -la --icons=always` |
| `tree` | `eza --icons=always -T` |
| `f` | `nvim $(fzf)` |
| `grep` | `rg` |
| `cp` | `rsync -ah --progress` |
| `l` | LLM prompt via Groq → rendered with `glow` |
---
## Useful Links
- [Nix manual](https://nixos.org/manual/nix/stable/)
- [nix-darwin docs](https://github.com/LnL7/nix-darwin)
- [nix-darwin](https://github.com/LnL7/nix-darwin)
- [Home Manager options](https://nix-community.github.io/home-manager/options.html)
- [sops-nix](https://github.com/Mic92/sops-nix)
- [nixvim](https://github.com/nix-community/nixvim)
# TODO
- [ ] Verify Linux build on `cyper-desktop`
```bash
sudo nixos-rebuild switch --flake ~/.config/nix#cyper-desktop
```
- [ ] Verify Darwin build on `cyper-mac`
```bash
darwin-rebuild switch --flake ~/.config/nix#cyper-mac
```
- [ ] Commit and push all current changes
```bash
git add -A && git commit -m "unify configs" && git push
```
- [ ] Test `nix-check` alias on both machines
- [ ] Test `nix-switch` alias on both machines after initial builds succeed
- [ ] Verify sops secrets are accessible on Darwin
```bash
echo $GROQ_API_KEY
```
- [ ] Verify sketchybar loads correctly with lua config on macOS
- [Catppuccin for Nix](https://github.com/catppuccin/nix)

View File

@@ -157,6 +157,12 @@
isServer = true;
};
"cyper-proxy" = mkSystem {
hostName = "cyper-proxy";
system = "x86_64-linux";
isServer = true;
};
"cyper-node-1" = mkSystem {
hostName = "cyper-node-1";
system = "x86_64-linux";

View File

@@ -75,6 +75,24 @@
};
};
# Doenst work
programs.iamb = {
enable = false;
settings = {
default_profile = "personal";
settings = {
notifications.enabled = true;
image_preview.protocol = {
type = "kitty";
size = {
height = 10;
width = 66;
};
};
};
};
};
programs.newsboat = {
enable = true;
autoReload = true;

View File

@@ -1,12 +1,12 @@
{
imports = [
./hardware-configuration.nix
./smb.nix
../../nixos/roles/monitoring.nix
../../nixos/roles/matrix.nix
../../nixos/roles/postgresql.nix
../../nixos/roles/matrix
../../nixos/roles/wyl.nix
../../nixos/roles/adguard.nix
# ../../nixos/roles/unifi.nix
../../nixos/roles/unifi.nix
../../nixos/roles/searxng.nix
../../nixos/roles/filebrowser.nix
../../nixos/roles/gitea.nix

View File

@@ -0,0 +1,90 @@
{
pkgs,
primaryUser,
...
}:
{
sops.secrets.smb_passwd = { };
users.users.${primaryUser}.extraGroups = [ "sambashare" ];
services.samba = {
enable = true;
openFirewall = true;
settings = {
global = {
"workgroup" = "WORKGROUP";
"server string" = "%h (Samba)";
"server role" = "standalone server";
"security" = "user";
"map to guest" = "Never";
"invalid users" = [ "root" ];
"socket options" = "TCP_NODELAY IPTOS_LOWDELAY";
"smb encrypt" = "required";
"use sendfile" = "yes";
"log level" = "1";
"log file" = "/var/log/samba/log.%m";
"max log size" = "1000";
};
storage-internal = {
"path" = "/storage/internal";
"comment" = "Internal storage (btrfs)";
"browseable" = "yes";
"read only" = "no";
"valid users" = primaryUser;
"create mask" = "0664";
"directory mask" = "0775";
"force user" = primaryUser;
};
storage-fast = {
"path" = "/storage/fast";
"comment" = "Fast storage";
"browseable" = "yes";
"read only" = "no";
"valid users" = primaryUser;
"create mask" = "0664";
"directory mask" = "0775";
"force user" = primaryUser;
};
storage-backup = {
"path" = "/storage/backup";
"comment" = "Backup storage";
"browseable" = "yes";
"read only" = "yes";
"valid users" = primaryUser;
"force user" = primaryUser;
};
};
};
systemd.services.samba-set-password = {
description = "Set Samba password for ${primaryUser}";
wantedBy = [ "multi-user.target" ];
after = [
"samba-smbd.service"
"sops-install-secrets.service"
];
requires = [ "samba-smbd.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "samba-set-password" ''
# Wait for smbd to initialize its passdb
for i in $(seq 1 10); do
[ -f /var/lib/samba/private/passdb.tdb ] && break
echo "Waiting for passdb.tdb... attempt $i"
sleep 1
done
PASSWORD=$(cat /run/secrets/smb_passwd)
(echo "$PASSWORD"; echo "$PASSWORD") | ${pkgs.samba}/bin/smbpasswd -a -s ${primaryUser} || \
(echo "$PASSWORD"; echo "$PASSWORD") | ${pkgs.samba}/bin/smbpasswd -s ${primaryUser}
'';
};
};
}

View File

@@ -2,6 +2,7 @@
config,
lib,
modulesPath,
primaryUser,
...
}:
@@ -32,6 +33,17 @@
'';
};
sops = {
secrets.smb_passwd = { };
templates.smb_credentials = {
content = ''
username=${primaryUser}
password=${config.sops.placeholder.smb_passwd}
'';
};
};
fileSystems = {
"/" = {
device = "/dev/disk/by-label/NIXROOT";
@@ -51,7 +63,40 @@
device = "/dev/disk/by-label/STORAGE";
fsType = "ext4";
};
};
}
// builtins.listToAttrs (
map
(share: {
name = "/shares/${share}";
value = {
device = "//192.168.2.2/${share}";
fsType = "cifs";
options = [
"credentials=${config.sops.templates.smb_credentials.path}"
"iocharset=utf8"
"_netdev"
"nofail"
"uid=${toString config.users.users.${primaryUser}.uid}"
"gid=${toString config.users.users.${primaryUser}.group}"
"file_mode=0664"
"dir_mode=0775"
"x-systemd.automount"
"x-systemd.idle-timeout=60"
];
};
})
[
"storage-internal"
"storage-fast"
"storage-backup"
]
);
systemd.tmpfiles.rules = [
"d /shares/storage-internal 0775 ${primaryUser} users -"
"d /shares/storage-fast 0775 ${primaryUser} users -"
"d /shares/storage-backup 0775 ${primaryUser} users -"
];
swapDevices = [ ];

View File

@@ -0,0 +1,30 @@
{
imports = [
./hardware-configuration.nix
../../nixos/roles/nginx.nix
];
networking = {
hostName = "cyper-proxy";
useNetworkd = true;
useDHCP = false;
firewall.enable = true;
};
systemd.network = {
enable = true;
networks."10-venet" = {
matchConfig.Name = "venet0";
networkConfig = {
Address = "178.254.8.35/24";
DNS = "178.254.16.141 178.254.16.151";
DHCP = "no";
};
routes = [
{ routeConfig.Destination = "0.0.0.0/0"; }
];
};
};
system.stateVersion = "26.05";
}

View File

@@ -0,0 +1,24 @@
{ lib, modulesPath, ... }:
{
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot = {
initrd.availableKernelModules = [ ];
initrd.kernelModules = [ ];
kernelModules = [ ];
extraModulePackages = [ ];
loader.grub.enable = false;
};
fileSystems."/" = {
device = "/dev/disk/by-label/NIXROOT";
fsType = "ext4";
};
swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

View File

@@ -122,7 +122,7 @@
configPath = pkgs.writeText "config.alloy" ''
loki.write "default" {
endpoint {
url = "http://192.168.2.30:3100/loki/api/v1/push"
url = "http://192.168.2.2:3100/loki/api/v1/push"
}
}

View File

@@ -8,6 +8,8 @@
address = "0.0.0.0";
baseURL = "/filebrowser";
root = "/storage";
username = "DerGrumpf";
password = "$2a$10$1LtSsdzJN4MqP7rjQhnQO.mL7nTuQBBCLbqSoFxL4XK1/I4b0sjdS";
};
openFirewall = true;

View File

@@ -100,14 +100,11 @@ in
START_SSH_SERVER = true;
};
# security = {
# SECRET_KEY_URI = "file://${config.sops.secrets."gitea/secretKey".path}";
# INTERNAL_TOKEN_URI = "file://${config.sops.secrets."gitea/internalToken".path}";
# };
#lfs = {
# JWT_SECRET_URI = "file://${config.sops.secrets."gitea/lfsJwtSecret".path}";
# };
metrics = {
ENABLED = true;
ENABLED_ISSUE_BY_LABEL = true;
ENABLED_ISSUE_BY_REPOSITORY = true;
};
ui = {
DEFAULT_THEME = "catppuccin-mocha-green";

View File

@@ -1,52 +0,0 @@
{ pkgs, config, ... }:
let
serverIP = builtins.head (
builtins.match "([0-9.]+)/.*" config.systemd.network.networks."10-ethernet".networkConfig.Address
);
in
{
networking.firewall.allowedTCPPorts = [
8008
8448
];
sops.secrets = {
matrix_macaroon_secret = { };
matrix_registration_secret = {
owner = "matrix-synapse";
group = "matrix-synapse";
};
};
services.matrix-synapse = {
enable = true;
settings = {
server_name = "cyperpunk.de";
public_baseurl = "http://matrix.cyperpunk.de";
enable_registration = false; # TODO: disable
enable_registration_without_verfication = true;
trusted_key_servers = [ { server_name = "matrix.org"; } ];
suppress_key_server_warning = true;
registration_shared_secret_path = config.sops.secrets.matrix_registration_secret.path;
macaroon_secret_key = "$__file{${config.sops.secrets.matrix_macaroon_secret.path}}";
listeners = [
{
port = 8008;
bind_addresses = [ "0.0.0.0" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
names = [
"client"
"federation"
];
compress = false;
}
];
}
];
};
};
}

View File

@@ -0,0 +1,208 @@
{
pkgs,
lib,
...
}:
let
flavours = [
{
name = "Latte";
slug = "latte";
is_dark = false;
}
{
name = "Frappé";
slug = "frappe";
is_dark = true;
}
{
name = "Macchiato";
slug = "macchiato";
is_dark = true;
}
{
name = "Mocha";
slug = "mocha";
is_dark = true;
}
];
accents = [
"rosewater"
"flamingo"
"pink"
"mauve"
"red"
"maroon"
"peach"
"yellow"
"green"
"teal"
"sky"
"sapphire"
"blue"
"lavender"
];
themeHashes = {
"latte/rosewater" = "0l1m4bhaxdam07rfqag6pjbzhdpyi5w3i14vp6rq7aj59pildw3a";
"latte/flamingo" = "1m8hh2l87xv2rfgpnnl5vzddmam0n82h25fwadb37blgab08vhsr";
"latte/pink" = "0ambrc42mvg0vdspfmnl31ka1nsxpdyv1p3nh045822y02q20wwh";
"latte/mauve" = "1nnn2w6nsr24a45jy497c2vhi8v64bwg99fj2dyhpfsn89c63lhn";
"latte/red" = "14lmw4c4llfz6zqvfymkc6k3msxcml2gwq9rhwsixdpc5mjjbn8n";
"latte/maroon" = "0ydpng9451mpn7hv5ag1ck8hryx8pdvrml3zksvzm2fiwzzjkpcf";
"latte/peach" = "1fn5804wv9z9iv65ikyv015b01a7c546rsaaks2a2sq2c37n75l0";
"latte/yellow" = "0hzgiyhqmwgp3h3v1y23sx3x5qp712sw106472lbnxbywqlavcza";
"latte/green" = "194kxv6d9hc4nixy16hy9nvf32qs3v214nr2r2qf2z9l89rk5pnp";
"latte/teal" = "12n25d38zpqxsskglymhmza972klg2hj3c23v2nb3jfj82llw6v4";
"latte/sky" = "0yghds3xpmbhkbcj2jkh8df82j6vrn9q1z0s2129nca7l5g5f9w2";
"latte/sapphire" = "18dl1srxp3xccvvy56za6kp05n68d918l0wrxga11746g9sib7r3";
"latte/blue" = "1zv9nap21d80flvd1jwmjph05jgykxngv5kqbhk95mvqh962ygnf";
"latte/lavender" = "03j4fwbscip1qm6px1qxkha0c5csq2wwvzg9vwjkc2ja48v1mp9k";
"frappe/rosewater" = "032qbgj32mvgpankl9777x2lxk18451kglsxg5215k8zrwcg9y95";
"frappe/flamingo" = "1grhgynn8q7isv18981km5k8ll72ihsjw2ciy8widl6wikv29j8p";
"frappe/pink" = "0h33g721bph8ihd6lmbc7szxy4dq85ng1cgg5cxjb5y2m7wpdbsy";
"frappe/mauve" = "121jmznc9q3p7crsy9p2khw8xnzvz4lxms26g1h5wqa67wqvalc4";
"frappe/red" = "07wm4h1giyy6a5nlh0d3qdarfsp6ikyr5nmg94n13lj4q03d0cn0";
"frappe/maroon" = "08vg70nr918n4ffi1wnbba4xrx5ak5vfgq7m5ik0rpkb2wdb4x6k";
"frappe/peach" = "1cg753w2dxs0sx97d8y0g62s8aw3w6b9hrll0lsrw3bc1bvm23fl";
"frappe/yellow" = "0g43g2if1pcm25i261zfw43bawqqdlgg2f6q2bqhyqvafk9yb3dy";
"frappe/green" = "1n71mndzds3zldb271g8hdw1yn29s68svzvh8ckjcsz4sb9h1i74";
"frappe/teal" = "0b6m9cibfwf8csh1pk5i76xi3wx3v2aqwgffzsidw8nwc7c1a3wk";
"frappe/sky" = "1l4d44399ixshlc9fdsx7iqwxm6kdkp6k4z3z6bdyyx6adw3z4q5";
"frappe/sapphire" = "03fa9rnclvs5ljd0lzz15vnkzpqpbrhfppg3zwfchs9fvak0n3ni";
"frappe/blue" = "0r4jjn3pab77w1aanlv3143ch60400q44mdzaqmcjbcr6l2knmjh";
"frappe/lavender" = "1mrkaz72w6j9hh4dpxwgd6ks5wsnq9ydgy6f9gms4jx1611aab96";
"macchiato/rosewater" = "001akfnhlvwaiz5faahl4qi0qp6as6ilvkbja6bjy9f5iasr4ygp";
"macchiato/flamingo" = "06xq3pbx4cb3pyblx2vydr4bp0ylm7866d66agg5wg5qnr356wb3";
"macchiato/pink" = "1hb32dj0n3wx4f1wxa4n7fib2mazghwsg2ljycza9macfn2n87qn";
"macchiato/mauve" = "1yrnp162blizc10fz2n6ls1x0di1sdjk53vpsl7mifrkcr1k2nq7";
"macchiato/red" = "1g9s39q7459lk830vhdrfqkbzz88p3fp8k98a2ygj2hz8sycpryq";
"macchiato/maroon" = "0ad7rx8sbkygvsgywhpjvvzmyflyhz7jlm13dr7cxj3801rxhl6d";
"macchiato/peach" = "1m5m6afcl8s1ghn2b9n1d20fhsygnhgn0205nhpxh4bih3kg8c8m";
"macchiato/yellow" = "0zcc26d28jaq71mz8nqssz8p0hylczirjwjxr2dkha1133vjmvy5";
"macchiato/green" = "055xdb5jilp5fq3a1g8773rv52zr68fp4l3hs56yj6dy3bq3q22v";
"macchiato/teal" = "1sfci2g2nvmj0v72gnxqbj0k8053qz0rl6iphfxs3pgpi1b0rczq";
"macchiato/sky" = "0vhfmdliy8cbb0vqq3v26isvcz4sxzq0xrb4p5a6gibvxaqi6bf3";
"macchiato/sapphire" = "1744jiv57aqz4qi52n92nrx0s1rhylgg08qqc31jr2clk9h6bw18";
"macchiato/blue" = "1arp8r2g8ivs1xipq39d3l6cvx0zrr1vwv9yac5j33d6c93wbb2i";
"macchiato/lavender" = "0kak1f574c07gqjfafg3w5avrci584iqxjkmvrl2pv1879g84nn3";
"mocha/rosewater" = "0p3ck9crskrhk1za6knaznjlj464mx4sdkkadna6k2152m3czjpz";
"mocha/flamingo" = "04xx1mky230saqxxqin2fph8cnnz1jhmvb9qd9f5yc3pai3q5wdw";
"mocha/pink" = "1cj9zdd72vcc45ziav625yq6hrp1zw21f7xsic0ip065xcqzdl3p";
"mocha/mauve" = "1wb0ibmdv6vn07bk570pikm43qdxj3n2zsqr5sip17ay05j5l6dm";
"mocha/red" = "1mnzrk57ar2cphyi2ry2lg5ilmb26gm4pr7ixch2ls0hk8ilp9p9";
"mocha/maroon" = "1mcpwz3yrg3kk0hkqv5nykxj07bm70403yyl8r60pqlh74dnhkbf";
"mocha/peach" = "0jglpcs41rfqxcm45mvnbdqhma0bv4h07nc7c3nrwz3g3h2djmzr";
"mocha/yellow" = "0jqkvcjiwid1zdvrj2ikqf5winm08qyd51nfsawfdspbfhqnzmis";
"mocha/green" = "0bg0014a77yx7f2r6n4mxm7rqgdnymqq7cq6bvpgkfk2z1gyr38l";
"mocha/teal" = "0kzvi3gfirpcxdhgsilm51lk3j1z6lavb7160chgd9jhzk0xg97c";
"mocha/sky" = "057nmp2aywdxzrkmzi65bh2mvf1a9cnri0g0jdyzdnrn7f8bbsiw";
"mocha/sapphire" = "0nfklzb0a7mxv6nzav7m2g0y9plm72vwadm06445myv3k9j3ffmj";
"mocha/blue" = "06ay46x2aq1q5ghz2zhzhn6qyqkrrf4p9j59qywnxh1jvv728ns8";
"mocha/lavender" = "0iip063f6km17998c7ak0lb3kq6iskyi3xv2phn618mhslnxhwm5";
};
catppuccinThemes = lib.concatMap (
flavour:
map (
accent:
builtins.fromJSON (
builtins.readFile (
pkgs.fetchurl {
url = "https://element.catppuccin.com/${flavour.slug}/${accent}.json";
sha256 = themeHashes."${flavour.slug}/${accent}";
}
)
)
) accents
) flavours;
elementConfig = builtins.toFile "element-config.json" (
builtins.toJSON {
default_server_config = {
"m.homeserver" = {
base_url = "https://matrix.cyperpunk.de";
server_name = "cyperpunk.de";
};
};
setting_defaults = {
custom_themes = catppuccinThemes;
feature_custom_themes = true;
};
features = {
feature_group_calls = true;
};
}
);
elementWebConfigured = pkgs.element-web.overrideAttrs (old: {
postInstall = (old.postInstall or "") + ''
cp ${elementConfig} $out/config.json
'';
});
synapseAdmin = pkgs.synapse-admin-etkecc.withConfig {
restrictBaseUrl = [ "https://matrix.cyperpunk.de" ];
loginFlows = [ "password" ];
};
in
{
networking.firewall.allowedTCPPorts = [
8009 # Cinny
8010 # Element
8011 # Synapse Admin
8012 # FluffyChat
];
services.nginx.virtualHosts = {
"cinny.cyperpunk.de" = {
listen = [
{
addr = "0.0.0.0";
port = 8009;
}
];
root = "${pkgs.cinny}";
};
"element.cyperpunk.de" = {
listen = [
{
addr = "0.0.0.0";
port = 8010;
}
];
root = "${elementWebConfigured}";
};
"fluffy.cyperpunk.de" = {
listen = [
{
addr = "0.0.0.0";
port = 8012;
}
];
locations."/" = {
proxyPass = "http://127.0.0.1:8082";
};
};
"admin.cyperpunk.de" = {
listen = [
{
addr = "0.0.0.0";
port = 8011;
}
];
root = "${synapseAdmin}";
};
};
virtualisation.oci-containers.containers.fluffychat = {
image = "ghcr.io/krille-chan/fluffychat:latest";
ports = [ "127.0.0.1:8082:80" ];
volumes = [
"${
builtins.toFile "fluffychat-config.json" (
builtins.toJSON {
default_homeserver = "matrix.cyperpunk.de";
preset_homeserver = "matrix.cyperpunk.de";
}
)
}:/app/config.json:ro"
];
};
}

View File

@@ -0,0 +1,39 @@
{
config,
...
}:
{
networking.firewall = {
allowedTCPPorts = [
3478 # TURN (coturn)
];
allowedUDPPorts = [
3478 # TURN (coturn)
];
allowedUDPPortRanges = [
{
from = 49152;
to = 65535; # TURN relay ports (coturn)
}
];
};
sops.secrets.matrix_turn_secret = {
owner = "matrix-synapse";
group = "matrix-synapse";
};
services.coturn = {
enable = true;
no-cli = true;
no-tcp-relay = true;
min-port = 49152;
max-port = 65535;
use-auth-secret = true;
static-auth-secret-file = config.sops.secrets.matrix_turn_secret.path;
realm = "turn.cyperpunk.de";
extraConfig = ''
no-multicast-peers
'';
};
}

View File

@@ -0,0 +1,31 @@
{
...
}:
{
imports = [
./synapse.nix
# ./coturn.nix
./clients.nix
];
#networking.firewall = {
# allowedTCPPorts = [
# 8008 # Matrix Synapse
# 8009 # Cinny
# 8010 # Element
# 8011 # Synapse Admin
# 8012 # FluffyChat
# 8448 # Matrix federation
# 3478 # TURN (coturn)
# ];
# allowedUDPPorts = [
# 3478 # TURN (coturn)
# ];
# allowedUDPPortRanges = [
# {
# from = 49152;
# to = 65535; # TURN relay ports (coturn)
# }
# ];
#};
}

View File

@@ -0,0 +1,77 @@
{
config,
pkgs,
...
}:
{
networking.firewall.allowedTCPPorts = [
8008 # Matrix Synapse
8448 # Matrix federation
];
sops.secrets = {
matrix_macaroon_secret = { };
matrix_registration_secret = {
owner = "matrix-synapse";
group = "matrix-synapse";
};
};
services = {
matrix-synapse = {
enable = true;
settings = {
server_name = "cyperpunk.de";
public_baseurl = "https://matrix.cyperpunk.de";
enable_registration = false; # TODO: disable
enable_registration_without_verification = false;
trusted_key_servers = [ { server_name = "matrix.org"; } ];
suppress_key_server_warning = true;
registration_shared_secret_path = config.sops.secrets.matrix_registration_secret.path;
macaroon_secret_key = "$__file{${config.sops.secrets.matrix_macaroon_secret.path}}";
experimental_features = {
"msc3266_enabled" = true;
};
listeners = [
{
port = 8008;
bind_addresses = [ "0.0.0.0" ];
type = "http";
tls = false;
x_forwarded = true;
resources = [
{
names = [
"client"
"federation"
];
compress = false;
}
];
}
{
port = 9009;
tls = false;
type = "metrics";
bind_addresses = [ "127.0.0.1" ];
resources = [ ];
}
];
enable_metrics = true;
};
};
postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
};
};
}

View File

@@ -1,13 +1,71 @@
{ config, ... }:
{
config,
lib,
...
}:
let
serverIP = builtins.head (
builtins.match "([0-9.]+)/.*" config.systemd.network.networks."10-ethernet".networkConfig.Address
);
nodePort = toString config.services.prometheus.exporters.node.port;
mkNodeJob = name: ip: {
job_name = name;
static_configs = [ { targets = [ "${ip}:${nodePort}" ]; } ];
};
extraNodes = {
"cyper-desktop" = "192.168.2.40";
"cyper-node-1" = "192.168.2.30";
"cyper-node-2" = "192.168.2.31";
};
mkWeatherScrapeConfigs =
cities:
map (city: {
job_name = "weather_${lib.strings.toLower (lib.strings.replaceStrings [ " " ] [ "_" ] city)}";
scrape_interval = "5m";
scrape_timeout = "30s";
metrics_path = "/${city}";
params.format = [ "p1" ];
static_configs = [ { targets = [ "wttr.in" ]; } ];
scheme = "https";
metric_relabel_configs = [
{
target_label = "location";
replacement = city;
}
];
}) cities;
weatherCities = [
# Braunschweig itself
"Braunschweig"
# Immediate surroundings (~15km)
"Wolfenbuettel"
"Salzgitter"
"Peine"
"Cremlingen"
"Wendeburg"
"Sickte"
# Greater region (~50km)
"Wolfsburg"
"Gifhorn"
"Goslar"
"Hildesheim"
"Hannover"
];
in
{
sops.secrets.grafana_secret_key = {
owner = "grafana";
group = "grafana";
sops.secrets = {
grafana_secret_key = {
owner = "grafana";
group = "grafana";
};
};
services = {
@@ -55,24 +113,38 @@ in
prometheus = {
enable = true;
port = 9001;
scrapeConfigs = [
(mkNodeJob config.networking.hostName serverIP)
{
job_name = config.networking.hostName;
job_name = "gitea";
static_configs = [
{
targets = [ "${serverIP}:${toString config.services.prometheus.exporters.node.port}" ];
targets = [
"localhost:${toString config.services.gitea.settings.server.HTTP_PORT}"
];
}
];
metrics_path = "/metrics";
}
{
job_name = "synapse";
scrape_interval = "15s";
metrics_path = "/_synapse/metrics";
static_configs = [
{
targets = [ "127.0.0.1:9009" ];
labels = {
instance = config.networking.hostName;
job = "master";
index = "1";
};
}
];
}
{
job_name = "cyper-desktop";
static_configs = [
{
targets = [ "192.168.2.40:${toString config.services.prometheus.exporters.node.port}" ];
}
];
}
];
]
++ (lib.mapAttrsToList mkNodeJob extraNodes)
++ (mkWeatherScrapeConfigs weatherCities);
};
loki = {

142
nixos/roles/nginx.nix Normal file
View File

@@ -0,0 +1,142 @@
{ ... }:
{
networking.firewall.allowedTCPPorts = [
80
443
];
security.acme = {
acceptTerms = true;
defaults.email = "your@email.de";
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedOptimisation = true;
recommendedGzipSettings = true;
virtualHosts = {
"git.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:9000";
};
};
"www.cyperpunk.de" = {
enableACME = true;
locations = {
"/" = {
proxyPass = "http://100.109.179.25:15005";
proxyWebsockets = true;
};
"/grafana" = {
proxyPass = "http://100.109.179.25:2342";
proxyWebsockets = true;
};
};
};
"search.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:11080";
};
};
"vault.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:8222";
proxyWebsockets = true;
};
};
"file.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:10000";
};
};
"calvin.cyperpunk.de" = {
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:15006";
};
};
"cyperpunk.de" = {
forceSSL = true;
enableACME = true;
http2 = true;
extraConfig = ''
client_max_body_size 50m;
'';
locations."/" = {
proxyPass = "http://100.109.179.25:8008";
proxyWebsockets = true;
};
};
"matrix.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
http2 = true;
extraConfig = ''
client_max_body_size 50m;
'';
locations."/" = {
proxyPass = "http://100.109.179.25:8008";
proxyWebsockets = true;
};
};
"cinny.cyperpunk.de" = {
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:8009";
proxyWebsockets = true;
};
};
"element.cyperpunk.de" = {
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:8010";
proxyWebsockets = true;
};
};
"fluffy.cyperpunk.de" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://100.109.179.25:8012";
proxyWebsockets = true;
};
};
"livekit.cyperpunk.de" = {
enableACME = true;
locations = {
"/" = {
proxyPass = "http://192.168.64.1:7880";
proxyWebsockets = true;
};
"/_matrix/livekit/jwt" = {
proxyPass = "http://192.168.64.1:8080";
proxyWebsockets = true;
};
};
};
};
};
}

View File

@@ -2,12 +2,6 @@
{
services.postgresql = {
enable = true;
initialScript = pkgs.writeText "synapse-init.sql" ''
CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
TEMPLATE template0
LC_COLLATE = "C"
LC_CTYPE = "C";
'';
};
}

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env bash
# Usage: sudo ./restore.sh gitea_dump_*.sql gitea_data_*.tar.gz
set -euo pipefail
SQL_DUMP="${1:?provide the .sql dump as first argument}"
DATA_ARCHIVE="${2:?provide the data .tar.gz as second argument}"
GITEA_HOME="/var/lib/gitea"
GITEA_USER="gitea"
DB_NAME="gitea"
DB_USER="gitea"
echo "[1/5] Stopping gitea..."
systemctl stop gitea
echo "[2/5] Waiting for postgres..."
until sudo -u postgres psql -c '\q' 2>/dev/null; do sleep 1; done
echo "[3/5] Restoring database..."
sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${DB_NAME};"
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};"
sudo -u postgres psql -d "$DB_NAME" < "$SQL_DUMP"
echo " done."
echo "[4/5] Restoring gitea data..."
# data/gitea from the docker volume -> /var/lib/gitea
tar xzf "$DATA_ARCHIVE" --strip-components=2 -C "$GITEA_HOME" ./data/gitea
# ssh host keys -> /var/lib/gitea/ssh
mkdir -p "$GITEA_HOME/ssh"
tar xzf "$DATA_ARCHIVE" --strip-components=1 -C "$GITEA_HOME/ssh" ./ssh
chown -R "$GITEA_USER":"$GITEA_USER" "$GITEA_HOME"
echo " done."
echo "[5/5] Starting gitea..."
systemctl start gitea
systemctl status gitea --no-pager

View File

@@ -9,18 +9,4 @@
mongodbPackage = pkgs.mongodb-7_0;
openFirewall = true; # opens 3478/udp, 10001/udp, 8080, 8443, 8843, 8880, 6789
};
networking.firewall = {
allowedTCPPorts = [
8443
8080
8880
8843
6789
];
allowedUDPPorts = [
3478
10001
];
};
}

View File

@@ -1,5 +1,6 @@
GROQ_API_KEY: ENC[AES256_GCM,data:OyuC4jfw67sCDa0XBGr78S6pzPV1ruy7KiIqPMgWWcOCVm3Y/khXEYPMjUTGrq9YLOw1MLso0OE=,iv:0y9klMYVtGsqAaLc2JidjZYSLhhbcbWbnBf8sZiC3rM=,tag:r6G2pzZn2d9JIaS+ozKnmg==,type:str]
OPENWEATHER_API_KEY: ENC[AES256_GCM,data:bcuLz70u40nZfNgPTaeNRXdR/zjx0SQjwMbMNNFqROI=,iv:VCzse1a1/k1ZDIpFPL1QhjuS6YaDyohWi61JZaoc0Ws=,tag:UJSNyniNNLfGGRY/uiJcRA==,type:str]
smb_passwd: ENC[AES256_GCM,data:+9RYomcnCZSME5DzuJWTLbS3IGJHhIYWZ5SmsgOn6YQ=,iv:VRPVR7DD+swjeUZKe54XYm3wn/KB4RqvQAyYXQbS+A8=,tag:NnA89efo6HVL0scHgyTZMQ==,type:str]
grafana_secret_key: ENC[AES256_GCM,data:d6tu4kL7flfbdeOYk21zkSRmVe+NvVwd14jgr9Ds0adfgYetN852G25Z8/g=,iv:uWuwGBZVK1syhEfO9nLZUWwa801759tNJx+Pmnz3xeg=,tag:X6/NcdGZHAdIlOwxNPo/Ew==,type:str]
matrix_macaroon_secret: ENC[AES256_GCM,data:a9nMar+p+FXIsxxSqO/to2OJOvD1erfwLwwBeKOcWBu7xykHxqD+pCmrGhg=,iv:rp4ZDVIlZ7SN1RFHB2CfSV5ISPMl9pC4U8Jgqpz48Qs=,tag:LxmWUZE3mG4acagQmlieag==,type:str]
matrix_registration_secret: ENC[AES256_GCM,data:KhKkJZqwE8xk4/tuQ7NYTv/Ot1qCAiy8yUbDyVvRa0H5BT4amCBIdATfR4Q=,iv:HBN+GorT1VpWCVkDugk4UxYLEYKJIoDZh2d+oUDLc8g=,tag:hHus458yVnH0qaQ4u37IZg==,type:str]
@@ -24,7 +25,7 @@ sops:
N3I5dzUwc3JtYzczMUhyT04vSHlZamMKT+FzYcDLmlEFYxm/XoBpJb8XaZzBH1v9
6fuez+zApathZfl14w41kAUojPWBznnxDqYtNvzVVLXwnpp3BMx+7w==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-04-11T21:27:16Z"
mac: ENC[AES256_GCM,data:6uKae66muzkB8qPMUSkya4r4wA4oYnNttu+Md0USTHyY0VlMZefucfgpPGr79JC4l0u99kVRNyEqIY1kp/tdspeKHtGfpWmX4Djx/jMOSPWfzX99Y9ICC9KIdZgYdVJeGiwJKZUEHU0vlAopFGODQ2++sHqjrIAxO1EkGFkAgRA=,iv:Z00NF38aNk26n8+d+3b2bKFJjlN5LVWplyyE6ED9rUw=,tag:5xdT9q0dQXW/SA3CduzJug==,type:str]
lastmodified: "2026-04-21T21:07:46Z"
mac: ENC[AES256_GCM,data:pMpc0UWS11OUvY1KS0D6GZkOP1EXM3b9+2VCS23P8js2MAktfzRjfhS2/KKx4XS1tpiHxmoF/eUmZqD+gqIIci4fVx3mpm2lMMx6HpOokM7Q8AEC2cOyJ9NInaZO5ogE7TY81oT8qnuOHPw3sFQARN9e0PLdJajrWWHX6gR2Odk=,iv:yks2AnUrP/6QeIrGGO4w66hvKHTtbFEPVC0GKptWa8g=,tag:VRuaTgfcM2dSi20jYYfp+w==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.2