I'll be bringing them back later once everything is more finished.
5CYGZZOMAR6NQP3EA7R3AT6G2GTBH3OLBLORMKVCITJC6EXAYSDQC 2VM4THXRZU7TZIYQOZNTHHDCET2I7HAPD3XDW52I5QWWVNDCQNVAC FEIJC27NDSY2EBPZJLEZCZGMBHKGVOL2G77EK6GG3Z6ULUKPNAHQC Z7475FGLUNCQMZ5WK4E3PPFDD4QQUJ6BLCCF76AJUJ3ONQ5L4AUAC B6UHS6ACZ7SMTJ5MDLKBOBADCKEEF62HDLHESCNWV4PS77TIPRYQC OPQP275ANFZA7TMX6WSBFDLR2YKIXLAQQCRP4776JK6PWSEGGVBQC MOEEB3HCSLVXDUZEZG3Y54GFGYMBOCLRUVDTGKIZKOKDLAS4EQCQC FMJD6HJVJETHM6E7FHF2X5MUN3ARQTC2H5EEJL4FH3KASMZ5KLYQC JMTWTJD37JISDPVCFL73YE34IHCLC6UBFX4AOC6GNE5TLOCOMQKAC 3GNDGKEVWQSPBYAZEGQ2HMPJLKMXIZPHEVE6DBBF27XX5W53UULAC 23GUJPL4GM74A3D2FOD5SETYPJGEZXOM6EITERBDMX2UN4C3Z32QC DSTJZR46W6HWZSHMT3B33WRSTJZ5BUICMJYHYG4BLMEZKG2HHAKQC V2VEMVKJ3NJM4WQO22ZUCU2ELEMZRXC3XIUPNL2BLV4SD5AF77UQC AN6E6FU2QXUOPIHQ6PMNTWILSBAVPFB53VVAQKNXLEAAQHD2RUZQC QXOXQGW2OPGJQP5HUCNITHM3K4HDX4ZDTCW7TO3UGYL3EGA2JCRQC RTBMBSBABSGTRICJ4AWBKWO3JJHBRKV6FGOMYPDD7X6SS6X35ZIQC ISKRPSY5MU5XKIV7FTBDYDPRKV6ONINH2SDIXY7QDGTXNCYN7YFQC KOXYNEPMHOWPUOUDAIDAVC2ZPUCLFGN23BM6QJ4UIDGVN73SUO7AC USCUP4XTLWPZDJYVDWN4QNUUE54Y64SNFC6KMQM7V3RTYE4GHHRAC 7P6ZBYH5MTUTYKTOVNFLD5HMFQOCBEDMEYQQOHZU2SX3MFEHCMXQC 2BGCCK64WA7SVJ2KLNVT6OHSW7MGRPUF4ZVRTSQWJ55RC652D2BQC QG4VKQAGLNRWCHV4TWI4Z3G7QSCI56MRPAHUGC6XD6KEWBDZIRLQC XVYKJKEMRBUZB3BWIMTM26W6URTE62JITSOZVQJFORDYMSX6ZLPAC 4GGQX4QFLTB4FMCYJOIBPOAMZVC5FXUNEZGF3A7E7GUNNVM5WQHAC SUJ7TKWXHFZCEEQFGLRT4PZ4TYAXFLNDZVE57SN2AHXTLEPX7WXAC HACMRPLPDJIRWINVHRKYM5N7RLT5273WQQYRJLA3URNXJBJ4FSVAC O5U2RS6S4RUGVSOZPTJGD6HJOOO5VM7SPZUDUHU4G3BHZOTJH47QC QEYE6IEG73H4YBRUA3QGQK5NOPWW4YYK2R4NHAUY65ST6WSZNKGAC UH6ZL2HFCGZTK5LBAIWXYWP7F7ZO5ZY3OLHVCY6DOCDSH6ATSILQC XRXDBN7R2R5E72KIXTUMYBMI52JZ7MUITBN4HMXRQCYA4IG2V6ZQC DQKCPBYIW34BPR3BNMCBID7HL3MHGKR47LGBJ6B7VSIKMCOV3EFAC CSG2VQG5JHDIRDRDB5BHCTLWZ7LRQDLOMATR7D67HSTRFEDYLDCAC CEOUX7SJMJXGN4SC6UZ3DN72RBPLEZVCHOL3TI6LZSEC5J2P57BAC 376IPKLC26EILJWYLCGA2UJEIH3XG3HCZA4EEZAQ44KMS5CNSUYQC TF7L7GT4UDTRC4YFK4RBXRJYK5SNTPNGDFOPLLKLEDK27GA3GAEQC K2QR5DH4B46SHW4YYNROPVXPNQ7PYEUL6ZNILU3CDHJBYJFZLBNAC 7GPZN4W5ILYQCEIRH4VXSKQ63JRU5A7IQX3LIEFXXUKN4AZM56JQC 3BGH2TFCMNYZDBD2EUTGHSU3GO7A5S7JNPQQYTFI3I6BIOBPWTNQC MYUBGFXQIHTXLR5F5A7ULNZHKZTWZ2O3FNIAFEK63AZOWBBKEO7QC VWR3XCGQZU4GXXBVWEFGPQXHUOXWPOZBNQHL67GAX3QGP7LNKG3AC 6AMUTT3LVY4SWBTA5W3E4CJANBRQFS3OFIZFUK3DAFG2O5T3TLWQC NF2AOCK6BNEVNF3XAL4FU2UWLGJZNPPLVSKHDPSYHRRWRHVC3VXAC AIZI53YJTA3UW6UGQE5BSVYTUBOP76S6ZJJYE6SXWHGVSV6K2NVAC B4J4KJXLDXXL6RUF3G5OIN75UXHLE3PB47MCBJ55RMF3F65IR4OQC U4YZ4PX7TPXHSM2BAYGLP26ZRKUV3CAGQ52J7WX2JDS4GKPFCLDAC KOOY3COZPQEG54NAEZK7P2EJKVZO2I25RU5Y34BWU63EJYFECFAAC 7AJ4T2F665FWRW47PCIZYIGQFSBEPRXBLUMBZO6FQBMOMKZ5HYXQC S6ZN4FXJQKJM6BQKDSAKZTAILRKYNFFEUDJKFQTDBWENQTJCPJZAC A4ACJV52DCYZKECMEKKQ7N2Q6VT2EYOQ3G3ERHJSZFHBRUX5VM6AC RNWOGVNATUUYTVBM44BM57OMCKDNOA5J4OKUNB5OJI2QGLFTTEIQC PA7CDI4CYEYVTGFRVLZ5STYSYS33GWBKLHWRUCSCFA33OQCZJVHQC UIRZ4SMZZC2QZ7D4QFQJZZKFA3QOA5SOQBOAGOG3RLXTCSLS3QJAC IJ5PUL5TPGAQNHH75MCRU6WPPFHYJWGVBWM6BXE2W2QJP3NRXZJAC L5S55TO3IBPBV2FCBZGMSXVU7ZDS4QKIJ5ED3VNJOSAOVGLLJPWAC 3C3SXK3RCUTNAF5BQCPP5FXJPCHJMP4BSKJLPI55XK56GBKSB2AAC { config, lib, ... }: letinherit (config.networking) domain;inherit (lib) mkValue;in {options.security.acme.users = mkValue [];config.users.groups.acme.members = config.security.acme.users;config.security.acme = {acceptTerms = true;defaults = {dnsProvider = "cloudflare";dnsResolver = "1.1.1.1";email = "security@${domain}";};certs.${domain} = {extraDomainNames = [ "*.${domain}" ];group = "acme";};};}environmentFile = config.age.secrets.acmeEnvironment.path;
{ self, config, lib, pkgs, ... }: letinherit (config.networking) domain;inherit (lib) enabled merge;fqdn = "uptime.${domain}";port = "3001"; # string for uptime-kumain {services.uptime-kuma = enabled {settings = {inherit port;HOST = "127.0.0.1";};};services.nginx.virtualHosts.${fqdn} = merge config.services.nginx.sslTemplate {serverAliases = [ "status.${domain}" ];locations."/" = {proxyPass = "http://127.0.0.1:${toString port}";proxyWebsockets = true;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;'';};};}imports = [./nginx.nix];
{ self, config, lib, pkgs, ... }: letinherit (config.networking) domain;inherit (lib) merge;inherit (lib.strings) toJSON;fqdn = "chat.${domain}";root = pkgs.cinny;cinnyConfig = {allowCustomHomeservers = false;homeserverList = [ domain ];defaultHomeserver = 0;hashRouter = {enabled = false;basename = "/";};featuredCommunities = {openAsDefault = false;spaces = [ ];rooms = [ ];};};in {imports = [ (self + /modules/nginx.nix) ];services.nginx.virtualHosts.${fqdn} = merge config.services.nginx.sslTemplate {inherit root;locations."= /config.json".extraConfig = /* nginx */ ''default_type application/json;return 200 '${toJSON cinnyConfig}';'';extraConfig = /* nginx */ ''rewrite ^/config.json$ /config.json break;rewrite ^/manifest.json$ /manifest.json break;rewrite ^/sw.js$ /sw.js break;rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break;rewrite ^/public/(.*)$ /public/$1 break;rewrite ^/assets/(.*)$ /assets/$1 break;rewrite ^(.+)$ /index.html break;'';};}'';locations."/".extraConfig = /* nginx */ ''proxy_hide_header Content-Security-Policy;add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain}; object-src 'self' ${domain} *.${domain}; img-src 'self' data: https: blob:; base-uri 'self'; frame-ancestors 'self';" always;add_header X-Frame-Options DENY always;add_header X-Content-Type-Options nosniff always;add_header X-XSS-Protection "1; mode=block" always;add_header Permissions-Policy "camera=(), geolocation=(), payment=(), usb=()" always;add_header Referrer-Policy no-referrer always;servers = [domain"matrix.org"];
{ self, config, lib, ... }: letinherit (config.networking) domain;fqdn = "matrix.${domain}";port = 8008;in {systemd.services.matrix-backup = {description = "Backup Matrix data and database";mkdir -p /var/backup/matrixcp -r /var/lib/matrix-synapse /var/backup/matrix/$(date +%Y%m%d_%H%M%S)# keep only last 7 backupsls -1t /var/backup/matrix/ | tail -n +8 | xargs -r rm -rf'';};systemd.timers.matrix-backup = {description = "Run Matrix backup daily";};services.matrix-synapse = enabled {withJemalloc = true;configureRedisLocally = true;server_name = domain;listeners = [{bind_addresses = [ "::1" ];compress = false;}];}];registration_requires_token = true;delete_stale_devices_after = "30d";max_upload_size = "512M";registration_shared_secret = config.age.secrets.matrixRegistrationSecret.path;trusted_key_servers = [];extras = [ "url-preview" "user-search" ];};};services.nginx.virtualHosts.${fqdn} = lib.merge config.services.nginx.sslTemplate {locations."/_synapse/client".proxyPass = "http://[::1]:${toString port}";};services.nginx.virtualHosts.${domain} = lib.merge config.services.nginx.sslTemplate {};}locations."/.well-known/matrix/server".extraConfig = ''return 200 '{"m.server": "${fqdn}:443"}';'';locations."/.well-known/matrix/client".extraConfig = ''return 200 '{"m.homeserver": {"base_url": "https://${fqdn}"}}';'';locations."/_synapse/admin".proxyPass = "http://[::1]:${toString port}";locations."/_matrix".proxyPass = "http://[::1]:${toString port}";extraConfig = '''';${config.services.nginx.goatCounterTemplate}signing_key_path = config.age.secrets.matrixSigningKey.path;url_preview_enabled = true;dynamic_thumbnails = true;media_store_path = "/var/lib/matrix-synapse/media_store";redis.enabled = true;allow_public_rooms_without_auth = true;allow_public_rooms_over_federation = true;report_stats = false;enable_registration = true;log_config = "/var/lib/matrix-synapse/log.yaml";log.root.level = "WARNING";database.name = "sqlite3";database.args.database = "/var/lib/matrix-synapse/homeserver.db";type = "http";tls = false;x_forwarded = true; # behind reverse proxyresources = [{names = [ "client" "federation" "media" ];port = port;settings = {timerConfig.OnCalendar = "daily";timerConfig.Persistent = true;};systemd.services.matrix-synapse.serviceConfig = {# sandboxingPrivateTmp = true;ProtectSystem = "strict";ProtectHome = true;# fs restrictionsReadWritePaths = [ "/var/lib/matrix-synapse" ];# network restrictionsRestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];# miscNoNewPrivileges = true;RestrictSUIDSGID = true;wantedBy = [ "timers.target" ];serviceConfig.Type = "oneshot";serviceConfig.User = "matrix-synapse";after = [ "matrix-synapse.service" ];script = ''age.secrets.matrixSigningKey = {owner = "matrix-synapse";group = "matrix-synapse";};age.secrets.matrixRegistrationSecret = {owner = "matrix-synapse";group = "matrix-synapse";};rekeyFile = self + /secrets/plum-matrix-registration-secret.age;rekeyFile = self + /secrets/plum-matrix-signing-key.age;imports = [(self + /modules/nginx.nix)] ++ (lib.collectNix ./. |> lib.remove ./default.nix);inherit (lib) enabled;
{ self, config, lib, ... }: letinherit (config.networking) domain;inherit (lib) enabled;fqdn = "analytics.${domain}";port = 8007;in {config = {services.goatcounter = enabled {inherit port;proxy = true;address = "127.0.0.1";};services.nginx.virtualHosts.${fqdn} = lib.merge config.services.nginx.sslTemplate {locations."/" = {proxyPass = "http://127.0.0.1:${toString port}";proxyWebsockets = true;extraConfig = ''proxy_hide_header X-Content-Type-Options;'';};};};}
{ self, config, lib, ... }: letinherit (config.networking) domain;fqdn = "metrics.${domain}";port = 8000;in {imports = [(self + /modules/nginx.nix)age.secrets.grafanaPassword = {owner = "grafana";};systemd.services.grafana = {after = [ "network.target" ];requires = [ "network.target" ];};services.grafana = enabled {provision = enabled;settings = {analytics.reporting_enabled = false;database.type = "sqlite3";server.domain = fqdn;server.http_addr = "::1";server.http_port = port;users.default_theme = "system";};settings.security = {admin_email = "metrics@${domain}";admin_password = "$__file{${config.age.secrets.grafanaPassword.path}}";admin_user = "admin";cookie_secure = true;disable_gravatar = true;};};services.nginx.virtualHosts.${fqdn} = merge config.services.nginx.sslTemplate {locations."/" = {extraConfig = /* nginx */ ''# grafana sets `nosniff` without correct content type so unset the headerproxy_hide_header X-Content-Type-Options;'';proxyPass = "http://[::1]:${toString port}";proxyWebsockets = true;};};}extraConfig = '''';${config.services.nginx.goatCounterTemplate}disable_initial_admin_creation = true; # after initial creationrekeyFile = self + /secrets/plum-grafana-password.age;] ++ (lib.collectNix ./. |> lib.remove ./default.nix);inherit (lib) enabled merge;
{ self, config, lib, ... }: letinherit (lib) enabled filterAttrs flatten mapAttrsToList;in {services.grafana.provision.datasources.settings = {datasources = [{name = "Prometheus";type = "prometheus";url = "http://[::1]:${toString config.services.prometheus.port}";orgId = 1;}];deleteDatasources = [{name = "Prometheus";orgId = 1;}];};services.prometheus = enabled {listenAddress = "[::]";retentionTime = "1w";scrapeConfigs = letconfigToScrapeConfig = hostName: { config, ... }: lethostConfig = config;in hostConfig.services.prometheus.exporters|> filterAttrs (exporterName: exporterConfig:exporterName != "minio" &&exporterName != "unifi-poller" &&exporterName != "tor" &&exporterConfig.enable or false)|> mapAttrsToList (exporterName: exporterConfig: {job_name = "${exporterName}-${hostName}";static_configs = [{targets = [ "${hostName}:${toString exporterConfig.port}" ];}];});in self.nixosConfigurations|> mapAttrsToList configToScrapeConfig|> flatten;};}
in {};};}hostPackages = [(inputs.fenix.packages.${pkgs.stdenv.hostPlatform.system}.complete.withComponents ["cargo""clippy""miri""rustc""rust-analyzer""rustfmt""rust-std""rust-src"])inputs.claude-code.packages.${pkgs.stdenv.hostPlatform.system}.defaultpkgs.bashpkgs.curlpkgs.forgejo-clipkgs.gccpkgs.gitpkgs.gnutarpkgs.gzippkgs.justpkgs.jqpkgs.nixpkgs.nodejspkgs.nushellpkgs.opencodepkgs.opensslpkgs.pkg-configpkgs.ripgreppkgs.sqlx-clipkgs.whichpkgs.xz] ++ lib.optionals config.ci-runner.withDocker [pkgs.dockerpkgs.docker-compose] ++ config.ci-runner.extraHostPackages;};settings.cache.enabled = false;};config = mkIf config.ci-runner.enable {users.groups.gitea-runner = {};age.secrets.forgejoRunnerToken.rekeyFile = config.ci-runner.tokenFile;url = mkOption {type = types.str;default = "https://git.plumj.am/";description = "Forgejo instance URL";};labels = mkOption {type = types.listOf types.str;default = [ "self-hosted:host" ];description = "Runner labels";};extraHostPackages = mkOption {type = types.listOf types.package;default = [ ];description = "Extra packages to add to the runner";};withDocker = mkOption {type = types.bool;default = false;description = "Include docker and docker-compose";};services.gitea-actions-runner = {package = pkgs.forgejo-runner;instances.${config.networking.hostName} = enabled {name = config.networking.hostName;url = config.ci-runner.url;labels = config.ci-runner.labels;tokenFile = config.age.secrets.forgejoRunnerToken.path;users.users.gitea-runner = {description = "gitea-runner";isSystemUser = true;group = "gitea-runner";};options.ci-runner = {enable = lib.mkEnableOption "forgejo CI runner";tokenFile = mkOption {type = types.path;description = "Path to the runner token file";};{ self, config, lib, pkgs, inputs, ... }: letinherit (lib) enabled mkIf mkOption types;
{ self, config, lib, ... }: letinherit (config.networking) domain;inherit (lib) enabled merge;fqdn = domain;root = "/var/www/site";in {imports = [(self + /modules/nginx.nix)];services.nginx = enabled {virtualHosts."www.${fqdn}" = merge config.services.nginx.sslTemplate {locations."/".return = "301 https://${fqdn}$request_uri";};virtualHosts._ = merge config.services.nginx.sslTemplate {locations."/".return = "301 https://${fqdn}/404";};};}virtualHosts.${fqdn} = merge config.services.nginx.sslTemplate {inherit root;locations."/" = {tryFiles = "$uri $uri.html $uri/index.html =404";extraConfig = ''proxy_hide_header Content-Security-Policy;add_header X-Frame-Options DENY always;add_header X-Content-Type-Options nosniff always;add_header X-XSS-Protection "1; mode=block" always;add_header Permissions-Policy "camera=(), geolocation=(), payment=(), usb=()" always;add_header Referrer-Policy no-referrer always;'';};locations."~ ^/assets/(fonts|icons|images)/".extraConfig = /* nginx */ ''expires max;${config.services.nginx.headers}add_header Cache-Control $cache_header always;'';extraConfig = /* nginx */ ''error_page 404 /404.html;${config.services.nginx.goatCounterTemplate}'';locations."/404".extraConfig = /* nginx */ ''internal;'';};add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain} kit.fontawesome.com https://kit.fontawesome.com; script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain} kit.fontawesome.com https://kit.fontawesome.com; img-src 'self' data: https: ghchart.rshah.org;" always;};virtualHosts."nerd.${fqdn}" = merge config.services.nginx.sslTemplate {locations."/" = {tryFiles = "$uri $uri.html $uri/index.html =404";extraConfig = ''proxy_hide_header Content-Security-Policy;add_header X-Frame-Options DENY always;add_header X-Content-Type-Options nosniff always;add_header X-XSS-Protection "1; mode=block" always;add_header Permissions-Policy "camera=(), geolocation=(), payment=(), usb=()" always;add_header Referrer-Policy no-referrer always;'';};locations."~ ^/assets/(fonts|icons|images)/".extraConfig = /* nginx */ ''expires max;${config.services.nginx.headers}add_header Cache-Control $cache_header always;'';extraConfig = /* nginx */ ''error_page 404 /404.html;${config.services.nginx.goatCounterTemplate}'';locations."/404".extraConfig = /* nginx */ ''internal;'';add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain} kit.fontawesome.com https://kit.fontawesome.com; script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain} kit.fontawesome.com https://kit.fontawesome.com; img-src 'self' data: https: ghchart.rshah.org;" always;root = "${root}/nerd";
{ self, config, lib, pkgs, ... }: letinherit (lib) enabled merge mkIf mkOption types;inherit (config.networking) domain;in {imports = [(self + /modules/nginx.nix)];options.cache = {enable = lib.mkEnableOption "nix-serve cache server";fqdn = lib.mkOption {type = types.str;example = "cache1.example.com";description = "Fully qualified domain name for the cache";};port = lib.mkOption {type = types.port;default = 8006;description = "Port for nix-serve to listen on";};secretKeyFile = lib.mkOption {type = types.path;example = "/run/agenix/nixServeKey";description = "Path to the secret key file for signing the cache";};};config = mkIf config.cache.enable {services.nix-serve = enabled {package = pkgs.nix-serve-ng;bindAddress = "127.0.0.1";port = config.cache.port;};services.nginx.virtualHosts.${config.cache.fqdn} = merge config.services.nginx.sslTemplate {locations."= /".return = "301 https://${domain}/404";locations."/".proxyPass = "http://127.0.0.1:${toString config.cache.port}";};};}secretKeyFile = config.age.secrets.nixServeKey.path;age.secrets.nixServeKey = {rekeyFile = config.cache.secretKeyFile;owner = "root";};
inherit (config.networking) domain;fqdn = "git.${domain}";port = 8001;in {imports = [];};};};database = {type = "sqlite3";services.forgejo = enabled {lfs = enabled;user = "forgejo";package = pkgs.forgejo; # The service version is ~11 so better to specify and get the latest.settings = letin {default.APP_NAME = description;attachment.ALLOWED_TYPES = "*/*";cache.ENABLED = true;# archive cleanup cron job"cron.archive_cleanup" = letinterval = "4h";in {SCHEDULE = "@every ${interval}";OLDER_THAN = interval;};other = {SHOW_FOOTER_TEMPLATE_LOAD_TIME = false;SHOW_FOOTER_VERSION = false;};repository = {DEFAULT_BRANCH = "master";DEFAULT_MERGE_STYLE = "rebase-merge";DEFAULT_REPO_UNITS = "repo.code, repo.issues, repo.pulls";DEFAULT_PUSH_CREATE_PRIVATE = false;ENABLE_PUSH_CREATE_ORG = true;ENABLE_PUSH_CREATE_USER = true;DISABLE_STARS = true;};"repository.upload" = {FILE_MAX_SIZE = 100;MAX_FILES = 10;};server = {DOMAIN = domain;ROOT_URL = "https://${fqdn}/";LANDING_PAGE = "/explore";HTTP_ADDR = "::1";HTTP_PORT = port;DISABLE_ROUTER_LOG = true;};service.DISABLE_REGISTRATION = true;session = {COOKIE_SECURE = true;SAME_SITE = "strict";};"ui.meta" = {AUTHOR = description;DESCRIPTION = description;};};}};services.nginx.virtualHosts.${fqdn} = lib.merge config.services.nginx.sslTemplate {locations."/".proxyPass = "http://[::1]:${toString port}";};extraConfig = '''';${config.services.nginx.goatCounterTemplate}SSH_DOMAIN = fqdn;SSH_PORT = 22;START_SSH_SERVER = false;packages.ENABLED = true;description = "PlumJam's Git Forge";timerConfig = {OnCalendar = "daily";Persistent = true;systemd.timers.forgejo-backup = {description = "Run Forgejo backup daily";wantedBy = [ "timers.target" ];serviceConfig = {Type = "oneshot";User = "forgejo";};};# keep only last 7 backupsls -1t /var/backup/forgejo/ | tail -n +8 | xargs -r rm -rf'';script = ''mkdir -p /var/backup/forgejocp -r /var/lib/forgejo /var/backup/forgejo/$(date +%Y%m%d_%H%M%S)# backup configuration for sqlite database and datasystemd.services.forgejo-backup = {description = "Backup Forgejo data and database";after = [ "forgejo.service" ];# combine AcceptEnv settings for SSH and Git protocolservices.openssh.settings.AcceptEnv = mkForce [ "SHELLS" "COLORTERM" "GIT_PROTOCOL" ];age.secrets.forgejoAdminPassword = {owner = "forgejo";};rekeyFile = self + /secrets/plum-forgejo-password.age;./nginx.nixinherit (lib) enabled mkForce;{ pkgs, self, config, lib, ... }: let
{ self, config, lib, pkgs, ... }: letinherit (config.networking) domain;inherit (lib) enabled merge;app_port = 3000;app_user = "dr-radka";app_group = "dr-radka";app_dir = "/var/lib/dr-radka";build_dir = "${app_dir}/build";in {imports = [(self + /modules/nginx.nix)];users.users.${app_user} = {isSystemUser = true;group = app_group;home = app_dir;createHome = true;};users.groups.${app_group} = {};systemd.services.dr-radka = {description = "Dr. Radka SvelteKit Application";after = [ "network.target" ];wantedBy = [ "multi-user.target" ];serviceConfig = {Type = "simple";User = app_user;Group = app_group;WorkingDirectory = build_dir;ExecStart = "${pkgs.bun}/bin/bun run ./index.js";Restart = "always";RestartSec = 5;EnvironmentFile = config.age.secrets.dr-radka-environment.path;# hardeningNoNewPrivileges = true;ProtectSystem = "strict";ProtectHome = true;ReadWritePaths = [ app_dir ];PrivateTmp = true;ProtectKernelTunables = true;ProtectKernelModules = true;ProtectControlGroups = true;};environment = {NODE_ENV = "production";PORT = toString app_port;HOST = "127.0.0.1";};path = with pkgs; [ bun ];};services.nginx = enabled {virtualHosts.${domain} = merge config.services.nginx.sslTemplate {extraConfig = '''';${config.services.nginx.goatCounterTemplate}locations."/" = {proxyPass = "http://127.0.0.1:${toString app_port}";proxyWebsockets = true;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;proxy_cache_bypass $http_upgrade;# override csp for built app requirements and maintain security headersproxy_hide_header Content-Security-Policy;add_header X-Frame-Options DENY always;add_header X-Content-Type-Options nosniff always;add_header X-XSS-Protection "1; mode=block" always;add_header Permissions-Policy "camera=(), geolocation=(), payment=(), usb=()" always;add_header Referrer-Policy no-referrer always;'';};};};environment.systemPackages = with pkgs; [nodejs_22bun];}# need to fix because I can't access nested routes in sanity presentation modeadd_header Content-Security-Policy "script-src 'self' 'unsafe-inline' 'unsafe-eval' ${domain} *.${domain} cdn.jsdelivr.net unpkg.com *.posthog.com *.sanity.io *.googletagmanager.com *.google-analytics.com analytics.plumj.am; object-src 'self' ${domain} *.${domain}; base-uri 'self'; frame-ancestors 'self' dr-radka.sanity.studio *.sanity.io; form-action 'self' ${domain} *.${domain}; font-src 'self' ${domain} *.${domain} cdn.jsdelivr.net; connect-src 'self' ${domain} *.${domain} unpkg.com *.posthog.com *.sanity.io *.googletagmanager.com *.google-analytics.com analytics.plumj.am; img-src 'self' ${domain} *.${domain} unpkg.com *.tile.openstreetmap.org *.sanity.io cdn.sanity.io www.googletagmanager.com data:;" always;# Redirect www.dr-radka.pl to dr-radka.plvirtualHosts."www.${domain}" = merge config.services.nginx.sslTemplate {locations."/".return = "301 https://${domain}$request_uri";};ORIGIN = "https://${domain}";