diff --git a/configs/nixos/guests/reverse-proxy.nix b/configs/nixos/guests/reverse-proxy.nix index 10e378e..3ad5fa9 100644 --- a/configs/nixos/guests/reverse-proxy.nix +++ b/configs/nixos/guests/reverse-proxy.nix @@ -13,11 +13,6 @@ in modules.services.reverse-proxy.enable = true; services.nginx.virtualHosts = { - "games.alexion.dev" = { - forceSSL = true; - enableACME = true; - locations."/".proxyPass = "http://10.23.20.32"; - }; "git.alexion.dev" = { forceSSL = true; enableACME = true; diff --git a/configs/nixos/guests/romm.nix b/configs/nixos/guests/romm.nix new file mode 100644 index 0000000..7a05853 --- /dev/null +++ b/configs/nixos/guests/romm.nix @@ -0,0 +1,181 @@ +{ + config, + lib, + ... +}: +let + cfg = config.guests.romm; + rommUid = config.registry."stable-uid".${config.networking.hostName}.romm; + redisUid = config.registry."stable-uid".${config.networking.hostName}."redis-romm"; +in +{ + options.guests.romm = { + dataPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/romm"; + description = "Host path mounted as RomM application data."; + }; + + postgresqlPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/romm/postgresql"; + description = "Host path mounted as RomM's PostgreSQL data."; + }; + + redisPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/romm/redis"; + description = "Host path mounted as RomM's Redis data."; + }; + + romsPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/roms"; + description = "Host path mounted as RomM's ROM library."; + }; + + biosPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/bios"; + description = "Host path mounted as RomM's BIOS library."; + }; + + usersPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "/srv/romm/users"; + description = "Host path mounted as RomM's user assets."; + }; + + }; + + config = lib.mkIf cfg.enable { + assertions = + map + (path: { + assertion = path.value != null; + message = "guests.romm.${path.name} must point at the host directory that stores ${path.description}."; + }) + [ + { + name = "dataPath"; + value = cfg.dataPath; + description = "RomM application data"; + } + { + name = "postgresqlPath"; + value = cfg.postgresqlPath; + description = "RomM PostgreSQL data"; + } + { + name = "redisPath"; + value = cfg.redisPath; + description = "RomM Redis data"; + } + { + name = "romsPath"; + value = cfg.romsPath; + description = "RomM's ROM library"; + } + { + name = "biosPath"; + value = cfg.biosPath; + description = "RomM's BIOS library"; + } + { + name = "usersPath"; + value = cfg.usersPath; + description = "RomM's user assets"; + } + ]; + + guests.romm = { + interior = { + modules.services.romm.enable = true; + services.romm.nginx.virtualHost = cfg.reverseProxy.route.domain; + }; + + mounts = { + "/var/lib/romm" = { + directory = cfg.dataPath; + owner = rommUid; + group = "storage"; + mode = "2770"; + }; + "/var/lib/postgresql" = { + directory = cfg.postgresqlPath; + owner = config.ids.uids.postgres; + group = "storage"; + mode = "0700"; + }; + "/var/lib/redis-romm" = { + directory = cfg.redisPath; + owner = redisUid; + group = "storage"; + mode = "0700"; + }; + "/var/lib/romm/library/roms" = { + directory = cfg.romsPath; + owner = rommUid; + group = "storage"; + mode = "2770"; + }; + "/var/lib/romm/library/bios" = { + directory = cfg.biosPath; + owner = rommUid; + group = "storage"; + mode = "2770"; + }; + "/var/lib/romm/assets/users" = { + directory = cfg.usersPath; + owner = rommUid; + group = "storage"; + mode = "2770"; + }; + "/run/secrets/romm.env" = { + file = config.sops.templates."romm.env".path; + readOnly = true; + }; + }; + + limits = { + memory = lib.mkDefault "4G"; + cpu = lib.mkDefault "200%"; + tasksMax = lib.mkDefault 512; + }; + }; + + sops.secrets = + lib.genAttrs + [ + "services/romm/igdb/client-id" + "services/romm/igdb/client-secret" + "services/romm/retroachievements/api-key" + "services/romm/screenscraper/user" + "services/romm/screenscraper/password" + "services/romm/steamgriddb/api-key" + ] + (_: { + sopsFile = ../secrets + "/${config.networking.hostName}.yaml"; + }); + + sops.templates."romm.env" = { + owner = "root"; + group = "storage"; + mode = "0440"; + content = '' + IGDB_CLIENT_ID=${config.sops.placeholder."services/romm/igdb/client-id"} + IGDB_CLIENT_SECRET=${config.sops.placeholder."services/romm/igdb/client-secret"} + SCREENSCRAPER_USER=${config.sops.placeholder."services/romm/screenscraper/user"} + SCREENSCRAPER_PASSWORD=${config.sops.placeholder."services/romm/screenscraper/password"} + RETROACHIEVEMENTS_API_KEY=${config.sops.placeholder."services/romm/retroachievements/api-key"} + STEAMGRIDDB_API_KEY=${config.sops.placeholder."services/romm/steamgriddb/api-key"} + ''; + }; + }; +} diff --git a/configs/nixos/hosts/pikachu/default.nix b/configs/nixos/hosts/pikachu/default.nix index b7bdf4d..9b2c2d3 100644 --- a/configs/nixos/hosts/pikachu/default.nix +++ b/configs/nixos/hosts/pikachu/default.nix @@ -88,6 +88,26 @@ }; }; + guests.romm = { + enable = true; + vlan = 20; + address = "10.23.20.112"; + dataPath = "/pikachu/data/romm"; + postgresqlPath = "/pikachu/data/romm/postgresql"; + redisPath = "/pikachu/data/romm/redis"; + romsPath = "/pikachu/media/games/emulation/roms"; + biosPath = "/pikachu/media/games/emulation/bios"; + usersPath = "/pikachu/media/games/emulation/users"; + reverseProxy = { + enable = true; + route = { + domain = "games.alexion.dev"; + protocol = "https"; + port = 80; + }; + }; + }; + guests.reverse-proxy = { enable = true; vlan = 20; diff --git a/configs/nixos/modules/services/romm.nix b/configs/nixos/modules/services/romm.nix new file mode 100644 index 0000000..0e0c134 --- /dev/null +++ b/configs/nixos/modules/services/romm.nix @@ -0,0 +1,47 @@ +{ + config, + lib, + ... +}: +let + cfg = config.modules.services.romm; +in +{ + options.modules.services.romm = { + enable = lib.mkEnableOption "RomM"; + + environmentFile = lib.mkOption { + type = lib.types.path; + default = "/run/secrets/romm.env"; + readOnly = true; + description = "Path to RomM's environment file."; + }; + }; + + config = lib.mkMerge [ + { + registry."stable-uid".${config.networking.hostName} = { + romm = 20005; + "redis-romm" = 20006; + }; + } + + (lib.mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 80 ]; + + services.romm = { + enable = true; + group = "storage"; + database.createLocally = true; + redis.createLocally = true; + watcher.enable = true; + inherit (cfg) environmentFile; + }; + + users.users = { + romm.uid = config.registry."stable-uid".${config.networking.hostName}.romm; + redis-romm.uid = config.registry."stable-uid".${config.networking.hostName}."redis-romm"; + }; + }) + ]; +} diff --git a/configs/nixos/registry/stable-uid.nix b/configs/nixos/registry/stable-uid.nix index 055bbea..06f821b 100644 --- a/configs/nixos/registry/stable-uid.nix +++ b/configs/nixos/registry/stable-uid.nix @@ -12,7 +12,7 @@ let in { - # Next stable UID: 20005 + # Next stable UID: 20007 type = lib.types.ints.positive; diff --git a/configs/nixos/secrets/pikachu.yaml b/configs/nixos/secrets/pikachu.yaml index a94d646..a2d4ae9 100644 --- a/configs/nixos/secrets/pikachu.yaml +++ b/configs/nixos/secrets/pikachu.yaml @@ -6,6 +6,17 @@ services: lastfm: api-key: ENC[AES256_GCM,data:Zb0I5TpdEqOxLAD6ed2XywFGZp7dfY69vcGiWOUJkJQ=,iv:pO53pHr09jWfzJyTsQbTHtzTmiQ9+RQkDwZJmInIiX8=,tag:io9jkSN7TF2I8+qlSymaYw==,type:str] secret: ENC[AES256_GCM,data:rKhd9i0/tbJlXhc9Wf81IDydjpD8khSdwJEEMK260E4=,iv:btBXWBKJp9br5a3yZsRVegN8Ytzc45Qbk2iNlPbENkQ=,tag:LUbu+6gxfqQvVpddZto7Tg==,type:str] + romm: + igdb: + client-id: ENC[AES256_GCM,data:tme7NzzbOlCmkMwVObWz793mOsCyVIzSjGoFvxpe,iv:5aOMvsDi8Z9aBymbqhbK79X4kJsqaET1y2Py6YUZZ/U=,tag:ih3GghlxiLsVyeTJoqU9/Q==,type:str] + client-secret: ENC[AES256_GCM,data:AqF7B9VaxM/X3sRqY6C5JVWPWqgRIoqy09P9ZUFj,iv:D+vDPUuGGdzKYnTVTRkI9TV/EOJgVECIeNDt9g8NGl0=,tag:MRaTT7TdEZSd+98xthVw1A==,type:str] + retroachievements: + api-key: ENC[AES256_GCM,data:LMzETQCMi9Ib783W0BZDe2PaSUSTVM5a61502RtZLg8=,iv:Hf5nBoVtZdJszQuc2n0LVNMtJCCpJrh40y4g+l+az70=,tag:esfKLpI1W8BG3Pfsg3xxKQ==,type:str] + steamgriddb: + api-key: ENC[AES256_GCM,data:+kBRieIB/dHbvQLbH2gQTQh2j6DQhybVWEGkSCRZrZY=,iv:3h6dhfiV7HfaZhMv93x8+7F3utzYG6fIbTDScA14PVI=,tag:Wo24YnvsIxe7iMRLyxrj2w==,type:str] + screenscraper: + user: ENC[AES256_GCM,data:7hGcFF4xqg==,iv:SFOdYej94Thfn+Te0kXsC9Kq568A1dsnaR+JB90MgBQ=,tag:inIWuFY7f5Tvv8EyV71dYA==,type:str] + password: ENC[AES256_GCM,data:eUWwDDQUSytm/6QZlG5XjbSi41nsBQbm4w==,iv:82S2a3rAVjaF0CXcjXUXErNHMY7MEkjpDL7EVX9cIHw=,tag:uGuVJCv8ThsaNnA0C0WR2Q==,type:str] sops: age: - enc: | @@ -26,7 +37,7 @@ sops: dyjzy0/9Ui1IIXH36AImO5hiEZcrGePV2qPLLQc7ZLngC1jlJuEKJA== -----END AGE ENCRYPTED FILE----- recipient: age1wf5s0n0tgt6ld2ysgu9dc67mj8ylwecgl4utzg7hqwy3kut9zyms7aglmh - lastmodified: "2026-09-12T22:54:25Z" - mac: ENC[AES256_GCM,data:4R3a0ytf4i8aM8iBQ9vRC/q+hvtjLrCTNgbpCB5DbqBMBYmhtGsBmO/0i1yT0N4tydyLaGOMkNfuuuVi3ASLEOKnViy9w3uD9IN0F79W4f2vguWUVIp/T8skvN9wbV9o9xd0LkJzmeVVgzb1H6HKw+rTGjbquleoIHEBDcDDM+k=,iv:0NlckMXlS+idY6UGBF+F0KFLt+pzD7Q/YEQOdXraAYg=,tag:JBBQduV8uhnKz9gPf6HfAQ==,type:str] + lastmodified: "2026-10-20T01:27:06Z" + mac: ENC[AES256_GCM,data:NCt6e/ctZwaBADfrhu1RXtILlClbHEqOAnIu8rE/zMPSwMIPB/kTtfd/ujf+1iRszoHaX4Se1xc0wz1th6NVToFVNarxBBgTk4JMUU8gdMweOjmwlh3GXU3ZcYY/xAXxWF1hxyXSEW78j3S2844H9XXENAq/Bf3mjdCYQ/qr/d4=,iv:4nFyBSmRvF4zSAwvL+xccMwtWrVbHtFKNoTb7f3rSH4=,tag:eCV/7P0TjyMZXKcWBUPXQA==,type:str] unencrypted_suffix: _unencrypted version: 3.13.3