add yisroelbaum web stack module

This commit is contained in:
Yisroel Baum 2026-07-31 11:17:25 +03:00
commit 4a4de4eb0a
Signed by: yisroelbaum
GPG key ID: 0FA60884F75520A9
6 changed files with 456 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*~
result
/.direnv/

41
README.md Normal file
View file

@ -0,0 +1,41 @@
# yisroelbaum.com web stack
This flake owns the production NixOS configuration for the web services under
`yisroelbaum.com`:
- the MyResume package at `yisroelbaum.com`;
- Jellyfin at `jellyfin.yisroelbaum.com`;
- Forgejo at `git.yisroelbaum.com`, including its Borg backup;
- Rabbi Gerzi at `rabbigerzi.yisroelbaum.com` and
`rabbigerziapi.yisroelbaum.com`;
- nginx and per-host ACME certificates for those endpoints.
The importing host remains responsible for provisioning secrets. Import
`nixosModules.yisroelbaum-web` or `nixosModules.default`, then pass the
resulting runtime paths:
```nix
services.yisroelbaum-web = {
enable = true;
secretFiles = {
borgPassphrase = "/run/secrets/borg-passphrase";
borgPrivateKey = "/run/secrets/borg-private-key";
forgejoMailerUser = "/run/secrets/forgejo-mailer-user";
forgejoMailerPassword = "/run/secrets/forgejo-mailer-password";
rabbiGerziEnvironment = "/run/secrets/rabbi-gerzi.env";
};
};
```
Run the checks with:
```shell
nix flake check
```
## First deployment
This repository is hosted by the Forgejo instance that it configures. For the
initial migration, publish and push this flake while the old host configuration
is still active. Only then add the remote flake input to the host configuration
and update its lock file.

103
flake.lock generated Normal file
View file

@ -0,0 +1,103 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1785318670,
"narHash": "sha256-dN6Ou5x/+23FZLEpYP3IffO+NyJFzUlGumt1uu3MMaY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0954f7ee2f6bb3dc7d4e3d0d8bcb8fd4bde4cfc5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"rabbi-gerzi": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"utils": "utils"
},
"locked": {
"lastModified": 1784294454,
"narHash": "sha256-oPxPCk4wMf0qvarGyZqixQI44RyirQA0ZBYs7J8liv4=",
"ref": "refs/heads/master",
"rev": "3e02a83dd48de378d3763fb27df3a665cca6cb18",
"revCount": 379,
"type": "git",
"url": "https://git.yisroelbaum.com/yisroelbaum/Rabbi_Gerzi"
},
"original": {
"type": "git",
"url": "https://git.yisroelbaum.com/yisroelbaum/Rabbi_Gerzi"
}
},
"resume": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1779106432,
"narHash": "sha256-EsVQ1SAEIbgiTDA8b5ygm904vDJd5xUQWld/KJRrJvM=",
"ref": "refs/heads/master",
"rev": "4ae8a76a478b5c1f3abd45ad3cbdaaa3439b4635",
"revCount": 11,
"type": "git",
"url": "https://git.yisroelbaum.com/yisroelbaum/MyResume"
},
"original": {
"type": "git",
"url": "https://git.yisroelbaum.com/yisroelbaum/MyResume"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"rabbi-gerzi": "rabbi-gerzi",
"resume": "resume"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"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"
}
}
},
"root": "root",
"version": 7
}

54
flake.nix Normal file
View file

@ -0,0 +1,54 @@
{
description = "NixOS module for the yisroelbaum.com web stack";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
resume = {
url = "git+https://git.yisroelbaum.com/yisroelbaum/MyResume";
inputs.nixpkgs.follows = "nixpkgs";
};
rabbi-gerzi = {
url = "git+https://git.yisroelbaum.com/yisroelbaum/Rabbi_Gerzi";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
resume,
rabbi-gerzi,
...
}:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
module = import ./nix/module.nix { inherit resume; };
in
{
nixosModules.yisroelbaum-web = {
imports = [
rabbi-gerzi.nixosModules.default
module
];
};
nixosModules.default = self.nixosModules.yisroelbaum-web;
checks.${system}.module-eval = import ./nix/tests/module.nix {
inherit
nixpkgs
self
system
;
};
formatter.${system} = pkgs.nixfmt-tree;
devShells.${system}.default = pkgs.mkShell {
packages = [ pkgs.nixfmt-tree ];
};
};
}

173
nix/module.nix Normal file
View file

@ -0,0 +1,173 @@
{ resume }:
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.yisroelbaum-web;
domainName = "yisroelbaum.com";
resumePackage = resume.packages.${pkgs.stdenv.hostPlatform.system}.default;
secretFileOption =
description:
lib.mkOption {
type = lib.types.path;
inherit description;
};
in
{
options.services.yisroelbaum-web = {
enable = lib.mkEnableOption "the yisroelbaum.com web stack";
secretFiles = {
borgPassphrase = secretFileOption "Borg repository passphrase file.";
borgPrivateKey = secretFileOption "Borg repository SSH private key file.";
forgejoMailerUser = secretFileOption "Forgejo SMTP username file.";
forgejoMailerPassword = secretFileOption "Forgejo SMTP password file.";
rabbiGerziEnvironment = secretFileOption "Rabbi Gerzi runtime dotenv file.";
};
};
config = lib.mkIf cfg.enable {
users.users.${config.services.nginx.user}.extraGroups = [ "acme" ];
services = {
jellyfin.enable = true;
forgejo = {
enable = true;
settings = {
server = {
ROOT_URL = "https://git.${domainName}/";
PROTOCOL = "http";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
DOMAIN = "git.${domainName}";
SSH_PORT = 2222;
START_SSH_SERVER = true;
};
session.COOKIE_SECURE = true;
service.DISABLE_REGISTRATION = true;
mailer = {
ENABLED = true;
SMTP_ADDR = "in-v3.mailjet.com";
SMTP_PORT = 587;
FROM = "me@${domainName}";
};
};
secrets.mailer = {
USER = cfg.secretFiles.forgejoMailerUser;
PASSWD = cfg.secretFiles.forgejoMailerPassword;
};
};
rabbi-gerzi = {
enable = true;
frontend = {
hostName = "rabbigerzi.${domainName}";
nginx = {
forceSSL = true;
enableACME = true;
};
};
backend = {
hostName = "rabbigerziapi.${domainName}";
environmentFile = cfg.secretFiles.rabbiGerziEnvironment;
nginx = {
forceSSL = true;
enableACME = true;
};
};
};
nginx = {
enable = true;
virtualHosts = {
"${domainName}" = {
forceSSL = true;
enableACME = true;
locations."/".root = "${resumePackage}";
};
"jellyfin.${domainName}" = {
forceSSL = true;
enableACME = true;
locations."/".proxyPass = "http://localhost:8096";
};
"git.${domainName}" = {
forceSSL = true;
enableACME = true;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:3000";
recommendedProxySettings = false;
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
};
};
borgbackup.jobs.forgejo = {
paths = [ config.services.forgejo.stateDir ];
repo = "ssh://n5lniyfd@n5lniyfd.repo.borgbase.com/./repo";
user = "root";
group = "root";
encryption = {
mode = "repokey-blake2";
passCommand = "${pkgs.coreutils}/bin/cat ${cfg.secretFiles.borgPassphrase}";
};
doInit = true;
compression = "auto,zstd";
startAt = "*-*-* 03:15:00";
persistentTimer = true;
prune.keep = {
daily = 7;
weekly = 4;
monthly = 6;
};
environment.BORG_RSH = "ssh -i ${cfg.secretFiles.borgPrivateKey} -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/root/.config/borg/known_hosts";
preHook = ''
forgejoWasActive=/root/.cache/borg/forgejo-was-active
if ${pkgs.systemd}/bin/systemctl is-active --quiet forgejo.service; then
touch "$forgejoWasActive"
${pkgs.systemd}/bin/systemctl stop forgejo.service
else
rm -f "$forgejoWasActive"
fi
'';
postHook = ''
forgejoWasActive=/root/.cache/borg/forgejo-was-active
if [ -e "$forgejoWasActive" ]; then
${pkgs.systemd}/bin/systemctl start forgejo.service
rm -f "$forgejoWasActive"
fi
'';
};
};
security.acme = {
defaults = {
webroot = "/var/lib/acme/acme-challenge/";
email = "yisroel.d.baum@gmail.com";
};
acceptTerms = true;
};
systemd.services.borgbackup-job-forgejo = {
after = [
"sops-install-secrets.service"
"network-online.target"
];
wants = [ "network-online.target" ];
};
};
}

82
nix/tests/module.nix Normal file
View file

@ -0,0 +1,82 @@
{
nixpkgs,
self,
system,
}:
let
pkgs = nixpkgs.legacyPackages.${system};
evaluatedSystem = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
self.nixosModules.default
{
system.stateVersion = "25.11";
services.yisroelbaum-web = {
enable = true;
secretFiles = {
borgPassphrase = "/run/secrets/borg-passphrase";
borgPrivateKey = "/run/secrets/borg-private-key";
forgejoMailerUser = "/run/secrets/forgejo-mailer-user";
forgejoMailerPassword = "/run/secrets/forgejo-mailer-password";
rabbiGerziEnvironment = "/run/secrets/rabbi-gerzi.env";
};
};
}
];
};
evaluatedConfig = evaluatedSystem.config;
virtualHosts = evaluatedConfig.services.nginx.virtualHosts;
certificates = evaluatedConfig.security.acme.certs;
forgejoBackup = evaluatedConfig.services.borgbackup.jobs.forgejo;
forgejoBackupService = evaluatedConfig.systemd.services.borgbackup-job-forgejo;
publicHostNames = [
"yisroelbaum.com"
"jellyfin.yisroelbaum.com"
"git.yisroelbaum.com"
"rabbigerzi.yisroelbaum.com"
"rabbigerziapi.yisroelbaum.com"
];
in
assert evaluatedConfig.services.nginx.enable;
assert evaluatedConfig.services.jellyfin.enable;
assert evaluatedConfig.services.forgejo.enable;
assert evaluatedConfig.services.rabbi-gerzi.enable;
assert
virtualHosts."yisroelbaum.com".locations."/".root
== "${self.inputs.resume.packages.${system}.default}";
assert virtualHosts."jellyfin.yisroelbaum.com".locations."/".proxyPass == "http://localhost:8096";
assert virtualHosts."git.yisroelbaum.com".locations."/".proxyPass == "http://127.0.0.1:3000";
assert !virtualHosts."git.yisroelbaum.com".locations."/".recommendedProxySettings;
assert nixpkgs.lib.hasInfix "proxy_set_header X-Forwarded-Proto $scheme;"
virtualHosts."git.yisroelbaum.com".locations."/".extraConfig;
assert builtins.all (
hostName:
virtualHosts.${hostName}.forceSSL
&& virtualHosts.${hostName}.enableACME
&& certificates.${hostName}.domain == hostName
) publicHostNames;
assert builtins.all (certificate: !(nixpkgs.lib.hasPrefix "*." certificate.domain)) (
builtins.attrValues certificates
);
assert evaluatedConfig.services.forgejo.settings.server.ROOT_URL == "https://git.yisroelbaum.com/";
assert evaluatedConfig.services.forgejo.settings.server.DOMAIN == "git.yisroelbaum.com";
assert evaluatedConfig.services.forgejo.settings.server.SSH_PORT == 2222;
assert evaluatedConfig.services.forgejo.settings.mailer.FROM == "me@yisroelbaum.com";
assert evaluatedConfig.services.forgejo.secrets.mailer.USER == "/run/secrets/forgejo-mailer-user";
assert
evaluatedConfig.services.forgejo.secrets.mailer.PASSWD == "/run/secrets/forgejo-mailer-password";
assert evaluatedConfig.services.rabbi-gerzi.frontend.hostName == "rabbigerzi.yisroelbaum.com";
assert evaluatedConfig.services.rabbi-gerzi.backend.hostName == "rabbigerziapi.yisroelbaum.com";
assert
evaluatedConfig.services.rabbi-gerzi.backend.environmentFile == "/run/secrets/rabbi-gerzi.env";
assert forgejoBackup.repo == "ssh://n5lniyfd@n5lniyfd.repo.borgbase.com/./repo";
assert forgejoBackup.startAt == "*-*-* 03:15:00";
assert forgejoBackup.persistentTimer;
assert forgejoBackup.prune.keep.daily == 7;
assert forgejoBackup.prune.keep.weekly == 4;
assert forgejoBackup.prune.keep.monthly == 6;
assert builtins.elem evaluatedConfig.services.forgejo.stateDir forgejoBackup.paths;
assert builtins.elem "sops-install-secrets.service" forgejoBackupService.after;
pkgs.runCommand "yisroelbaum-web-module-eval" { } ''
touch "$out"
''