Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621ca5dde6 | ||
|
|
a00916864c | ||
|
|
3ab8bfbb91 | ||
|
|
70deb25080 | ||
|
|
ac57abf255 | ||
|
|
83ee518091 | ||
|
|
3ee2b1a9d8 |
+3
-1
@@ -1 +1,3 @@
|
|||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
*.dec
|
||||||
|
keys.txt
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
keys:
|
||||||
|
- &admin_finn age1a0v442ct2qfjcdhyktn25spw02yq4fl08plrrvuradwwxzsle4mq8thln4
|
||||||
|
- &host_mibook age1n3uqtee5ymykrls6k04dznq50cwmj99hw7ny83yxjndt4y2ncf5qhjwa2p
|
||||||
|
# - &host_jupiter age1... # TODO: Phase 9, see docs/secrets.md
|
||||||
|
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: ^secrets/mibook\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *admin_finn
|
||||||
|
- *host_mibook
|
||||||
|
# - path_regex: ^secrets/jupiter\.yaml$ # TODO: Phase 9
|
||||||
|
# key_groups:
|
||||||
|
# - age: [ *admin_finn, *host_jupiter ]
|
||||||
|
# - path_regex: ^secrets/common\.yaml$ # TODO: Phase 9
|
||||||
|
# key_groups:
|
||||||
|
# - age: [ *admin_finn, *host_mibook, *host_jupiter ]
|
||||||
@@ -35,6 +35,7 @@ flake.nix
|
|||||||
│ ├── core.nix # system packages, locale, timezone
|
│ ├── core.nix # system packages, locale, timezone
|
||||||
│ ├── network.nix
|
│ ├── network.nix
|
||||||
│ ├── nix.nix
|
│ ├── nix.nix
|
||||||
|
│ ├── sops.nix # sops-nix decryption config, sops/age/ssh-to-age on PATH
|
||||||
│ └── users.nix
|
│ └── users.nix
|
||||||
├── modules/ # custom NixOS option modules (my.profiles.*, my.hardware.*, my.services.*)
|
├── modules/ # custom NixOS option modules (my.profiles.*, my.hardware.*, my.services.*)
|
||||||
│ ├── environments/ # per-service/app profiles
|
│ ├── environments/ # per-service/app profiles
|
||||||
@@ -43,10 +44,21 @@ flake.nix
|
|||||||
└── machines/<name>/
|
└── machines/<name>/
|
||||||
├── configuration.nix # machine-specific NixOS settings
|
├── configuration.nix # machine-specific NixOS settings
|
||||||
├── environments.nix # enables profiles via my.profiles.* / my.hardware.* options
|
├── environments.nix # enables profiles via my.profiles.* / my.hardware.* options
|
||||||
|
├── secrets.nix # sops.secrets declared for this host
|
||||||
├── disks.nix
|
├── disks.nix
|
||||||
└── hardware-configuration.nix
|
└── hardware-configuration.nix
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Secrets (sops-nix)
|
||||||
|
|
||||||
|
Secrets are encrypted with sops, decrypted per-host from that host's SSH host
|
||||||
|
key. `.sops.yaml` defines who can decrypt what; `secrets/<host>.yaml` holds
|
||||||
|
the encrypted values; `machines/<host>/secrets.nix` declares which secrets
|
||||||
|
that host exposes and consumes them via `config.sops.secrets.<name>.path`.
|
||||||
|
Always `git add` a secrets file before `nix build` — untracked files are
|
||||||
|
invisible to flake evaluation. Full workflow (adding a secret, adding a host,
|
||||||
|
rotating keys): see [`docs/secrets.md`](docs/secrets.md).
|
||||||
|
|
||||||
### Profile / Module Pattern
|
### Profile / Module Pattern
|
||||||
|
|
||||||
Every module under `modules/` follows the same structure:
|
Every module under `modules/` follows the same structure:
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Secrets (sops-nix)
|
||||||
|
|
||||||
|
Secrets are encrypted with [sops](https://github.com/getsops/sops) and decrypted
|
||||||
|
at activation by [sops-nix](https://github.com/Mic92/sops-nix). Each host decrypts
|
||||||
|
with its own SSH host key (converted to an age key in-memory — no separate host
|
||||||
|
private key is ever stored on disk).
|
||||||
|
|
||||||
|
Tooling (`sops`, `age`, `ssh-to-age`) is available via `nix develop` in this repo,
|
||||||
|
and installed system-wide on every machine via `machines/core/sops.nix`.
|
||||||
|
|
||||||
|
## Adding a secret to an existing host
|
||||||
|
|
||||||
|
```
|
||||||
|
nix develop -c sops secrets/<host>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens `$EDITOR` on the decrypted content. Add your key, save, quit — sops
|
||||||
|
re-encrypts on write. Then declare it in `machines/<host>/secrets.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
sops.secrets.my-secret = {
|
||||||
|
owner = "someuser"; # defaults to root
|
||||||
|
group = "someuser";
|
||||||
|
mode = "0400";
|
||||||
|
restartUnits = [ "foo.service" ]; # if a running service needs to pick it up
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Consume it in the owning module as `config.sops.secrets.my-secret.path`
|
||||||
|
(resolves to `/run/secrets/my-secret`) — never inline the decrypted value into
|
||||||
|
a Nix string, since that copies it into the world-readable `/nix/store`.
|
||||||
|
|
||||||
|
Then:
|
||||||
|
|
||||||
|
```
|
||||||
|
nix build '.#nixosConfigurations.<host>.config.system.build.toplevel'
|
||||||
|
sudo nixos-rebuild test --flake '.#<host>'
|
||||||
|
# verify, then:
|
||||||
|
sudo nixos-rebuild switch --flake '.#<host>'
|
||||||
|
```
|
||||||
|
|
||||||
|
**The secrets file must be `git add`ed before `nix build`** — untracked files
|
||||||
|
are invisible to flake evaluation, and `sops.validateSopsFiles` needs it in the
|
||||||
|
Nix store.
|
||||||
|
|
||||||
|
## Adding a new host
|
||||||
|
|
||||||
|
1. On the new host: `ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub` to get its
|
||||||
|
age public key.
|
||||||
|
2. In `.sops.yaml`: add a `&host_<name>` anchor with that key, and a
|
||||||
|
`creation_rules` entry for `secrets/<name>.yaml` including both the new
|
||||||
|
host key and `*admin_finn`.
|
||||||
|
3. Create `machines/<name>/secrets.nix`, add it to that machine's
|
||||||
|
`configuration.nix` imports.
|
||||||
|
4. If the host needs to read an existing shared file, run
|
||||||
|
`sops updatekeys secrets/<file>.yaml` after step 2 so it's re-encrypted to
|
||||||
|
include the new recipient.
|
||||||
|
|
||||||
|
## Rotating keys
|
||||||
|
|
||||||
|
```
|
||||||
|
# after editing .sops.yaml (new/removed recipient):
|
||||||
|
nix develop -c sops updatekeys secrets/<file>.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
for every file the change affects. `updatekeys` requires you to already be
|
||||||
|
able to decrypt the file — this is why `*admin_finn` is a recipient on every
|
||||||
|
rule, as the standing fallback.
|
||||||
|
|
||||||
|
Reinstalling a host regenerates its SSH host key, which changes its age
|
||||||
|
identity. Any file previously encrypted to that host must be `updatekeys`'d
|
||||||
|
with the new key, or the host's next activation fails to decrypt.
|
||||||
|
|
||||||
|
## Where private keys live
|
||||||
|
|
||||||
|
- **Operator** (you): `~/.config/sops/age/keys.txt` — sops' default lookup
|
||||||
|
path, so `SOPS_AGE_KEY_FILE` never needs to be set. Never committed. Back
|
||||||
|
this up out-of-band (password manager, offline copy) — if it's lost, only
|
||||||
|
the surviving host keys can still decrypt their own files, and any file that
|
||||||
|
was only ever readable by an operator-only rule becomes permanently
|
||||||
|
unreadable.
|
||||||
|
- **Hosts**: `/etc/ssh/ssh_host_ed25519_key` — already managed by NixOS,
|
||||||
|
converted to age in-memory at activation time. Nothing extra to back up.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
.sops.yaml # age recipients + creation rules (which keys can decrypt which files)
|
||||||
|
secrets/<host>.yaml # per-host encrypted secrets (default sopsFile for that host)
|
||||||
|
machines/core/sops.nix # applied to every machine: decryption config, sops/age/ssh-to-age on PATH
|
||||||
|
machines/<host>/secrets.nix # per-host: which secrets exist, owner/group/mode
|
||||||
|
```
|
||||||
Generated
+42
-38
@@ -21,11 +21,11 @@
|
|||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782949081,
|
"lastModified": 1785627969,
|
||||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -79,22 +79,6 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grafana-content": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1785940786,
|
|
||||||
"narHash": "sha256-flH5pwYtB3QWnZ/OYJKoj9twb1fxYrowr9FCh8L1GMY=",
|
|
||||||
"ref": "refs/heads/master",
|
|
||||||
"rev": "4017b678ffa3bde4136a3468f18f1eb3bba52374",
|
|
||||||
"revCount": 1,
|
|
||||||
"type": "git",
|
|
||||||
"url": "ssh://git@rechberg.online:222/finn.markwitz/dashboards.git"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "ssh://git@rechberg.online:222/finn.markwitz/dashboards.git"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nix": {
|
"nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
@@ -105,11 +89,11 @@
|
|||||||
"nixpkgs-regression": "nixpkgs-regression"
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784762557,
|
"lastModified": 1787274306,
|
||||||
"narHash": "sha256-R/r6jRnANV50c8F5Fz5+1Q1moab0IGWRk+cg5ME2nMY=",
|
"narHash": "sha256-Qg9f9td5iphUWSQS6zmvyZWO1F+D7j8Z3U6dGyUTg08=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nix",
|
"repo": "nix",
|
||||||
"rev": "d10c84cd0cc0efdcb29cf2611caf5fbcd10fa071",
|
"rev": "649e823fb24ed118d72e613be35fa8ea1b64afe7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -159,11 +143,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784723954,
|
"lastModified": 1787144466,
|
||||||
"narHash": "sha256-1CfD8ZUjCkTgjsneLZ/lxCHhgDfqxxE7/GX0MmsgiqA=",
|
"narHash": "sha256-HHfv2/HkNSKbbSyU9iD/g8lbP6r4tl33sSw1W4rXCk0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "a017f5b72210026af5b3ac5949f08d94380a6fbd",
|
"rev": "0471accf8d0a8210b31d947497d179ecc99e0021",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -203,11 +187,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1782614948,
|
"lastModified": 1785031560,
|
||||||
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
|
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "nixpkgs.lib",
|
"repo": "nixpkgs.lib",
|
||||||
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
|
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -234,11 +218,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-unstable": {
|
"nixpkgs-unstable": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784796856,
|
"lastModified": 1787135253,
|
||||||
"narHash": "sha256-vwxWgF+Gj276WznzGb1LxGsK/39HaQwgQXiU3EkC844=",
|
"narHash": "sha256-M5/r2v++FjVhdsxXYMb4BDJ5YLAdCWFt3aZotcshocA=",
|
||||||
"rev": "e2587caef70cea85dd97d7daab492899902dbf5d",
|
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1040357.e2587caef70c/nixexprs.tar.xz"
|
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1058091.ffb3c9b700e7/nixexprs.tar.xz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
@@ -261,11 +245,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1784707089,
|
"lastModified": 1787101114,
|
||||||
"narHash": "sha256-DUedXhD2Rg8q4Xyd07Sb90eZGy4gg6W+Vl/WbLNwAZo=",
|
"narHash": "sha256-BA7sSNjLDuPGSOYBGpr6WQjke1MQ8AZpJ8GlYZM/mOc=",
|
||||||
"rev": "b3fe9581c9061c749abef42b6d4ee7b7c05c33fa",
|
"rev": "b18a4b905f8d028dc4476412e6d6891728695379",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.5845.b3fe9581c906/nixexprs.tar.xz"
|
"url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.8045.b18a4b905f8d/nixexprs.tar.xz"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"id": "nixpkgs",
|
"id": "nixpkgs",
|
||||||
@@ -276,12 +260,32 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"grafana-content": "grafana-content",
|
|
||||||
"nix": "nix",
|
"nix": "nix",
|
||||||
"nixos-generators": "nixos-generators",
|
"nixos-generators": "nixos-generators",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixpkgs": "nixpkgs_3",
|
"nixpkgs": "nixpkgs_3",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable"
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
|
"sops-nix": "sops-nix"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sops-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1788337237,
|
||||||
|
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "Mic92",
|
||||||
|
"repo": "sops-nix",
|
||||||
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,11 +11,9 @@
|
|||||||
url = "github:nix-community/nixos-generators";
|
url = "github:nix-community/nixos-generators";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
sops-nix = {
|
||||||
# Grafana content (dashboards, datasources, alert rules) — provisioned from the store.
|
url = "github:Mic92/sops-nix";
|
||||||
grafana-content = {
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
url = "git+ssh://git@rechberg.online:222/finn.markwitz/dashboards.git";
|
|
||||||
flake = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -42,6 +40,15 @@
|
|||||||
_module.args.pkgs = import inputs.nixpkgs {
|
_module.args.pkgs = import inputs.nixpkgs {
|
||||||
inherit system;
|
inherit system;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
sops
|
||||||
|
age
|
||||||
|
ssh-to-age
|
||||||
|
nixfmt
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
systems = [ "x86_64-linux" ];
|
systems = [ "x86_64-linux" ];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ let
|
|||||||
nixpkgs
|
nixpkgs
|
||||||
nixpkgs-unstable
|
nixpkgs-unstable
|
||||||
nixos-hardware
|
nixos-hardware
|
||||||
|
sops-nix
|
||||||
#hyprland
|
#hyprland
|
||||||
;
|
;
|
||||||
nixosSystem = nixpkgs.lib.makeOverridable nixpkgs.lib.nixosSystem;
|
nixosSystem = nixpkgs.lib.makeOverridable nixpkgs.lib.nixosSystem;
|
||||||
@@ -40,6 +41,7 @@ let
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
../modules
|
../modules
|
||||||
|
sops-nix.nixosModules.sops
|
||||||
# ../profiles
|
# ../profiles
|
||||||
];
|
];
|
||||||
defaultModules = baseModules ++ customModules;
|
defaultModules = baseModules ++ customModules;
|
||||||
|
|||||||
@@ -3,5 +3,6 @@
|
|||||||
./modules.nix
|
./modules.nix
|
||||||
./network.nix
|
./network.nix
|
||||||
./nix.nix
|
./nix.nix
|
||||||
|
./sops.nix
|
||||||
./users.nix
|
./users.nix
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# sops-nix plumbing — applied to every machine.
|
||||||
|
# See docs/secrets.md for the day-to-day workflow.
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
# Each host defaults to its own secrets/<hostname>.yaml. Override per-secret
|
||||||
|
# with sops.secrets.<name>.sopsFile for shared files.
|
||||||
|
sops.defaultSopsFile = lib.mkDefault (../../secrets + "/${config.networking.hostName}.yaml");
|
||||||
|
sops.defaultSopsFormat = "yaml";
|
||||||
|
sops.validateSopsFiles = true;
|
||||||
|
|
||||||
|
# Decrypt with the host's own SSH host key, converted to age.
|
||||||
|
# Explicit rather than inheriting the openssh-derived default, so this does
|
||||||
|
# not silently break if services.openssh is ever disabled.
|
||||||
|
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
sops.age.generateKey = false;
|
||||||
|
|
||||||
|
# Age only. The default here is the RSA host key imported as a GPG key,
|
||||||
|
# which is deprecated and an extra failure surface.
|
||||||
|
sops.gnupg.sshKeyPaths = [ ];
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
sops
|
||||||
|
age
|
||||||
|
ssh-to-age
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
# enabled profiles
|
# enabled profiles
|
||||||
{ config, lib, ... }:
|
_: {
|
||||||
let
|
|
||||||
secrets = config.sops.secrets;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
my.profiles = {
|
my.profiles = {
|
||||||
hyprland.enable = false;
|
hyprland.enable = false;
|
||||||
zsh.enable = true;
|
zsh.enable = true;
|
||||||
@@ -22,7 +18,6 @@ in
|
|||||||
jellyfin.enable = true;
|
jellyfin.enable = true;
|
||||||
jellyseerr.enable = true;
|
jellyseerr.enable = true;
|
||||||
immich.enable = true;
|
immich.enable = true;
|
||||||
monitoring.enable = true;
|
|
||||||
development.enable = true;
|
development.enable = true;
|
||||||
home-assistant.enable = true;
|
home-assistant.enable = true;
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
./environments.nix
|
./environments.nix
|
||||||
./network.nix
|
./network.nix
|
||||||
|
./secrets.nix
|
||||||
# ./system.nix use docker here
|
# ./system.nix use docker here
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -21,7 +22,6 @@
|
|||||||
useOSProber = true;
|
useOSProber = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
# Configure keymap in X11
|
# Configure keymap in X11
|
||||||
services.xserver.xkb = {
|
services.xserver.xkb = {
|
||||||
layout = "de";
|
layout = "de";
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
services.printing.enable = true;
|
services.printing.enable = true;
|
||||||
nixpkgs.config.allowUnfree = true;
|
nixpkgs.config.allowUnfree = true;
|
||||||
|
|
||||||
|
|
||||||
hardware.nvidia.prime = {
|
hardware.nvidia.prime = {
|
||||||
sync.enable = false;
|
sync.enable = false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
# enabled profiles
|
# enabled profiles
|
||||||
{ config, lib, ... }:
|
_: {
|
||||||
let
|
|
||||||
secrets = config.sops.secrets;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
my.profiles = {
|
my.profiles = {
|
||||||
kde-desktop.enable = true;
|
kde-desktop.enable = true;
|
||||||
zsh.enable = true;
|
zsh.enable = true;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Secrets decrypted on mibook. sopsFile defaults to secrets/mibook.yaml
|
||||||
|
# via machines/core/sops.nix.
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# Pipeline smoke test — proves host-key decryption works end to end.
|
||||||
|
# Safe to delete once a real secret is in place.
|
||||||
|
sops.secrets.placeholder = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
./docker
|
./docker
|
||||||
./homepage
|
./homepage
|
||||||
./kde-desktop
|
./kde-desktop
|
||||||
./monitoring
|
|
||||||
./readarr
|
./readarr
|
||||||
./sonarr
|
./sonarr
|
||||||
./jellyfin
|
./jellyfin
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ in
|
|||||||
mediaLocation = "/var/lib/immich";
|
mediaLocation = "/var/lib/immich";
|
||||||
machine-learning.enable = true;
|
machine-learning.enable = true;
|
||||||
accelerationDevices = [ "/dev/dri/renderD128" ];
|
accelerationDevices = [ "/dev/dri/renderD128" ];
|
||||||
settings.server.externalDomain = "http://${hostName}:${toString port}";
|
# Setting `settings` puts Immich in config-file mode: the admin settings
|
||||||
|
# UI becomes read-only and system config is managed declaratively here.
|
||||||
|
settings = {
|
||||||
|
server.externalDomain = "http://${hostName}:${toString port}";
|
||||||
|
# Intel Quick Sync hardware transcoding (jupiter's iGPU).
|
||||||
|
ffmpeg.accel = "qsv";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# The native module does not add GPU groups; required for VAAPI/QSV transcoding.
|
# The native module does not add GPU groups; required for VAAPI/QSV transcoding.
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
# Grafana Alloy: ships the full systemd journal to Loki.
|
|
||||||
# The nixpkgs module reads /etc/alloy/*.alloy and already runs with the
|
|
||||||
# systemd-journal supplementary group. Alloy exposes /metrics + UI on :12345.
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.my.profiles.monitoring;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.alloy.enable = true;
|
|
||||||
|
|
||||||
environment.etc."alloy/config.alloy".text = ''
|
|
||||||
loki.source.journal "journal" {
|
|
||||||
max_age = "24h"
|
|
||||||
forward_to = [loki.write.local.receiver]
|
|
||||||
relabel_rules = loki.relabel.journal.rules
|
|
||||||
labels = { host = "jupiter" }
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.relabel "journal" {
|
|
||||||
forward_to = []
|
|
||||||
rule {
|
|
||||||
source_labels = ["__journal__systemd_unit"]
|
|
||||||
target_label = "unit"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__journal_priority_keyword"]
|
|
||||||
target_label = "priority"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__journal__transport"]
|
|
||||||
target_label = "transport"
|
|
||||||
}
|
|
||||||
rule {
|
|
||||||
source_labels = ["__journal__boot_id"]
|
|
||||||
target_label = "boot_id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loki.write "local" {
|
|
||||||
endpoint {
|
|
||||||
url = "http://127.0.0.1:3100/loki/api/v1/push"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# Observability stack: Grafana + Prometheus + Loki + Alloy.
|
|
||||||
# Content (dashboards, datasources, alert rules) lives in a separate repo,
|
|
||||||
# consumed as the `grafana-content` flake input and provisioned from the store.
|
|
||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./grafana.nix
|
|
||||||
./prometheus.nix
|
|
||||||
./loki.nix
|
|
||||||
./alloy.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
options.my.profiles.monitoring = {
|
|
||||||
enable = lib.mkEnableOption "Grafana + Prometheus + Loki + Alloy observability stack";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# Grafana service + provisioning from the grafana-content flake input.
|
|
||||||
# Provisioning shape verified against nixpkgs 26.05 grafana module:
|
|
||||||
# - datasources.path takes a directory (lndir'd into the provisioning dir)
|
|
||||||
# - dashboard provider is generated here so the store path can be injected
|
|
||||||
# - each alerting resource takes its own YAML *file* (they share one target dir,
|
|
||||||
# so pointing them at a directory would collide)
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
inputs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.my.profiles.monitoring;
|
|
||||||
content = inputs.grafana-content;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.grafana = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
server = {
|
|
||||||
http_addr = "0.0.0.0";
|
|
||||||
http_port = 3000;
|
|
||||||
domain = "jupiter.solar.internal";
|
|
||||||
root_url = "http://jupiter.solar.internal:3000/";
|
|
||||||
};
|
|
||||||
security.admin_password = "$__file{/etc/grafana/admin_password}";
|
|
||||||
# 26.05 removed the built-in default; encrypts secrets in Grafana's DB.
|
|
||||||
security.secret_key = "$__file{/etc/grafana/secret_key}";
|
|
||||||
"auth.anonymous" = {
|
|
||||||
enabled = true;
|
|
||||||
org_role = "Viewer";
|
|
||||||
};
|
|
||||||
metrics.enabled = true; # /metrics for Prometheus self-scrape
|
|
||||||
unified_alerting.enabled = true;
|
|
||||||
alerting.enabled = false; # disable legacy alerting
|
|
||||||
};
|
|
||||||
|
|
||||||
provision = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
# static datasources dir → all *.yaml linked in
|
|
||||||
datasources.path = "${content}/provisioning/datasources";
|
|
||||||
|
|
||||||
# dashboard provider generated here with the store path injected
|
|
||||||
dashboards.settings.providers = [
|
|
||||||
{
|
|
||||||
name = "content";
|
|
||||||
type = "file";
|
|
||||||
disableDeletion = true;
|
|
||||||
allowUiUpdates = false;
|
|
||||||
options = {
|
|
||||||
path = "${content}/dashboards";
|
|
||||||
foldersFromFilesStructure = true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# per-resource alerting files (share one target dir → must be files)
|
|
||||||
alerting = {
|
|
||||||
rules.path = "${content}/provisioning/alerting/rules.yaml";
|
|
||||||
contactPoints.path = "${content}/provisioning/alerting/contactpoints.yaml";
|
|
||||||
policies.path = "${content}/provisioning/alerting/policies.yaml";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 3000 ];
|
|
||||||
|
|
||||||
# Register a homepage-dashboard tile (matches the other profiles).
|
|
||||||
my.homepage.services = [
|
|
||||||
{
|
|
||||||
group = "Monitoring";
|
|
||||||
name = "Grafana";
|
|
||||||
description = "Metrics & logs dashboards";
|
|
||||||
href = "http://jupiter.solar.internal:3000";
|
|
||||||
icon = "grafana.png";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# Loki single-binary, filesystem storage, tsdb schema, 90d retention via compactor.
|
|
||||||
# Localhost only — Grafana is the sole consumer.
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.my.profiles.monitoring;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.loki = {
|
|
||||||
enable = true;
|
|
||||||
configuration = {
|
|
||||||
server = {
|
|
||||||
http_listen_address = "127.0.0.1";
|
|
||||||
http_listen_port = 3100;
|
|
||||||
};
|
|
||||||
auth_enabled = false;
|
|
||||||
|
|
||||||
common = {
|
|
||||||
instance_addr = "127.0.0.1";
|
|
||||||
path_prefix = "/var/lib/loki";
|
|
||||||
storage.filesystem = {
|
|
||||||
chunks_directory = "/var/lib/loki/chunks";
|
|
||||||
rules_directory = "/var/lib/loki/rules";
|
|
||||||
};
|
|
||||||
replication_factor = 1;
|
|
||||||
ring.kvstore.store = "inmemory";
|
|
||||||
};
|
|
||||||
|
|
||||||
schema_config.configs = [
|
|
||||||
{
|
|
||||||
from = "2024-01-01";
|
|
||||||
store = "tsdb";
|
|
||||||
object_store = "filesystem";
|
|
||||||
schema = "v13";
|
|
||||||
index = {
|
|
||||||
prefix = "index_";
|
|
||||||
period = "24h";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
limits_config.retention_period = "2160h"; # 90d
|
|
||||||
compactor = {
|
|
||||||
working_directory = "/var/lib/loki/compactor";
|
|
||||||
retention_enabled = true;
|
|
||||||
delete_request_store = "filesystem";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# Prometheus + node_exporter. Prometheus UI reachable on the LAN (:9090);
|
|
||||||
# node_exporter bound to localhost. Scrapes the host plus the stack itself.
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.my.profiles.monitoring;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
config = lib.mkIf cfg.enable {
|
|
||||||
services.prometheus = {
|
|
||||||
enable = true;
|
|
||||||
listenAddress = "0.0.0.0";
|
|
||||||
port = 9090;
|
|
||||||
retentionTime = "90d";
|
|
||||||
|
|
||||||
exporters.node = {
|
|
||||||
enable = true;
|
|
||||||
listenAddress = "127.0.0.1";
|
|
||||||
port = 9100;
|
|
||||||
# default collectors
|
|
||||||
};
|
|
||||||
|
|
||||||
scrapeConfigs = [
|
|
||||||
{
|
|
||||||
job_name = "node";
|
|
||||||
static_configs = [ { targets = [ "127.0.0.1:9100" ]; } ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
job_name = "prometheus";
|
|
||||||
static_configs = [ { targets = [ "127.0.0.1:9090" ]; } ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
job_name = "grafana";
|
|
||||||
static_configs = [ { targets = [ "127.0.0.1:3000" ]; } ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
job_name = "loki";
|
|
||||||
static_configs = [ { targets = [ "127.0.0.1:3100" ]; } ];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
job_name = "alloy";
|
|
||||||
static_configs = [ { targets = [ "127.0.0.1:12345" ]; } ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [ 9090 ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
placeholder: ENC[AES256_GCM,data:tji/6di1F2GsD60TEBGAVvZR9m4jxQ8=,iv:BoBQVR735vq38iGx40VHIpk185ylf79mKmnpewcq/+o=,tag:lXNNk4/HYB0sw44KaQ2KYg==,type:str]
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4ZUk5cjhGTlVveUFRdHdS
|
||||||
|
elpVV0pZVU54U3plYjNBai9ZZVBxTDhyRFFnCmpGWW81VGJoMnd4eWxCWnovQnFF
|
||||||
|
WXR1MVBTZ3VRUnNMUjI5Rko4NHpBdjQKLS0tIDkxYWNxTmhUS2dYTkFnTWZXaS9V
|
||||||
|
ejdtSmF2M1ZVdFNiWENMWlNraUYxVzAKdlz4xLhTKjJfFESDcym2xCOa71rXj6BE
|
||||||
|
mdeaCDhE11CAXqrpKU+j0/kwTsZRzyqlwQsfeI4ratu1K1vY66ONsw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1a0v442ct2qfjcdhyktn25spw02yq4fl08plrrvuradwwxzsle4mq8thln4
|
||||||
|
- enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYT1hRSGt6TDRSMklQNDA3
|
||||||
|
Z2lYS2pMRWxxeEtGRGJSeXRDSlY5UTFBNEZvCm5RK0huT282MXpwWjdrTHByY3Ev
|
||||||
|
bTBMczR1MzVEcnNDVUxadlREbGg3UUUKLS0tIHhydUlmRExkbmNNNkVTcW1OSnpZ
|
||||||
|
aEtLZkRTRE9GZ0I1bEN0ZEl3RjVMeDgKoCY7NyxFMS+WuBUez2Irf91Xuy9QpM5g
|
||||||
|
RTmhK6mw2wd8oUMdgPOUESNwh1xkCz4hDHPrDQnfd4sPhSAvh28jwg==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
recipient: age1n3uqtee5ymykrls6k04dznq50cwmj99hw7ny83yxjndt4y2ncf5qhjwa2p
|
||||||
|
lastmodified: "2026-09-08T13:29:12Z"
|
||||||
|
mac: ENC[AES256_GCM,data:YjMMIUdfCl8dkuvtvBZTFIhLwsUjI6iSeN929O4igbwpRTyGVxgLmAMl81YYHJSyEibelT7ZBKiuVVnL2KjLvAKTj3H1O+AQrcAEJDPC9NwPJwbtAIpLl2li5060Kdj4kr8AY+UJ2kHycuS9dg6wPZTLBJcM/YCw0AoVftvzryM=,iv:GEQZF9RvW+MJlMIjn1ToD74RubWMQtCyE5LqW9KQQfA=,tag:NR6u88eBY1asQZM6f45hTw==,type:str]
|
||||||
|
unencrypted_suffix: _unencrypted
|
||||||
|
version: 3.13.3
|
||||||
Reference in New Issue
Block a user