commit 7e444146d2c0db66c496b1eb89206ed628a1ed94 Author: Philipp Conrad Date: Thu May 14 13:39:10 2026 +0200 initial commit diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..0f3c676 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,4 @@ +creation_rules: + - path_regex: secrets.yaml$ + key_group: + age: "age1d8lk7psq7dc3v0dkdymhyp4afvy056twxlersgzgmnp64h2vw39q22ertf" diff --git a/configuration/acme.nix b/configuration/acme.nix new file mode 100644 index 0000000..23bc473 --- /dev/null +++ b/configuration/acme.nix @@ -0,0 +1,24 @@ +{ config, lib, ... }: +{ + options.domainName = lib.mkOption { type = lib.types.str; }; + config = { + domainName = "fredinand.xyz"; + + security.acme = { + acceptTerms = true; + defaults.email = "admin@${config.domainName}"; + certs = { + "${config.domainName}" = { + dnsProvider = "porkbun"; + environmentFile = config.sops.secrets."porkbun".path; + group = config.services.nginx.group; + extraDomainNames = [ + "mail.${config.domainName}" + "www.${config.domainName}" + ]; + }; + }; + }; + sops.secrets."porkbun" = {}; + }; +} diff --git a/configuration/benjamin.pub b/configuration/benjamin.pub new file mode 100644 index 0000000..7b32a40 --- /dev/null +++ b/configuration/benjamin.pub @@ -0,0 +1,2 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXHTk4/OdvFYjhyg+gfHYumJ79UjAESFjj5iy+u7k17 benjamin@DESKTOP- +RVCJMSB diff --git a/configuration/configuration.nix b/configuration/configuration.nix new file mode 100644 index 0000000..3e14a7d --- /dev/null +++ b/configuration/configuration.nix @@ -0,0 +1,79 @@ +{ pkgs, user, inputs, lib, ... }: +{ + boot = { + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + loader.systemd-boot.configurationLimit = 5; + }; + + programs.nix-ld.enable = true; + programs.nix-ld.libraries = with pkgs; [ + libgcc.lib + glibc_multi.out + ]; + + networking.firewall.enable = true; + networking.networkmanager.enable = true; + + time.timeZone = "Europe/Berlin"; + i18n.defaultLocale = "en_US.UTF-8"; + i18n.extraLocaleSettings = { + LC_ADDRESS = "de_DE.UTF-8"; + LC_IDENTIFICATION = "de_DE.UTF-8"; + LC_MEASUREMENT = "de_DE.UTF-8"; + LC_MONETARY = "de_DE.UTF-8"; + LC_NAME = "de_DE.UTF-8"; + LC_NUMERIC = "de_DE.UTF-8"; + LC_PAPER = "de_DE.UTF-8"; + LC_TELEPHONE = "de_DE.UTF-8"; + LC_TIME = "de_DE.UTF-8"; + }; + + console.keyMap = "de"; + + users.users.${user} = { + shell = pkgs.zsh; + isNormalUser = true; + description = "${user}"; + group = "${user}"; + extraGroups = [ + "networkmanager" + "wheel" + ]; + initialPassword = "123"; + openssh.authorizedKeys.keyFiles = [ + ./benjamin.pub + ./philipp.pub + ]; + }; + users.groups.admin = {}; + programs.zsh.enable = true; + programs.zsh.enableCompletion = false; + + environment.systemPackages = with pkgs; [ + neovim + nettools + git + htop + ]; + + nixpkgs.config.allowUnfree = true; + + services.openssh.enable = true; + services.openssh.settings.PasswordAuthentication = false; + + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + nix.settings.trusted-users = [ "@wheel" ]; + nix.nixPath = [ "nixpkgs=${inputs.nixpkgs}" ]; + + system.stateVersion = "25.11"; + + documentation.enable = true; + documentation.man.enable = true; + documentation.doc.enable = true; + + boot.kernel.sysctl."kernel.sysrq" = 502; + +} diff --git a/configuration/default.nix b/configuration/default.nix new file mode 100644 index 0000000..f604a4b --- /dev/null +++ b/configuration/default.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + files = lib.attrNames (lib.filterAttrs (_: type: type == "regular") (builtins.readDir ./.)); + dirs = lib.attrNames (lib.filterAttrs (_: type: type == "directory") (builtins.readDir ./.)); + nixFiles = lib.filter (f: lib.hasSuffix ".nix" f && !lib.hasSuffix "default.nix" f) files; + imports = lib.map (f: ./${f}) (nixFiles ++ dirs); +in { imports = imports; } diff --git a/configuration/factorio/default.nix b/configuration/factorio/default.nix new file mode 100644 index 0000000..e9d525c --- /dev/null +++ b/configuration/factorio/default.nix @@ -0,0 +1,13 @@ +{ pkgs, config, ... }: +{ + services.factorio = { + enable = true; + package = pkgs.factorio-headless.override { versionsJson = ./versions.json; }; + extraArgs = [ + "--rcon-bind=localhost:27015" + "--rcon-password=$(cat ${config.sops.secrets."factorio/rconpass".path})" + ]; + }; + sops.secrets."factorio/rconpass" = {}; + networking.firewall.allowedUDPPorts = [ 34197 ]; +} diff --git a/configuration/factorio/update.py b/configuration/factorio/update.py new file mode 100755 index 0000000..a2322a3 --- /dev/null +++ b/configuration/factorio/update.py @@ -0,0 +1,289 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ ps.absl-py ps.requests ])" + +from collections import defaultdict +import copy +from dataclasses import dataclass +import json +import os.path +from typing import Callable, Dict + +from absl import app +from absl import flags +from absl import logging +import requests + + +FACTORIO_RELEASES = "https://factorio.com/api/latest-releases" +FACTORIO_HASHES = "https://factorio.com/download/sha256sums/" + + +FLAGS = flags.FLAGS + +flags.DEFINE_string("out", "", "Output path for versions.json.") +flags.DEFINE_list( + "release_type", + "", + "If non-empty, a comma-separated list of release types to update (e.g. alpha).", +) +flags.DEFINE_list( + "release_channel", + "", + "If non-empty, a comma-separated list of release channels to update (e.g. experimental).", +) + + +@dataclass +class System: + nix_name: str + url_name: str + tar_name: str + + +@dataclass +class ReleaseType: + name: str + hash_filename_format: list[str] + needs_auth: bool = False + + +@dataclass +class ReleaseChannel: + name: str + + +FactorioVersionsJSON = Dict[str, Dict[str, str]] +OurVersionJSON = Dict[str, Dict[str, Dict[str, Dict[str, str]]]] + +FactorioHashes = Dict[str, str] + + +SYSTEMS = [ + System(nix_name="x86_64-linux", url_name="linux64", tar_name="x64"), +] + +RELEASE_TYPES = [ + ReleaseType( + "alpha", + needs_auth=True, + hash_filename_format=["factorio_linux_{version}.tar.xz"], + ), + ReleaseType("demo", hash_filename_format=["factorio-demo_linux_{version}.tar.xz"]), + ReleaseType( + "headless", + hash_filename_format=[ + "factorio-headless_linux_{version}.tar.xz", + "factorio_headless_x64_{version}.tar.xz", + ], + ), + ReleaseType( + "expansion", + needs_auth=True, + hash_filename_format=["factorio-space-age_linux_{version}.tar.xz"], + ), +] + +RELEASE_CHANNELS = [ + ReleaseChannel("experimental"), + ReleaseChannel("stable"), +] + + +def find_versions_json() -> str: + if FLAGS.out: + return FLAGS.out + try_paths = ["pkgs/by-name/fa/factorio/versions.json", "versions.json"] + for path in try_paths: + if os.path.exists(path): + return path + raise Exception( + "Couldn't figure out where to write versions.json; try specifying --out" + ) + + +def fetch_versions() -> FactorioVersionsJSON: + return json.loads(requests.get(FACTORIO_RELEASES).text) + + +def fetch_hashes() -> FactorioHashes: + resp = requests.get(FACTORIO_HASHES) + resp.raise_for_status() + out = {} + for ln in resp.text.split("\n"): + ln = ln.strip() + if not ln: + continue + sha256, filename = ln.split() + out[filename] = sha256 + return out + + +def generate_our_versions(factorio_versions: FactorioVersionsJSON) -> OurVersionJSON: + def rec_dd(): + return defaultdict(rec_dd) + + output = rec_dd() + + # Deal with times where there's no experimental version + for rc in RELEASE_CHANNELS: + if rc.name not in factorio_versions or not factorio_versions[rc.name]: + factorio_versions[rc.name] = factorio_versions["stable"] + for rt in RELEASE_TYPES: + if ( + rt.name not in factorio_versions[rc.name] + or not factorio_versions[rc.name][rt.name] + ): + factorio_versions[rc.name][rt.name] = factorio_versions["stable"][ + rt.name + ] + + for system in SYSTEMS: + for release_type in RELEASE_TYPES: + for release_channel in RELEASE_CHANNELS: + version = factorio_versions[release_channel.name].get(release_type.name) + if version is None: + continue + this_release = { + "name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz", + "url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}", + "version": version, + "needsAuth": release_type.needs_auth, + "candidateHashFilenames": [ + fmt.format(version=version) + for fmt in release_type.hash_filename_format + ], + "tarDirectory": system.tar_name, + } + output[system.nix_name][release_type.name][release_channel.name] = ( + this_release + ) + return output + + +def iter_version( + versions: OurVersionJSON, + it: Callable[[str, str, str, Dict[str, str]], Dict[str, str]], +) -> OurVersionJSON: + versions = copy.deepcopy(versions) + for system_name, system in versions.items(): + for release_type_name, release_type in system.items(): + for release_channel_name, release in release_type.items(): + release_type[release_channel_name] = it( + system_name, release_type_name, release_channel_name, dict(release) + ) + return versions + + +def merge_versions(old: OurVersionJSON, new: OurVersionJSON) -> OurVersionJSON: + """Copies already-known hashes from version.json to avoid having to re-fetch.""" + + def _merge_version( + system_name: str, + release_type_name: str, + release_channel_name: str, + release: Dict[str, str], + ) -> Dict[str, str]: + old_system = old.get(system_name, {}) + old_release_type = old_system.get(release_type_name, {}) + old_release = old_release_type.get(release_channel_name, {}) + if FLAGS.release_type and release_type_name not in FLAGS.release_type: + logging.info( + "%s/%s/%s: not in --release_type, not updating", + system_name, + release_type_name, + release_channel_name, + ) + return old_release + if FLAGS.release_channel and release_channel_name not in FLAGS.release_channel: + logging.info( + "%s/%s/%s: not in --release_channel, not updating", + system_name, + release_type_name, + release_channel_name, + ) + return old_release + if "sha256" not in old_release: + logging.info( + "%s/%s/%s: not copying sha256 since it's missing", + system_name, + release_type_name, + release_channel_name, + ) + return release + if not all( + old_release.get(k, None) == release[k] for k in ["name", "version", "url"] + ): + logging.info( + "%s/%s/%s: not copying sha256 due to mismatch", + system_name, + release_type_name, + release_channel_name, + ) + return release + release["sha256"] = old_release["sha256"] + return release + + return iter_version(new, _merge_version) + + +def fill_in_hash( + versions: OurVersionJSON, factorio_hashes: FactorioHashes +) -> OurVersionJSON: + """Fill in sha256 hashes for anything missing them.""" + + def _fill_in_hash( + system_name: str, + release_type_name: str, + release_channel_name: str, + release: Dict[str, str], + ) -> Dict[str, str]: + for candidate_filename in release["candidateHashFilenames"]: + if candidate_filename in factorio_hashes: + release["sha256"] = factorio_hashes[candidate_filename] + break + else: + logging.error( + "%s/%s/%s: failed to find any of %s in %s", + system_name, + release_type_name, + release_channel_name, + release["candidateHashFilenames"], + FACTORIO_HASHES, + ) + return release + if "sha256" in release: + logging.info( + "%s/%s/%s: skipping fetch, sha256 already present", + system_name, + release_type_name, + release_channel_name, + ) + return release + return release + + return iter_version(versions, _fill_in_hash) + + +def main(argv): + factorio_versions = fetch_versions() + factorio_hashes = fetch_hashes() + new_our_versions = generate_our_versions(factorio_versions) + old_our_versions = None + our_versions_path = find_versions_json() + if our_versions_path: + logging.info("Loading old versions.json from %s", our_versions_path) + with open(our_versions_path, "r") as f: + old_our_versions = json.load(f) + if old_our_versions: + logging.info("Merging in old hashes") + new_our_versions = merge_versions(old_our_versions, new_our_versions) + logging.info("Updating hashes from Factorio SHA256") + new_our_versions = fill_in_hash(new_our_versions, factorio_hashes) + with open(our_versions_path, "w") as f: + logging.info("Writing versions.json to %s", our_versions_path) + json.dump(new_our_versions, f, sort_keys=True, indent=2) + f.write("\n") + + +if __name__ == "__main__": + app.run(main) diff --git a/configuration/factorio/versions.json b/configuration/factorio/versions.json new file mode 100644 index 0000000..2e0208d --- /dev/null +++ b/configuration/factorio/versions.json @@ -0,0 +1,102 @@ +{ + "x86_64-linux": { + "alpha": { + "experimental": { + "candidateHashFilenames": [ + "factorio_linux_2.0.76.tar.xz" + ], + "name": "factorio_alpha_x64-2.0.76.tar.xz", + "needsAuth": true, + "sha256": "b1e50891bdc69cce3fdaee4f840cbe4658e18d465309ac87915b6fc41900754c", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/alpha/linux64", + "version": "2.0.76" + }, + "stable": { + "candidateHashFilenames": [ + "factorio_linux_2.0.76.tar.xz" + ], + "name": "factorio_alpha_x64-2.0.76.tar.xz", + "needsAuth": true, + "sha256": "b1e50891bdc69cce3fdaee4f840cbe4658e18d465309ac87915b6fc41900754c", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/alpha/linux64", + "version": "2.0.76" + } + }, + "demo": { + "experimental": { + "candidateHashFilenames": [ + "factorio-demo_linux_2.0.76.tar.xz" + ], + "name": "factorio_demo_x64-2.0.76.tar.xz", + "needsAuth": false, + "sha256": "d5caee49636290b678d35adc59d2fc80ecbdbad8bf420731f1317971c89f941b", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/demo/linux64", + "version": "2.0.76" + }, + "stable": { + "candidateHashFilenames": [ + "factorio-demo_linux_2.0.76.tar.xz" + ], + "name": "factorio_demo_x64-2.0.76.tar.xz", + "needsAuth": false, + "sha256": "d5caee49636290b678d35adc59d2fc80ecbdbad8bf420731f1317971c89f941b", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/demo/linux64", + "version": "2.0.76" + } + }, + "expansion": { + "experimental": { + "candidateHashFilenames": [ + "factorio-space-age_linux_2.0.76.tar.xz" + ], + "name": "factorio_expansion_x64-2.0.76.tar.xz", + "needsAuth": true, + "sha256": "dc24bbbc1b6a619e4425d9a720bcfafce3463d908ab369a6cd1bee1a99332b4f", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/expansion/linux64", + "version": "2.0.76" + }, + "stable": { + "candidateHashFilenames": [ + "factorio-space-age_linux_2.0.76.tar.xz" + ], + "name": "factorio_expansion_x64-2.0.76.tar.xz", + "needsAuth": true, + "sha256": "dc24bbbc1b6a619e4425d9a720bcfafce3463d908ab369a6cd1bee1a99332b4f", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/expansion/linux64", + "version": "2.0.76" + } + }, + "headless": { + "experimental": { + "candidateHashFilenames": [ + "factorio-headless_linux_2.0.76.tar.xz", + "factorio_headless_x64_2.0.76.tar.xz" + ], + "name": "factorio_headless_x64-2.0.76.tar.xz", + "needsAuth": false, + "sha256": "ef3663f66146d76342f7c09a3f743792636f8cd95c39ea26cfca5bd2e0e92430", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/headless/linux64", + "version": "2.0.76" + }, + "stable": { + "candidateHashFilenames": [ + "factorio-headless_linux_2.0.76.tar.xz", + "factorio_headless_x64_2.0.76.tar.xz" + ], + "name": "factorio_headless_x64-2.0.76.tar.xz", + "needsAuth": false, + "sha256": "ef3663f66146d76342f7c09a3f743792636f8cd95c39ea26cfca5bd2e0e92430", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/2.0.76/headless/linux64", + "version": "2.0.76" + } + } + } +} diff --git a/configuration/foundryvtt/default.nix b/configuration/foundryvtt/default.nix new file mode 100644 index 0000000..4bd1558 --- /dev/null +++ b/configuration/foundryvtt/default.nix @@ -0,0 +1,93 @@ +{ pkgs, lib, config, user, ... }: +let fhs = (pkgs.buildFHSEnv { + name = "node-fhs-env"; + targetPkgs = pkgs: (with pkgs; [ + libgcc.lib + glibc_multi.out + ]); + runScript = "${lib.getExe pkgs.nodejs_25}"; +}); +in { + # systemd.packages = [ + # (pkgs.writeTextFile { + # name = "foundryvtt@.service"; + # destination = "/etc/systemd/system/foundryvtt@.service"; + # text = '' + # [Unit] + # Description = "foundryvtt %i" + # After=network.target + # + # [Service] + # Type=simple + # User=foundryvtt%i + # DynamicUser=yes + # StateDirectory=foundryvtt%i + # ExecStart=${lib.getExe pkgs.nodejs_25} /var/lib/foundryvtt%i/foundryvtt/main.js --dataPath=/var/lib/foundryvtt%i/foundrydata --port=3000%i + # Restart=on-failure + # RestartSec=30 + # + # [Install] + # WantedBy=default.target + # ''; + # }) + # ]; + # security.acme.certs."${config.domainName}".extraDomainNames = [ "foundry.${config.domainName}" ]; + # services.nginx.virtualHosts."foundry.${config.domainName}" = { + # useACMEHost = config.domainName; + # addSSL = true; + # locations."/".proxyPass = "http://localhost:30000"; + # }; + security.acme.certs."${config.domainName}".extraDomainNames = [ + "mitchskeller.${config.domainName}" + "inferno.${config.domainName}" + "nixland.${config.domainName}" + ]; + services.nginx.virtualHosts."mitchskeller.${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/" = { + # make sure websocket connection is forwarded + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ''; + proxyPass = "http://localhost:30000"; + }; + }; + services.nginx.virtualHosts."inferno.${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/" = { + # make sure websocket connection is forwarded + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ''; + proxyPass = "http://localhost:30001"; + }; + }; + services.nginx.virtualHosts."nixland.${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/" = { + # make sure websocket connection is forwarded + extraConfig = '' + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + ''; + proxyPass = "http://localhost:30002"; + }; + }; + + virtualisation.docker.enable = true; + users.users.${user}.extraGroups = [ "docker" ]; + + users.users.foundry = { + shell = pkgs.zsh; + isNormalUser = true; + group = "${user}"; + openssh.authorizedKeys.keyFiles = [ + ./mitch.pub + ] ++ config.users.users.${user}.openssh.authorizedKeys.keyFiles; + }; +} diff --git a/configuration/foundryvtt/mitch.pub b/configuration/foundryvtt/mitch.pub new file mode 100644 index 0000000..21cc645 --- /dev/null +++ b/configuration/foundryvtt/mitch.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBvSPgHhppL0aKGIPJlpu6oWgQ5yEsBYyRExp9J5ofTc michi@Mitch-Rechner diff --git a/configuration/foundryvtt/package.nix b/configuration/foundryvtt/package.nix new file mode 100644 index 0000000..8d3a695 --- /dev/null +++ b/configuration/foundryvtt/package.nix @@ -0,0 +1,10 @@ +{ lib +, stdenvNoCC +, unzip +}: stdenvNoCC.mkDerivation (finalAttrs: { + pname = "foundryvtt"; + version = "14.360"; + src = ./FoundryVTT-Node-14.360.zip; + unpackPhase = "${lib.getExe unzip} $src"; + buildPhase = "cp -r . $out"; +}) diff --git a/configuration/gitea.nix b/configuration/gitea.nix new file mode 100644 index 0000000..4f693be --- /dev/null +++ b/configuration/gitea.nix @@ -0,0 +1,10 @@ +{ config, ... }: +{ + services.gitea.enable = true; + security.acme.certs."${config.domainName}".extraDomainNames = [ "git.${config.domainName}" ]; + services.nginx.virtualHosts."git.${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/".proxyPass = "http://localhost:${toString config.services.gitea.settings.server.HTTP_PORT}"; + }; +} diff --git a/configuration/homepage.nix b/configuration/homepage.nix new file mode 100644 index 0000000..d237a81 --- /dev/null +++ b/configuration/homepage.nix @@ -0,0 +1,100 @@ +{ config, ... }: +{ + services.homepage-dashboard = { + enable = true; + services = [ + { + "Filesharing" = [ + { + "Nextcloud" = { + icon = "nextcloud.png"; + description = "Nextcloud"; + href = "https://${config.services.nextcloud.hostName}/"; + widget = { + type = "nextcloud"; + url = "https://${config.services.nextcloud.hostName}/"; + key = "d8287cef8704255787c7207de873c478"; + }; + }; + } + { + "Syncthing" = { + icon = "syncthing.png"; + href = "https://syncthing.${config.domainName}/"; + }; + } + ]; + } + { + "Communication" = [ + { + "Matrix" = { + icon = "matrix-light.png"; + href = "https://${config.services.dendrite.settings.global.server_name}"; + description = "Matrix Powered Chat"; + }; + } + { + "Teamspeak" = { + icon = "teamspeak.png"; + description = "connect to ${config.domainName}"; + }; + } + ]; + } + { + "Games" = [ + { + "Factorio" = { + icon = "https://wiki.factorio.com/images/Factorio-logo.png"; + description = "connect to ${config.domainName}"; + }; + } + ]; + } + { + "Development" = [ + { + "Gitea Server" = { + icon = "gitea.png"; + href = "https://git.${config.domainName}"; + widget = { + type = "gitea"; + url = "https://git.${config.domainName}"; + key = "23a6cc9ffd664d9ba815c9c0ab6beb42c13578b2"; + }; + }; + } + ]; + } + { + "FoundryVTT Server" = [ + { + "Mitchs Keller" = { + icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png"; + href = "https://mitchskeller.${config.domainName}"; + }; + } + { + "Inferno" = { + icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png"; + href = "https://inferno.${config.domainName}"; + }; + } + { + "Nixland" = { + icon = "https://r2.foundryvtt.com/website-static-public/assets/icons/fvtt.png"; + href = "https://nixland.${config.domainName}"; + }; + } + ]; + } + ]; + }; + + services.nginx.virtualHosts."${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/".proxyPass = "http://localhost:${toString config.services.homepage-dashboard.listenPort}"; + }; +} diff --git a/configuration/matrix.nix b/configuration/matrix.nix new file mode 100644 index 0000000..98b3aaf --- /dev/null +++ b/configuration/matrix.nix @@ -0,0 +1,37 @@ +{ config, ... }: +{ + services.dendrite = { + enable = true; + httpsPort = 44443; + tlsCert = "${config.security.acme.certs.${config.domainName}.directory}/fullchain.pem"; + tlsKey = "${config.security.acme.certs.${config.domainName}.directory}/key.pem"; + environmentFile = config.sops.secrets."matrix/registration".path; + settings = { + global = { + private_key = config.sops.secrets."matrix/private_key".path; + server_name = "matrix.${config.domainName}"; + }; + }; + }; + + users.users.matrix.isNormalUser = true; + systemd.services.dendrite.serviceConfig.User = "matrix"; + sops.secrets."matrix/registration" = { owner = "matrix"; }; + sops.secrets."matrix/private_key" = { owner = "matrix"; }; + systemd.services.dendrite.serviceConfig.Group = "nginx"; + security.acme.certs."${config.domainName}".extraDomainNames = [ config.services.dendrite.settings.global.server_name ]; + services.nginx.virtualHosts.${config.services.dendrite.settings.global.server_name} = { + useACMEHost = config.domainName; + addSSL = true; + locations."/".proxyPass = "https://localhost:${toString config.services.dendrite.httpsPort}"; + locations."/.well-known/matrix/server".extraConfig = + let inherit (config.services.dendrite) httpsPort settings; + in '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '{"m.server":"${settings.global.server_name}:${toString httpsPort}"}'; + ''; + }; + # reachability via ip required for federation + networking.firewall.allowedTCPPorts = [ config.services.dendrite.httpsPort ]; +} diff --git a/configuration/nextcloud.nix b/configuration/nextcloud.nix new file mode 100644 index 0000000..e7df7e1 --- /dev/null +++ b/configuration/nextcloud.nix @@ -0,0 +1,17 @@ +{ pkgs, config, ... }: +{ + services.nextcloud = { + enable = true; + hostName = "nextcloud.${config.domainName}"; + package = pkgs.nextcloud32; + config.dbtype = "pgsql"; + database.createLocally = true; + config.adminpassFile = config.sops.secrets."nextcloud/adminpass".path; + }; + sops.secrets."nextcloud/adminpass" = {}; + security.acme.certs."${config.domainName}".extraDomainNames = [ config.services.nextcloud.hostName ]; + services.nginx.virtualHosts.${config.services.nextcloud.hostName} = { + useACMEHost = config.domainName; + addSSL = true; + }; +} diff --git a/configuration/nginx/backup.nix b/configuration/nginx/backup.nix new file mode 100644 index 0000000..8fe71db --- /dev/null +++ b/configuration/nginx/backup.nix @@ -0,0 +1,68 @@ +{ pkgs, config, ... }: +{ + services.nginx = { + enable = true; + additionalModules = [ pkgs.nginxModules.rtmp ]; + + appendConfig = '' + rtmp { + server { + listen 1935; + chunk_size 1024; + application stream { + live on; + meta copy; + hls on; + hls_path /var/www/html/stream/hls; + hls_playlist_length 10s; + hls_fragment 1s; + dash on; + dash_path /var/www/html/stream/dash; + allow publish all; + allow play all; + on_publish http://127.0.0.1:8000/publish; + on_publish_done http://127.0.0.1:8000/unpublish; + } + } + } + ''; + + virtualHosts."main" = { + listen = [ {addr = "0.0.0.0"; port = 80; } ]; + locations = { + "/" = { + root = ./html; + index = "index.html"; + }; + # "/rtmp_stat" = { + # rtmp_stat = "all"; + # rtmp_stat_stylesheet = "/stat.xsl"; + # }; + "/hls" = { + # types = { + # "application/vnd.apple.mpegurl" = "m3u8"; + # "video/mp2t" = "ts"; + # }; + root = "/tmp"; + }; + "/dash" = { + root = "/tmp"; + }; + "/watch" = { + root = "/usr/local/nginx/html"; + # try_files = "$uri /watch/index.html"; + }; + # # some issue with the go server not handeling this well even with prefix (in config.toml) set correctly + # "/auth" = { + # basicAuth.admin = "123g"; + # proxyPass = "http://localhost:${toString config.services.rtmp-auth.frontendPort}/auth"; + # }; + }; + }; + }; + + systemd.tmpfiles.rules = [ + "d /var/www/html/stream/hls 0755 ${config.services.nginx.user} ${config.services.nginx.user} - -" + "d /var/www/html/stream/dash 0755 ${config.services.nginx.user} ${config.services.nginx.user} - -" + ]; +} diff --git a/configuration/nginx/default.nix b/configuration/nginx/default.nix new file mode 100644 index 0000000..ca83edd --- /dev/null +++ b/configuration/nginx/default.nix @@ -0,0 +1,20 @@ +{ pkgs, ... }: +{ + services.nginx = { + enable = true; + additionalModules = [ pkgs.nginxModules.rtmp ]; + + appendConfig = '' + rtmp { + server { + listen 1935; + chunk_size 1024; + application stream { + live on; + } + } + } + ''; + }; + networking.firewall.allowedTCPPorts = [ 80 443 ]; +} diff --git a/configuration/nginx/html/Fadefillo_Banner_2.png b/configuration/nginx/html/Fadefillo_Banner_2.png new file mode 100644 index 0000000..2a285bc Binary files /dev/null and b/configuration/nginx/html/Fadefillo_Banner_2.png differ diff --git a/configuration/nginx/html/Fadefillo_T-Shirt_3.png b/configuration/nginx/html/Fadefillo_T-Shirt_3.png new file mode 100644 index 0000000..d3f896c Binary files /dev/null and b/configuration/nginx/html/Fadefillo_T-Shirt_3.png differ diff --git a/configuration/nginx/html/Rockabilly.ttf b/configuration/nginx/html/Rockabilly.ttf new file mode 100644 index 0000000..21a5d28 Binary files /dev/null and b/configuration/nginx/html/Rockabilly.ttf differ diff --git a/configuration/nginx/html/favicon.ico b/configuration/nginx/html/favicon.ico new file mode 100644 index 0000000..35efe67 Binary files /dev/null and b/configuration/nginx/html/favicon.ico differ diff --git a/configuration/nginx/html/index.html b/configuration/nginx/html/index.html new file mode 100644 index 0000000..f459b95 --- /dev/null +++ b/configuration/nginx/html/index.html @@ -0,0 +1,228 @@ + + + + + + MitcherWorldWide + + + + +

I Love Mitch

+
+ + +
+
+ + Small Left + Small Left + Small Left + +
+
+ + Left Placeholder +
+
+
+
+ +
+ +
+ +
+ + + + + + diff --git a/configuration/philipp.pub b/configuration/philipp.pub new file mode 100644 index 0000000..86a2c38 --- /dev/null +++ b/configuration/philipp.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDXwc0kkJ5ID0VWDZJ0jW+xdHlVu5KZE0H5RyLka5SrMUaDlWPvHaveEwN8Ty6gGTabzMPoDxd7Tf8xH9w8zTlNmF968a7YU+sB7ChJ9pmFFZZ7EVrWAKNg8FYYDb4Swqd3b+eIlWLz9nA1vdqo2yAUXx7RJrGB6qzI76Jb9tuyX218YygGVOjQoeUWDAA6aJPrcyJ/GzwBfLlC1Iu1+z9FHo/E+sF19MpnE57D/eEbrpTAG+DvlHsZYZRW/stbNV7QXNmQlPjWiuy2cKindOzJqEnD6JQjut9xUg/hObHfZi6a1erJ+RLsX63IMVxKun2x4hTXBCrMwPIGLpj4Q+HYzfhhYT+tQs4h4+unon6YIM0ZysTFvssM2ZTQQRFkKe2SwP4X/ZQcon3p8/foXvNJ9Ba/D8jsGyL8ss2vvciFW3kO7laiVIcLSuVDBaRQ6L4JOUKH4X1y4rTKDkaL2TCzE+Qa2oZT5QfN3zYz4lbyfintY68SUWtCRa4lJgelJoc= philipp@arch diff --git a/configuration/pihole.nix b/configuration/pihole.nix new file mode 100644 index 0000000..d0f5138 --- /dev/null +++ b/configuration/pihole.nix @@ -0,0 +1,30 @@ +{ config, ... }: +let port = "9123"; +host = "pihole.${config.domainName}"; +in { + services.pihole-ftl = { + enable = false; + openFirewallDNS = true; + lists = [ { + url = "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txt"; + type = "block"; + enabled = true; + description = "hagezi blocklist"; + } ]; + settings.dns.upstreams = [ "9.9.9.9" "1.1.1.1" ]; + }; + services.pihole-web = { + enable = config.services.pihole-ftl.enable; + ports = [ "${port}s" ]; + hostName = host; + }; + + security.acme.certs."${config.domainName}".extraDomainNames = [ host ]; + services.nginx.virtualHosts.${host} = { + # TODO get secrets from sops + basicAuth.admin = ""; + useACMEHost = config.domainName; + addSSL = true; + locations."/".proxyPass = "https://localhost:${port}"; + }; +} diff --git a/configuration/rtmp-auth/config.toml b/configuration/rtmp-auth/config.toml new file mode 100644 index 0000000..febfbbc --- /dev/null +++ b/configuration/rtmp-auth/config.toml @@ -0,0 +1,17 @@ +[http] +# List of RTMP apps +applications = ["stream"] + +# Path prefix to allow frontend to run on a subpath +prefix = "" + +# Allow CSRF cookie to be sent across http-connection, not recommended for production +insecure = true + +[store] +# Set store backend (file|consul) +#backend = "file" + +[store.file] +# Configure file storage path relative to working directory +#path = "store.db" diff --git a/configuration/rtmp-auth/default.nix b/configuration/rtmp-auth/default.nix new file mode 100644 index 0000000..6328512 --- /dev/null +++ b/configuration/rtmp-auth/default.nix @@ -0,0 +1,7 @@ +{ + # imports = [ + # ./module.nix + # ]; + # + # services.rtmp-auth.enable = true; +} diff --git a/configuration/rtmp-auth/module.nix b/configuration/rtmp-auth/module.nix new file mode 100644 index 0000000..287a10f --- /dev/null +++ b/configuration/rtmp-auth/module.nix @@ -0,0 +1,40 @@ +{ pkgs, lib, config, ... }: +let cfg = config.services.rtmp-auth; in +{ + options.services.rtmp-auth = { + enable = lib.mkEnableOption "rtmp-auth"; + package = lib.mkOption { + description = "package to use"; + default = pkgs.callPackage ./package.nix {}; + type = lib.types.package; + }; + host = lib.mkOption { + description = "hostname or address to bind to"; + default = "localhost"; + type = lib.types.str; + }; + apiPort = lib.mkOption { + description = "portnumber for the api"; + default = 8000; + type = lib.types.port; + }; + frontendPort = lib.mkOption { + description = "portnumber for the frontend"; + default = 8001; + type = lib.types.port; + }; + }; + config = lib.mkIf cfg.enable { + systemd.services.rtmp-auth = { + wantedBy = [ "default.target" ]; + requiredBy = [ "network.target" ]; + description = "rtmp authentication"; + serviceConfig = { + Type = "simple"; + ExecStart = ''${pkgs.lib.getExe cfg.package} -config ${./config.toml} -apiAddr "${cfg.host}:${toString cfg.apiPort}" -frontendAddr "${cfg.host}:${toString cfg.frontendPort}" ''; + Restart = "on-failure"; + RestartSec = 30; + }; + }; + }; +} diff --git a/configuration/rtmp-auth/package.nix b/configuration/rtmp-auth/package.nix new file mode 100644 index 0000000..2724348 --- /dev/null +++ b/configuration/rtmp-auth/package.nix @@ -0,0 +1,28 @@ +{ lib +, buildGoModule +, fetchFromGitHub +, statik +, protobuf +, protoc-gen-go +}: buildGoModule (finalAttrs: { + pname = "rtmp-auth"; + version = "1.1.0"; + + src = ./src; /* fetchFromGitHub { + owner = "voc"; + repo = "${finalAttrs.pname}"; + rev = "master"; + hash = "sha256-71PvNBKdvkNSqwCHWZZVAHPa1eEx1Ba3RZqmLy4CBn8="; + }; + */ + + preBuild = '' + PATH=$PATH:${protoc-gen-go}/bin ${lib.getExe protobuf} -I=storage/ --go_opt=paths=source_relative --go_out=storage/ storage/storage.proto + ${lib.getExe statik} -f -src=public/ -dest=. + ''; + + vendorHash = "sha256-rZZMLZtCvXJmMKYr4rPLTaHqtV6QouKClZgRYlJM1sw="; + + meta.mainProgram = finalAttrs.pname; + + }) diff --git a/configuration/sops.nix b/configuration/sops.nix new file mode 100644 index 0000000..321679c --- /dev/null +++ b/configuration/sops.nix @@ -0,0 +1,8 @@ +{ inputs, user, ... }: +{ + imports = [ inputs.sops-nix.nixosModules.sops ]; + sops.defaultSopsFile = ../secrets.yaml; + sops.defaultSopsFormat = "yaml"; + sops.age.keyFile = "/home/${user}/.config/sops/age/keys.txt"; +} + diff --git a/configuration/syncthing.nix b/configuration/syncthing.nix new file mode 100644 index 0000000..422d18b --- /dev/null +++ b/configuration/syncthing.nix @@ -0,0 +1,11 @@ +{ config, ... }: +{ + services.syncthing.enable = true; + services.syncthing.guiAddress = "0.0.0.0:8384"; + security.acme.certs."${config.domainName}".extraDomainNames = [ "syncthing.${config.domainName}" ]; + services.nginx.virtualHosts."syncthing.${config.domainName}" = { + useACMEHost = config.domainName; + addSSL = true; + locations."/".proxyPass = "http://localhost:8384"; + }; +} diff --git a/configuration/teamspeak/default.nix b/configuration/teamspeak/default.nix new file mode 100644 index 0000000..c85c62e --- /dev/null +++ b/configuration/teamspeak/default.nix @@ -0,0 +1,34 @@ +{ pkgs, lib, system, ... }: +let ts = pkgs.callPackage ./package.nix { inherit system; }; +in { + systemd.services.teamspeak6-server = { + description = "Teamspeak6 voice communication server daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment.LD_LIBRARY_PATH="${ts}/lib"; + serviceConfig = { + ExecStart = '' + ${lib.getExe ts} --accept-license --db-sql-create-path ${ts}/share/teamspeak/sql/create_sqlite/ --db-sql-path ${ts}/share/teamspeak/sql/ --log-path /var/lib/teamspeak/ + ''; + StateDirectory = "teamspeak"; + WorkingDirectory = "/var/lib/teamspeak"; + DynamicUser = true; + User = "teamspeak"; + Group = "teamspeak"; + Restart = "on-failure"; + }; + }; + networking.firewall.allowedTCPPorts = [ + 30033 # file transfer + 9987 # voice + 10080 # server query http + 10022 # server query ssh + ]; + networking.firewall.allowedUDPPorts = [ + 30033 # file transfer + 9987 # voice + 10080 # server query http + 10022 # server query ssh + ]; +} diff --git a/configuration/teamspeak/package.nix b/configuration/teamspeak/package.nix new file mode 100644 index 0000000..bba1d26 --- /dev/null +++ b/configuration/teamspeak/package.nix @@ -0,0 +1,31 @@ +{ stdenvNoCC +, libssh +, system +}: let +currentSystem = { + "x86_64-linux" = { arch = "amd64"; os = "linux"; }; + "aarch64-linux" = { arch = "arm64"; os = "linux"; }; +}.${system}; +in stdenvNoCC.mkDerivation (finalAttrs: { + name = "teamspeak6-server"; + version = "v6.0.0-beta10"; + src = fetchTarball { + url = "https://github.com/teamspeak/${finalAttrs.name}/releases/download/${finalAttrs.version}/${finalAttrs.name}-${currentSystem.os}-${currentSystem.arch}.tar.xz"; + sha256 = "0lcx1zsab951dywjq6wjkqa9ckzpy6wszvgyjaa97ad8mkk4vdfk"; + }; + builtInputs = [ libssh ]; + + installPhase = '' + mkdir -p $out/bin $out/lib $out/share/teamspeak/ + cp libtsdb_sqlite3.so $out/lib/ + cp libtsdb_mariadb.so $out/lib/ + cp tsserver $out/bin + cp -r serverquerydocs $out/share/teamspeak + cp -r sql $out/share/teamspeak + cp -r doc $out/share/teamspeak + cp CHANGELOG $out/share/teamspeak + cp LICENSE $out/share/teamspeak + cp THIRD_PARTY_LICENSES $out/share/teamspeak + ''; + meta.mainProgram = "tsserver"; +}) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2a9303f --- /dev/null +++ b/flake.lock @@ -0,0 +1,122 @@ +{ + "nodes": { + "flake-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" + } + }, + "nixlib": { + "locked": { + "lastModified": 1736643958, + "narHash": "sha256-tmpqTSWVRJVhpvfSN9KXBvKEXplrwKnSZNAoNPf/S/s=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "1418bc28a52126761c02dd3d89b2d8ca0f521181", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixos-generators": { + "inputs": { + "nixlib": "nixlib", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769813415, + "narHash": "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=", + "owner": "nix-community", + "repo": "nixos-generators", + "rev": "8946737ff703382fda7623b9fab071d037e897d5", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixos-generators", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1775710090, + "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "4c1018dae018162ec878d42fec712642d214fdfa", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixos-generators": "nixos-generators", + "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix", + "systems": "systems" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777944972, + "narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "c591bf665727040c6cc5cb409079acb22dcce33c", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9473bbb --- /dev/null +++ b/flake.nix @@ -0,0 +1,48 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixos-generators = { + url = "github:nix-community/nixos-generators"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + systems.url = "github:nix-systems/default"; + flake-utils = { + url = "github:numtide/flake-utils"; + inputs.systems.follows = "systems"; + }; + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { nixpkgs, nixos-generators, ... }@inputs: + inputs.flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + config.allowUnfree = true; + }; + user = "admin"; + self = inputs.self.packages.${system}; + in + { + packages = { + nixosConfigurations = builtins.mapAttrs (name: modules: + nixpkgs.lib.nixosSystem { + specialArgs = { inherit inputs self user system; }; + inherit system modules; + } + ) self.nixosModules; + nixosModules = (builtins.mapAttrs (name: modules: [ ./configuration ./host/${name} ] ++ modules) { + server = [ ] ; + }); + image = nixos-generators.nixosGenerate { + system = "x86_64-linux"; + format = "qcow-efi"; + specialArgs = { inherit inputs self user system; }; + modules = self.nixosModules.server; + }; + }; + }); +} diff --git a/host/server/default.nix b/host/server/default.nix new file mode 100644 index 0000000..c33d73b --- /dev/null +++ b/host/server/default.nix @@ -0,0 +1,9 @@ +{ + imports = [ + ./hardware-configuration.nix + ]; + networking = { + hostId = "8bccc72b"; + hostName = "server"; + }; +} diff --git a/host/server/hardware-configuration.nix b/host/server/hardware-configuration.nix new file mode 100644 index 0000000..9e46765 --- /dev/null +++ b/host/server/hardware-configuration.nix @@ -0,0 +1,24 @@ +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: + +{ + imports = + [ (modulesPath + "/profiles/qemu-guest.nix") + ]; + + boot.initrd.availableKernelModules = [ "ata_piix" "sr_mod" ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ ]; + boot.extraModulePackages = [ ]; + + fileSystems."/" = + { device = "/dev/disk/by-uuid/b76f5c4c-3d9f-4d26-a698-4d1c3bb60d3a"; + fsType = "ext4"; + }; + + swapDevices = [ ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; +} diff --git a/secrets.yaml b/secrets.yaml new file mode 100644 index 0000000..b07561b --- /dev/null +++ b/secrets.yaml @@ -0,0 +1,23 @@ +nextcloud: + adminpass: ENC[AES256_GCM,data:rLczsAngJv8VVa+7vquHGezRXSt1PxQ6Nr5MzQqqgfu2EP2TC8/qk/LnSqw9YyAIGFZ6lZVX2Qg/Yyly3qSukQ==,iv:TFx10AS2mm1VhlhOH0eGfxWyP5epNR2nnzny3hYlgbw=,tag:cOh+FxRRVEsZL2TiUFS69A==,type:str] +matrix: + registration: ENC[AES256_GCM,data:AVnoSkwClOPw5tYdUvHWKGv5vDqRgT7C4tIzEdoOTrS7O+UzSNMwYZVdb9drNhLLBdOOsROaEoKD6/Jnm53BwpRBcaf1m+M=,iv:J/xK1/UnZ9SF7o4CbLUEIzmelq9QmloEB+FqyHWGUvg=,tag:E1BLQZDc74xyzoKyumrNlw==,type:str] + private_key: ENC[AES256_GCM,data:FMg3JcD6GG4ajPMqXl5XGgC5RDOzkYiA2kHkkkEek8TGcVMoqK/cOrNPQ03+BfYAHaaCXqmmjbIkkDuFSMZgwQ0wKBLHdAtLEsXFnRk3jYgMkP8ftLNlePMy0B/BVrD783C0rsOQrfs7iaj0Ss+PsKvgnwxxMEni5GmjBVyAqwvVtm3kMTwi/A==,iv:alZ2U+zuheJu9/c9ktju13jdvLfkCKsjx8j6KSsFlfw=,tag:lb93QTe62qJDPzeSK6nd9Q==,type:str] +factorio: + rconpass: ENC[AES256_GCM,data:kV7bDnJ3JAnm+snScTCoDW/oATdpkQ+FzbpL60i/pJ4=,iv:TXag8LtF0loSoVN8QO6BD92a+/+8gsEYyeEom4kJdFA=,tag:MXWzxZ42L3vFOWcy8rx5tg==,type:str] +porkbun: ENC[AES256_GCM,data:e49mPfmaDDCKUQ+LnM7YobTkDs6Kt++mNDSDNiSfc6wgZzcBbzMNMiuDO4O7h+CFnMg4qQsXGf5lXw1tnUfMKd2i32EBIzQGJs7qC5Ioydt33rSlTkWmiQM0LKqyd1+Vewv6vHiombFcU38ZLDw30Buy3yNmXOMfzJCQNEGNNafRNH0OdjTReW3miWN4852YXugje6k64ZNCC58htUyx+C0a1fIawPySo1zG+xCzP04=,iv:I3wBqi3qRB3pHNDUklB8mHdwXgOQbvuzPc78rQwAJm0=,tag:c258l7FcCygWKFqXr4SD7g==,type:str] +sops: + age: + - recipient: age1d8lk7psq7dc3v0dkdymhyp4afvy056twxlersgzgmnp64h2vw39q22ertf + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhbGp5ZkJQUlZ5L0RyQXJ4 + QS9lZnpkWGt2ZEJwV1VSWVlVK3JQVzlCYVVJCnc0UXhOczhMYS9uQmFHZWRyelor + bnJQc3V2MmI1bjdyTllnSmdOdFIvd2MKLS0tIEZKM1ZicUEvWHNLNVZyNHRlekhX + bUVpTTk0R2wvb05qM3NycTY1ZVh1N3MKd/ziZmsm09Nn4p01q8ZU9MrdZBs32vrF + 7BlJJUtDEyz2JVpoj0FUbQzngOYxfWeta7+td4OyYog3ktJdTfwwDA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-14T11:07:48Z" + mac: ENC[AES256_GCM,data:xDm/vCRm/HSRPWD75WmE25gPOy465uSD8mAslak6ziFBJKd689LzyaeqhIgZPLGVQsaXYISTJK7faRLOlyE9S08AoC9DQ1JgBiEB535OGGBg/RowFwe7/p3C8SupstbHuXjSX1Sp1uz+3mcpgoDBAcxelM07WheSQitnBe2dDkI=,iv:D5+L0VD3tXeVw0V37LYFuhLPcKN/yUX4BbyYbEdnzLM=,tag:OYExfEWhBuZhY/y+fONJVA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.12.2