diff --git a/flake.nix b/flake.nix index 32dbd1b..f3074c1 100644 --- a/flake.nix +++ b/flake.nix @@ -175,6 +175,7 @@ system = "x86_64-linux"; nixosModules = serverModules; hmModules = serverHomeModules; + homeManagerUsers = false; }; }; }; diff --git a/home/modules/programs/ssh.nix b/home/modules/programs/ssh.nix index 2d17f1b..79db2a9 100644 --- a/home/modules/programs/ssh.nix +++ b/home/modules/programs/ssh.nix @@ -23,12 +23,12 @@ "blogbox-2" = { hostname = "10.1.0.11"; proxyJump = "alisceon-core"; - user = "alisceon"; + user = "opc"; }; "blogbox-1" = { - hostname = "10.1.0.10"; + hostname = "10.1.0.247"; proxyJump = "alisceon-core"; - user = "alisceon"; + user = "opc"; }; "filurbox" = { hostname = "oci.malice.zone"; diff --git a/nixos/hosts/alisceon-core/configuration.nix b/nixos/hosts/alisceon-core/configuration.nix index 3f1a941..222cfa6 100644 --- a/nixos/hosts/alisceon-core/configuration.nix +++ b/nixos/hosts/alisceon-core/configuration.nix @@ -6,7 +6,6 @@ ../../modules/services/forgejo.nix ../../modules/services/nginx.nix ../../modules/services/oci-authorized-keys.nix - ../../modules/services/oci-secondary-vnics.nix ../../modules/services/tor.nix ]; @@ -70,7 +69,6 @@ defaultShell = "/run/current-system/sw/bin/xonsh"; }; ociAuthorizedKeys.enable = true; - ociSecondaryVnics.enable = true; }; security = { diff --git a/nixos/hosts/blogbox/configuration.nix b/nixos/hosts/blogbox/configuration.nix index 16c259d..22bb128 100644 --- a/nixos/hosts/blogbox/configuration.nix +++ b/nixos/hosts/blogbox/configuration.nix @@ -1,23 +1,121 @@ { lib, pkgs, modulesPath, ... }: +let + siteDomain = "blogbox.alisceon.com"; + repoDir = "/home/alisceon/blogbox-site"; + stateDir = "/var/lib/blogbox"; + publicDir = "${stateDir}/www"; + + updateBlogboxSite = pkgs.writeShellApplication { + name = "update-blogbox-site"; + runtimeInputs = [ + pkgs.coreutils + pkgs.git + pkgs.hugo + pkgs.rsync + ]; + text = '' + set -euo pipefail + + if [ ! -d ${lib.escapeShellArg repoDir}/.git ]; then + echo "${repoDir} is not a git checkout yet; skipping Hugo publish" + exit 0 + fi + + install -d -m 0755 ${lib.escapeShellArg stateDir} ${lib.escapeShellArg publicDir} + + git -C ${lib.escapeShellArg repoDir} pull --ff-only + git -C ${lib.escapeShellArg repoDir} submodule sync --recursive + git -C ${lib.escapeShellArg repoDir} submodule update --init --recursive + + rm -rf ${lib.escapeShellArg stateDir}/hugo-public + hugo \ + --source ${lib.escapeShellArg repoDir} \ + --destination ${lib.escapeShellArg stateDir}/hugo-public \ + --minify \ + --cleanDestinationDir + + rsync -a --delete ${lib.escapeShellArg stateDir}/hugo-public/ ${lib.escapeShellArg publicDir}/ + ''; + }; + + updateNamecheapDyndns = pkgs.writeShellApplication { + name = "update-namecheap-dyndns"; + runtimeInputs = [ + pkgs.coreutils + pkgs.ddclient + ]; + text = '' + set -euo pipefail + + : "''${NAMECHEAP_DOMAIN:?Set NAMECHEAP_DOMAIN in /etc/blogbox-namecheap-ddns.env}" + : "''${NAMECHEAP_PASSWORD:?Set NAMECHEAP_PASSWORD in /etc/blogbox-namecheap-ddns.env}" + : "''${NAMECHEAP_HOSTS:?Set NAMECHEAP_HOSTS in /etc/blogbox-namecheap-ddns.env}" + + config_file="''${RUNTIME_DIRECTORY}/ddclient.conf" + install -m 0600 /dev/null "$config_file" + + { + printf 'daemon=0\n' + printf 'cache=/var/cache/blogbox-dyndns/ddclient.cache\n' + printf 'ssl=yes\n' + printf 'protocol=namecheap\n' + printf 'usev4=webv4, webv4=dynamicdns.park-your-domain.com/getip\n' + printf 'server=dynamicdns.park-your-domain.com\n' + printf 'login=%s\n' "$NAMECHEAP_DOMAIN" + printf 'password=%s\n' "$NAMECHEAP_PASSWORD" + printf '%s\n' "$NAMECHEAP_HOSTS" + } > "$config_file" + + ddclient -file "$config_file" + ''; + }; + + ensureBlogboxSwapfile = pkgs.writeShellApplication { + name = "ensure-blogbox-swapfile"; + runtimeInputs = [ + pkgs.coreutils + pkgs.gnugrep + pkgs.util-linux + ]; + text = '' + set -euo pipefail + + if swapon --show=NAME --noheadings | grep -Fxq /swapfile; then + exit 0 + fi + + if [ ! -f /swapfile ] || [ "$(stat -c %s /swapfile)" -ne 8589934592 ]; then + rm -f /swapfile + install -m 0600 /dev/null /swapfile + fallocate -l 8G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=8192 status=none + chmod 0600 /swapfile + fi + + mkswap -f /swapfile + swapon /swapfile + ''; + }; +in { imports = [ "${modulesPath}/virtualisation/oci-image.nix" - ../../modules/services/blogbox.nix ../../modules/services/cloud-init.nix + ../../modules/services/nginx.nix ../../modules/services/oci-authorized-keys.nix ]; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; oci.efi = lib.mkForce false; - - virtualisation.diskSize = lib.mkForce (8 * 1024); + virtualisation.diskSize = lib.mkForce (16 * 1024); networking = { hostName = "blogbox"; networkmanager.enable = lib.mkForce false; firewall.allowedTCPPorts = [ 22 + 80 + 443 ]; }; @@ -36,16 +134,59 @@ "crash_kexec_post_notifiers" "console=tty1" "console=ttyS0,115200n8" - "earlyprintk=serial,ttyS0,115200" - "loglevel=7" - "systemd.log_target=console" - "systemd.journald.forward_to_console=1" ]; kernelPackages = lib.mkForce pkgs.linuxPackages; - loader.grub.configurationLimit = lib.mkForce 3; loader.systemd-boot.configurationLimit = lib.mkForce 3; }; + documentation = { + enable = lib.mkForce false; + man.enable = lib.mkForce false; + doc.enable = lib.mkForce false; + info.enable = lib.mkForce false; + nixos.enable = lib.mkForce false; + }; + + environment = { + defaultPackages = lib.mkForce [ ]; + shells = lib.mkForce [ pkgs.bash ]; + systemPackages = lib.mkForce (with pkgs; [ + curl + git + hugo + vim + ]); + }; + + programs = { + command-not-found.enable = lib.mkForce false; + fish.enable = lib.mkForce false; + fzf.fuzzyCompletion = lib.mkForce false; + xonsh.enable = lib.mkForce false; + }; + + users = { + defaultUserShell = lib.mkForce pkgs.bash; + groups.blogbox-dyndns = { }; + users.alisceon = { + createHome = true; + extraGroups = lib.mkForce [ "wheel" "systemd-journal" ]; + openssh.authorizedKeys.keys = [ + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPN1Cd2UlHo03Jqgi5Yb4io/3gh/X4wCb8LcmKlpAovQa271CKDBtYOUKn+Fts03g6dBMfaWMty6VGPMGDMONmc= alisceon@electra" + ]; + shell = lib.mkForce pkgs.bash; + }; + users.blogbox-dyndns = { + group = "blogbox-dyndns"; + isSystemUser = true; + }; + }; + + alisceon = { + cloud-init.enable = true; + ociAuthorizedKeys.enable = true; + }; + nix = { settings = { cores = lib.mkForce 1; @@ -59,22 +200,9 @@ }; }; - virtualisation = { - containers.enable = lib.mkForce false; - docker.enable = lib.mkForce false; - libvirtd = { - enable = lib.mkForce false; - qemu.swtpm.enable = lib.mkForce false; - }; - podman.enable = lib.mkForce false; - }; - - users.users.alisceon.extraGroups = [ "systemd-journal" ]; - - alisceon = { - blogbox.enable = true; - cloud-init.enable = true; - ociAuthorizedKeys.enable = true; + system.autoUpgrade = { + persistent = lib.mkForce false; + randomizedDelaySec = lib.mkForce "4h"; }; security = { @@ -85,57 +213,188 @@ sudo-rs.wheelNeedsPassword = false; }; - services.openssh.settings = { - KbdInteractiveAuthentication = false; - PasswordAuthentication = false; - PermitRootLogin = lib.mkForce "no"; - }; + services = { + openssh.settings = { + KbdInteractiveAuthentication = false; + PasswordAuthentication = false; + PermitRootLogin = lib.mkForce "prohibit-password"; + }; - services.journald.extraConfig = '' - SystemMaxUse=64M - RuntimeMaxUse=32M - ''; + journald.extraConfig = '' + SystemMaxUse=64M + RuntimeMaxUse=32M + ''; - system.autoUpgrade = { - enable = lib.mkForce true; - persistent = lib.mkForce true; + nginx.virtualHosts.${siteDomain} = { + serverName = siteDomain; + forceSSL = true; + enableACME = true; + root = publicDir; + locations."/".extraConfig = '' + try_files $uri $uri/ =404; + ''; + }; }; systemd = { - services = { - dev-flake-garbage-collect.enable = lib.mkForce false; - nixos-upgrade.serviceConfig = { + tmpfiles.rules = [ + "d ${repoDir} 0755 alisceon users - -" + "d ${stateDir} 0755 alisceon users - -" + "d ${publicDir} 0755 alisceon users - -" + ]; + + services.update-blogbox-site = { + description = "Pull and publish the Blogbox Hugo site"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + User = "alisceon"; + Group = "users"; + ExecStart = lib.getExe updateBlogboxSite; + Nice = 10; IOSchedulingClass = "idle"; - MemoryHigh = "512M"; - MemoryMax = "900M"; - Nice = 15; + LockPersonality = true; + MemoryHigh = "384M"; + MemoryMax = "512M"; + NoNewPrivileges = true; OOMPolicy = "stop"; + PrivateDevices = true; + PrivateIPC = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + ReadWritePaths = [ + repoDir + stateDir + ]; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + TimeoutStartSec = "15min"; + UMask = "0022"; + }; + }; + + services.blogbox-dyndns = { + description = "Update Namecheap dynamic DNS records for Blogbox"; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + unitConfig.ConditionPathExists = "/etc/blogbox-namecheap-ddns.env"; + serviceConfig = { + Type = "oneshot"; + User = "blogbox-dyndns"; + Group = "blogbox-dyndns"; + ExecStart = lib.getExe updateNamecheapDyndns; + CacheDirectory = "blogbox-dyndns"; + EnvironmentFile = "/etc/blogbox-namecheap-ddns.env"; + LockPersonality = true; + MemoryMax = "128M"; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + OOMPolicy = "stop"; + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RuntimeDirectory = "blogbox-dyndns"; + RuntimeDirectoryMode = "0700"; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = "@system-service"; + TimeoutStartSec = "2min"; + UMask = "0177"; + }; + }; + + timers.update-blogbox-site = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "10min"; + OnUnitInactiveSec = "5min"; + Persistent = true; + }; + }; + + timers.blogbox-dyndns = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5min"; + OnUnitInactiveSec = "10min"; + Persistent = true; }; }; - timers.dev-flake-garbage-collect.enable = lib.mkForce false; }; - services.cloud-init.settings.disable_root = true; + systemd.services.nixos-upgrade.serviceConfig = { + IOSchedulingClass = "idle"; + MemoryHigh = "512M"; + MemoryMax = "768M"; + Nice = 15; + OOMPolicy = "stop"; + }; - environment.systemPackages = with pkgs; [ - curl - git - htop - jq - vim - wget - ]; + systemd.services.growpart.serviceConfig = { + IOSchedulingClass = "idle"; + Nice = 15; + TimeoutStartSec = "2min"; + }; - swapDevices = [ - { - device = "/swapfile"; - size = 4096; - } - ]; + systemd.services.blogbox-swapfile = { + description = "Create and enable Blogbox swapfile"; + after = [ + "sshd.service" + "systemd-growfs-root.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = lib.getExe ensureBlogboxSwapfile; + IOSchedulingClass = "idle"; + Nice = 19; + RemainAfterExit = true; + TimeoutStartSec = "20min"; + }; + }; zramSwap = { enable = true; - memoryPercent = 75; + memoryPercent = 50; + }; + + virtualisation = { + containers.enable = lib.mkForce false; + docker.enable = lib.mkForce false; + libvirtd = { + enable = lib.mkForce false; + qemu.swtpm.enable = lib.mkForce false; + }; + podman.enable = lib.mkForce false; }; system.stateVersion = lib.mkForce "25.11"; diff --git a/nixos/modules/profiles/workstation.nix b/nixos/modules/profiles/workstation.nix index fa0a50f..2fadb28 100644 --- a/nixos/modules/profiles/workstation.nix +++ b/nixos/modules/profiles/workstation.nix @@ -114,7 +114,6 @@ in pkgs.plymouth pkgs.xhost (pkgs.bottles.override { removeWarningPopup = true; }) - pkgs.hydra-check ]; sessionVariables.NIXOS_OZONE_WL = "1"; }; diff --git a/nixos/modules/services/blogbox.nix b/nixos/modules/services/blogbox.nix deleted file mode 100644 index fe1d695..0000000 --- a/nixos/modules/services/blogbox.nix +++ /dev/null @@ -1,313 +0,0 @@ -{ - config, - lib, - pkgs, - pkgs-unstable, - ... -}: -let - cfg = config.alisceon.blogbox; - user = config.users.users.${cfg.user}; - repoDir = "${user.home}/${cfg.repoDirectoryName}"; - outputDir = toString cfg.outputDir; - outputParent = dirOf outputDir; - - caddyfile = pkgs.writeText "blogbox-Caddyfile" '' - {$BLOGBOX_DOMAIN} { - encode zstd gzip - root * ${outputDir} - file_server - } - ''; - - publishSite = pkgs.writeShellApplication { - name = "blogbox-publish-site"; - runtimeInputs = [ - pkgs.coreutils - pkgs.git - pkgs.gnugrep - pkgs-unstable.hugo - ]; - text = '' - repo_dir="''${BLOGBOX_REPO_DIR:-${repoDir}}" - output_dir="''${BLOGBOX_OUTPUT_DIR:-${outputDir}}" - hugo_args="''${BLOGBOX_HUGO_ARGS:-}" - - if [ ! -d "$repo_dir/.git" ]; then - echo "Skipping blog publish: $repo_dir is not an initialized git repository" - exit 0 - fi - - cd "$repo_dir" - - old_rev="$(git rev-parse HEAD 2>/dev/null || true)" - if [ -z "$old_rev" ]; then - echo "Skipping blog publish: unable to resolve HEAD in $repo_dir" - exit 0 - fi - - if ! git remote get-url origin >/dev/null 2>&1; then - echo "Skipping blog publish: $repo_dir has no origin remote" - exit 0 - fi - - git fetch --prune --tags origin - - upstream="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)" - if [ -n "$upstream" ]; then - git merge --ff-only "$upstream" - else - git pull --ff-only - fi - - new_rev="$(git rev-parse HEAD)" - built_rev_file="$output_dir/.blogbox-built-rev" - - if [ -f "$built_rev_file" ] && [ "$old_rev" = "$new_rev" ] && grep -qxF "$new_rev" "$built_rev_file"; then - echo "No blog repository updates since $new_rev; skipping Hugo build" - exit 0 - fi - - output_parent="$(dirname "$output_dir")" - mkdir -p "$output_parent" - - build_dir="$(mktemp -d "$output_parent/.hugo-build.XXXXXX")" - trap 'rm -rf "$build_dir"' EXIT - - # shellcheck disable=SC2086 - hugo --source "$repo_dir" --destination "$build_dir/public" --cleanDestinationDir $hugo_args - printf '%s\n' "$new_rev" > "$build_dir/public/.blogbox-built-rev" - - rm -rf "$output_dir.previous" - if [ -d "$output_dir" ]; then - mv "$output_dir" "$output_dir.previous" - fi - - mv "$build_dir/public" "$output_dir" - rm -rf "$output_dir.previous" - trap - EXIT - rm -rf "$build_dir" - - echo "Published blog revision $new_rev to $output_dir" - ''; - }; - - updateNamecheap = pkgs.writeShellApplication { - name = "blogbox-namecheap-ddns"; - runtimeInputs = [ - pkgs.curl - pkgs.gnugrep - ]; - text = '' - domain="''${NAMECHEAP_DOMAIN:-''${BLOGBOX_DOMAIN:-}}" - host="''${NAMECHEAP_HOST:-@}" - password="''${NAMECHEAP_DDNS_PASSWORD:-''${NAMECHEAP_PASSWORD:-}}" - - if [ -z "$domain" ] || [ -z "$password" ]; then - echo "Skipping Namecheap DDNS: BLOGBOX_DOMAIN/NAMECHEAP_DOMAIN or NAMECHEAP_DDNS_PASSWORD is not configured" - exit 0 - fi - - ip="''${NAMECHEAP_IP:-}" - if [ -z "$ip" ]; then - ip_url="''${NAMECHEAP_IP_URL:-https://dynamicdns.park-your-domain.com/getip}" - ip="$(curl --fail --silent --show-error --max-time 15 "$ip_url")" - fi - - response="$( - curl --fail --silent --show-error --get "https://dynamicdns.park-your-domain.com/update" \ - --data-urlencode "host=$host" \ - --data-urlencode "domain=$domain" \ - --data-urlencode "password=$password" \ - --data-urlencode "ip=$ip" - )" - - printf '%s\n' "$response" - - if printf '%s\n' "$response" | grep -qiE '0|true'; then - exit 0 - fi - - echo "Namecheap DDNS response did not indicate success" - exit 1 - ''; - }; -in -{ - options.alisceon.blogbox = { - enable = lib.mkEnableOption "blogbox static site publishing"; - - user = lib.mkOption { - type = lib.types.str; - default = "alisceon"; - description = "User that owns and updates the blog git repository."; - }; - - group = lib.mkOption { - type = lib.types.str; - default = "users"; - description = "Group for blogbox writable state."; - }; - - repoDirectoryName = lib.mkOption { - type = lib.types.str; - default = "blogbox-site"; - description = "Directory name under the user's home where the runtime git repository lives."; - }; - - outputDir = lib.mkOption { - type = lib.types.path; - default = "/var/lib/blogbox/public"; - description = "Stable directory served by Caddy and replaced after successful Hugo builds."; - }; - - environmentFile = lib.mkOption { - type = lib.types.path; - default = "/etc/blogbox/blogbox.env"; - description = "Runtime environment file for non-secret domain and repository settings."; - }; - - ddnsEnvironmentFile = lib.mkOption { - type = lib.types.path; - default = "/etc/blogbox/namecheap.env"; - description = "Runtime environment file for Namecheap dynamic DNS credentials."; - }; - - publishInterval = lib.mkOption { - type = lib.types.str; - default = "2min"; - description = "Interval for git pull and Hugo publish attempts."; - }; - - ddnsInterval = lib.mkOption { - type = lib.types.str; - default = "5min"; - description = "Interval for Namecheap dynamic DNS updates."; - }; - }; - - config = lib.mkIf cfg.enable { - networking.firewall.allowedTCPPorts = [ - 80 - 443 - ]; - - environment.etc."blogbox/blogbox.env.example".text = '' - # Required for HTTPS virtual hosting. - BLOGBOX_DOMAIN=example.com - - # Optional. Defaults to /home/${cfg.user}/${cfg.repoDirectoryName}. - # BLOGBOX_REPO_DIR=/home/${cfg.user}/${cfg.repoDirectoryName} - - # Optional. Defaults to ${outputDir}. - # BLOGBOX_OUTPUT_DIR=${outputDir} - - # Optional extra flags passed to hugo. Keep this shell-word-safe. - # BLOGBOX_HUGO_ARGS=--minify - ''; - - environment.etc."blogbox/namecheap.env.example".text = '' - # Required for Namecheap DDNS. - # NAMECHEAP_DOMAIN defaults to BLOGBOX_DOMAIN from blogbox.env when omitted. - # NAMECHEAP_DOMAIN=example.com - NAMECHEAP_HOST=@ - NAMECHEAP_DDNS_PASSWORD=change-me - - # Optional. If unset, the updater asks Namecheap for the current public IP. - # NAMECHEAP_IP=203.0.113.10 - ''; - - services.caddy = { - enable = true; - adapter = "caddyfile"; - configFile = caddyfile; - environmentFile = cfg.environmentFile; - }; - - systemd.tmpfiles.rules = [ - "d /etc/blogbox 0750 root root -" - "d ${outputParent} 0755 ${cfg.user} ${cfg.group} -" - "d ${outputDir} 0755 ${cfg.user} ${cfg.group} -" - ]; - - systemd.services = { - caddy.unitConfig.ConditionPathExists = cfg.environmentFile; - - blogbox-publish-site = { - description = "Pull and publish the blogbox Hugo site"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - serviceConfig = { - Type = "oneshot"; - User = cfg.user; - Group = cfg.group; - EnvironmentFile = [ "-${toString cfg.environmentFile}" ]; - Environment = [ - "HOME=${user.home}" - "GIT_TERMINAL_PROMPT=0" - ]; - Nice = 10; - IOSchedulingClass = "idle"; - TimeoutStartSec = "10min"; - }; - script = lib.getExe publishSite; - }; - - blogbox-namecheap-ddns = { - description = "Update Namecheap dynamic DNS for blogbox"; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - serviceConfig = { - Type = "oneshot"; - DynamicUser = true; - EnvironmentFile = [ - "-${toString cfg.environmentFile}" - "-${toString cfg.ddnsEnvironmentFile}" - ]; - Nice = 10; - IOSchedulingClass = "idle"; - TimeoutStartSec = "45s"; - }; - script = lib.getExe updateNamecheap; - }; - - blogbox-reload-caddy = { - description = "Restart Caddy after blogbox runtime configuration changes"; - serviceConfig.Type = "oneshot"; - script = '' - ${pkgs.systemd}/bin/systemctl restart caddy.service - ''; - }; - }; - - systemd.timers = { - blogbox-publish-site = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnBootSec = "2min"; - OnUnitActiveSec = cfg.publishInterval; - RandomizedDelaySec = "20s"; - Persistent = true; - }; - }; - - blogbox-namecheap-ddns = { - wantedBy = [ "timers.target" ]; - timerConfig = { - OnBootSec = "1min"; - OnUnitActiveSec = cfg.ddnsInterval; - RandomizedDelaySec = "30s"; - Persistent = true; - }; - }; - }; - - systemd.paths.blogbox-reload-caddy = { - wantedBy = [ "multi-user.target" ]; - pathConfig = { - PathExists = cfg.environmentFile; - PathChanged = cfg.environmentFile; - }; - }; - }; -} diff --git a/nixos/modules/services/oci-authorized-keys.nix b/nixos/modules/services/oci-authorized-keys.nix index ac5a866..8b9312b 100644 --- a/nixos/modules/services/oci-authorized-keys.nix +++ b/nixos/modules/services/oci-authorized-keys.nix @@ -84,11 +84,7 @@ in config = lib.mkIf cfg.enable { systemd.services.fetch-oci-authorized-keys = { description = "Fetch OCI metadata authorized_keys for ${cfg.user}"; - wantedBy = [ - "sshd.service" - "multi-user.target" - ]; - before = [ "sshd.service" ]; + wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; serviceConfig = { diff --git a/nixos/modules/services/oci-secondary-vnics.nix b/nixos/modules/services/oci-secondary-vnics.nix deleted file mode 100644 index 6d6b94d..0000000 --- a/nixos/modules/services/oci-secondary-vnics.nix +++ /dev/null @@ -1,126 +0,0 @@ -{ config, lib, pkgs, ... }: -let - cfg = config.alisceon.ociSecondaryVnics; - - configureSecondaryVnics = pkgs.writeShellApplication { - name = "configure-oci-secondary-vnics"; - runtimeInputs = [ - pkgs.coreutils - pkgs.curl - pkgs.gnugrep - pkgs.iproute2 - pkgs.jq - pkgs.systemd - ]; - text = '' - metadata_url="http://169.254.169.254/opc/v2/vnics" - network_dir="/run/systemd/network" - mkdir -p "$network_dir" - - vnics="$(curl --fail --silent --show-error --max-time 10 \ - -H "Authorization: Bearer Oracle" \ - "$metadata_url")" - - index=0 - configured=0 - - while IFS= read -r vnic; do - index=$((index + 1)) - - mac="$(jq -r '.macAddr // empty' <<< "$vnic" | tr '[:upper:]' '[:lower:]')" - address="$(jq -r '.privateIp // empty' <<< "$vnic")" - cidr="$(jq -r '.subnetCidrBlock // empty' <<< "$vnic")" - gateway="$(jq -r '.virtualRouterIp // empty' <<< "$vnic")" - - if [ -z "$mac" ] || [ -z "$address" ] || [ -z "$cidr" ] || [ -z "$gateway" ]; then - echo "Skipping incomplete OCI VNIC metadata entry: $vnic" - continue - fi - - iface="$( - for candidate in /sys/class/net/*; do - [ -e "$candidate/address" ] || continue - candidate_mac="$(tr '[:upper:]' '[:lower:]' < "$candidate/address")" - if [ "$candidate_mac" = "$mac" ]; then - basename "$candidate" - break - fi - done - )" - - if [ -z "$iface" ]; then - echo "Skipping OCI VNIC $mac: no matching Linux interface" - continue - fi - - if ip -4 address show dev "$iface" | grep -q "inet $address/"; then - echo "OCI VNIC $iface already has $address configured" - continue - fi - - prefix="''${cidr#*/}" - table=$((1000 + index)) - priority=$((1000 + index)) - unit_name="$(systemd-escape --template=20-oci-secondary-vnic@.network "$iface")" - network_file="$network_dir/$unit_name" - - { - printf '%s\n' \ - "[Match]" \ - "MACAddress=$mac" \ - "" \ - "[Link]" \ - "MTUBytes=9000" \ - "" \ - "[Network]" \ - "Address=$address/$prefix" \ - "" \ - "[Route]" \ - "Destination=$cidr" \ - "Scope=link" \ - "Table=$table" \ - "" \ - "[Route]" \ - "Gateway=$gateway" \ - "GatewayOnLink=yes" \ - "Table=$table" \ - "" \ - "[RoutingPolicyRule]" \ - "From=$address/32" \ - "Table=$table" \ - "Priority=$priority" - } > "$network_file" - - echo "Configuring OCI secondary VNIC $iface as $address/$prefix via $gateway in table $table" - networkctl reload - networkctl reconfigure "$iface" - configured=1 - done < <(jq -c '.[]' <<< "$vnics") - - if [ "$configured" = 0 ]; then - echo "No unconfigured OCI secondary VNICs found" - fi - ''; - }; -in -{ - options.alisceon.ociSecondaryVnics.enable = - lib.mkEnableOption "runtime configuration of OCI secondary VNICs from instance metadata"; - - config = lib.mkIf cfg.enable { - systemd.services.configure-oci-secondary-vnics = { - description = "Configure OCI secondary VNICs from instance metadata"; - after = [ - "network-online.target" - "systemd-networkd.service" - ]; - wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = lib.getExe configureSecondaryVnics; - }; - }; -}