From 767677a7e8fb403a5173cf075c6f04c5413c23e6 Mon Sep 17 00:00:00 2001 From: Jet Date: Wed, 6 May 2026 10:46:11 -0700 Subject: [PATCH] feat: add encrypted APOD greeter setup --- configuration.nix | 148 ++++++++++++++++++++++++++++++++++----- flake.lock | 94 +++++++++++++++++++++++-- flake.nix | 6 ++ home-modules/core.nix | 5 ++ home-modules/desktop.nix | 3 + home-modules/lib.nix | 99 ++++++++++++++++++++------ home-modules/sway.nix | 13 ++++ secrets/nasa-api.env.age | 7 ++ secrets/secrets.nix | 7 ++ ssh-public-keys.nix | 6 ++ 10 files changed, 343 insertions(+), 45 deletions(-) create mode 100644 secrets/nasa-api.env.age create mode 100644 secrets/secrets.nix create mode 100644 ssh-public-keys.nix diff --git a/configuration.nix b/configuration.nix index d1da7a2..249062b 100644 --- a/configuration.nix +++ b/configuration.nix @@ -3,6 +3,31 @@ let greetdApodDir = "/var/lib/greetd/apod"; greetdApodCurrent = "${greetdApodDir}/current"; + swaySession = pkgs.writeTextDir "share/wayland-sessions/sway.desktop" '' + [Desktop Entry] + Name=Sway + Comment=An i3-compatible Wayland compositor + Exec=${config.programs.sway.package}/bin/sway + Type=Application + DesktopNames=sway + ''; + regreetPasswordPrompt = pkgs.regreet.overrideAttrs (oldAttrs: { + postPatch = (oldAttrs.postPatch or "") + '' + substituteInPlace src/gui/model.rs \ + --replace-fail $' } else {\n let username = if let Some(username) = self.get_current_username() {' \ + $' } else if self.sys_util.get_sessions().len() == 1 {\n let (session, sess_info) = self.sys_util.get_sessions().iter().next().expect("one session");\n info!("No session selected; using only available session: {session}");\n (Some(session.to_string()), Some(sess_info.clone()))\n } else {\n let username = if let Some(username) = self.get_current_username() {' + + substituteInPlace src/gui/component.rs \ + --replace-fail $' // Set the default behaviour of pressing the Return key to act like the login button.\n root.set_default_widget(Some(&widgets.ui.login_button));\n\n AsyncComponentParts { model, widgets }' \ + $' // Set the default behaviour of pressing the Return key to act like the login button.\n root.set_default_widget(Some(&widgets.ui.login_button));\n\n // Immediately start authentication so the password entry appears and receives focus.\n sender.input(Self::Input::Login {\n input: String::new(),\n info: UserSessInfo::extract(\n &widgets.ui.usernames_box,\n &widgets.ui.username_entry,\n &widgets.ui.sessions_box,\n &widgets.ui.session_entry,\n ),\n });\n\n AsyncComponentParts { model, widgets }' + ''; + }); + regreetState = pkgs.writeText "regreet-state.toml" '' + last_user = "jet" + + [user_to_last_sess] + jet = "Sway" + ''; fetchGreetdApod = pkgs.writeShellApplication { name = "greetd-apod-wallpaper"; runtimeInputs = [ @@ -34,29 +59,87 @@ let install_current "$user_current" "$state_dir/bootstrap" fi - curl_args=( + read_api_key_file() { + local key_file="$1" + + if [ -r "$key_file" ]; then + while IFS= read -r line; do + case "$line" in + NASA_API_KEY=*) + api_key="''${line#NASA_API_KEY=}" + ;; + esac + done < "$key_file" + fi + } + + api_key="''${NASA_API_KEY:-}" + if [ -z "$api_key" ]; then + read_api_key_file "''${NASA_API_KEY_FILE:-/home/jet/.config/nasa-api.env}" + fi + if [ -z "$api_key" ]; then + read_api_key_file /etc/nasa-api.env + fi + if [ -z "$api_key" ]; then + api_key="DEMO_KEY" + fi + + today="$(date +%F)" + for cached in "$state_dir/apod-$today".*; do + if [ -s "$cached" ]; then + ln -sfn "$cached" "$current_link" + exit 0 + fi + done + + api_curl_args=( --fail --silent --show-error --location - --retry 30 - --retry-all-errors - --retry-delay 2 - --connect-timeout 10 - --max-time 300 + --connect-timeout 5 + --max-time 20 ) - json="$(curl "''${curl_args[@]}" 'https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY' || true)" + image_curl_args=( + --fail + --silent + --show-error + --location + --retry 2 + --retry-delay 5 + --retry-max-time 120 + --connect-timeout 10 + --max-time 60 + ) + + api_request="$(mktemp)" + trap 'rm -f "$api_request"' EXIT + { + printf '%s\n' 'url = "https://api.nasa.gov/planetary/apod"' + printf '%s\n' 'get' + printf 'data-urlencode = "api_key=%s"\n' "$api_key" + printf '%s\n' 'data-urlencode = "thumbs=True"' + } > "$api_request" + chmod 0600 "$api_request" + + json="$(curl "''${api_curl_args[@]}" --config "$api_request" || true)" if [ -z "$json" ]; then exit 0 fi media_type="$(printf '%s' "$json" | jq -r '.media_type // empty')" - if [ "$media_type" != "image" ]; then - exit 0 - fi - - image_url="$(printf '%s' "$json" | jq -r '.hdurl // .url // empty')" + case "$media_type" in + image) + image_url="$(printf '%s' "$json" | jq -r '.hdurl // .url // empty')" + ;; + video) + image_url="$(printf '%s' "$json" | jq -r '.thumbnail_url // empty')" + ;; + *) + exit 0 + ;; + esac if [ -z "$image_url" ]; then exit 0 fi @@ -77,7 +160,7 @@ let tmp="$target.tmp" if [ ! -s "$target" ]; then - if curl "''${curl_args[@]}" "$image_url" -o "$tmp" && [ -s "$tmp" ]; then + if curl "''${image_curl_args[@]}" "$image_url" -o "$tmp" && [ -s "$tmp" ]; then mv "$tmp" "$target" chmod 0644 "$target" else @@ -257,16 +340,17 @@ in services.greetd = { enable = true; settings.default_session = { - command = "env GTK_USE_PORTAL=0 GDK_DEBUG=no-portals XDG_DATA_DIRS=/run/current-system/sw/share ${pkgs.dbus}/bin/dbus-run-session ${pkgs.cage}/bin/cage -s -d -- ${config.programs.regreet.package}/bin/regreet"; + command = "env GTK_USE_PORTAL=0 GDK_DEBUG=no-portals SESSION_DIRS=/run/current-system/sw/share/wayland-sessions XDG_DATA_DIRS=/run/current-system/sw/share ${pkgs.dbus}/bin/dbus-run-session ${pkgs.cage}/bin/cage -s -d -- ${config.programs.regreet.package}/bin/regreet"; user = "greeter"; }; }; programs.regreet = { enable = true; + package = regreetPasswordPrompt; font = { - package = pkgs.nerd-fonts.commit-mono; - name = "CommitMono Nerd Font"; + package = pkgs.atkinson-hyperlegible-next; + name = "Atkinson Hyperlegible Next"; size = 16; }; settings = { @@ -285,14 +369,42 @@ in services.accounts-daemon.enable = true; + age = { + identityPaths = [ "/home/jet/.ssh/id_ed25519" ]; + secrets.nasa-api-env = { + file = ./secrets/nasa-api.env.age; + owner = "jet"; + group = "users"; + mode = "0400"; + }; + }; + + system.activationScripts.regreetDefaultSession.text = '' + ${pkgs.coreutils}/bin/install -D -m 0644 ${regreetState} /var/lib/regreet/state.toml + chown greeter:greeter /var/lib/regreet/state.toml + ''; + + fonts = { + packages = [ + pkgs.atkinson-hyperlegible-next + pkgs.nerd-fonts.commit-mono + ]; + fontconfig.defaultFonts = { + sansSerif = [ "Atkinson Hyperlegible Next" ]; + serif = [ "Atkinson Hyperlegible Next" ]; + monospace = [ "CommitMono Nerd Font" ]; + }; + }; + systemd.services.greetd-apod-wallpaper = { description = "Fetch NASA APOD wallpaper for greetd"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = "${fetchGreetdApod}/bin/greetd-apod-wallpaper"; + EnvironmentFile = "-${config.age.secrets.nasa-api-env.path}"; + TimeoutStartSec = "3min"; }; }; @@ -428,9 +540,11 @@ in docker docker-compose flatpak + swaySession wget nh ]; + environment.pathsToLink = [ "/share/wayland-sessions" ]; programs.steam.enable = true; programs.nix-index-database.comma.enable = true; diff --git a/flake.lock b/flake.lock index c11a0ef..012e292 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,50 @@ { "nodes": { + "agenix": { + "inputs": { + "darwin": "darwin", + "home-manager": "home-manager", + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1770165109, + "narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=", + "owner": "ryantm", + "repo": "agenix", + "rev": "b027ee29d959fda4b60b57566d64c98a202e0feb", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, + "darwin": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1744478979, + "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", + "owner": "lnl7", + "repo": "nix-darwin", + "rev": "43975d782b418ebf4969e9ccba82466728c2851b", + "type": "github" + }, + "original": { + "owner": "lnl7", + "ref": "master", + "repo": "nix-darwin", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -55,9 +100,9 @@ "ghostty": { "inputs": { "flake-compat": "flake-compat", - "home-manager": "home-manager", + "home-manager": "home-manager_2", "nixpkgs": "nixpkgs", - "systems": "systems", + "systems": "systems_2", "zig": "zig", "zon2nix": "zon2nix" }, @@ -97,6 +142,27 @@ } }, "home-manager": { + "inputs": { + "nixpkgs": [ + "agenix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1745494811, + "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "home-manager_2": { "inputs": { "nixpkgs": [ "ghostty", @@ -117,7 +183,7 @@ "type": "github" } }, - "home-manager_2": { + "home-manager_3": { "inputs": { "nixpkgs": [ "nixpkgs" @@ -137,7 +203,7 @@ "type": "github" } }, - "home-manager_3": { + "home-manager_4": { "inputs": { "nixpkgs": [ "zen-browser", @@ -312,9 +378,10 @@ }, "root": { "inputs": { + "agenix": "agenix", "ghostty": "ghostty", "helix": "helix", - "home-manager": "home-manager_2", + "home-manager": "home-manager_3", "nix-index-database": "nix-index-database", "nixos-hardware": "nixos-hardware", "nixpkgs": "nixpkgs_3", @@ -346,6 +413,21 @@ } }, "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { "flake": false, "locked": { "lastModified": 1681028828, @@ -382,7 +464,7 @@ }, "zen-browser": { "inputs": { - "home-manager": "home-manager_3", + "home-manager": "home-manager_4", "nixpkgs": [ "nixpkgs" ] diff --git a/flake.nix b/flake.nix index 0740e91..d0330c8 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,10 @@ url = "github:nix-community/nix-index-database"; inputs.nixpkgs.follows = "nixpkgs"; }; + agenix = { + url = "github:ryantm/agenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; nur = { url = "github:nix-community/NUR"; inputs.nixpkgs.follows = "nixpkgs"; @@ -44,6 +48,7 @@ nixos-hardware.nixosModules.framework-amd-ai-300-series home-manager.nixosModules.home-manager inputs.nix-index-database.nixosModules.default + inputs.agenix.nixosModules.default { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; @@ -84,6 +89,7 @@ pkgs.mkShell { packages = [ pkgs.nh + inputs.agenix.packages.x86_64-linux.default nhs ]; }; diff --git a/home-modules/core.nix b/home-modules/core.nix index 718c727..a2c92e2 100644 --- a/home-modules/core.nix +++ b/home-modules/core.nix @@ -34,6 +34,11 @@ name = "Adwaita-dark"; package = pkgs.gnome-themes-extra; }; + font = { + name = "Atkinson Hyperlegible Next"; + package = pkgs.atkinson-hyperlegible-next; + size = 11; + }; gtk3.extraConfig.gtk-application-prefer-dark-theme = 1; gtk4 = { theme = config.gtk.theme; diff --git a/home-modules/desktop.nix b/home-modules/desktop.nix index ad5b8bc..a4ab9e5 100644 --- a/home-modules/desktop.nix +++ b/home-modules/desktop.nix @@ -8,7 +8,10 @@ color-scheme = "prefer-dark"; cursor-size = 28; cursor-theme = "Adwaita"; + document-font-name = "Atkinson Hyperlegible Next 11"; enable-animations = false; + font-name = "Atkinson Hyperlegible Next 11"; + monospace-font-name = "CommitMono Nerd Font 11"; }; "org/gtk/gtk4/settings/file-chooser" = { show-hidden = true; diff --git a/home-modules/lib.nix b/home-modules/lib.nix index 4d795ad..be210ee 100644 --- a/home-modules/lib.nix +++ b/home-modules/lib.nix @@ -6,13 +6,10 @@ }: let + sshPublicKeys = (import ../ssh-public-keys.nix).jet; name = "Jet"; email = "jet@extremist.software"; sshSigningKey = "~/.ssh/id_ed25519"; - sshPublicKeys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu jet@extremist.software" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyic30I+SaDw0Lz/EFpMNeHCwxpwPfkgfR6uz3g7io7 jet@corp.primitive.dev" - ]; wrappedOpencode = pkgs.symlinkJoin { name = "opencode-wrapped"; paths = [ pkgs.opencode ]; @@ -157,16 +154,48 @@ let state_dir="${config.home.homeDirectory}/.local/state/nasa-apod" current_link="$state_dir/current" mkdir -p "$state_dir" - curl_args=( + + read_api_key_file() { + local key_file="$1" + + if [ -r "$key_file" ]; then + while IFS= read -r line; do + case "$line" in + NASA_API_KEY=*) + api_key="''${line#NASA_API_KEY=}" + ;; + esac + done < "$key_file" + fi + } + + api_key="''${NASA_API_KEY:-}" + if [ -z "$api_key" ]; then + read_api_key_file "''${NASA_API_KEY_FILE:-${config.home.homeDirectory}/.config/nasa-api.env}" + fi + if [ -z "$api_key" ]; then + api_key="DEMO_KEY" + fi + + api_curl_args=( --fail --silent --show-error --location - --retry 30 - --retry-all-errors - --retry-delay 2 + --connect-timeout 5 + --max-time 20 + ) + + image_curl_args=( + --fail + --silent + --show-error + --location + --retry 2 + --retry-delay 5 + --retry-max-time 120 --connect-timeout 10 - --max-time 300 + --max-time 60 ) set_wallpaper() { @@ -181,18 +210,42 @@ let set_wallpaper "$current_link" fi - json="$(curl "''${curl_args[@]}" 'https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY' || true)" + today="$(date +%F)" + for cached in "$state_dir/apod-$today".*; do + if [ -s "$cached" ]; then + ln -sfn "$cached" "$current_link" + set_wallpaper "$current_link" + exit 0 + fi + done + + api_request="$(mktemp)" + trap 'rm -f "$api_request"' EXIT + { + printf '%s\n' 'url = "https://api.nasa.gov/planetary/apod"' + printf '%s\n' 'get' + printf 'data-urlencode = "api_key=%s"\n' "$api_key" + printf '%s\n' 'data-urlencode = "thumbs=True"' + } > "$api_request" + chmod 0600 "$api_request" + + json="$(curl "''${api_curl_args[@]}" --config "$api_request" || true)" if [ -z "$json" ]; then exit 0 fi media_type="$(printf '%s' "$json" | jq -r '.media_type // empty')" - - if [ "$media_type" != "image" ]; then - exit 0 - fi - - image_url="$(printf '%s' "$json" | jq -r '.hdurl // .url // empty')" + case "$media_type" in + image) + image_url="$(printf '%s' "$json" | jq -r '.hdurl // .url // empty')" + ;; + video) + image_url="$(printf '%s' "$json" | jq -r '.thumbnail_url // empty')" + ;; + *) + exit 0 + ;; + esac if [ -z "$image_url" ]; then exit 0 fi @@ -211,14 +264,16 @@ let target="$state_dir/apod-$date_stamp.$ext" tmp="$target.tmp" - if curl "''${curl_args[@]}" "$image_url" -o "$tmp" && [ -s "$tmp" ]; then - mv "$tmp" "$target" - ln -sfn "$target" "$current_link" - else - rm -f "$tmp" + if [ ! -s "$target" ]; then + if curl "''${image_curl_args[@]}" "$image_url" -o "$tmp" && [ -s "$tmp" ]; then + mv "$tmp" "$target" + else + rm -f "$tmp" + fi fi - if [ -e "$current_link" ]; then + if [ -e "$target" ]; then + ln -sfn "$target" "$current_link" set_wallpaper "$current_link" fi ''; diff --git a/home-modules/sway.nix b/home-modules/sway.nix index 589fd18..5692b60 100644 --- a/home-modules/sway.nix +++ b/home-modules/sway.nix @@ -3,10 +3,21 @@ pkgs, homeLib, hostname, + osConfig ? null, ... }: let + apodSecretEnvironmentFile = + if + osConfig != null + && osConfig ? age + && osConfig.age ? secrets + && builtins.hasAttr "nasa-api-env" osConfig.age.secrets + then + "-${osConfig.age.secrets."nasa-api-env".path}" + else + "-%h/.config/nasa-api.env"; apodCurrent = "${config.home.homeDirectory}/.local/state/nasa-apod/current"; swayOutputs = "${config.home.homeDirectory}/.config/sway/outputs"; lockCommand = pkgs.writeShellScript "sway-lock-apod" '' @@ -285,6 +296,8 @@ in Service = { Type = "oneshot"; ExecStart = "${homeLib.nasaApodWallpaper}/bin/nasa-apod-wallpaper"; + EnvironmentFile = apodSecretEnvironmentFile; + TimeoutStartSec = "3min"; }; }; diff --git a/secrets/nasa-api.env.age b/secrets/nasa-api.env.age new file mode 100644 index 0000000..302efbe --- /dev/null +++ b/secrets/nasa-api.env.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 Ziw7aw +DWaSJE/UkXo6jnXZMElPreAbfHtMdzd2kxTlPUMPTc +2I0jH1tG73LcRLO6UvxSOMD3T0XKKfXjuZCXhKGypFc +-> ssh-ed25519 LB5l3A qNcgWT2QN4NSpehI2ku+2+NKLS0Q93/D3Taqjd4+mFQ +rEKPREqfGWXoZAuYeEkR1pMtc+/0JTqaTDL+My7jnWM +--- 1dVemchD/oaHJR0aeje1CTps9NahLLivBSfvQhqPJWQ +#>h9qswiMnz0oUMR4dVpPkx_ g"WNu TAn \ No newline at end of file diff --git a/secrets/secrets.nix b/secrets/secrets.nix new file mode 100644 index 0000000..8012825 --- /dev/null +++ b/secrets/secrets.nix @@ -0,0 +1,7 @@ +let + sshPublicKeys = import ../ssh-public-keys.nix; +in + +{ + "secrets/nasa-api.env.age".publicKeys = sshPublicKeys.jet; +} diff --git a/ssh-public-keys.nix b/ssh-public-keys.nix new file mode 100644 index 0000000..d3f836c --- /dev/null +++ b/ssh-public-keys.nix @@ -0,0 +1,6 @@ +{ + jet = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu jet@extremist.software" + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyic30I+SaDw0Lz/EFpMNeHCwxpwPfkgfR6uz3g7io7 jet@corp.primitive.dev" + ]; +}