Compare commits
1 commit
main
...
sops-imple
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ad3058a3 |
10 changed files with 141 additions and 16 deletions
25
.sops.yaml
Normal file
25
.sops.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# To set up sops-nix:
|
||||||
|
# 1. Generate an age key on each host:
|
||||||
|
# mkdir -p ~/.config/sops/age
|
||||||
|
# age-keygen -o ~/.config/sops/age/keys.txt
|
||||||
|
# Or derive from the host SSH key:
|
||||||
|
# nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
|
||||||
|
#
|
||||||
|
# 2. Replace the placeholder age keys below with the actual public keys.
|
||||||
|
#
|
||||||
|
# 3. Encrypt secret files:
|
||||||
|
# sops secrets/homelab.yaml
|
||||||
|
#
|
||||||
|
# 4. To re-key after changing keys:
|
||||||
|
# sops updatekeys secrets/homelab.yaml
|
||||||
|
|
||||||
|
keys:
|
||||||
|
- &homelab age1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # replace with: ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub
|
||||||
|
- &admin age1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX # replace with: age-keygen output from your admin machine
|
||||||
|
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: secrets/homelab\.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *homelab
|
||||||
|
- *admin
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ homelab, ... }: {
|
{ config, homelab, ... }: {
|
||||||
services.pocket-id = {
|
services.pocket-id = {
|
||||||
enable = true;
|
enable = true;
|
||||||
credentials.ENCRYPTION_KEY = "/mnt/data/pocketid/encryption-key";
|
credentials.ENCRYPTION_KEY = config.sops.secrets.pocketid_encryption_key.path;
|
||||||
dataDir = "/mnt/data/pocketid/data";
|
dataDir = "/mnt/data/pocketid/data";
|
||||||
settings = {
|
settings = {
|
||||||
PORT = "1411";
|
PORT = "1411";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ timezone, homelab, ... }: let
|
{ config, timezone, homelab, ... }: let
|
||||||
rss = [
|
rss = [
|
||||||
"https://www.raspberrypi.com/news/feed/"
|
"https://www.raspberrypi.com/news/feed/"
|
||||||
"https://www.jeffgeerling.com/blog.xml"
|
"https://www.jeffgeerling.com/blog.xml"
|
||||||
|
|
@ -96,7 +96,7 @@ in {
|
||||||
};
|
};
|
||||||
services.glance = {
|
services.glance = {
|
||||||
enable = true;
|
enable = true;
|
||||||
environmentFile = "/var/lib/glance/.env";
|
environmentFile = config.sops.secrets.glance_env.path;
|
||||||
settings = {
|
settings = {
|
||||||
server = {
|
server = {
|
||||||
host = "127.0.0.1";
|
host = "127.0.0.1";
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{ homelab, ... }: {
|
{ config, homelab, ... }: {
|
||||||
services.vaultwarden = {
|
services.vaultwarden = {
|
||||||
enable = true;
|
enable = true;
|
||||||
domain = "pass.proxy.${homelab.domain}";
|
domain = "pass.proxy.${homelab.domain}";
|
||||||
backupDir = "/mnt/data/vaultwarden/backups";
|
backupDir = "/mnt/data/vaultwarden/backups";
|
||||||
environmentFile = "/mnt/data/vaultwarden/.env";
|
environmentFile = config.sops.secrets.vaultwarden_env.path;
|
||||||
config = {
|
config = {
|
||||||
ROCKET_PORT = 8060;
|
ROCKET_PORT = 8060;
|
||||||
ROCKET_ADDRESS = "127.0.0.1";
|
ROCKET_ADDRESS = "127.0.0.1";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ homelab, lib, ... }: let
|
{ config, homelab, lib, ... }: let
|
||||||
base = "proxy.${homelab.domain}";
|
base = "proxy.${homelab.domain}";
|
||||||
hosts = {
|
hosts = {
|
||||||
"server" = { dest = "https://server.dns.${homelab.domain}:8006"; auth = false; };
|
"server" = { dest = "https://server.dns.${homelab.domain}:8006"; auth = false; };
|
||||||
|
|
@ -45,8 +45,7 @@ in {
|
||||||
domain = "*.${base}";
|
domain = "*.${base}";
|
||||||
extraDomainNames = [ base ];
|
extraDomainNames = [ base ];
|
||||||
dnsProvider = "cloudflare";
|
dnsProvider = "cloudflare";
|
||||||
environmentFile = "/var/lib/acme/cloudflare.env";
|
environmentFile = config.sops.templates."cloudflare.env".path;
|
||||||
# ^^^contents: CLOUDFLARE_DNS_API_TOKEN=XXXXX
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -81,7 +80,7 @@ in {
|
||||||
locations."/" = {
|
locations."/" = {
|
||||||
proxyPass = cfg.dest;
|
proxyPass = cfg.dest;
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
basicAuthFile = if cfg.auth then "/var/lib/nginx/.htpasswd" else null;
|
basicAuthFile = if cfg.auth then config.sops.secrets.nginx_htpasswd.path else null;
|
||||||
extraConfig = exta-conf;
|
extraConfig = exta-conf;
|
||||||
};
|
};
|
||||||
}) hosts;
|
}) hosts;
|
||||||
|
|
|
||||||
59
modules/system/homelab/sops.nix
Normal file
59
modules/system/homelab/sops.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{ config, ... }: {
|
||||||
|
sops = {
|
||||||
|
defaultSopsFile = ../../../secrets/homelab.yaml;
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
cloudflare_dns_api_token = {
|
||||||
|
owner = "acme";
|
||||||
|
group = "acme";
|
||||||
|
};
|
||||||
|
|
||||||
|
cloudflared_tunnel_credentials = {
|
||||||
|
owner = "cloudflared";
|
||||||
|
group = "cloudflared";
|
||||||
|
};
|
||||||
|
|
||||||
|
cloudflared_cert = {
|
||||||
|
owner = "cloudflared";
|
||||||
|
group = "cloudflared";
|
||||||
|
};
|
||||||
|
|
||||||
|
vaultwarden_env = {
|
||||||
|
owner = "vaultwarden";
|
||||||
|
group = "vaultwarden";
|
||||||
|
restartUnits = [ "vaultwarden.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
glance_env = {
|
||||||
|
owner = "glance";
|
||||||
|
group = "glance";
|
||||||
|
restartUnits = [ "glance.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
pocketid_encryption_key = {
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
restartUnits = [ "pocket-id.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
tailscale_authkey = {
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
restartUnits = [ "tailscaled.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nginx_htpasswd = {
|
||||||
|
owner = "nginx";
|
||||||
|
group = "nginx";
|
||||||
|
restartUnits = [ "nginx.service" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
templates."cloudflare.env" = {
|
||||||
|
owner = "acme";
|
||||||
|
group = "acme";
|
||||||
|
content = "CLOUDFLARE_DNS_API_TOKEN=${config.sops.placeholder.cloudflare_dns_api_token}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ pkgs, lib, homelab, ... }: let
|
{ config, pkgs, lib, homelab, ... }: let
|
||||||
routes = {
|
routes = {
|
||||||
"git.${homelab.domain}" = "http://localhost:5080";
|
"git.${homelab.domain}" = "http://localhost:5080";
|
||||||
"auth.${homelab.domain}" = "http://localhost:1411";
|
"auth.${homelab.domain}" = "http://localhost:1411";
|
||||||
|
|
@ -10,8 +10,8 @@ in {
|
||||||
services.cloudflared = {
|
services.cloudflared = {
|
||||||
enable = true;
|
enable = true;
|
||||||
tunnels.homelab = {
|
tunnels.homelab = {
|
||||||
credentialsFile = "/mnt/data/cloudflared/homelab.json";
|
credentialsFile = config.sops.secrets.cloudflared_tunnel_credentials.path;
|
||||||
certificateFile = "/mnt/data/cloudflared/cert.pem";
|
certificateFile = config.sops.secrets.cloudflared_cert.path;
|
||||||
default = "http_status:404";
|
default = "http_status:404";
|
||||||
ingress = routes;
|
ingress = routes;
|
||||||
};
|
};
|
||||||
|
|
@ -31,7 +31,7 @@ in {
|
||||||
|
|
||||||
script = lib.concatMapStringsSep "\n" (domain: ''
|
script = lib.concatMapStringsSep "\n" (domain: ''
|
||||||
echo "Ensuring DNS route for ${domain}..."
|
echo "Ensuring DNS route for ${domain}..."
|
||||||
${pkgs.cloudflared}/bin/cloudflared tunnel --origincert /mnt/data/cloudflared/cert.pem route dns ${homelab.cf-tunnel-id} ${domain} || true
|
${pkgs.cloudflared}/bin/cloudflared tunnel --origincert ${config.sops.secrets.cloudflared_cert.path} route dns ${homelab.cf-tunnel-id} ${domain} || true
|
||||||
'') (builtins.attrNames routes);
|
'') (builtins.attrNames routes);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ lib, homelab, ... }: let
|
{ config, lib, homelab, ... }: let
|
||||||
ts-flags = [
|
ts-flags = [
|
||||||
"--advertise-exit-node"
|
"--advertise-exit-node"
|
||||||
"--advertise-routes=10.3.14.0/24,192.168.1.0/24"
|
"--advertise-routes=10.3.14.0/24,192.168.1.0/24"
|
||||||
|
|
@ -20,6 +20,7 @@ in {
|
||||||
./homelab/dns.nix
|
./homelab/dns.nix
|
||||||
./homelab/git.nix
|
./homelab/git.nix
|
||||||
./homelab/ai.nix
|
./homelab/ai.nix
|
||||||
|
./homelab/sops.nix
|
||||||
|
|
||||||
./core/swapfile.nix
|
./core/swapfile.nix
|
||||||
./core/oom.nix
|
./core/oom.nix
|
||||||
|
|
@ -29,7 +30,7 @@ in {
|
||||||
|
|
||||||
services.tailscale = {
|
services.tailscale = {
|
||||||
enable = true;
|
enable = true;
|
||||||
authKeyFile = "/mnt/data/tailscale/authkey";
|
authKeyFile = config.sops.secrets.tailscale_authkey.path;
|
||||||
useRoutingFeatures = "server";
|
useRoutingFeatures = "server";
|
||||||
extraUpFlags = ts-flags;
|
extraUpFlags = ts-flags;
|
||||||
extraSetFlags = ts-flags;
|
extraSetFlags = ts-flags;
|
||||||
|
|
|
||||||
30
scripts/check-sops.sh
Executable file
30
scripts/check-sops.sh
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-commit hook: block commits containing unencrypted sops secret files.
|
||||||
|
# Install with: ln -sf ../../scripts/check-sops.sh .git/hooks/pre-commit
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
staged_secrets=$(git diff --cached --name-only --diff-filter=ACM -- 'secrets/*.yaml' 'secrets/*.yml' 'secrets/*.json')
|
||||||
|
|
||||||
|
if [ -z "$staged_secrets" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
for file in $staged_secrets; do
|
||||||
|
# sops-encrypted YAML/JSON files always contain a top-level "sops" key with metadata
|
||||||
|
if ! git show ":$file" | grep -q '"sops"\|sops:'; then
|
||||||
|
echo "ERROR: $file is not encrypted with sops! Encrypt it first:"
|
||||||
|
echo " sops $file"
|
||||||
|
echo
|
||||||
|
echo "hint: bypass with: git commit --no-verify"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$failed" -ne 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Commit aborted. Encrypt secret files before committing."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
11
secrets/homelab.yaml
Normal file
11
secrets/homelab.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# This file should be encrypted with sops before committing.
|
||||||
|
# Run: sops secrets/homelab.yaml
|
||||||
|
# All values below are placeholders. Replace them with actual values.
|
||||||
|
cloudflare_dns_api_token: REPLACE_ME
|
||||||
|
cloudflared_tunnel_credentials: REPLACE_ME
|
||||||
|
cloudflared_cert: REPLACE_ME
|
||||||
|
vaultwarden_env: REPLACE_ME
|
||||||
|
glance_env: REPLACE_ME
|
||||||
|
pocketid_encryption_key: REPLACE_ME
|
||||||
|
tailscale_authkey: REPLACE_ME
|
||||||
|
nginx_htpasswd: REPLACE_ME
|
||||||
Loading…
Add table
Add a link
Reference in a new issue