From b3a36d9dbef837ef74740377b40e0328f9cf0817 Mon Sep 17 00:00:00 2001 From: alisceon Date: Fri, 29 May 2026 14:25:51 +0200 Subject: [PATCH 1/6] add wg support --- flake.nix | 1 + home/modules/programs/waybar/default.nix | 33 ++++++++----- nixos/modules/profiles/workstation.nix | 2 + nixos/modules/services/wireguard-peer.nix | 57 +++++++++++++++++++++++ util/toggle_wg.xsh | 25 ++++++++++ 5 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 nixos/modules/services/wireguard-peer.nix create mode 100755 util/toggle_wg.xsh diff --git a/flake.nix b/flake.nix index 5d1fcde..77d2c4f 100644 --- a/flake.nix +++ b/flake.nix @@ -57,6 +57,7 @@ mkSharedModules = pkgs: pkgs-unstable: [ ./nixos/modules/base.nix + ./nixos/modules/services/wireguard-peer.nix inputs.home-manager.nixosModules.home-manager ({ ... }: { home-manager.useGlobalPkgs = true; diff --git a/home/modules/programs/waybar/default.nix b/home/modules/programs/waybar/default.nix index 5889e60..e475dc7 100644 --- a/home/modules/programs/waybar/default.nix +++ b/home/modules/programs/waybar/default.nix @@ -1,8 +1,9 @@ -{ pkgs, ... }: +{ pkgs, repoRoot, ... }: let commands = import ../../../../lib/commands.nix { inherit pkgs; }; inherit (commands) uwsm term; height = 20; + wireguardToggle = "${pkgs.xonsh}/bin/xonsh ${repoRoot}/util/toggle_wg.xsh"; in { imports = [ @@ -37,6 +38,7 @@ in "clock" "sway/language" "network" + "custom/wireguard" "bluetooth" "pulseaudio" "idle_inhibitor" @@ -53,7 +55,7 @@ in spacing = 8; }; idle_inhibitor = { - format = "| {icon}"; + format = "| {icon} "; start-activated = true; format-icons = { activated = "🫨"; @@ -61,7 +63,7 @@ in }; }; "sway/language" = { - format = "| {short}"; + format = "| {short} "; tooltip-format = "{long}"; }; clock = { @@ -72,29 +74,36 @@ in }; battery = { interval = 60; - format = "| {capacity}%"; + format = "| {capacity}% "; format-charging = "| ch:{capacity}%"; }; network = { tooltip-format = "{ifname} = {ipaddr}/{cidr}"; - format-wifi = "| w:{ipaddr}"; - format-ethernet = "| e:{ipaddr}"; - format-linked = "| l:{ipaddr}"; - format-disconnected = "| d"; + format-wifi = "| w:{essid} "; + format-ethernet = "| e:{ipaddr} "; + format-linked = "| l:{ipaddr} "; + format-disconnected = "| d "; interval = 15; on-click = "${uwsm} ${term} -e nmtui"; }; + "custom/wireguard" = { + exec = "${wireguardToggle} status"; + on-click = "${wireguardToggle} toggle"; + format = "| wg:{text} "; + interval = 15; + tooltip = false; + }; bluetooth = { - format = "| bt:{num_connections}"; + format = "| bt:{num_connections} "; format-disabled = ""; format-no-controller = ""; interval = 15; on-click = "${uwsm} ${term} -e bluetui"; }; pulseaudio = { - format = "| snd{volume}%"; - format-muted = "| snd:-"; - format-bluetooth = "| snd(bt):{volume}%"; + format = "| snd{volume}% "; + format-muted = "| snd:- "; + format-bluetooth = "| snd(bt):{volume}% "; on-click = "${uwsm} pavucontrol"; }; }; diff --git a/nixos/modules/profiles/workstation.nix b/nixos/modules/profiles/workstation.nix index d39d714..2fadb28 100644 --- a/nixos/modules/profiles/workstation.nix +++ b/nixos/modules/profiles/workstation.nix @@ -24,6 +24,8 @@ in security.sudo.wheelNeedsPassword = false; + alisceon.wireguardPeer.enable = true; + services = { printing.enable = true; pulseaudio.enable = false; diff --git a/nixos/modules/services/wireguard-peer.nix b/nixos/modules/services/wireguard-peer.nix new file mode 100644 index 0000000..65d07a1 --- /dev/null +++ b/nixos/modules/services/wireguard-peer.nix @@ -0,0 +1,57 @@ +{ config, lib, pkgs, repoLocalPath, ... }: + +let + cfg = config.alisceon.wireguardPeer; +in +{ + options.alisceon.wireguardPeer = { + enable = lib.mkEnableOption "a single WireGuard peer managed by wg-quick"; + + interface = lib.mkOption { + type = lib.types.str; + default = "wg0"; + description = "WireGuard interface name."; + }; + + configFile = lib.mkOption { + type = lib.types.str; + default = "/etc/wireguard/${cfg.interface}.conf"; + defaultText = "/etc/wireguard/.conf"; + description = '' + Path to an external wg-quick config file. Keep it root-owned and mode + 0600 so private keys and peer material stay outside Git and the Nix store. + ''; + }; + + autostart = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to bring the WireGuard interface up at boot."; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = lib.hasPrefix "/" cfg.configFile; + message = "alisceon.wireguardPeer.configFile must be an absolute path outside the repo."; + } + { + assertion = !(lib.hasPrefix repoLocalPath cfg.configFile); + message = "alisceon.wireguardPeer.configFile must be outside ${repoLocalPath}."; + } + ]; + + networking.wg-quick.interfaces.${cfg.interface} = { + inherit (cfg) autostart configFile; + }; + + systemd.services."wg-quick-${cfg.interface}".unitConfig.ConditionPathExists = cfg.configFile; + + environment.systemPackages = [ pkgs.wireguard-tools ]; + + systemd.tmpfiles.rules = [ + "d /etc/wireguard 0700 root root -" + ]; + }; +} diff --git a/util/toggle_wg.xsh b/util/toggle_wg.xsh new file mode 100755 index 0000000..bc88bc4 --- /dev/null +++ b/util/toggle_wg.xsh @@ -0,0 +1,25 @@ +import sys + +isup = "does not exist." not in $(ip link show dev wg0 2>&1) + +try: + match sys.argv[1]: + case "toggle": + if isup: + footclient wg-quick down wg0 + else: + footclient wg-quick up wg0 + case "status": + if isup: + print("u") + else: + print("d") + case _: + raise RuntimeError + +except (RuntimeError, IndexError): + print('"toggle" or "status" must be provided') + exit(1) + +exit(0) + From ec5edefad8c769a39e8812e4c8226ecec330d63e Mon Sep 17 00:00:00 2001 From: alisceon Date: Fri, 29 May 2026 14:42:02 +0200 Subject: [PATCH 2/6] small fix for waybar widget --- home/modules/programs/waybar/default.nix | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/home/modules/programs/waybar/default.nix b/home/modules/programs/waybar/default.nix index e475dc7..e2def8f 100644 --- a/home/modules/programs/waybar/default.nix +++ b/home/modules/programs/waybar/default.nix @@ -36,12 +36,12 @@ in }; "modules" = [ "clock" + "idle_inhibitor" "sway/language" "network" "custom/wireguard" "bluetooth" "pulseaudio" - "idle_inhibitor" ]; }; "sway/workspaces" = { @@ -63,7 +63,8 @@ in }; }; "sway/language" = { - format = "| {short} "; + # this one gobbles all trailing ascii whitespace for some reason. use this unicode instead + format = "| {short} "; tooltip-format = "{long}"; }; clock = { @@ -75,14 +76,14 @@ in battery = { interval = 60; format = "| {capacity}% "; - format-charging = "| ch:{capacity}%"; + format-charging = "| ch:{capacity}% "; }; network = { tooltip-format = "{ifname} = {ipaddr}/{cidr}"; format-wifi = "| w:{essid} "; format-ethernet = "| e:{ipaddr} "; format-linked = "| l:{ipaddr} "; - format-disconnected = "| d "; + format-disconnected = "| w:d "; interval = 15; on-click = "${uwsm} ${term} -e nmtui"; }; From 6c60161d1a12f77b587b1c6f3dee89fd3fea2bdc Mon Sep 17 00:00:00 2001 From: alisceon Date: Fri, 29 May 2026 15:17:42 +0200 Subject: [PATCH 3/6] add syncthing to alisceon-core --- nixos/hosts/alisceon-core/configuration.nix | 76 ++++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/nixos/hosts/alisceon-core/configuration.nix b/nixos/hosts/alisceon-core/configuration.nix index 8468905..50b0fa4 100644 --- a/nixos/hosts/alisceon-core/configuration.nix +++ b/nixos/hosts/alisceon-core/configuration.nix @@ -1,7 +1,7 @@ { lib, pkgs, modulesPath, ... }: let forgejoDomain = "git.alisceon.com"; - forgejoRunnerTokenFile = "/var/lib/forgejo/runner_token"; + syncthingDomain = "syncthing.alisceon.com"; fetchOciAuthorizedKeys = pkgs.writeShellApplication { name = "fetch-oci-authorized-keys"; @@ -34,7 +34,7 @@ let pkgs.util-linux ]; text = '' - token_file=${lib.escapeShellArg forgejoRunnerTokenFile} + token_file=${lib.escapeShellArg "/var/lib/forgejo/runner_token"} if [ -s "$token_file" ]; then chmod 0600 "$token_file" @@ -69,8 +69,12 @@ in 22 80 443 + 22000 24601 ]; + firewall.allowedUDPPorts = [ + 22000 + ]; }; boot = { @@ -123,6 +127,32 @@ in PermitRootLogin = lib.mkForce "prohibit-password"; }; + services.syncthing = { + enable = true; + dataDir = "/var/lib/syncthing"; + guiAddress = "127.0.0.1:8384"; + openDefaultPorts = false; + overrideDevices = false; + overrideFolders = false; + settings = { + gui = { + insecureAdminAccess = false; + insecureSkipHostcheck = false; + }; + options = { + globalAnnounceEnabled = false; + localAnnounceEnabled = false; + listenAddresses = [ + "tcp://0.0.0.0:22000" + "quic://0.0.0.0:22000" + ]; + natEnabled = false; + relaysEnabled = false; + urAccepted = -1; + }; + }; + }; + services.forgejo = { enable = true; package = pkgs.forgejo-lts; @@ -164,7 +194,7 @@ in enable = true; name = "alisceon-core-podman"; url = "https://${forgejoDomain}"; - tokenFile = forgejoRunnerTokenFile; + tokenFile = "/var/lib/forgejo/runner_token"; labels = [ "ubuntu-latest:docker://node:22-bookworm" "debian-latest:docker://node:22-bookworm" @@ -203,6 +233,24 @@ in recommendedProxySettings = true; }; }; + ${syncthingDomain} = { + serverName = syncthingDomain; + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8384"; + recommendedProxySettings = false; + extraConfig = '' + proxy_set_header Host $proxy_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-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + ''; + }; + }; }; }; @@ -264,6 +312,28 @@ in systemd.services.fetch-ssh-keys.enable = false; + systemd.services.syncthing = { + serviceConfig = { + LockPersonality = true; + PrivateIPC = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectHome = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ReadWritePaths = [ "/var/lib/syncthing" ]; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + ]; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + }; + systemd.services.forgejo-runner-token = { description = "Generate Forgejo runner registration token"; wantedBy = [ "multi-user.target" ]; From e2b41c81290058f120f7d262f6cf361a0d8f98a1 Mon Sep 17 00:00:00 2001 From: alisceon Date: Fri, 29 May 2026 18:13:23 +0200 Subject: [PATCH 4/6] cleaning and refactoring alisceon-core --- nixos/hosts/alisceon-core/configuration.nix | 196 ++++---------------- nixos/modules/services/forgejo.nix | 118 ++++++++++++ nixos/modules/services/nginx.nix | 17 ++ nixos/modules/services/tor.nix | 27 +++ 4 files changed, 194 insertions(+), 164 deletions(-) create mode 100644 nixos/modules/services/forgejo.nix create mode 100644 nixos/modules/services/nginx.nix create mode 100644 nixos/modules/services/tor.nix diff --git a/nixos/hosts/alisceon-core/configuration.nix b/nixos/hosts/alisceon-core/configuration.nix index 50b0fa4..d51f60f 100644 --- a/nixos/hosts/alisceon-core/configuration.nix +++ b/nixos/hosts/alisceon-core/configuration.nix @@ -27,37 +27,13 @@ let ''; }; - generateForgejoRunnerToken = pkgs.writeShellApplication { - name = "generate-forgejo-runner-token"; - runtimeInputs = [ - pkgs.coreutils - pkgs.util-linux - ]; - text = '' - token_file=${lib.escapeShellArg "/var/lib/forgejo/runner_token"} - - if [ -s "$token_file" ]; then - chmod 0600 "$token_file" - chown root:root "$token_file" - exit 0 - fi - - install -d -m 0750 -o forgejo -g forgejo /var/lib/forgejo - token="$(runuser -u forgejo -- env \ - FORGEJO_WORK_DIR=/var/lib/forgejo \ - FORGEJO_CUSTOM=/var/lib/forgejo/custom \ - ${lib.getExe pkgs.forgejo-lts} actions generate-runner-token)" - - umask 0077 - printf 'TOKEN=%s\n' "$token" > "$token_file" - chown root:root "$token_file" - chmod 0600 "$token_file" - ''; - }; in { imports = [ "${modulesPath}/virtualisation/oci-image.nix" + ../../modules/services/forgejo.nix + ../../modules/services/nginx.nix + ../../modules/services/tor.nix ]; nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; @@ -153,129 +129,41 @@ in }; }; - services.forgejo = { - enable = true; - package = pkgs.forgejo-lts; - database.type = "sqlite3"; - lfs.enable = true; - settings = { - server = { - DOMAIN = forgejoDomain; - ROOT_URL = "https://${forgejoDomain}/"; - HTTP_ADDR = "127.0.0.1"; - HTTP_PORT = 3000; - SSH_DOMAIN = forgejoDomain; - SSH_PORT = 22; - DISABLE_SSH = false; - }; - session.COOKIE_SECURE = true; - service = { - DISABLE_REGISTRATION = true; - REQUIRE_SIGNIN_VIEW = false; - }; - actions.ENABLED = true; - repository = { - DEFAULT_PRIVATE = "private"; - DISABLE_HTTP_GIT = false; - }; - "cron.archive_cleanup" = { - ENABLED = true; - RUN_AT_START = true; - SCHEDULE = "@every 24h"; - OLDER_THAN = "72h"; - }; - log.LEVEL = "Warn"; - }; - }; + alisceon.forgejo.domain = forgejoDomain; - services.gitea-actions-runner = { - package = pkgs.forgejo-runner; - instances.alisceon-core-podman = { - enable = true; - name = "alisceon-core-podman"; - url = "https://${forgejoDomain}"; - tokenFile = "/var/lib/forgejo/runner_token"; - labels = [ - "ubuntu-latest:docker://node:22-bookworm" - "debian-latest:docker://node:22-bookworm" - ]; - settings = { - container = { - network = "host"; - privileged = false; - valid_volumes = [ ]; - }; - cache.enabled = false; + services.gitea-actions-runner.instances.alisceon-core-podman.labels = [ + "podman" + "aarch64" + "arm64" + ]; + + services.nginx.virtualHosts = { + ${forgejoDomain} = { + serverName = forgejoDomain; + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:3000"; + recommendedProxySettings = true; }; }; - }; - - services.nginx = { - enable = true; - recommendedGzipSettings = true; - recommendedOptimisation = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - virtualHosts = { - "_" = { - default = true; - rejectSSL = true; + ${syncthingDomain} = { + serverName = syncthingDomain; + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8384"; + recommendedProxySettings = false; extraConfig = '' - return 421; + proxy_set_header Host $proxy_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-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 600s; + proxy_send_timeout 600s; ''; }; - ${forgejoDomain} = { - serverName = forgejoDomain; - forceSSL = true; - enableACME = true; - locations."/" = { - proxyPass = "http://127.0.0.1:3000"; - recommendedProxySettings = true; - }; - }; - ${syncthingDomain} = { - serverName = syncthingDomain; - forceSSL = true; - enableACME = true; - locations."/" = { - proxyPass = "http://127.0.0.1:8384"; - recommendedProxySettings = false; - extraConfig = '' - proxy_set_header Host $proxy_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-Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 600s; - proxy_send_timeout 600s; - ''; - }; - }; - }; - }; - - services.tor = { - enable = true; - client.enable = false; - relay = { - enable = true; - role = "relay"; - }; - settings = { - Nickname = "alisceondotcom"; - ORPort = 24601; - DataDirectory = "/var/lib/tor"; - ExitRelay = false; - ExitPolicy = [ "reject *:*" ]; - RelayBandwidthRate = "25 MBytes"; - RelayBandwidthBurst = "25 MBytes"; - BandwidthRate = "25 MBytes"; - BandwidthBurst = "25 MBytes"; - AccountingStart = "month 1 00:00"; - AccountingMax = "8500 GBytes"; - DirCache = true; - AvoidDiskWrites = 1; - Sandbox = false; }; }; @@ -334,28 +222,8 @@ in }; }; - systemd.services.forgejo-runner-token = { - description = "Generate Forgejo runner registration token"; - wantedBy = [ "multi-user.target" ]; - after = [ "forgejo.service" ]; - requires = [ "forgejo.service" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - StandardError = "journal+console"; - StandardOutput = "journal+console"; - }; - script = lib.getExe generateForgejoRunnerToken; - }; - - systemd.services."gitea-runner-alisceon\\x2dcore\\x2dpodman" = { - after = [ "forgejo-runner-token.service" ]; - requires = [ "forgejo-runner-token.service" ]; - }; - environment.systemPackages = with pkgs; [ curl - forgejo-lts git htop jq diff --git a/nixos/modules/services/forgejo.nix b/nixos/modules/services/forgejo.nix new file mode 100644 index 0000000..5b5b878 --- /dev/null +++ b/nixos/modules/services/forgejo.nix @@ -0,0 +1,118 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.alisceon.forgejo; + forgejoDomain = cfg.domain; + + generateForgejoRunnerToken = pkgs.writeShellApplication { + name = "generate-forgejo-runner-token"; + runtimeInputs = [ + pkgs.coreutils + pkgs.util-linux + ]; + text = '' + token_file=${lib.escapeShellArg "/var/lib/forgejo/runner_token"} + + if [ -s "$token_file" ]; then + chmod 0600 "$token_file" + chown root:root "$token_file" + exit 0 + fi + + install -d -m 0750 -o forgejo -g forgejo /var/lib/forgejo + token="$(runuser -u forgejo -- env \ + FORGEJO_WORK_DIR=/var/lib/forgejo \ + FORGEJO_CUSTOM=/var/lib/forgejo/custom \ + ${lib.getExe pkgs.forgejo-lts} actions generate-runner-token)" + + umask 0077 + printf 'TOKEN=%s\n' "$token" > "$token_file" + chown root:root "$token_file" + chmod 0600 "$token_file" + ''; + }; +in +{ + options.alisceon.forgejo.domain = lib.mkOption { + type = lib.types.str; + description = "Public domain name for Forgejo."; + }; + + config = { + services.forgejo = { + enable = true; + package = pkgs.forgejo-lts; + database.type = "sqlite3"; + lfs.enable = true; + settings = { + server = { + DOMAIN = forgejoDomain; + ROOT_URL = "https://${forgejoDomain}/"; + HTTP_ADDR = "127.0.0.1"; + HTTP_PORT = 3000; + SSH_DOMAIN = forgejoDomain; + SSH_PORT = 22; + DISABLE_SSH = false; + }; + session.COOKIE_SECURE = true; + service = { + DISABLE_REGISTRATION = true; + REQUIRE_SIGNIN_VIEW = false; + }; + actions.ENABLED = true; + repository = { + DEFAULT_PRIVATE = "private"; + DISABLE_HTTP_GIT = false; + }; + "cron.archive_cleanup" = { + ENABLED = true; + RUN_AT_START = true; + SCHEDULE = "@every 24h"; + OLDER_THAN = "72h"; + }; + log.LEVEL = "Warn"; + }; + }; + + services.gitea-actions-runner = { + package = pkgs.forgejo-runner; + instances.alisceon-core-podman = { + enable = true; + name = "alisceon-core-podman"; + url = "https://${forgejoDomain}"; + tokenFile = "/var/lib/forgejo/runner_token"; + labels = lib.mkDefault [ + "podman" + ]; + settings = { + container = { + network = "host"; + privileged = false; + valid_volumes = [ ]; + }; + cache.enabled = false; + }; + }; + }; + + systemd.services.forgejo-runner-token = { + description = "Generate Forgejo runner registration token"; + wantedBy = [ "multi-user.target" ]; + after = [ "forgejo.service" ]; + requires = [ "forgejo.service" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + StandardError = "journal+console"; + StandardOutput = "journal+console"; + }; + script = lib.getExe generateForgejoRunnerToken; + }; + + systemd.services."gitea-runner-alisceon\\x2dcore\\x2dpodman" = { + after = [ "forgejo-runner-token.service" ]; + requires = [ "forgejo-runner-token.service" ]; + }; + + environment.systemPackages = [ pkgs.forgejo-lts ]; + }; +} diff --git a/nixos/modules/services/nginx.nix b/nixos/modules/services/nginx.nix new file mode 100644 index 0000000..4846f56 --- /dev/null +++ b/nixos/modules/services/nginx.nix @@ -0,0 +1,17 @@ +{ ... }: +{ + services.nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + virtualHosts."_" = { + default = true; + rejectSSL = true; + extraConfig = '' + return 421; + ''; + }; + }; +} diff --git a/nixos/modules/services/tor.nix b/nixos/modules/services/tor.nix new file mode 100644 index 0000000..29ee2ff --- /dev/null +++ b/nixos/modules/services/tor.nix @@ -0,0 +1,27 @@ +{ ... }: +{ + services.tor = { + enable = true; + client.enable = false; + relay = { + enable = true; + role = "relay"; + }; + settings = { + Nickname = "alisceondotcom"; + ORPort = 24601; + DataDirectory = "/var/lib/tor"; + ExitRelay = false; + ExitPolicy = [ "reject *:*" ]; + RelayBandwidthRate = "25 MBytes"; + RelayBandwidthBurst = "25 MBytes"; + BandwidthRate = "25 MBytes"; + BandwidthBurst = "25 MBytes"; + AccountingStart = "month 1 00:00"; + AccountingMax = "8500 GBytes"; + DirCache = true; + AvoidDiskWrites = 1; + Sandbox = false; + }; + }; +} From 2ac05607a252a6da9bcf8aeeaae584d447de879a Mon Sep 17 00:00:00 2001 From: alisceon Date: Fri, 29 May 2026 19:30:07 +0200 Subject: [PATCH 5/6] add blogbox target --- flake.nix | 11 +- nixos/hosts/alisceon-core/configuration.nix | 82 ++----- nixos/hosts/blogbox/configuration.nix | 225 ++++++++++++++++++ nixos/modules/services/cloud-init.nix | 71 ++++++ .../modules/services/oci-authorized-keys.nix | 73 ++++++ 5 files changed, 394 insertions(+), 68 deletions(-) create mode 100644 nixos/hosts/blogbox/configuration.nix create mode 100644 nixos/modules/services/cloud-init.nix create mode 100644 nixos/modules/services/oci-authorized-keys.nix diff --git a/flake.nix b/flake.nix index 77d2c4f..f3074c1 100644 --- a/flake.nix +++ b/flake.nix @@ -76,6 +76,7 @@ nixosModules ? [ ], hmModules ? [ ], extraModules ? [ ], + homeManagerUsers ? true, }: let pkgs = mkPkgs system; @@ -92,7 +93,7 @@ ++ [ (./nixos/hosts + "/${hostName}/configuration.nix") ] ++ nixosModules ++ extraModules - ++ [ + ++ nixpkgs.lib.optionals homeManagerUsers [ { home-manager.users.alisceon.imports = [ @@ -168,6 +169,14 @@ nixosModules = serverModules; hmModules = serverHomeModules; }; + + blogbox = mkHost { + hostName = "blogbox"; + system = "x86_64-linux"; + nixosModules = serverModules; + hmModules = serverHomeModules; + homeManagerUsers = false; + }; }; }; } diff --git a/nixos/hosts/alisceon-core/configuration.nix b/nixos/hosts/alisceon-core/configuration.nix index d51f60f..222cfa6 100644 --- a/nixos/hosts/alisceon-core/configuration.nix +++ b/nixos/hosts/alisceon-core/configuration.nix @@ -1,38 +1,11 @@ { lib, pkgs, modulesPath, ... }: -let - forgejoDomain = "git.alisceon.com"; - syncthingDomain = "syncthing.alisceon.com"; - - fetchOciAuthorizedKeys = pkgs.writeShellApplication { - name = "fetch-oci-authorized-keys"; - runtimeInputs = [ - pkgs.coreutils - pkgs.curl - ]; - text = '' - install -d -m 0700 -o alisceon -g users /home/alisceon/.ssh - - if [ -s /home/alisceon/.ssh/authorized_keys ]; then - echo "OCI authorized_keys already present for alisceon" - exit 0 - fi - - curl --fail --silent --show-error --location \ - --header "Authorization: Bearer Oracle" \ - --output /home/alisceon/.ssh/authorized_keys \ - http://169.254.169.254/opc/v2/instance/metadata/ssh_authorized_keys - - chown alisceon:users /home/alisceon/.ssh/authorized_keys - chmod 0600 /home/alisceon/.ssh/authorized_keys - ''; - }; - -in { imports = [ "${modulesPath}/virtualisation/oci-image.nix" + ../../modules/services/cloud-init.nix ../../modules/services/forgejo.nix ../../modules/services/nginx.nix + ../../modules/services/oci-authorized-keys.nix ../../modules/services/tor.nix ]; @@ -90,6 +63,14 @@ in users.users.alisceon.extraGroups = [ "systemd-journal" ]; + alisceon = { + cloud-init = { + enable = true; + defaultShell = "/run/current-system/sw/bin/xonsh"; + }; + ociAuthorizedKeys.enable = true; + }; + security = { acme = { acceptTerms = true; @@ -129,7 +110,7 @@ in }; }; - alisceon.forgejo.domain = forgejoDomain; + alisceon.forgejo.domain = "forgejo.alisceon.com"; services.gitea-actions-runner.instances.alisceon-core-podman.labels = [ "podman" @@ -138,8 +119,8 @@ in ]; services.nginx.virtualHosts = { - ${forgejoDomain} = { - serverName = forgejoDomain; + ${"forgejo.alisceon.com"} = { + serverName = "forgejo.alisceon.com"; forceSSL = true; enableACME = true; locations."/" = { @@ -147,8 +128,8 @@ in recommendedProxySettings = true; }; }; - ${syncthingDomain} = { - serverName = syncthingDomain; + ${"syncthing.alisceon.com"} = { + serverName = "syncthing.alisceon.com"; forceSSL = true; enableACME = true; locations."/" = { @@ -167,39 +148,6 @@ in }; }; - services.cloud-init = { - enable = true; - network.enable = true; - settings = { - datasource_list = [ "Oracle" "ConfigDrive" "NoCloud" ]; - users = [ "default" ]; - system_info.default_user = { - name = "alisceon"; - gecos = "Alisceon"; - groups = [ "wheel" "systemd-journal" ]; - shell = "/run/current-system/sw/bin/xonsh"; - lock_passwd = true; - }; - }; - }; - - systemd.services.fetch-oci-authorized-keys = { - description = "Fetch OCI metadata authorized_keys for alisceon"; - wantedBy = [ "sshd.service" ]; - before = [ "sshd.service" ]; - after = [ "network-online.target" ]; - wants = [ "network-online.target" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - StandardError = "journal+console"; - StandardOutput = "journal+console"; - }; - script = lib.getExe fetchOciAuthorizedKeys; - }; - - systemd.services.fetch-ssh-keys.enable = false; - systemd.services.syncthing = { serviceConfig = { LockPersonality = true; diff --git a/nixos/hosts/blogbox/configuration.nix b/nixos/hosts/blogbox/configuration.nix new file mode 100644 index 0000000..ec91d7a --- /dev/null +++ b/nixos/hosts/blogbox/configuration.nix @@ -0,0 +1,225 @@ +{ 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}/ + ''; + }; +in +{ + imports = [ + "${modulesPath}/virtualisation/oci-image.nix" + ../../modules/services/cloud-init.nix + ../../modules/services/nginx.nix + ../../modules/services/oci-authorized-keys.nix + ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + networking = { + hostName = "blogbox"; + networkmanager.enable = lib.mkForce false; + useDHCP = lib.mkDefault true; + firewall.allowedTCPPorts = [ + 22 + 80 + 443 + ]; + }; + + boot = { + kernelPackages = lib.mkForce pkgs.linuxPackages; + 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; + users.alisceon = { + createHome = true; + extraGroups = lib.mkForce [ "wheel" "systemd-journal" ]; + shell = lib.mkForce pkgs.bash; + }; + }; + + alisceon = { + cloud-init.enable = true; + ociAuthorizedKeys.enable = true; + }; + + nix = { + settings = { + min-free = lib.mkForce (256 * 1024 * 1024); + max-free = lib.mkForce (1024 * 1024 * 1024); + }; + gc = { + dates = lib.mkForce "daily"; + options = lib.mkForce "--delete-older-than 3d"; + }; + }; + + security = { + acme = { + acceptTerms = true; + defaults.email = "acme@alisceon.com"; + }; + sudo-rs.wheelNeedsPassword = false; + }; + + services = { + openssh.settings = { + PasswordAuthentication = false; + PermitRootLogin = lib.mkForce "prohibit-password"; + }; + + journald.extraConfig = '' + SystemMaxUse=64M + RuntimeMaxUse=32M + ''; + + nginx.virtualHosts.${siteDomain} = { + serverName = siteDomain; + forceSSL = true; + enableACME = true; + root = publicDir; + locations."/".extraConfig = '' + try_files $uri $uri/ =404; + ''; + }; + }; + + systemd = { + 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"; + LockPersonality = true; + NoNewPrivileges = true; + 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"; + UMask = "0022"; + }; + }; + + timers.update-blogbox-site = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = "5m"; + Persistent = true; + }; + }; + }; + + zramSwap = { + enable = true; + memoryPercent = 50; + }; + + swapDevices = [ + { + device = "/swapfile"; + size = 8 * 1024; + } + ]; + + 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/services/cloud-init.nix b/nixos/modules/services/cloud-init.nix new file mode 100644 index 0000000..b5dd3ec --- /dev/null +++ b/nixos/modules/services/cloud-init.nix @@ -0,0 +1,71 @@ +{ config, lib, ... }: +let + cfg = config.alisceon.cloud-init; + defaultShell = + if cfg.defaultShell != null then + cfg.defaultShell + else + lib.getExe config.users.users.${cfg.user}.shell; +in +{ + options.alisceon.cloud-init = { + enable = lib.mkEnableOption "shared cloud-init defaults"; + + user = lib.mkOption { + type = lib.types.str; + default = "alisceon"; + description = "Default cloud-init user to configure."; + }; + + gecos = lib.mkOption { + type = lib.types.str; + default = "Alisceon"; + description = "GECOS field for the default cloud-init user."; + }; + + groups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "wheel" + "systemd-journal" + ]; + description = "Groups assigned to the default cloud-init user."; + }; + + defaultShell = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Shell path for the default cloud-init user."; + }; + + datasourceList = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ + "Oracle" + "ConfigDrive" + "NoCloud" + ]; + description = "cloud-init datasources to allow."; + }; + }; + + config = lib.mkIf cfg.enable { + networking.useNetworkd = lib.mkDefault true; + + services.cloud-init = { + enable = true; + network.enable = true; + settings = { + datasource_list = cfg.datasourceList; + users = [ "default" ]; + system_info.default_user = { + name = cfg.user; + gecos = cfg.gecos; + groups = cfg.groups; + shell = defaultShell; + lock_passwd = true; + }; + }; + }; + }; +} diff --git a/nixos/modules/services/oci-authorized-keys.nix b/nixos/modules/services/oci-authorized-keys.nix new file mode 100644 index 0000000..c6fb11d --- /dev/null +++ b/nixos/modules/services/oci-authorized-keys.nix @@ -0,0 +1,73 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.alisceon.ociAuthorizedKeys; + home = config.users.users.${cfg.user}.home; + sshDir = "${home}/.ssh"; + authorizedKeysFile = "${sshDir}/authorized_keys"; + + fetchOciAuthorizedKeys = pkgs.writeShellApplication { + name = "fetch-oci-authorized-keys"; + runtimeInputs = [ + pkgs.coreutils + pkgs.curl + ]; + text = '' + install -d -m 0700 -o ${lib.escapeShellArg cfg.user} -g ${lib.escapeShellArg cfg.group} ${lib.escapeShellArg sshDir} + + if [ -s ${lib.escapeShellArg authorizedKeysFile} ]; then + echo "OCI authorized_keys already present for ${cfg.user}" + exit 0 + fi + + curl --fail --silent --show-error --location \ + --header ${lib.escapeShellArg "Authorization: Bearer Oracle"} \ + --output ${lib.escapeShellArg authorizedKeysFile} \ + ${lib.escapeShellArg cfg.metadataUrl} + + chown ${lib.escapeShellArg "${cfg.user}:${cfg.group}"} ${lib.escapeShellArg authorizedKeysFile} + chmod 0600 ${lib.escapeShellArg authorizedKeysFile} + ''; + }; +in +{ + options.alisceon.ociAuthorizedKeys = { + enable = lib.mkEnableOption "fetching SSH authorized_keys from OCI metadata"; + + user = lib.mkOption { + type = lib.types.str; + default = "alisceon"; + description = "User whose authorized_keys file should be populated."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "users"; + description = "Group owner for the user's SSH files."; + }; + + metadataUrl = lib.mkOption { + type = lib.types.str; + default = "http://169.254.169.254/opc/v2/instance/metadata/ssh_authorized_keys"; + description = "OCI metadata endpoint containing SSH authorized keys."; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.fetch-oci-authorized-keys = { + description = "Fetch OCI metadata authorized_keys for ${cfg.user}"; + wantedBy = [ "sshd.service" ]; + before = [ "sshd.service" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + StandardError = "journal+console"; + StandardOutput = "journal+console"; + }; + script = lib.getExe fetchOciAuthorizedKeys; + }; + + systemd.services.fetch-ssh-keys.enable = false; + }; +} From c41de5cd4689e5e6be7609b6399efbe2d5390082 Mon Sep 17 00:00:00 2001 From: alisceon Date: Sat, 30 May 2026 09:38:41 +0200 Subject: [PATCH 6/6] still debugging blogbox --- nixos/hosts/blogbox/configuration.nix | 194 +++++++++++++++++- .../modules/services/oci-authorized-keys.nix | 43 +++- 2 files changed, 221 insertions(+), 16 deletions(-) diff --git a/nixos/hosts/blogbox/configuration.nix b/nixos/hosts/blogbox/configuration.nix index ec91d7a..22bb128 100644 --- a/nixos/hosts/blogbox/configuration.nix +++ b/nixos/hosts/blogbox/configuration.nix @@ -37,6 +37,64 @@ let 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 = [ @@ -48,10 +106,12 @@ in nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + oci.efi = lib.mkForce false; + virtualisation.diskSize = lib.mkForce (16 * 1024); + networking = { hostName = "blogbox"; networkmanager.enable = lib.mkForce false; - useDHCP = lib.mkDefault true; firewall.allowedTCPPorts = [ 22 80 @@ -60,6 +120,21 @@ in }; boot = { + initrd.availableKernelModules = [ + "virtio_pci" + "virtio_blk" + "virtio_scsi" + "virtio_net" + "xhci_pci" + ]; + kernelParams = lib.mkForce [ + "nvme.shutdown_timeout=10" + "nvme_core.shutdown_timeout=10" + "libiscsi.debug_libiscsi_eh=1" + "crash_kexec_post_notifiers" + "console=tty1" + "console=ttyS0,115200n8" + ]; kernelPackages = lib.mkForce pkgs.linuxPackages; loader.systemd-boot.configurationLimit = lib.mkForce 3; }; @@ -92,11 +167,19 @@ in 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 = { @@ -106,6 +189,8 @@ in nix = { settings = { + cores = lib.mkForce 1; + max-jobs = lib.mkForce 1; min-free = lib.mkForce (256 * 1024 * 1024); max-free = lib.mkForce (1024 * 1024 * 1024); }; @@ -115,6 +200,11 @@ in }; }; + system.autoUpgrade = { + persistent = lib.mkForce false; + randomizedDelaySec = lib.mkForce "4h"; + }; + security = { acme = { acceptTerms = true; @@ -125,6 +215,7 @@ in services = { openssh.settings = { + KbdInteractiveAuthentication = false; PasswordAuthentication = false; PermitRootLogin = lib.mkForce "prohibit-password"; }; @@ -164,7 +255,10 @@ in Nice = 10; IOSchedulingClass = "idle"; LockPersonality = true; + MemoryHigh = "384M"; + MemoryMax = "512M"; NoNewPrivileges = true; + OOMPolicy = "stop"; PrivateDevices = true; PrivateIPC = true; ProtectClock = true; @@ -186,17 +280,106 @@ in 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 = { - OnCalendar = "5m"; + OnBootSec = "10min"; + OnUnitInactiveSec = "5min"; Persistent = true; }; }; + + timers.blogbox-dyndns = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "5min"; + OnUnitInactiveSec = "10min"; + Persistent = true; + }; + }; + }; + + systemd.services.nixos-upgrade.serviceConfig = { + IOSchedulingClass = "idle"; + MemoryHigh = "512M"; + MemoryMax = "768M"; + Nice = 15; + OOMPolicy = "stop"; + }; + + systemd.services.growpart.serviceConfig = { + IOSchedulingClass = "idle"; + Nice = 15; + TimeoutStartSec = "2min"; + }; + + 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 = { @@ -204,13 +387,6 @@ in memoryPercent = 50; }; - swapDevices = [ - { - device = "/swapfile"; - size = 8 * 1024; - } - ]; - virtualisation = { containers.enable = lib.mkForce false; docker.enable = lib.mkForce false; diff --git a/nixos/modules/services/oci-authorized-keys.nix b/nixos/modules/services/oci-authorized-keys.nix index c6fb11d..8b9312b 100644 --- a/nixos/modules/services/oci-authorized-keys.nix +++ b/nixos/modules/services/oci-authorized-keys.nix @@ -4,25 +4,54 @@ let home = config.users.users.${cfg.user}.home; sshDir = "${home}/.ssh"; authorizedKeysFile = "${sshDir}/authorized_keys"; + staticAuthorizedKeys = config.users.users.${cfg.user}.openssh.authorizedKeys.keys or [ ]; + staticAuthorizedKeysText = + (lib.concatStringsSep "\n" staticAuthorizedKeys) + + lib.optionalString (staticAuthorizedKeys != [ ]) "\n"; + staticAuthorizedKeysFile = pkgs.writeText "static-authorized-keys-${cfg.user}" staticAuthorizedKeysText; fetchOciAuthorizedKeys = pkgs.writeShellApplication { name = "fetch-oci-authorized-keys"; runtimeInputs = [ pkgs.coreutils pkgs.curl + pkgs.gnugrep ]; text = '' install -d -m 0700 -o ${lib.escapeShellArg cfg.user} -g ${lib.escapeShellArg cfg.group} ${lib.escapeShellArg sshDir} - if [ -s ${lib.escapeShellArg authorizedKeysFile} ]; then - echo "OCI authorized_keys already present for ${cfg.user}" - exit 0 + if [ ! -e ${lib.escapeShellArg authorizedKeysFile} ]; then + install -m 0600 -o ${lib.escapeShellArg cfg.user} -g ${lib.escapeShellArg cfg.group} /dev/null ${lib.escapeShellArg authorizedKeysFile} fi + chown ${lib.escapeShellArg "${cfg.user}:${cfg.group}"} ${lib.escapeShellArg authorizedKeysFile} + chmod 0600 ${lib.escapeShellArg authorizedKeysFile} + + append_keys() { + while IFS= read -r key; do + [ -n "$key" ] || continue + grep -qxF -- "$key" ${lib.escapeShellArg authorizedKeysFile} || printf '%s\n' "$key" >> ${lib.escapeShellArg authorizedKeysFile} + done < "$1" + } + + append_keys ${lib.escapeShellArg staticAuthorizedKeysFile} + + metadata_keys="$(mktemp)" + trap 'rm -f "$metadata_keys"' EXIT + curl --fail --silent --show-error --location \ + --connect-timeout 3 \ + --max-time 10 \ + --retry 3 \ + --retry-delay 2 \ --header ${lib.escapeShellArg "Authorization: Bearer Oracle"} \ - --output ${lib.escapeShellArg authorizedKeysFile} \ - ${lib.escapeShellArg cfg.metadataUrl} + --output "$metadata_keys" \ + ${lib.escapeShellArg cfg.metadataUrl} || { + echo "Unable to fetch OCI authorized_keys for ${cfg.user}; leaving existing keys unchanged" + exit 0 + } + + append_keys "$metadata_keys" chown ${lib.escapeShellArg "${cfg.user}:${cfg.group}"} ${lib.escapeShellArg authorizedKeysFile} chmod 0600 ${lib.escapeShellArg authorizedKeysFile} @@ -55,8 +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" ]; - before = [ "sshd.service" ]; + wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; serviceConfig = { @@ -64,6 +92,7 @@ in RemainAfterExit = true; StandardError = "journal+console"; StandardOutput = "journal+console"; + TimeoutStartSec = "30s"; }; script = lib.getExe fetchOciAuthorizedKeys; };