@jaspermayone.com's dotfiles

nix fmt

+1002 -758
+50 -40
darwin/default.nix
··· 1 1 # Darwin (macOS) specific configuration 2 - { config, pkgs, lib, inputs, hostname, ... }: 2 + { 3 + config, 4 + pkgs, 5 + lib, 6 + inputs, 7 + hostname, 8 + ... 9 + }: 3 10 4 11 { 5 12 # Nix configuration 6 - nix.settings.experimental-features = [ "nix-command" "flakes" ]; 13 + nix.settings.experimental-features = [ 14 + "nix-command" 15 + "flakes" 16 + ]; 7 17 8 18 # Fix GID mismatch for nixbld group (new installs use 350, old used 30000) 9 19 ids.gids.nixbld = 350; ··· 39 49 lazygit 40 50 redis 41 51 mkcert 42 - inetutils # telnet, ftp, etc. 52 + inetutils # telnet, ftp, etc. 43 53 watchman 44 54 pipx 45 55 pwgen ··· 58 68 enable = true; 59 69 onActivation = { 60 70 autoUpdate = true; 61 - cleanup = "zap"; # Remove unlisted packages 71 + cleanup = "zap"; # Remove unlisted packages 62 72 upgrade = true; 63 73 }; 64 74 ··· 78 88 # CLI tools (only macOS-specific, special taps, or unavailable in nixpkgs) 79 89 brews = [ 80 90 # macOS specific 81 - "mas" # Mac App Store CLI 82 - "libyaml" # Required for mise-installed Ruby (psych gem) 91 + "mas" # Mac App Store CLI 92 + "libyaml" # Required for mise-installed Ruby (psych gem) 83 93 84 94 # Font tools (bramstein tap) 85 95 "sfnt2woff" ··· 109 119 "composer" 110 120 "openjdk" 111 121 "openjdk@21" 112 - "rust" # Keep for toolchain management 122 + "rust" # Keep for toolchain management 113 123 114 124 # Databases (specific versions) 115 125 "mysql@8.0" ··· 140 150 141 151 # Mac App Store apps (requires `mas` CLI) 142 152 # masApps = { 143 - # "Xcode" = 497799835; 153 + # "Xcode" = 497799835; 144 154 # }; 145 155 }; 146 156 ··· 169 179 InitialKeyRepeat = 15; 170 180 KeyRepeat = 2; 171 181 ApplePressAndHoldEnabled = false; 172 - AppleKeyboardUIMode = 3; # Full keyboard access 182 + AppleKeyboardUIMode = 3; # Full keyboard access 173 183 174 184 # Scrolling 175 - "com.apple.swipescrolldirection" = false; # (true = Natural scrolling 185 + "com.apple.swipescrolldirection" = false; # (true = Natural scrolling 176 186 177 187 # Appearance 178 188 AppleShowAllExtensions = true; ··· 184 194 AppleTemperatureUnit = "Fahrenheit"; 185 195 186 196 # Window behavior 187 - NSWindowResizeTime = 0.001; # Faster window resize 188 - NSNavPanelExpandedStateForSaveMode = true; # Expand save panel 197 + NSWindowResizeTime = 0.001; # Faster window resize 198 + NSNavPanelExpandedStateForSaveMode = true; # Expand save panel 189 199 NSNavPanelExpandedStateForSaveMode2 = true; 190 - PMPrintingExpandedStateForPrint = true; # Expand print panel 200 + PMPrintingExpandedStateForPrint = true; # Expand print panel 191 201 PMPrintingExpandedStateForPrint2 = true; 192 - NSDocumentSaveNewDocumentsToCloud = false; # Save to disk, not iCloud 193 - NSDisableAutomaticTermination = true; # Prevent auto-termination of apps 202 + NSDocumentSaveNewDocumentsToCloud = false; # Save to disk, not iCloud 203 + NSDisableAutomaticTermination = true; # Prevent auto-termination of apps 194 204 }; 195 205 196 206 # Finder ··· 199 209 AppleShowAllFiles = false; 200 210 CreateDesktop = true; 201 211 FXEnableExtensionChangeWarning = false; 202 - FXPreferredViewStyle = "clmv"; # Column view 212 + FXPreferredViewStyle = "clmv"; # Column view 203 213 QuitMenuItem = true; 204 214 ShowPathbar = true; 205 215 ShowStatusBar = true; ··· 211 221 ShowRemovableMediaOnDesktop = false; 212 222 # Sorting and search 213 223 _FXSortFoldersFirst = true; 214 - FXDefaultSearchScope = "SCcf"; # Search current folder 224 + FXDefaultSearchScope = "SCcf"; # Search current folder 215 225 }; 216 226 217 227 # Screenshots ··· 236 246 237 247 # Dock 238 248 dock = { 239 - autohide = true; # Auto-hide the dock when not in use 240 - autohide-delay = 0.0; # Delay before dock appears on hover (0 = instant) 241 - mineffect = "scale"; # Minimize animation: "scale" or "genie" 242 - minimize-to-application = false; # Minimize windows into app icon vs separate dock item 243 - mru-spaces = false; # Rearrange spaces based on most recent use 244 - orientation = "left"; # Dock position: "left", "bottom", or "right" 245 - show-recents = false; # Show recently used apps in separate dock section 246 - tilesize = 48; # Icon size in pixels 247 - launchanim = false; # Animate app launch (bouncing icon) 248 - expose-animation-duration = 0.1; # Mission Control animation speed (lower = faster) 249 - showhidden = false; # Dim hidden app icons (Cmd+H) to show they're hidden 249 + autohide = true; # Auto-hide the dock when not in use 250 + autohide-delay = 0.0; # Delay before dock appears on hover (0 = instant) 251 + mineffect = "scale"; # Minimize animation: "scale" or "genie" 252 + minimize-to-application = false; # Minimize windows into app icon vs separate dock item 253 + mru-spaces = false; # Rearrange spaces based on most recent use 254 + orientation = "left"; # Dock position: "left", "bottom", or "right" 255 + show-recents = false; # Show recently used apps in separate dock section 256 + tilesize = 48; # Icon size in pixels 257 + launchanim = false; # Animate app launch (bouncing icon) 258 + expose-animation-duration = 0.1; # Mission Control animation speed (lower = faster) 259 + showhidden = false; # Dim hidden app icons (Cmd+H) to show they're hidden 250 260 }; 251 261 252 262 # Trackpad 253 263 trackpad = { 254 - Clicking = true; # Tap to click 264 + Clicking = true; # Tap to click 255 265 TrackpadRightClick = true; 256 266 TrackpadThreeFingerDrag = true; 257 267 Dragging = true; ··· 278 288 CustomUserPreferences = { 279 289 # System sound 280 290 "com.apple.systemsound" = { 281 - "com.apple.sound.uiaudio.enabled" = 0; # Disable boot sound 291 + "com.apple.sound.uiaudio.enabled" = 0; # Disable boot sound 282 292 }; 283 293 284 294 # Help Viewer non-floating ··· 304 314 # Finder extras 305 315 "com.apple.finder" = { 306 316 ShowRecentTags = false; 307 - OpenWindowForNewRemovableDisk = true; # Auto-open for mounted volumes 317 + OpenWindowForNewRemovableDisk = true; # Auto-open for mounted volumes 308 318 }; 309 319 310 320 # Disk images: skip verification ··· 321 331 322 332 # Safari 323 333 "com.apple.Safari" = { 324 - UniversalSearchEnabled = false; # Don't send search queries to Apple 334 + UniversalSearchEnabled = false; # Don't send search queries to Apple 325 335 SuppressSearchSuggestions = true; 326 336 ShowFullURLInSmartSearchField = true; 327 337 HomePage = "about:blank"; ··· 334 344 335 345 # Mail 336 346 "com.apple.mail" = { 337 - AddressesIncludeNameOnPasteboard = false; # Copy addresses without name 347 + AddressesIncludeNameOnPasteboard = false; # Copy addresses without name 338 348 NSUserKeyEquivalents = { 339 - Send = "@\\U21a9"; # Cmd+Enter to send 349 + Send = "@\\U21a9"; # Cmd+Enter to send 340 350 }; 341 351 DisableInlineAttachmentViewing = true; 342 352 }; 343 353 344 354 # Terminal 345 355 "com.apple.terminal" = { 346 - StringEncodings = [ 4 ]; # UTF-8 only 356 + StringEncodings = [ 4 ]; # UTF-8 only 347 357 }; 348 358 349 359 # iTerm2 ··· 359 369 # Activity Monitor 360 370 "com.apple.ActivityMonitor" = { 361 371 OpenMainWindow = true; 362 - ShowCategory = 0; # All processes 372 + ShowCategory = 0; # All processes 363 373 SortColumn = "CPUUsage"; 364 374 SortDirection = 0; 365 375 }; 366 376 367 377 # TextEdit 368 378 "com.apple.TextEdit" = { 369 - RichText = 0; # Plain text mode 370 - PlainTextEncoding = 4; # UTF-8 379 + RichText = 0; # Plain text mode 380 + PlainTextEncoding = 4; # UTF-8 371 381 PlainTextEncodingForWrite = 4; 372 382 }; 373 383 ··· 386 396 # Software Update 387 397 "com.apple.SoftwareUpdate" = { 388 398 AutomaticCheckEnabled = true; 389 - ScheduleFrequency = 1; # Daily 399 + ScheduleFrequency = 1; # Daily 390 400 AutomaticDownload = 1; 391 - CriticalUpdateInstall = 1; # Auto-install security updates 401 + CriticalUpdateInstall = 1; # Auto-install security updates 392 402 AutoUpdate = true; 393 403 }; 394 404
+123 -112
flake.nix
··· 55 55 }; 56 56 }; 57 57 58 - outputs = { 59 - self, 60 - nixpkgs, 61 - nixpkgs-unstable, 62 - agenix, 63 - home-manager, 64 - nur, 65 - nix-darwin, 66 - deploy-rs, 67 - tangled, 68 - tgirlpkgs, 69 - rust-fp, 70 - ... 71 - }@inputs: 72 - let 73 - outputs = inputs.self.outputs; 58 + outputs = 59 + { 60 + self, 61 + nixpkgs, 62 + nixpkgs-unstable, 63 + agenix, 64 + home-manager, 65 + nur, 66 + nix-darwin, 67 + deploy-rs, 68 + tangled, 69 + tgirlpkgs, 70 + rust-fp, 71 + ... 72 + }@inputs: 73 + let 74 + outputs = inputs.self.outputs; 74 75 75 - # Overlay to make unstable packages available as pkgs.unstable.* 76 - # Also includes custom packages 77 - unstable-overlays = { 78 - nixpkgs.overlays = [ 79 - (final: prev: { 80 - unstable = import nixpkgs-unstable { 81 - system = final.stdenv.hostPlatform.system; 82 - config.allowUnfree = true; 83 - }; 76 + # Overlay to make unstable packages available as pkgs.unstable.* 77 + # Also includes custom packages 78 + unstable-overlays = { 79 + nixpkgs.overlays = [ 80 + (final: prev: { 81 + unstable = import nixpkgs-unstable { 82 + system = final.stdenv.hostPlatform.system; 83 + config.allowUnfree = true; 84 + }; 84 85 85 - # Custom packages 86 - zmx-binary = prev.callPackage ./packages/zmx.nix { }; 86 + # Custom packages 87 + zmx-binary = prev.callPackage ./packages/zmx.nix { }; 87 88 88 - # Caddy with Cloudflare DNS plugin for ACME DNS challenges 89 - caddy-cloudflare = prev.caddy.withPlugins { 90 - plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ]; 91 - hash = "sha256-dnhEjopeA0UiI+XVYHYpsjcEI6Y1Hacbi28hVKYQURg="; 92 - }; 93 - }) 94 - ]; 95 - }; 89 + # Caddy with Cloudflare DNS plugin for ACME DNS challenges 90 + caddy-cloudflare = prev.caddy.withPlugins { 91 + plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ]; 92 + hash = "sha256-dnhEjopeA0UiI+XVYHYpsjcEI6Y1Hacbi28hVKYQURg="; 93 + }; 94 + }) 95 + ]; 96 + }; 96 97 97 - # Helper function to create NixOS configurations 98 - mkNixos = hostname: system: nixpkgs.lib.nixosSystem { 99 - inherit system; 100 - specialArgs = { inherit inputs outputs hostname; }; 101 - modules = [ 102 - ./hosts/${hostname}/configuration.nix 103 - agenix.nixosModules.default 104 - tgirlpkgs.nixosModules.default 105 - unstable-overlays 106 - nur.modules.nixos.default 107 - home-manager.nixosModules.home-manager 108 - { 109 - home-manager.useGlobalPkgs = true; 110 - home-manager.useUserPackages = true; 111 - home-manager.backupFileExtension = "backup"; 112 - home-manager.extraSpecialArgs = { inherit inputs outputs hostname; isDarwin = false; }; 113 - home-manager.users.jsp = import ./home; 114 - } 115 - ]; 116 - }; 98 + # Helper function to create NixOS configurations 99 + mkNixos = 100 + hostname: system: 101 + nixpkgs.lib.nixosSystem { 102 + inherit system; 103 + specialArgs = { inherit inputs outputs hostname; }; 104 + modules = [ 105 + ./hosts/${hostname}/configuration.nix 106 + agenix.nixosModules.default 107 + tgirlpkgs.nixosModules.default 108 + unstable-overlays 109 + nur.modules.nixos.default 110 + home-manager.nixosModules.home-manager 111 + { 112 + home-manager.useGlobalPkgs = true; 113 + home-manager.useUserPackages = true; 114 + home-manager.backupFileExtension = "backup"; 115 + home-manager.extraSpecialArgs = { 116 + inherit inputs outputs hostname; 117 + isDarwin = false; 118 + }; 119 + home-manager.users.jsp = import ./home; 120 + } 121 + ]; 122 + }; 117 123 118 - # Helper function to create Darwin configurations 119 - mkDarwin = hostname: system: nix-darwin.lib.darwinSystem { 120 - inherit system; 121 - specialArgs = { inherit inputs outputs hostname; }; 122 - modules = [ 123 - ./darwin 124 - ./hosts/${hostname} 125 - agenix.darwinModules.default 126 - unstable-overlays 127 - nur.modules.darwin.default 128 - home-manager.darwinModules.home-manager 129 - { 130 - home-manager.useGlobalPkgs = true; 131 - home-manager.useUserPackages = true; 132 - home-manager.backupFileExtension = "backup"; 133 - home-manager.extraSpecialArgs = { inherit inputs outputs hostname; isDarwin = true; }; 134 - home-manager.users.jsp = import ./home; 135 - } 136 - ]; 137 - }; 138 - in 139 - { 140 - # NixOS configurations 141 - # Available through 'nixos-rebuild --flake .#hostname' 142 - nixosConfigurations = { 143 - alastor = mkNixos "alastor" "aarch64-linux"; 144 - horace = mkNixos "horace" "x86_64-linux"; 145 - }; 124 + # Helper function to create Darwin configurations 125 + mkDarwin = 126 + hostname: system: 127 + nix-darwin.lib.darwinSystem { 128 + inherit system; 129 + specialArgs = { inherit inputs outputs hostname; }; 130 + modules = [ 131 + ./darwin 132 + ./hosts/${hostname} 133 + agenix.darwinModules.default 134 + unstable-overlays 135 + nur.modules.darwin.default 136 + home-manager.darwinModules.home-manager 137 + { 138 + home-manager.useGlobalPkgs = true; 139 + home-manager.useUserPackages = true; 140 + home-manager.backupFileExtension = "backup"; 141 + home-manager.extraSpecialArgs = { 142 + inherit inputs outputs hostname; 143 + isDarwin = true; 144 + }; 145 + home-manager.users.jsp = import ./home; 146 + } 147 + ]; 148 + }; 149 + in 150 + { 151 + # NixOS configurations 152 + # Available through 'nixos-rebuild --flake .#hostname' 153 + nixosConfigurations = { 154 + alastor = mkNixos "alastor" "aarch64-linux"; 155 + horace = mkNixos "horace" "x86_64-linux"; 156 + }; 146 157 147 - # Darwin configurations 148 - # Available through 'darwin-rebuild switch --flake .#hostname' 149 - darwinConfigurations = { 150 - remus = mkDarwin "remus" "aarch64-darwin"; 151 - dippet = mkDarwin "dippet" "aarch64-darwin"; 152 - }; 158 + # Darwin configurations 159 + # Available through 'darwin-rebuild switch --flake .#hostname' 160 + darwinConfigurations = { 161 + remus = mkDarwin "remus" "aarch64-darwin"; 162 + dippet = mkDarwin "dippet" "aarch64-darwin"; 163 + }; 153 164 154 - # Formatters 155 - formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-tree; 156 - formatter.aarch64-darwin = nixpkgs.legacyPackages.aarch64-darwin.nixfmt-tree; 165 + # Formatters 166 + formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-tree; 167 + formatter.aarch64-darwin = nixpkgs.legacyPackages.aarch64-darwin.nixfmt-tree; 157 168 158 - # Deploy-rs configurations 159 - # Available through 'deploy .#alastor' 160 - deploy.nodes = { 161 - alastor = { 162 - hostname = "alastor"; 163 - profiles.system = { 164 - sshUser = "jsp"; 165 - user = "root"; 166 - path = deploy-rs.lib.aarch64-linux.activate.nixos self.nixosConfigurations.alastor; 169 + # Deploy-rs configurations 170 + # Available through 'deploy .#alastor' 171 + deploy.nodes = { 172 + alastor = { 173 + hostname = "alastor"; 174 + profiles.system = { 175 + sshUser = "jsp"; 176 + user = "root"; 177 + path = deploy-rs.lib.aarch64-linux.activate.nixos self.nixosConfigurations.alastor; 178 + }; 167 179 }; 168 - }; 169 - horace = { 170 - hostname = "horace"; 171 - profiles.system = { 172 - sshUser = "jsp"; 173 - user = "root"; 174 - path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.horace; 180 + horace = { 181 + hostname = "horace"; 182 + profiles.system = { 183 + sshUser = "jsp"; 184 + user = "root"; 185 + path = deploy-rs.lib.x86_64-linux.activate.nixos self.nixosConfigurations.horace; 186 + }; 175 187 }; 176 188 }; 177 - }; 178 189 179 - # Validation checks for deploy-rs 180 - checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; 181 - }; 190 + # Validation checks for deploy-rs 191 + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; 192 + }; 182 193 }
+12 -3
home/default.nix
··· 1 1 # Home Manager configuration 2 2 # Shared between NixOS and Darwin 3 - { config, pkgs, lib, hostname, isDarwin, ... }: 3 + { 4 + config, 5 + pkgs, 6 + lib, 7 + hostname, 8 + isDarwin, 9 + ... 10 + }: 4 11 5 12 { 6 13 imports = [ ··· 65 72 home.file = builtins.listToAttrs ( 66 73 map (name: { 67 74 name = name; 68 - value = { source = ../rc/${name}; }; 75 + value = { 76 + source = ../rc/${name}; 77 + }; 69 78 }) (builtins.attrNames (builtins.readDir ../rc)) 70 79 ); 71 80 ··· 92 101 hostname = "tun.hogwarts.channel"; 93 102 user = "jsp"; 94 103 identityFile = "~/.ssh/id_ed25519"; 95 - zmx = true; # auto-attach zmx session 104 + zmx = true; # auto-attach zmx session 96 105 }; 97 106 98 107 # Horace (named after Horace Slughorn)
+35 -12
hosts/alastor/configuration.nix
··· 1 1 # Alastor - NixOS server running frp tunnel service (named after Mad-Eye Moody) 2 - { config, pkgs, lib, inputs, hostname, ... }: 2 + { 3 + config, 4 + pkgs, 5 + lib, 6 + inputs, 7 + hostname, 8 + ... 9 + }: 3 10 4 11 { 5 12 imports = [ ··· 21 28 22 29 # Nix settings 23 30 nix = { 24 - settings.experimental-features = [ "nix-command" "flakes" ]; 31 + settings.experimental-features = [ 32 + "nix-command" 33 + "flakes" 34 + ]; 25 35 optimise.automatic = true; 26 36 }; 27 37 ··· 43 53 jq 44 54 tmux 45 55 bluesky-pds 46 - inputs.agenix.packages.${pkgs.stdenv.hostPlatform.system}.default # agenix CLI 56 + inputs.agenix.packages.${pkgs.stdenv.hostPlatform.system}.default # agenix CLI 47 57 ]; 48 58 49 59 # NH - NixOS helper ··· 82 92 extraGroups = [ "wheel" ]; 83 93 shell = pkgs.zsh; 84 94 openssh.authorizedKeys.keys = [ 85 - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm7lo7umraewipgQu1Pifmoo/V8jYGDHjBTmt+7SOCe jsp@remus" 95 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm7lo7umraewipgQu1Pifmoo/V8jYGDHjBTmt+7SOCe jsp@remus" 86 96 ]; 87 97 }; 88 98 ··· 111 121 github-token = { 112 122 file = ../../secrets/github-token.age; 113 123 mode = "400"; 114 - owner = "git"; # tangled uses git user 124 + owner = "git"; # tangled uses git user 115 125 }; 116 126 pds = { 117 127 file = ../../secrets/pds.age; ··· 161 171 enable = true; 162 172 hostname = "alastor"; 163 173 domain = "alastor.hogwarts.channel"; 164 - services = [ "frps" "caddy" "tailscaled" "tangled-knot" "atuin-server" ]; 165 - remoteHosts = [ "remus" "dippet" ]; 174 + services = [ 175 + "frps" 176 + "caddy" 177 + "tailscaled" 178 + "tangled-knot" 179 + "atuin-server" 180 + ]; 181 + remoteHosts = [ 182 + "remus" 183 + "dippet" 184 + ]; 166 185 cloudflareCredentialsFile = config.age.secrets.cloudflare-credentials.path; 167 186 }; 168 187 ··· 184 203 adminEmail = "pds-admin@hogwarts.dev"; 185 204 environmentFile = config.age.secrets.pds.path; 186 205 mailerEnvironmentFile = config.age.secrets.pds-mailer.path; 187 - enableGatekeeper = false; # Disabled for now - was causing pdsadmin issues 206 + enableGatekeeper = false; # Disabled for now - was causing pdsadmin issues 188 207 enableAgeAssurance = true; 189 208 }; 190 209 ··· 194 213 hostname = "atuin.hogwarts.dev"; 195 214 cloudflareCredentialsFile = config.age.secrets.cloudflare-credentials.path; 196 215 }; 197 - 198 216 199 217 # Knot to GitHub sync service 200 218 jsp.services.knot-sync = { ··· 223 241 }; 224 242 }; 225 243 226 - systemd.services.caddy.serviceConfig.EnvironmentFile = config.age.secrets.cloudflare-credentials.path; 244 + systemd.services.caddy.serviceConfig.EnvironmentFile = 245 + config.age.secrets.cloudflare-credentials.path; 227 246 228 - networking.firewall.allowedTCPPorts = [ 80 443 2222 ]; # 2222 for knot SSH 247 + networking.firewall.allowedTCPPorts = [ 248 + 80 249 + 443 250 + 2222 251 + ]; # 2222 for knot SSH 229 252 230 253 # Castle backup system (disabled for now - enable when secrets are ready) 231 254 # To enable: ··· 280 303 enable = true; 281 304 flake = "github:jaspermayone/dots#alastor"; 282 305 dates = "04:00"; 283 - allowReboot = false; # Set to true if you want automatic reboots when needed 306 + allowReboot = false; # Set to true if you want automatic reboots when needed 284 307 }; 285 308 }
+20 -4
hosts/alastor/hardware-configuration.nix
··· 1 1 # Hardware configuration for alastor VPS 2 2 # Generated by nix-infect on OCI ARM 3 - { config, lib, pkgs, modulesPath, ... }: 3 + { 4 + config, 5 + lib, 6 + pkgs, 7 + modulesPath, 8 + ... 9 + }: 4 10 5 11 { 6 12 imports = [ ··· 15 21 }; 16 22 17 23 # Filesystems 18 - fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; }; 19 - fileSystems."/boot" = { device = "/dev/disk/by-uuid/3807-C85F"; fsType = "vfat"; }; 24 + fileSystems."/" = { 25 + device = "/dev/sda1"; 26 + fsType = "ext4"; 27 + }; 28 + fileSystems."/boot" = { 29 + device = "/dev/disk/by-uuid/3807-C85F"; 30 + fsType = "vfat"; 31 + }; 20 32 21 33 # OCI/Xen kernel modules 22 - boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ]; 34 + boot.initrd.availableKernelModules = [ 35 + "ata_piix" 36 + "uhci_hcd" 37 + "xen_blkfront" 38 + ]; 23 39 boot.initrd.kernelModules = [ "nvme" ]; 24 40 25 41 # Enable QEMU guest agent for better VM integration
+14 -2
hosts/dippet/default.nix
··· 1 1 # Dippet - Mac Mini (server + desktop) 2 - { config, pkgs, lib, inputs, hostname, ... }: 2 + { 3 + config, 4 + pkgs, 5 + lib, 6 + inputs, 7 + hostname, 8 + ... 9 + }: 3 10 4 11 { 5 12 # Disable nix-darwin's Nix management (using Determinate Nix installer) ··· 11 18 /run/current-system/sw/bin/darwin-rebuild switch --flake github:jaspermayone/dots#dippet 12 19 ''; 13 20 serviceConfig = { 14 - StartCalendarInterval = [{ Hour = 4; Minute = 0; }]; 21 + StartCalendarInterval = [ 22 + { 23 + Hour = 4; 24 + Minute = 0; 25 + } 26 + ]; 15 27 StandardOutPath = "/var/log/nix-darwin-upgrade.log"; 16 28 StandardErrorPath = "/var/log/nix-darwin-upgrade.log"; 17 29 };
+17 -4
hosts/horace/configuration.nix
··· 1 1 # Horace - NixOS desktop (named after Horace Slughorn) 2 - { config, pkgs, lib, inputs, hostname, ... }: 2 + { 3 + config, 4 + pkgs, 5 + lib, 6 + inputs, 7 + hostname, 8 + ... 9 + }: 3 10 4 11 { 5 12 imports = [ ··· 22 29 23 30 # Nix settings 24 31 nix = { 25 - settings.experimental-features = [ "nix-command" "flakes" ]; 32 + settings.experimental-features = [ 33 + "nix-command" 34 + "flakes" 35 + ]; 26 36 optimise.automatic = true; 27 37 }; 28 38 ··· 123 133 users.users.jsp = { 124 134 isNormalUser = true; 125 135 description = "Jasper"; 126 - extraGroups = [ "networkmanager" "wheel" ]; 136 + extraGroups = [ 137 + "networkmanager" 138 + "wheel" 139 + ]; 127 140 shell = pkgs.zsh; 128 141 openssh.authorizedKeys.keys = [ 129 - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm7lo7umraewipgQu1Pifmoo/V8jYGDHjBTmt+7SOCe jsp@remus" 142 + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHm7lo7umraewipgQu1Pifmoo/V8jYGDHjBTmt+7SOCe jsp@remus" 130 143 ]; 131 144 }; 132 145
+30 -17
hosts/horace/hardware-configuration.nix
··· 1 1 # Hardware configuration for horace 2 2 # Generated by 'nixos-generate-config' 3 - { config, lib, pkgs, modulesPath, ... }: 3 + { 4 + config, 5 + lib, 6 + pkgs, 7 + modulesPath, 8 + ... 9 + }: 4 10 5 11 { 6 - imports = 7 - [ (modulesPath + "/installer/scan/not-detected.nix") 8 - ]; 12 + imports = [ 13 + (modulesPath + "/installer/scan/not-detected.nix") 14 + ]; 9 15 10 - boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "sdhci_pci" ]; 16 + boot.initrd.availableKernelModules = [ 17 + "xhci_pci" 18 + "ahci" 19 + "sdhci_pci" 20 + ]; 11 21 boot.initrd.kernelModules = [ ]; 12 22 boot.kernelModules = [ "kvm-intel" ]; 13 23 boot.extraModulePackages = [ ]; 14 24 15 - fileSystems."/" = 16 - { device = "/dev/disk/by-uuid/4a20f65f-aba4-4436-979e-3fb6150f9189"; 17 - fsType = "ext4"; 18 - }; 25 + fileSystems."/" = { 26 + device = "/dev/disk/by-uuid/4a20f65f-aba4-4436-979e-3fb6150f9189"; 27 + fsType = "ext4"; 28 + }; 19 29 20 - fileSystems."/boot" = 21 - { device = "/dev/disk/by-uuid/A280-6001"; 22 - fsType = "vfat"; 23 - options = [ "fmask=0077" "dmask=0077" ]; 24 - }; 30 + fileSystems."/boot" = { 31 + device = "/dev/disk/by-uuid/A280-6001"; 32 + fsType = "vfat"; 33 + options = [ 34 + "fmask=0077" 35 + "dmask=0077" 36 + ]; 37 + }; 25 38 26 - swapDevices = 27 - [ { device = "/dev/disk/by-uuid/8516975c-9b9c-4cb9-9f11-67d3527b832d"; } 28 - ]; 39 + swapDevices = [ 40 + { device = "/dev/disk/by-uuid/8516975c-9b9c-4cb9-9f11-67d3527b832d"; } 41 + ]; 29 42 30 43 nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 31 44 hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+8 -1
hosts/remus/default.nix
··· 1 1 # Remus - MacBook Pro M4 (dev laptop) 2 - { config, pkgs, lib, inputs, hostname, ... }: 2 + { 3 + config, 4 + pkgs, 5 + lib, 6 + inputs, 7 + hostname, 8 + ... 9 + }: 3 10 4 11 { 5 12 # Host-specific overrides go here
+6 -1
modules/atuin-server/default.nix
··· 1 1 # modules/atuin-server/default.nix 2 2 # NixOS module for self-hosted Atuin sync server 3 - { lib, config, pkgs, ... }: 3 + { 4 + lib, 5 + config, 6 + pkgs, 7 + ... 8 + }: 4 9 let 5 10 cfg = config.atelier.services.atuin-server; 6 11 in
+33 -25
modules/bluesky-pds/default.nix
··· 1 1 # modules/bluesky-pds/default.nix 2 2 # NixOS module enabling Bluesky PDS with Caddy reverse proxy and optional gatekeeper 3 - { lib, config, pkgs, ... }: 3 + { 4 + lib, 5 + config, 6 + pkgs, 7 + ... 8 + }: 4 9 let 5 10 cfg = config.services.bluesky-pds-hosting; 6 11 pdsSettings = config.services.bluesky-pds.settings; 7 12 gatekeeperPort = 3001; 8 13 # When gatekeeper is enabled, Caddy proxies to gatekeeper; otherwise directly to PDS 9 - proxyTarget = if cfg.enableGatekeeper then "localhost:${toString gatekeeperPort}" else "localhost:${toString cfg.port}"; 14 + proxyTarget = 15 + if cfg.enableGatekeeper then 16 + "localhost:${toString gatekeeperPort}" 17 + else 18 + "localhost:${toString cfg.port}"; 10 19 in 11 20 { 12 21 options.services.bluesky-pds-hosting = { ··· 49 58 config = lib.mkIf cfg.enable { 50 59 services.bluesky-pds = { 51 60 enable = true; 52 - environmentFiles = 53 - lib.lists.flatten [ 54 - [ cfg.environmentFile ] 55 - (lib.optional (cfg.mailerEnvironmentFile != null) cfg.mailerEnvironmentFile) 56 - ]; 61 + environmentFiles = lib.lists.flatten [ 62 + [ cfg.environmentFile ] 63 + (lib.optional (cfg.mailerEnvironmentFile != null) cfg.mailerEnvironmentFile) 64 + ]; 57 65 settings = { 58 66 PDS_PORT = cfg.port; 59 67 PDS_HOSTNAME = cfg.hostname; ··· 95 103 } 96 104 97 105 ${lib.optionalString cfg.enableAgeAssurance '' 98 - handle /xrpc/app.bsky.unspecced.getAgeAssuranceState { 99 - header content-type "application/json" 100 - header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 101 - header access-control-allow-origin "*" 102 - respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200 103 - } 106 + handle /xrpc/app.bsky.unspecced.getAgeAssuranceState { 107 + header content-type "application/json" 108 + header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 109 + header access-control-allow-origin "*" 110 + respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200 111 + } 104 112 105 - handle /xrpc/app.bsky.ageassurance.getConfig { 106 - header content-type "application/json" 107 - header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 108 - header access-control-allow-origin "*" 109 - respond `{"regions":[]}` 200 110 - } 113 + handle /xrpc/app.bsky.ageassurance.getConfig { 114 + header content-type "application/json" 115 + header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 116 + header access-control-allow-origin "*" 117 + respond `{"regions":[]}` 200 118 + } 111 119 112 - handle /xrpc/app.bsky.ageassurance.getState { 113 - header content-type "application/json" 114 - header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 115 - header access-control-allow-origin "*" 116 - respond `{"state":{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured","access":"full"},"metadata":{"accountCreatedAt":"2022-11-17T00:35:16.391Z"}}` 200 117 - } 120 + handle /xrpc/app.bsky.ageassurance.getState { 121 + header content-type "application/json" 122 + header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" 123 + header access-control-allow-origin "*" 124 + respond `{"state":{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured","access":"full"},"metadata":{"accountCreatedAt":"2022-11-17T00:35:16.391Z"}}` 200 125 + } 118 126 ''} 119 127 120 128 reverse_proxy ${proxyTarget}
+327 -324
modules/bore/default.nix
··· 8 8 cfg = config.atelier.bore; 9 9 10 10 boreScript = pkgs.writeShellScript "bore" '' 11 - CONFIG_FILE="bore.toml" 11 + CONFIG_FILE="bore.toml" 12 12 13 - # Trap exit signals to ensure cleanup and exit immediately 14 - trap 'exit 130' INT 15 - trap 'exit 143' TERM 16 - trap 'exit 129' HUP 13 + # Trap exit signals to ensure cleanup and exit immediately 14 + trap 'exit 130' INT 15 + trap 'exit 143' TERM 16 + trap 'exit 129' HUP 17 17 18 - # Enable immediate exit on error or pipe failure 19 - set -e 20 - set -o pipefail 18 + # Enable immediate exit on error or pipe failure 19 + set -e 20 + set -o pipefail 21 21 22 - # Check for flags 23 - if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then 24 - ${pkgs.gum}/bin/gum style --bold --foreground 212 "Active tunnels" 25 - echo 22 + # Check for flags 23 + if [ "$1" = "--list" ] || [ "$1" = "-l" ]; then 24 + ${pkgs.gum}/bin/gum style --bold --foreground 212 "Active tunnels" 25 + echo 26 26 27 - tunnels=$(${pkgs.curl}/bin/curl -s https://${cfg.domain}/api/proxy/http) 27 + tunnels=$(${pkgs.curl}/bin/curl -s https://${cfg.domain}/api/proxy/http) 28 28 29 - if ! echo "$tunnels" | ${pkgs.jq}/bin/jq -e '.proxies | length > 0' >/dev/null 2>&1; then 30 - ${pkgs.gum}/bin/gum style --foreground 117 "No active tunnels" 31 - exit 0 32 - fi 29 + if ! echo "$tunnels" | ${pkgs.jq}/bin/jq -e '.proxies | length > 0' >/dev/null 2>&1; then 30 + ${pkgs.gum}/bin/gum style --foreground 117 "No active tunnels" 31 + exit 0 32 + fi 33 33 34 - # Filter only online tunnels with valid conf 35 - echo "$tunnels" | ${pkgs.jq}/bin/jq -r '.proxies[] | select(.status == "online" and .conf != null) | if .type == "http" then "\(.name) → https://\(.conf.subdomain).${cfg.domain} [http]" elif .type == "tcp" then "\(.name) → tcp://\(.conf.remotePort) → localhost:\(.conf.localPort) [tcp]" elif .type == "udp" then "\(.name) → udp://\(.conf.remotePort) → localhost:\(.conf.localPort) [udp]" else "\(.name) [\(.type)]" end' | while read -r line; do 36 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ $line" 37 - done 38 - exit 0 39 - fi 34 + # Filter only online tunnels with valid conf 35 + echo "$tunnels" | ${pkgs.jq}/bin/jq -r '.proxies[] | select(.status == "online" and .conf != null) | if .type == "http" then "\(.name) → https://\(.conf.subdomain).${cfg.domain} [http]" elif .type == "tcp" then "\(.name) → tcp://\(.conf.remotePort) → localhost:\(.conf.localPort) [tcp]" elif .type == "udp" then "\(.name) → udp://\(.conf.remotePort) → localhost:\(.conf.localPort) [udp]" else "\(.name) [\(.type)]" end' | while read -r line; do 36 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ $line" 37 + done 38 + exit 0 39 + fi 40 + 41 + if [ "$1" = "--saved" ] || [ "$1" = "-s" ]; then 42 + if [ ! -f "$CONFIG_FILE" ]; then 43 + ${pkgs.gum}/bin/gum style --foreground 117 "No bore.toml found in current directory" 44 + exit 0 45 + fi 40 46 41 - if [ "$1" = "--saved" ] || [ "$1" = "-s" ]; then 42 - if [ ! -f "$CONFIG_FILE" ]; then 43 - ${pkgs.gum}/bin/gum style --foreground 117 "No bore.toml found in current directory" 44 - exit 0 45 - fi 47 + ${pkgs.gum}/bin/gum style --bold --foreground 212 "Saved tunnels in bore.toml" 48 + echo 46 49 47 - ${pkgs.gum}/bin/gum style --bold --foreground 212 "Saved tunnels in bore.toml" 48 - echo 50 + # Parse TOML and show tunnels 51 + while IFS= read -r line; do 52 + if [[ "$line" =~ ^\[([^]]+)\] ]]; then 53 + current_tunnel="''${BASH_REMATCH[1]}" 54 + elif [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then 55 + port="''${BASH_REMATCH[1]}" 56 + elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 57 + protocol="''${BASH_REMATCH[1]}" 58 + elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 59 + label="''${BASH_REMATCH[1]}" 60 + proto_display="''${protocol:-http}" 61 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]" 62 + label="" 63 + protocol="" 64 + elif [[ -z "$line" ]] && [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then 65 + proto_display="''${protocol:-http}" 66 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]" 67 + current_tunnel="" 68 + port="" 69 + protocol="" 70 + fi 71 + done < "$CONFIG_FILE" 49 72 50 - # Parse TOML and show tunnels 51 - while IFS= read -r line; do 52 - if [[ "$line" =~ ^\[([^]]+)\] ]]; then 53 - current_tunnel="''${BASH_REMATCH[1]}" 54 - elif [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then 55 - port="''${BASH_REMATCH[1]}" 56 - elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 57 - protocol="''${BASH_REMATCH[1]}" 58 - elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 59 - label="''${BASH_REMATCH[1]}" 60 - proto_display="''${protocol:-http}" 61 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]" 62 - label="" 63 - protocol="" 64 - elif [[ -z "$line" ]] && [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then 65 - proto_display="''${protocol:-http}" 66 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]" 67 - current_tunnel="" 68 - port="" 69 - protocol="" 73 + # Handle last entry if file doesn't end with blank line 74 + if [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then 75 + proto_display="''${protocol:-http}" 76 + if [[ -n "$label" ]]; then 77 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]" 78 + else 79 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]" 80 + fi 81 + fi 82 + exit 0 70 83 fi 71 - done < "$CONFIG_FILE" 72 84 73 - # Handle last entry if file doesn't end with blank line 74 - if [[ -n "$current_tunnel" ]] && [[ -n "$port" ]]; then 75 - proto_display="''${protocol:-http}" 76 - if [[ -n "$label" ]]; then 77 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display] [$label]" 85 + # Get tunnel name/subdomain 86 + if [ -n "$1" ]; then 87 + tunnel_name="$1" 78 88 else 79 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ $current_tunnel → localhost:$port [$proto_display]" 80 - fi 81 - fi 82 - exit 0 83 - fi 89 + # Check if we have a bore.toml in current directory 90 + if [ -f "$CONFIG_FILE" ]; then 91 + # Count tunnels in TOML 92 + tunnel_count=$(${pkgs.gnugrep}/bin/grep -c '^\[' "$CONFIG_FILE" 2>/dev/null || echo "0") 84 93 85 - # Get tunnel name/subdomain 86 - if [ -n "$1" ]; then 87 - tunnel_name="$1" 88 - else 89 - # Check if we have a bore.toml in current directory 90 - if [ -f "$CONFIG_FILE" ]; then 91 - # Count tunnels in TOML 92 - tunnel_count=$(${pkgs.gnugrep}/bin/grep -c '^\[' "$CONFIG_FILE" 2>/dev/null || echo "0") 94 + if [ "$tunnel_count" -gt 0 ]; then 95 + ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 96 + echo 93 97 94 - if [ "$tunnel_count" -gt 0 ]; then 95 - ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 96 - echo 98 + # Show choice between new or saved 99 + choice=$(${pkgs.gum}/bin/gum choose "New tunnel" "Use saved tunnel") 97 100 98 - # Show choice between new or saved 99 - choice=$(${pkgs.gum}/bin/gum choose "New tunnel" "Use saved tunnel") 101 + if [ "$choice" = "Use saved tunnel" ]; then 102 + # Extract tunnel names from TOML 103 + saved_names=$(${pkgs.gnugrep}/bin/grep '^\[' "$CONFIG_FILE" | ${pkgs.gnused}/bin/sed 's/^\[\(.*\)\]$/\1/') 104 + tunnel_name=$(echo "$saved_names" | ${pkgs.gum}/bin/gum choose) 100 105 101 - if [ "$choice" = "Use saved tunnel" ]; then 102 - # Extract tunnel names from TOML 103 - saved_names=$(${pkgs.gnugrep}/bin/grep '^\[' "$CONFIG_FILE" | ${pkgs.gnused}/bin/sed 's/^\[\(.*\)\]$/\1/') 104 - tunnel_name=$(echo "$saved_names" | ${pkgs.gum}/bin/gum choose) 106 + if [ -z "$tunnel_name" ]; then 107 + ${pkgs.gum}/bin/gum style --foreground 196 "No tunnel selected" 108 + exit 1 109 + fi 105 110 106 - if [ -z "$tunnel_name" ]; then 107 - ${pkgs.gum}/bin/gum style --foreground 196 "No tunnel selected" 108 - exit 1 109 - fi 111 + # Parse TOML for this tunnel's config 112 + in_section=false 113 + while IFS= read -r line; do 114 + if [[ "$line" =~ ^\[([^]]+)\] ]]; then 115 + if [[ "''${BASH_REMATCH[1]}" = "$tunnel_name" ]]; then 116 + in_section=true 117 + else 118 + in_section=false 119 + fi 120 + elif [[ "$in_section" = true ]]; then 121 + if [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then 122 + port="''${BASH_REMATCH[1]}" 123 + elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 124 + protocol="''${BASH_REMATCH[1]}" 125 + elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 126 + label="''${BASH_REMATCH[1]}" 127 + fi 128 + fi 129 + done < "$CONFIG_FILE" 130 + 131 + proto_display="''${protocol:-http}" 132 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ Loaded from bore.toml: $tunnel_name → localhost:$port [$proto_display]''${label:+ [$label]}" 133 + else 134 + # New tunnel - prompt for protocol first to determine what to ask for 135 + protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 136 + if [ -z "$protocol" ]; then 137 + protocol="http" 138 + fi 110 139 111 - # Parse TOML for this tunnel's config 112 - in_section=false 113 - while IFS= read -r line; do 114 - if [[ "$line" =~ ^\[([^]]+)\] ]]; then 115 - if [[ "''${BASH_REMATCH[1]}" = "$tunnel_name" ]]; then 116 - in_section=true 140 + if [ "$protocol" = "http" ]; then 141 + tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ") 117 142 else 118 - in_section=false 143 + tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ") 119 144 fi 120 - elif [[ "$in_section" = true ]]; then 121 - if [[ "$line" =~ ^port[[:space:]]*=[[:space:]]*([0-9]+) ]]; then 122 - port="''${BASH_REMATCH[1]}" 123 - elif [[ "$line" =~ ^protocol[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 124 - protocol="''${BASH_REMATCH[1]}" 125 - elif [[ "$line" =~ ^label[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then 126 - label="''${BASH_REMATCH[1]}" 145 + 146 + if [ -z "$tunnel_name" ]; then 147 + ${pkgs.gum}/bin/gum style --foreground 196 "No name provided" 148 + exit 1 127 149 fi 128 150 fi 129 - done < "$CONFIG_FILE" 151 + else 152 + ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 153 + echo 154 + # Prompt for protocol first 155 + protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 156 + if [ -z "$protocol" ]; then 157 + protocol="http" 158 + fi 159 + 160 + if [ "$protocol" = "http" ]; then 161 + tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ") 162 + else 163 + tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ") 164 + fi 130 165 131 - proto_display="''${protocol:-http}" 132 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ Loaded from bore.toml: $tunnel_name → localhost:$port [$proto_display]''${label:+ [$label]}" 166 + if [ -z "$tunnel_name" ]; then 167 + ${pkgs.gum}/bin/gum style --foreground 196 "No name provided" 168 + exit 1 169 + fi 170 + fi 133 171 else 134 - # New tunnel - prompt for protocol first to determine what to ask for 172 + ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 173 + echo 174 + # Prompt for protocol first 135 175 protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 136 176 if [ -z "$protocol" ]; then 137 177 protocol="http" ··· 148 188 exit 1 149 189 fi 150 190 fi 151 - else 152 - ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 153 - echo 154 - # Prompt for protocol first 155 - protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 156 - if [ -z "$protocol" ]; then 157 - protocol="http" 158 - fi 191 + fi 159 192 160 - if [ "$protocol" = "http" ]; then 161 - tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ") 162 - else 163 - tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ") 164 - fi 165 - 166 - if [ -z "$tunnel_name" ]; then 167 - ${pkgs.gum}/bin/gum style --foreground 196 "No name provided" 193 + # Validate tunnel name (only for http subdomains) 194 + if [ "$protocol" = "http" ]; then 195 + if ! echo "$tunnel_name" | ${pkgs.gnugrep}/bin/grep -qE '^[a-z0-9-]+$'; then 196 + ${pkgs.gum}/bin/gum style --foreground 196 "Invalid subdomain (use only lowercase letters, numbers, and hyphens)" 168 197 exit 1 169 198 fi 170 199 fi 171 - else 172 - ${pkgs.gum}/bin/gum style --bold --foreground 212 "Creating bore tunnel" 173 - echo 174 - # Prompt for protocol first 175 - protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 176 - if [ -z "$protocol" ]; then 177 - protocol="http" 178 - fi 179 200 180 - if [ "$protocol" = "http" ]; then 181 - tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "myapp" --prompt "Subdomain: ") 182 - else 183 - tunnel_name=$(${pkgs.gum}/bin/gum input --placeholder "my-tunnel" --prompt "Tunnel name: ") 201 + # Get port (skip if loaded from saved config) 202 + if [ -z "$port" ]; then 203 + if [ -n "$2" ]; then 204 + port="$2" 205 + else 206 + port=$(${pkgs.gum}/bin/gum input --placeholder "8000" --prompt "Local port: ") 207 + if [ -z "$port" ]; then 208 + ${pkgs.gum}/bin/gum style --foreground 196 "No port provided" 209 + exit 1 210 + fi 211 + fi 184 212 fi 185 213 186 - if [ -z "$tunnel_name" ]; then 187 - ${pkgs.gum}/bin/gum style --foreground 196 "No name provided" 214 + # Validate port 215 + if ! echo "$port" | ${pkgs.gnugrep}/bin/grep -qE '^[0-9]+$'; then 216 + ${pkgs.gum}/bin/gum style --foreground 196 "Invalid port (must be a number)" 188 217 exit 1 189 218 fi 190 - fi 191 - fi 192 219 193 - # Validate tunnel name (only for http subdomains) 194 - if [ "$protocol" = "http" ]; then 195 - if ! echo "$tunnel_name" | ${pkgs.gnugrep}/bin/grep -qE '^[a-z0-9-]+$'; then 196 - ${pkgs.gum}/bin/gum style --foreground 196 "Invalid subdomain (use only lowercase letters, numbers, and hyphens)" 197 - exit 1 198 - fi 199 - fi 220 + # Get optional protocol, label and save flag (skip if loaded from saved config) 221 + save_config=false 222 + if [ -z "$label" ]; then 223 + shift 2 2>/dev/null || true 224 + while [[ $# -gt 0 ]]; do 225 + case "$1" in 226 + --protocol|-p) 227 + protocol="$2" 228 + shift 2 229 + ;; 230 + --label|-l) 231 + label="$2" 232 + shift 2 233 + ;; 234 + --save) 235 + save_config=true 236 + shift 237 + ;; 238 + *) 239 + shift 240 + ;; 241 + esac 242 + done 200 243 201 - # Get port (skip if loaded from saved config) 202 - if [ -z "$port" ]; then 203 - if [ -n "$2" ]; then 204 - port="$2" 205 - else 206 - port=$(${pkgs.gum}/bin/gum input --placeholder "8000" --prompt "Local port: ") 207 - if [ -z "$port" ]; then 208 - ${pkgs.gum}/bin/gum style --foreground 196 "No port provided" 209 - exit 1 210 - fi 211 - fi 212 - fi 244 + # Prompt for protocol if not provided via flag and not loaded from saved config and not already set 245 + if [ -z "$protocol" ]; then 246 + protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 247 + if [ -z "$protocol" ]; then 248 + protocol="http" 249 + fi 250 + fi 213 251 214 - # Validate port 215 - if ! echo "$port" | ${pkgs.gnugrep}/bin/grep -qE '^[0-9]+$'; then 216 - ${pkgs.gum}/bin/gum style --foreground 196 "Invalid port (must be a number)" 217 - exit 1 218 - fi 252 + # Prompt for label if not provided via flag and not loaded from saved config 253 + if [ -z "$label" ]; then 254 + # Allow multiple labels selection 255 + labels=$(${pkgs.gum}/bin/gum choose --no-limit --header "Labels (select multiple):" "dev" "prod" "custom") 219 256 220 - # Get optional protocol, label and save flag (skip if loaded from saved config) 221 - save_config=false 222 - if [ -z "$label" ]; then 223 - shift 2 2>/dev/null || true 224 - while [[ $# -gt 0 ]]; do 225 - case "$1" in 226 - --protocol|-p) 227 - protocol="$2" 228 - shift 2 229 - ;; 230 - --label|-l) 231 - label="$2" 232 - shift 2 233 - ;; 234 - --save) 235 - save_config=true 236 - shift 237 - ;; 238 - *) 239 - shift 240 - ;; 241 - esac 242 - done 257 + if [ -n "$labels" ]; then 258 + # Check if custom was selected 259 + if echo "$labels" | ${pkgs.gnugrep}/bin/grep -q "custom"; then 260 + custom_label=$(${pkgs.gum}/bin/gum input --placeholder "my-label" --prompt "Custom label: ") 261 + if [ -z "$custom_label" ]; then 262 + ${pkgs.gum}/bin/gum style --foreground 196 "No custom label provided" 263 + exit 1 264 + fi 265 + # Replace 'custom' with the actual custom label 266 + labels=$(echo "$labels" | ${pkgs.gnused}/bin/sed "s/custom/$custom_label/") 267 + fi 268 + # Join labels with comma 269 + label=$(echo "$labels" | ${pkgs.coreutils}/bin/tr '\n' ',' | ${pkgs.gnused}/bin/sed 's/,$//') 270 + fi 271 + fi 272 + fi 243 273 244 - # Prompt for protocol if not provided via flag and not loaded from saved config and not already set 245 - if [ -z "$protocol" ]; then 246 - protocol=$(${pkgs.gum}/bin/gum choose --header "Protocol:" "http" "tcp" "udp") 274 + # Default protocol to http if still not set 247 275 if [ -z "$protocol" ]; then 248 276 protocol="http" 249 277 fi 250 - fi 251 - 252 - # Prompt for label if not provided via flag and not loaded from saved config 253 - if [ -z "$label" ]; then 254 - # Allow multiple labels selection 255 - labels=$(${pkgs.gum}/bin/gum choose --no-limit --header "Labels (select multiple):" "dev" "prod" "custom") 256 278 257 - if [ -n "$labels" ]; then 258 - # Check if custom was selected 259 - if echo "$labels" | ${pkgs.gnugrep}/bin/grep -q "custom"; then 260 - custom_label=$(${pkgs.gum}/bin/gum input --placeholder "my-label" --prompt "Custom label: ") 261 - if [ -z "$custom_label" ]; then 262 - ${pkgs.gum}/bin/gum style --foreground 196 "No custom label provided" 263 - exit 1 264 - fi 265 - # Replace 'custom' with the actual custom label 266 - labels=$(echo "$labels" | ${pkgs.gnused}/bin/sed "s/custom/$custom_label/") 267 - fi 268 - # Join labels with comma 269 - label=$(echo "$labels" | ${pkgs.coreutils}/bin/tr '\n' ',' | ${pkgs.gnused}/bin/sed 's/,$//') 279 + # Check if local port is accessible 280 + if ! ${pkgs.netcat}/bin/nc -z 127.0.0.1 "$port" 2>/dev/null; then 281 + ${pkgs.gum}/bin/gum style --foreground 214 "! Warning: Nothing listening on localhost:$port" 270 282 fi 271 - fi 272 - fi 273 283 274 - # Default protocol to http if still not set 275 - if [ -z "$protocol" ]; then 276 - protocol="http" 277 - fi 278 - 279 - # Check if local port is accessible 280 - if ! ${pkgs.netcat}/bin/nc -z 127.0.0.1 "$port" 2>/dev/null; then 281 - ${pkgs.gum}/bin/gum style --foreground 214 "! Warning: Nothing listening on localhost:$port" 282 - fi 283 - 284 - # Save configuration if requested 285 - if [ "$save_config" = true ]; then 286 - # Check if tunnel already exists in TOML 287 - if [ -f "$CONFIG_FILE" ] && ${pkgs.gnugrep}/bin/grep -q "^\[$tunnel_name\]" "$CONFIG_FILE"; then 288 - # Update existing entry 289 - ${pkgs.gnused}/bin/sed -i "/^\[$tunnel_name\]/,/^\[/{ 290 - s/^port[[:space:]]*=.*/port = $port/ 291 - s/^protocol[[:space:]]*=.*/protocol = \"$protocol\"/ 292 - ''${label:+s/^label[[:space:]]*=.*/label = \"$label\"/} 293 - }" "$CONFIG_FILE" 294 - else 295 - # Append new entry 296 - { 297 - echo "" 298 - echo "[$tunnel_name]" 299 - echo "port = $port" 300 - if [ "$protocol" != "http" ]; then 301 - echo "protocol = \"$protocol\"" 284 + # Save configuration if requested 285 + if [ "$save_config" = true ]; then 286 + # Check if tunnel already exists in TOML 287 + if [ -f "$CONFIG_FILE" ] && ${pkgs.gnugrep}/bin/grep -q "^\[$tunnel_name\]" "$CONFIG_FILE"; then 288 + # Update existing entry 289 + ${pkgs.gnused}/bin/sed -i "/^\[$tunnel_name\]/,/^\[/{ 290 + s/^port[[:space:]]*=.*/port = $port/ 291 + s/^protocol[[:space:]]*=.*/protocol = \"$protocol\"/ 292 + ''${label:+s/^label[[:space:]]*=.*/label = \"$label\"/} 293 + }" "$CONFIG_FILE" 294 + else 295 + # Append new entry 296 + { 297 + echo "" 298 + echo "[$tunnel_name]" 299 + echo "port = $port" 300 + if [ "$protocol" != "http" ]; then 301 + echo "protocol = \"$protocol\"" 302 + fi 303 + if [ -n "$label" ]; then 304 + echo "label = \"$label\"" 305 + fi 306 + } >> "$CONFIG_FILE" 302 307 fi 303 - if [ -n "$label" ]; then 304 - echo "label = \"$label\"" 305 - fi 306 - } >> "$CONFIG_FILE" 307 - fi 308 308 309 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ Configuration saved to bore.toml" 310 - echo 311 - fi 309 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ Configuration saved to bore.toml" 310 + echo 311 + fi 312 312 313 - # Create config file 314 - config_file=$(${pkgs.coreutils}/bin/mktemp) 315 - trap "${pkgs.coreutils}/bin/rm -f $config_file" EXIT 313 + # Create config file 314 + config_file=$(${pkgs.coreutils}/bin/mktemp) 315 + trap "${pkgs.coreutils}/bin/rm -f $config_file" EXIT 316 316 317 - # Encode label into proxy name if provided (format: tunnel_name[label1,label2]) 318 - proxy_name="$tunnel_name" 319 - if [ -n "$label" ]; then 320 - proxy_name="''${tunnel_name}[''${label}]" 321 - fi 317 + # Encode label into proxy name if provided (format: tunnel_name[label1,label2]) 318 + proxy_name="$tunnel_name" 319 + if [ -n "$label" ]; then 320 + proxy_name="''${tunnel_name}[''${label}]" 321 + fi 322 322 323 - # Build proxy configuration based on protocol 324 - if [ "$protocol" = "http" ]; then 325 - ${pkgs.coreutils}/bin/cat > $config_file <<EOF 326 - serverAddr = "${cfg.serverAddr}" 327 - serverPort = ${toString cfg.serverPort} 323 + # Build proxy configuration based on protocol 324 + if [ "$protocol" = "http" ]; then 325 + ${pkgs.coreutils}/bin/cat > $config_file <<EOF 326 + serverAddr = "${cfg.serverAddr}" 327 + serverPort = ${toString cfg.serverPort} 328 328 329 - auth.method = "token" 330 - auth.tokenSource.type = "file" 331 - auth.tokenSource.file.path = "${cfg.authTokenFile}" 329 + auth.method = "token" 330 + auth.tokenSource.type = "file" 331 + auth.tokenSource.file.path = "${cfg.authTokenFile}" 332 332 333 - [[proxies]] 334 - name = "$proxy_name" 335 - type = "http" 336 - localIP = "127.0.0.1" 337 - localPort = $port 338 - subdomain = "$tunnel_name" 339 - EOF 340 - elif [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then 341 - # For TCP/UDP, enable admin API to query allocated port 342 - admin_port=$(${pkgs.python3}/bin/python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()') 333 + [[proxies]] 334 + name = "$proxy_name" 335 + type = "http" 336 + localIP = "127.0.0.1" 337 + localPort = $port 338 + subdomain = "$tunnel_name" 339 + EOF 340 + elif [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then 341 + # For TCP/UDP, enable admin API to query allocated port 342 + admin_port=$(${pkgs.python3}/bin/python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()') 343 343 344 - ${pkgs.coreutils}/bin/cat > $config_file <<EOF 345 - serverAddr = "${cfg.serverAddr}" 346 - serverPort = ${toString cfg.serverPort} 344 + ${pkgs.coreutils}/bin/cat > $config_file <<EOF 345 + serverAddr = "${cfg.serverAddr}" 346 + serverPort = ${toString cfg.serverPort} 347 347 348 - auth.method = "token" 349 - auth.tokenSource.type = "file" 350 - auth.tokenSource.file.path = "${cfg.authTokenFile}" 348 + auth.method = "token" 349 + auth.tokenSource.type = "file" 350 + auth.tokenSource.file.path = "${cfg.authTokenFile}" 351 351 352 - webServer.addr = "127.0.0.1" 353 - webServer.port = $admin_port 352 + webServer.addr = "127.0.0.1" 353 + webServer.port = $admin_port 354 354 355 - [[proxies]] 356 - name = "$proxy_name" 357 - type = "$protocol" 358 - localIP = "127.0.0.1" 359 - localPort = $port 360 - remotePort = 0 361 - EOF 362 - else 363 - ${pkgs.gum}/bin/gum style --foreground 196 "Invalid protocol: $protocol (must be http, tcp, or udp)" 364 - exit 1 365 - fi 355 + [[proxies]] 356 + name = "$proxy_name" 357 + type = "$protocol" 358 + localIP = "127.0.0.1" 359 + localPort = $port 360 + remotePort = 0 361 + EOF 362 + else 363 + ${pkgs.gum}/bin/gum style --foreground 196 "Invalid protocol: $protocol (must be http, tcp, or udp)" 364 + exit 1 365 + fi 366 366 367 - # Start tunnel 368 - echo 369 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel configured" 370 - ${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port" 371 - if [ "$protocol" = "http" ]; then 372 - public_url="https://$tunnel_name.${cfg.domain}" 373 - ${pkgs.gum}/bin/gum style --foreground 117 " Public: $public_url" 374 - else 375 - ${pkgs.gum}/bin/gum style --foreground 117 " Protocol: $protocol" 376 - ${pkgs.gum}/bin/gum style --foreground 214 " Waiting for server to allocate port..." 377 - fi 378 - echo 379 - ${pkgs.gum}/bin/gum style --foreground 214 "Connecting to ${cfg.serverAddr}:${toString cfg.serverPort}..." 380 - echo 367 + # Start tunnel 368 + echo 369 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel configured" 370 + ${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port" 371 + if [ "$protocol" = "http" ]; then 372 + public_url="https://$tunnel_name.${cfg.domain}" 373 + ${pkgs.gum}/bin/gum style --foreground 117 " Public: $public_url" 374 + else 375 + ${pkgs.gum}/bin/gum style --foreground 117 " Protocol: $protocol" 376 + ${pkgs.gum}/bin/gum style --foreground 214 " Waiting for server to allocate port..." 377 + fi 378 + echo 379 + ${pkgs.gum}/bin/gum style --foreground 214 "Connecting to ${cfg.serverAddr}:${toString cfg.serverPort}..." 380 + echo 381 381 382 - # For TCP/UDP, capture output to parse allocated port 383 - if [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then 384 - ${pkgs.frp}/bin/frpc -c $config_file 2>&1 | while IFS= read -r line; do 385 - echo "$line" 382 + # For TCP/UDP, capture output to parse allocated port 383 + if [ "$protocol" = "tcp" ] || [ "$protocol" = "udp" ]; then 384 + ${pkgs.frp}/bin/frpc -c $config_file 2>&1 | while IFS= read -r line; do 385 + echo "$line" 386 386 387 - # Look for successful proxy start 388 - if echo "$line" | ${pkgs.gnugrep}/bin/grep -q "start proxy success"; then 389 - sleep 1 387 + # Look for successful proxy start 388 + if echo "$line" | ${pkgs.gnugrep}/bin/grep -q "start proxy success"; then 389 + sleep 1 390 390 391 - proxy_status=$(${pkgs.curl}/bin/curl -s http://127.0.0.1:$admin_port/api/status 2>/dev/null || echo "{}") 391 + proxy_status=$(${pkgs.curl}/bin/curl -s http://127.0.0.1:$admin_port/api/status 2>/dev/null || echo "{}") 392 392 393 - remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".tcp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null) 394 - if [ -z "$remote_addr" ] || [ "$remote_addr" = "null" ]; then 395 - remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".udp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null) 396 - fi 393 + remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".tcp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null) 394 + if [ -z "$remote_addr" ] || [ "$remote_addr" = "null" ]; then 395 + remote_addr=$(echo "$proxy_status" | ${pkgs.jq}/bin/jq -r ".udp[]? | select(.name == \"$proxy_name\") | .remote_addr" 2>/dev/null) 396 + fi 397 397 398 - remote_port=$(echo "$remote_addr" | ${pkgs.gnugrep}/bin/grep -oP ':\K[0-9]+$') 398 + remote_port=$(echo "$remote_addr" | ${pkgs.gnugrep}/bin/grep -oP ':\K[0-9]+$') 399 399 400 - if [ -n "$remote_port" ] && [ "$remote_port" != "null" ]; then 401 - echo 402 - ${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel established" 403 - ${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port" 404 - ${pkgs.gum}/bin/gum style --foreground 117 " Remote: ${cfg.serverAddr}:$remote_port" 405 - ${pkgs.gum}/bin/gum style --foreground 117 " Type: $protocol" 406 - echo 407 - fi 400 + if [ -n "$remote_port" ] && [ "$remote_port" != "null" ]; then 401 + echo 402 + ${pkgs.gum}/bin/gum style --foreground 35 "✓ Tunnel established" 403 + ${pkgs.gum}/bin/gum style --foreground 117 " Local: localhost:$port" 404 + ${pkgs.gum}/bin/gum style --foreground 117 " Remote: ${cfg.serverAddr}:$remote_port" 405 + ${pkgs.gum}/bin/gum style --foreground 117 " Type: $protocol" 406 + echo 407 + fi 408 + fi 409 + done 410 + else 411 + exec ${pkgs.frp}/bin/frpc -c $config_file 408 412 fi 409 - done 410 - else 411 - exec ${pkgs.frp}/bin/frpc -c $config_file 412 - fi 413 413 ''; 414 414 415 415 bore = pkgs.stdenv.mkDerivation { ··· 418 418 419 419 dontUnpack = true; 420 420 421 - nativeBuildInputs = with pkgs; [ pandoc installShellFiles ]; 421 + nativeBuildInputs = with pkgs; [ 422 + pandoc 423 + installShellFiles 424 + ]; 422 425 423 426 manPageSrc = ./bore.1.md; 424 427 zshCompletionSrc = ./completions/bore.zsh;
+54 -41
modules/configs.nix
··· 1 1 # Centralized application configs 2 2 # Manages configs for espanso, btop, gh, wakatime, etc. 3 - { config, lib, pkgs, isDarwin, ... }: 3 + { 4 + config, 5 + lib, 6 + pkgs, 7 + isDarwin, 8 + ... 9 + }: 4 10 5 11 let 6 12 # Paths for secrets - platform-specific ··· 24 30 home.file = lib.mkMerge [ 25 31 # macOS espanso paths 26 32 (lib.mkIf isDarwin { 27 - "Library/Application Support/espanso/config/default.yml".source = ../configs/espanso/config/default.yml; 33 + "Library/Application Support/espanso/config/default.yml".source = 34 + ../configs/espanso/config/default.yml; 28 35 "Library/Application Support/espanso/match/base.yml".source = ../configs/espanso/match/base.yml; 29 36 }) 30 37 ··· 37 44 # VS Code settings (macOS) 38 45 (lib.mkIf isDarwin { 39 46 "Library/Application Support/Code/User/settings.json".source = ../configs/vscode/settings.json; 40 - "Library/Application Support/Code/User/keybindings.json".source = ../configs/vscode/keybindings.json; 47 + "Library/Application Support/Code/User/keybindings.json".source = 48 + ../configs/vscode/keybindings.json; 41 49 }) 42 50 43 51 # VS Code settings (Linux) ··· 49 57 50 58 # Activation script to decrypt secrets for user configs 51 59 # This runs on every home-manager activation 52 - home.activation.decryptUserSecrets = lib.hm.dag.entryAfter ["writeBoundary"] '' 53 - SECRETS_DIR="${dotsDir}/secrets" 54 - AGE="${pkgs.age}/bin/age" 55 - SSH_KEY="$HOME/.ssh/id_ed25519" 56 - ${if isDarwin then '' 57 - ESPANSO_DIR="$HOME/Library/Application Support/espanso/match" 58 - '' else '' 59 - ESPANSO_DIR="$HOME/.config/espanso/match" 60 - ''} 60 + home.activation.decryptUserSecrets = lib.hm.dag.entryAfter [ "writeBoundary" ] '' 61 + SECRETS_DIR="${dotsDir}/secrets" 62 + AGE="${pkgs.age}/bin/age" 63 + SSH_KEY="$HOME/.ssh/id_ed25519" 64 + ${ 65 + if isDarwin then 66 + '' 67 + ESPANSO_DIR="$HOME/Library/Application Support/espanso/match" 68 + '' 69 + else 70 + '' 71 + ESPANSO_DIR="$HOME/.config/espanso/match" 72 + '' 73 + } 61 74 62 - # Only proceed if we have the SSH key for decryption 63 - if [ -f "$SSH_KEY" ]; then 64 - # Decrypt espanso secrets 65 - ESPANSO_SECRETS="$SECRETS_DIR/espanso-secrets.age" 66 - if [ -f "$ESPANSO_SECRETS" ]; then 67 - $DRY_RUN_CMD mkdir -p "$ESPANSO_DIR" 68 - $DRY_RUN_CMD $AGE -d -i "$SSH_KEY" "$ESPANSO_SECRETS" > "$ESPANSO_DIR/secrets.yml" 2>/dev/null || echo "Warning: Failed to decrypt espanso secrets" 69 - fi 75 + # Only proceed if we have the SSH key for decryption 76 + if [ -f "$SSH_KEY" ]; then 77 + # Decrypt espanso secrets 78 + ESPANSO_SECRETS="$SECRETS_DIR/espanso-secrets.age" 79 + if [ -f "$ESPANSO_SECRETS" ]; then 80 + $DRY_RUN_CMD mkdir -p "$ESPANSO_DIR" 81 + $DRY_RUN_CMD $AGE -d -i "$SSH_KEY" "$ESPANSO_SECRETS" > "$ESPANSO_DIR/secrets.yml" 2>/dev/null || echo "Warning: Failed to decrypt espanso secrets" 82 + fi 83 + 84 + # Decrypt wakatime API key and merge with config 85 + WAKATIME_SECRET="$SECRETS_DIR/wakatime-api-key.age" 86 + if [ -f "$WAKATIME_SECRET" ]; then 87 + API_KEY=$($AGE -d -i "$SSH_KEY" "$WAKATIME_SECRET" 2>/dev/null || echo "") 88 + if [ -n "$API_KEY" ]; then 89 + $DRY_RUN_CMD cat > "$HOME/.wakatime.cfg" << EOF 90 + [settings] 91 + api_url = https://waka.hogwarts.dev/api 92 + api_key = $API_KEY 93 + debug = false 94 + status_bar_coding_activity = true 95 + status_bar_enabled = false 96 + EOF 97 + fi 98 + fi 70 99 71 - # Decrypt wakatime API key and merge with config 72 - WAKATIME_SECRET="$SECRETS_DIR/wakatime-api-key.age" 73 - if [ -f "$WAKATIME_SECRET" ]; then 74 - API_KEY=$($AGE -d -i "$SSH_KEY" "$WAKATIME_SECRET" 2>/dev/null || echo "") 75 - if [ -n "$API_KEY" ]; then 76 - $DRY_RUN_CMD cat > "$HOME/.wakatime.cfg" << EOF 77 - [settings] 78 - api_url = https://waka.hogwarts.dev/api 79 - api_key = $API_KEY 80 - debug = false 81 - status_bar_coding_activity = true 82 - status_bar_enabled = false 83 - EOF 100 + # Decrypt npmrc (contains registry auth tokens) 101 + NPMRC_SECRET="$SECRETS_DIR/npmrc.age" 102 + if [ -f "$NPMRC_SECRET" ]; then 103 + $DRY_RUN_CMD $AGE -d -i "$SSH_KEY" "$NPMRC_SECRET" > "$HOME/.npmrc" 2>/dev/null || echo "Warning: Failed to decrypt npmrc" 104 + fi 84 105 fi 85 - fi 86 - 87 - # Decrypt npmrc (contains registry auth tokens) 88 - NPMRC_SECRET="$SECRETS_DIR/npmrc.age" 89 - if [ -f "$NPMRC_SECRET" ]; then 90 - $DRY_RUN_CMD $AGE -d -i "$SSH_KEY" "$NPMRC_SECRET" > "$HOME/.npmrc" 2>/dev/null || echo "Warning: Failed to decrypt npmrc" 91 - fi 92 - fi 93 106 ''; 94 107 }
+19 -7
modules/frps/default.nix
··· 32 32 allowedTCPPorts = lib.mkOption { 33 33 type = lib.types.listOf lib.types.port; 34 34 default = lib.lists.range 20000 20099; 35 - example = [ 20000 20001 20002 20003 20004 ]; 35 + example = [ 36 + 20000 37 + 20001 38 + 20002 39 + 20003 40 + 20004 41 + ]; 36 42 description = "TCP port range to allow for TCP tunnels (default: 20000-20099)"; 37 43 }; 38 44 39 45 allowedUDPPorts = lib.mkOption { 40 46 type = lib.types.listOf lib.types.port; 41 47 default = lib.lists.range 20000 20099; 42 - example = [ 20000 20001 20002 20003 20004 ]; 48 + example = [ 49 + 20000 50 + 20001 51 + 20002 52 + 20003 53 + 20004 54 + ]; 43 55 description = "UDP port range to allow for UDP tunnels (default: 20000-20099)"; 44 56 }; 45 57 ··· 91 103 '' 92 104 else 93 105 ''auth.token = "${cfg.authToken}"''; 94 - 106 + 95 107 configFile = pkgs.writeText "frps.toml" '' 96 108 bindAddr = "${cfg.bindAddr}" 97 109 bindPort = ${toString cfg.bindPort} ··· 108 120 109 121 # Subdomain support for *.${cfg.domain} 110 122 subDomainHost = "${cfg.domain}" 111 - 123 + 112 124 # Allow port ranges for TCP/UDP tunnels 113 125 # Format: [[{"start": 20000, "end": 20099}]] 114 126 allowPorts = [ ··· 138 150 # Automatically configure Caddy for wildcard domain 139 151 services.caddy = lib.mkIf cfg.enableCaddy { 140 152 enable = true; 141 - 153 + 142 154 # Dashboard for base domain 143 155 virtualHosts."${cfg.domain}" = { 144 156 extraConfig = '' ··· 148 160 header { 149 161 Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" 150 162 } 151 - 163 + 152 164 # Proxy /api/* to frps dashboard 153 165 handle /api/* { 154 166 reverse_proxy localhost:7400 155 167 } 156 - 168 + 157 169 # Serve dashboard HTML 158 170 handle { 159 171 root * ${./.}
+14 -8
modules/git.nix
··· 214 214 diff = { 215 215 algorithm = "histogram"; 216 216 tool = "windsurf"; 217 - renames = "copies"; # Detect copies as well as renames 217 + renames = "copies"; # Detect copies as well as renames 218 218 }; 219 219 220 220 "difftool \"windsurf\"".cmd = "windsurf --diff $LOCAL $REMOTE"; ··· 285 285 286 286 branch.sort = "-committerdate"; 287 287 column.ui = "auto"; 288 - } // (if isDarwin then { 289 - # macOS specific 290 - credential = { 291 - helper = "osxkeychain"; 292 - }; 293 - "credential \"https://dev.azure.com\"".useHttpPath = true; 294 - } else { }); 288 + } 289 + // ( 290 + if isDarwin then 291 + { 292 + # macOS specific 293 + credential = { 294 + helper = "osxkeychain"; 295 + }; 296 + "credential \"https://dev.azure.com\"".useHttpPath = true; 297 + } 298 + else 299 + { } 300 + ); 295 301 }; 296 302 297 303 # Delta for better diffs
+1 -1
modules/knot/sync.nix
··· 49 49 description = "Sync Knot repositories to GitHub"; 50 50 serviceConfig = { 51 51 Type = "oneshot"; 52 - User = "git"; # official tangled module uses git user 52 + User = "git"; # official tangled module uses git user 53 53 EnvironmentFile = cfg.secretsFile; 54 54 ExecStart = pkgs.writeShellScript "knot-sync" '' 55 55 set -euo pipefail
+24 -12
modules/restic/cli.nix
··· 16 16 # castle deploy - Remote deployment tools 17 17 # castle logs - Service log viewer 18 18 19 - { config, lib, pkgs, ... }: 19 + { 20 + config, 21 + lib, 22 + pkgs, 23 + ... 24 + }: 20 25 21 26 let 22 27 cfg = config.castle.backup; ··· 25 30 allBackupServices = lib.attrNames cfg.services; 26 31 27 32 # Generate manifest for disaster recovery 28 - backupManifest = pkgs.writeText "backup-manifest.json" (builtins.toJSON { 29 - version = 1; 30 - generated = "nixos-rebuild"; 31 - services = lib.mapAttrs (name: backupCfg: { 32 - paths = backupCfg.paths; 33 - exclude = backupCfg.exclude or []; 34 - tags = backupCfg.tags or []; 35 - }) cfg.services; 36 - }); 33 + backupManifest = pkgs.writeText "backup-manifest.json" ( 34 + builtins.toJSON { 35 + version = 1; 36 + generated = "nixos-rebuild"; 37 + services = lib.mapAttrs (name: backupCfg: { 38 + paths = backupCfg.paths; 39 + exclude = backupCfg.exclude or [ ]; 40 + tags = backupCfg.tags or [ ]; 41 + }) cfg.services; 42 + } 43 + ); 37 44 38 45 castleCliScript = pkgs.writeShellScript "castle" '' 39 46 set -e ··· 351 358 }; 352 359 }; 353 360 354 - in { 361 + in 362 + { 355 363 config = lib.mkIf cfg.enable { 356 - environment.systemPackages = [ castleCli pkgs.gum pkgs.jq ]; 364 + environment.systemPackages = [ 365 + castleCli 366 + pkgs.gum 367 + pkgs.jq 368 + ]; 357 369 358 370 # Store manifest for reference 359 371 environment.etc."castle/backup-manifest.json".source = backupManifest;
+19 -10
modules/restic/default.nix
··· 25 25 passwordFile = config.age.secrets."restic/password".path; 26 26 27 27 # Tags for easier filtering during restore 28 - extraBackupArgs = 29 - (map (t: "--tag ${t}") (serviceCfg.tags or [ "service:${name}" ])) 30 - ++ [ "--verbose" ]; 28 + extraBackupArgs = (map (t: "--tag ${t}") (serviceCfg.tags or [ "service:${name}" ])) ++ [ 29 + "--verbose" 30 + ]; 31 31 32 32 # Retention policy 33 33 pruneOpts = [ ··· 46 46 }; 47 47 48 48 # Pre/post backup hooks for database consistency 49 - backupPrepareCommand = lib.optionalString (serviceCfg.preBackup or null != null) serviceCfg.preBackup; 50 - backupCleanupCommand = lib.optionalString (serviceCfg.postBackup or null != null) serviceCfg.postBackup; 49 + backupPrepareCommand = lib.optionalString ( 50 + serviceCfg.preBackup or null != null 51 + ) serviceCfg.preBackup; 52 + backupCleanupCommand = lib.optionalString ( 53 + serviceCfg.postBackup or null != null 54 + ) serviceCfg.postBackup; 51 55 }; 52 56 53 57 in ··· 74 78 75 79 exclude = lib.mkOption { 76 80 type = lib.types.listOf lib.types.str; 77 - default = [ "*.log" "node_modules" ".git" ]; 81 + default = [ 82 + "*.log" 83 + "node_modules" 84 + ".git" 85 + ]; 78 86 description = "Glob patterns to exclude from backup"; 79 87 }; 80 88 ··· 121 129 ]; 122 130 123 131 # Create restic backup jobs for each enabled service 124 - services.restic.backups = lib.mapAttrs mkBackupJob ( 125 - lib.filterAttrs (n: v: v.enable) cfg.services 126 - ); 132 + services.restic.backups = lib.mapAttrs mkBackupJob (lib.filterAttrs (n: v: v.enable) cfg.services); 127 133 128 134 # Add restic and sqlite to system packages for manual operations 129 - environment.systemPackages = [ pkgs.restic pkgs.sqlite ]; 135 + environment.systemPackages = [ 136 + pkgs.restic 137 + pkgs.sqlite 138 + ]; 130 139 }; 131 140 }
+17 -11
modules/shell.nix
··· 1 1 # Shell configuration 2 - { config, lib, pkgs, hostname, ... }: 2 + { 3 + config, 4 + lib, 5 + pkgs, 6 + hostname, 7 + ... 8 + }: 3 9 4 10 let 5 11 # Tangled setup script for configuring git remotes ··· 286 292 zsh_indicator = ""; 287 293 bash_indicator = "bsh"; 288 294 fish_indicator = "fish"; 289 - disabled = true; # enable if you switch shells often 295 + disabled = true; # enable if you switch shells often 290 296 }; 291 297 }; 292 298 }; ··· 353 359 keysync = "gpg --keyserver pgp.mit.edu --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --keyserver keyserver.ubuntu.com --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --keyserver keys.openpgp.org --send-keys 00E643C21FAC965FFB28D3B714D0D45A1DADAAFA && gpg --export me@jaspermayone.com | curl -T - https://keys.openpgp.org"; 354 360 gpgend = "gpg --keyserver hkps://keys.openpgp.org --send-keys 14D0D45A1DADAAFA"; 355 361 356 - path="echo -e \${PATH//:/\\n}"; 362 + path = "echo -e \${PATH//:/\\n}"; 357 363 358 364 # Vim 359 365 vi = "vim"; 360 366 361 - afk="/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend"; 362 - reload="exec \${SHELL} -l"; 367 + afk = "/System/Library/CoreServices/Menu\ Extras/User.menu/Contents/Resources/CGSession -suspend"; 368 + reload = "exec \${SHELL} -l"; 363 369 }; 364 370 365 371 initContent = '' ··· 521 527 fzf 522 528 tmux 523 529 watch 524 - gum # Required for tangled-setup script 530 + gum # Required for tangled-setup script 525 531 526 532 # Dev tools 527 - mise # Version manager (formerly rtx) 528 - flyctl # Fly.io CLI 529 - bun # JavaScript runtime 530 - nodePackages.pnpm # Package manager 531 - zmx-binary # Session persistence for terminal processes 533 + mise # Version manager (formerly rtx) 534 + flyctl # Fly.io CLI 535 + bun # JavaScript runtime 536 + nodePackages.pnpm # Package manager 537 + zmx-binary # Session persistence for terminal processes 532 538 ]; 533 539 534 540 # Fuzzy finder integration
+45 -41
modules/ssh.nix
··· 106 106 matchBlocks = 107 107 let 108 108 # Convert jsp.ssh.hosts to SSH matchBlocks 109 - hostConfigs = mapAttrs ( 110 - name: hostCfg: 111 - { 112 - hostname = mkIf (hostCfg.hostname != null) hostCfg.hostname; 113 - port = mkIf (hostCfg.port != null) hostCfg.port; 114 - user = mkIf (hostCfg.user != null) hostCfg.user; 115 - identityFile = mkIf (hostCfg.identityFile != null) hostCfg.identityFile; 116 - identitiesOnly = mkIf (hostCfg.identitiesOnly != null) hostCfg.identitiesOnly; 117 - forwardAgent = mkIf (hostCfg.forwardAgent != null) hostCfg.forwardAgent; 118 - addKeysToAgent = mkIf (hostCfg.addKeysToAgent != null) hostCfg.addKeysToAgent; 119 - extraOptions = hostCfg.extraOptions // ( 109 + hostConfigs = mapAttrs (name: hostCfg: { 110 + hostname = mkIf (hostCfg.hostname != null) hostCfg.hostname; 111 + port = mkIf (hostCfg.port != null) hostCfg.port; 112 + user = mkIf (hostCfg.user != null) hostCfg.user; 113 + identityFile = mkIf (hostCfg.identityFile != null) hostCfg.identityFile; 114 + identitiesOnly = mkIf (hostCfg.identitiesOnly != null) hostCfg.identitiesOnly; 115 + forwardAgent = mkIf (hostCfg.forwardAgent != null) hostCfg.forwardAgent; 116 + addKeysToAgent = mkIf (hostCfg.addKeysToAgent != null) hostCfg.addKeysToAgent; 117 + extraOptions = 118 + hostCfg.extraOptions 119 + // ( 120 120 if hostCfg.zmx then 121 121 { 122 122 RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %n"; ··· 128 128 else 129 129 { } 130 130 ); 131 - } 132 - ) cfg.hosts; 131 + }) cfg.hosts; 133 132 134 133 # Create zmx pattern hosts if enabled 135 - zmxPatternHosts = if cfg.zmx.enable then 136 - listToAttrs ( 137 - map (pattern: 138 - let 139 - patternHost = cfg.hosts.${pattern} or {}; 140 - in { 141 - name = pattern; 142 - value = { 143 - hostname = mkIf (patternHost.hostname or null != null) patternHost.hostname; 144 - port = mkIf (patternHost.port or null != null) patternHost.port; 145 - user = mkIf (patternHost.user or null != null) patternHost.user; 146 - extraOptions = { 147 - RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %k"; 148 - RequestTTY = "yes"; 149 - ControlPath = "~/.ssh/cm-%r@%h:%p"; 150 - ControlMaster = "auto"; 151 - ControlPersist = "10m"; 134 + zmxPatternHosts = 135 + if cfg.zmx.enable then 136 + listToAttrs ( 137 + map ( 138 + pattern: 139 + let 140 + patternHost = cfg.hosts.${pattern} or { }; 141 + in 142 + { 143 + name = pattern; 144 + value = { 145 + hostname = mkIf (patternHost.hostname or null != null) patternHost.hostname; 146 + port = mkIf (patternHost.port or null != null) patternHost.port; 147 + user = mkIf (patternHost.user or null != null) patternHost.user; 148 + extraOptions = { 149 + RemoteCommand = "export PATH=$HOME/.nix-profile/bin:$PATH; zmx attach %k"; 150 + RequestTTY = "yes"; 151 + ControlPath = "~/.ssh/cm-%r@%h:%p"; 152 + ControlMaster = "auto"; 153 + ControlPersist = "10m"; 154 + }; 152 155 }; 153 - }; 154 - }) cfg.zmx.hosts 155 - ) 156 - else 157 - { }; 156 + } 157 + ) cfg.zmx.hosts 158 + ) 159 + else 160 + { }; 158 161 159 162 # Default match block for extraConfig 160 - defaultBlock = if cfg.extraConfig != "" then 161 - { 162 - "*" = { }; 163 - } 164 - else 165 - { }; 163 + defaultBlock = 164 + if cfg.extraConfig != "" then 165 + { 166 + "*" = { }; 167 + } 168 + else 169 + { }; 166 170 in 167 171 defaultBlock // hostConfigs // zmxPatternHosts; 168 172
+66 -51
modules/status/default.nix
··· 1 1 # Status monitoring module - serves /status endpoints for shields.io badges 2 - { config, lib, pkgs, ... }: 2 + { 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 3 8 4 9 with lib; 5 10 ··· 13 18 mkdir -p "$STATUS_DIR" 14 19 15 20 # Check each configured service 16 - ${concatStringsSep "\n" (map (svc: '' 17 - if systemctl is-active --quiet ${escapeShellArg svc}; then 18 - echo "ok" > "$STATUS_DIR/${svc}" 19 - else 20 - rm -f "$STATUS_DIR/${svc}" 21 - fi 22 - '') cfg.services)} 21 + ${concatStringsSep "\n" ( 22 + map (svc: '' 23 + if systemctl is-active --quiet ${escapeShellArg svc}; then 24 + echo "ok" > "$STATUS_DIR/${svc}" 25 + else 26 + rm -f "$STATUS_DIR/${svc}" 27 + fi 28 + '') cfg.services 29 + )} 23 30 24 31 # Always write host status (if this runs, host is up) 25 32 echo "ok" > "$STATUS_DIR/${cfg.hostname}" 26 33 27 34 # Check remote hosts via ping (Tailscale) 28 - ${concatStringsSep "\n" (map (host: '' 29 - if ${pkgs.iputils}/bin/ping -c 1 -W 2 ${escapeShellArg host} >/dev/null 2>&1; then 30 - echo "ok" > "$STATUS_DIR/${host}" 31 - else 32 - rm -f "$STATUS_DIR/${host}" 33 - fi 34 - '') cfg.remoteHosts)} 35 + ${concatStringsSep "\n" ( 36 + map (host: '' 37 + if ${pkgs.iputils}/bin/ping -c 1 -W 2 ${escapeShellArg host} >/dev/null 2>&1; then 38 + echo "ok" > "$STATUS_DIR/${host}" 39 + else 40 + rm -f "$STATUS_DIR/${host}" 41 + fi 42 + '') cfg.remoteHosts 43 + )} 35 44 36 45 # Build services JSON 37 46 SERVICES_JSON="{" 38 - ${concatStringsSep "\n" (imap0 (i: svc: '' 39 - if systemctl is-active --quiet ${escapeShellArg svc}; then 40 - SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":true" 41 - else 42 - SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":false" 43 - fi 44 - '') cfg.services)} 47 + ${concatStringsSep "\n" ( 48 + imap0 (i: svc: '' 49 + if systemctl is-active --quiet ${escapeShellArg svc}; then 50 + SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":true" 51 + else 52 + SERVICES_JSON="$SERVICES_JSON${if i > 0 then "," else ""}\"${svc}\":false" 53 + fi 54 + '') cfg.services 55 + )} 45 56 SERVICES_JSON="$SERVICES_JSON}" 46 57 47 58 # Write full status JSON ··· 70 81 71 82 services = mkOption { 72 83 type = types.listOf types.str; 73 - default = []; 84 + default = [ ]; 74 85 description = "List of systemd services to monitor"; 75 86 }; 76 87 77 88 remoteHosts = mkOption { 78 89 type = types.listOf types.str; 79 - default = []; 90 + default = [ ]; 80 91 description = "List of remote hosts to check via ping (e.g. Tailscale hosts)"; 81 92 }; 82 93 ··· 114 125 # Caddy virtual host for status 115 126 services.caddy.virtualHosts."${cfg.domain}".extraConfig = '' 116 127 ${optionalString (cfg.cloudflareCredentialsFile != null) '' 117 - tls { 118 - dns cloudflare {env.CLOUDFLARE_API_TOKEN} 119 - } 128 + tls { 129 + dns cloudflare {env.CLOUDFLARE_API_TOKEN} 130 + } 120 131 ''} 121 132 122 133 # Individual host status (returns 200 if file exists) ··· 132 143 } 133 144 134 145 # Service status endpoints 135 - ${concatStringsSep "\n" (map (svc: '' 136 - @status_${svc} path /status/service/${svc} 137 - handle @status_${svc} { 138 - @online_${svc} file /var/lib/status/${svc} 139 - handle @online_${svc} { 140 - respond "ok" 200 141 - } 142 - handle { 143 - respond "offline" 503 144 - } 145 - } 146 - '') cfg.services)} 146 + ${concatStringsSep "\n" ( 147 + map (svc: '' 148 + @status_${svc} path /status/service/${svc} 149 + handle @status_${svc} { 150 + @online_${svc} file /var/lib/status/${svc} 151 + handle @online_${svc} { 152 + respond "ok" 200 153 + } 154 + handle { 155 + respond "offline" 503 156 + } 157 + } 158 + '') cfg.services 159 + )} 147 160 148 161 # Remote host status endpoints (Tailscale) 149 - ${concatStringsSep "\n" (map (host: '' 150 - @status_${host} path /status/${host} 151 - handle @status_${host} { 152 - @online_${host} file /var/lib/status/${host} 153 - handle @online_${host} { 154 - respond "ok" 200 155 - } 156 - handle { 157 - respond "offline" 503 158 - } 159 - } 160 - '') cfg.remoteHosts)} 162 + ${concatStringsSep "\n" ( 163 + map (host: '' 164 + @status_${host} path /status/${host} 165 + handle @status_${host} { 166 + @online_${host} file /var/lib/status/${host} 167 + handle @online_${host} { 168 + respond "ok" 200 169 + } 170 + handle { 171 + respond "offline" 503 172 + } 173 + } 174 + '') cfg.remoteHosts 175 + )} 161 176 162 177 # Full status JSON 163 178 @status_json path /status
+32 -19
packages/zmx.nix
··· 1 - { pkgs, lib, stdenv, fetchurl, autoPatchelfHook }: 1 + { 2 + pkgs, 3 + lib, 4 + stdenv, 5 + fetchurl, 6 + autoPatchelfHook, 7 + }: 2 8 3 9 stdenv.mkDerivation rec { 4 10 pname = "zmx"; 5 11 version = "0.1.0"; 6 12 7 13 src = fetchurl { 8 - url = if stdenv.isLinux then 9 - (if stdenv.isAarch64 then 10 - "https://zmx.sh/a/zmx-${version}-linux-aarch64.tar.gz" 14 + url = 15 + if stdenv.isLinux then 16 + ( 17 + if stdenv.isAarch64 then 18 + "https://zmx.sh/a/zmx-${version}-linux-aarch64.tar.gz" 19 + else 20 + "https://zmx.sh/a/zmx-${version}-linux-x86_64.tar.gz" 21 + ) 22 + else if stdenv.isDarwin then 23 + ( 24 + if stdenv.isAarch64 then 25 + "https://zmx.sh/a/zmx-${version}-macos-aarch64.tar.gz" 26 + else 27 + "https://zmx.sh/a/zmx-${version}-macos-x86_64.tar.gz" 28 + ) 11 29 else 12 - "https://zmx.sh/a/zmx-${version}-linux-x86_64.tar.gz") 13 - else if stdenv.isDarwin then 14 - (if stdenv.isAarch64 then 15 - "https://zmx.sh/a/zmx-${version}-macos-aarch64.tar.gz" 30 + throw "Unsupported platform"; 31 + 32 + hash = 33 + if stdenv.isLinux && stdenv.isAarch64 then 34 + "sha256-cMGo+Af0VRY3c2EoNzVZFU53Kz5wKL8zsSSXIOtZVU8=" 35 + else if stdenv.isLinux then 36 + "sha256-Zmqs/Y3be2z9KMuSwyTLZWKbIInzHgoC9Bm0S2jv3XI=" 37 + else if stdenv.isDarwin && stdenv.isAarch64 then 38 + "sha256-34k5Q1cIr3+foubtMJVoHVHZtCLoSjwJK00e1p0JdLg=" 16 39 else 17 - "https://zmx.sh/a/zmx-${version}-macos-x86_64.tar.gz") 18 - else throw "Unsupported platform"; 19 - 20 - hash = if stdenv.isLinux && stdenv.isAarch64 then 21 - "sha256-cMGo+Af0VRY3c2EoNzVZFU53Kz5wKL8zsSSXIOtZVU8=" 22 - else if stdenv.isLinux then 23 - "sha256-Zmqs/Y3be2z9KMuSwyTLZWKbIInzHgoC9Bm0S2jv3XI=" 24 - else if stdenv.isDarwin && stdenv.isAarch64 then 25 - "sha256-34k5Q1cIr3+foubtMJVoHVHZtCLoSjwJK00e1p0JdLg=" 26 - else 27 - "sha256-0epjoQhUSBYlE0L7Ubwn/sJF61+4BbxeaRx6EY/SklE="; 40 + "sha256-0epjoQhUSBYlE0L7Ubwn/sJF61+4BbxeaRx6EY/SklE="; 28 41 }; 29 42 30 43 nativeBuildInputs = lib.optionals stdenv.isLinux [ autoPatchelfHook ];
+7 -4
profiles/bore.nix
··· 1 1 # Bore tunnel client configuration 2 - { config, lib, pkgs, ... }: 2 + { 3 + config, 4 + lib, 5 + pkgs, 6 + ... 7 + }: 3 8 4 9 { 5 10 imports = [ ../modules/bore ]; ··· 10 15 serverPort = 7000; 11 16 domain = "tun.hogwarts.channel"; 12 17 authTokenFile = 13 - if pkgs.stdenv.isDarwin 14 - then "/Users/jsp/.config/bore/token" 15 - else "/home/jsp/.config/bore/token"; 18 + if pkgs.stdenv.isDarwin then "/Users/jsp/.config/bore/token" else "/home/jsp/.config/bore/token"; 16 19 }; 17 20 }
+29 -8
secrets/secrets.nix
··· 14 14 15 15 # Groups for convenience 16 16 allUsers = [ jsp ]; 17 - allHosts = [ alastor dippet horace ]; 17 + allHosts = [ 18 + alastor 19 + dippet 20 + horace 21 + ]; 18 22 all = allUsers ++ allHosts; 19 23 in 20 24 { ··· 24 28 25 29 # Cloudflare API credentials for ACME DNS challenge 26 30 # Format: CF_DNS_API_TOKEN=xxxxx 27 - "cloudflare-credentials.age".publicKeys = [ jsp alastor ]; 31 + "cloudflare-credentials.age".publicKeys = [ 32 + jsp 33 + alastor 34 + ]; 28 35 29 36 # Bore client token (same as frps-token, but separate file for clarity) 30 37 # Used on client machines (remus, etc) ··· 34 41 # Generate with: openssl rand -hex 32 35 42 "knot-secret.age".publicKeys = all; 36 43 37 - "pds.age".publicKeys = [ jsp alastor ]; 44 + "pds.age".publicKeys = [ 45 + jsp 46 + alastor 47 + ]; 38 48 39 49 # If using Resend SMTP, include API key here too 40 - "pds-mailer.age".publicKeys = [ jsp alastor ]; 41 - 50 + "pds-mailer.age".publicKeys = [ 51 + jsp 52 + alastor 53 + ]; 42 54 43 55 # WiFi passwords for NixOS machines 44 56 # Format: NETWORK_PSK=password ··· 68 80 # restic/env.age: B2_ACCOUNT_ID and B2_ACCOUNT_KEY (or AWS_ACCESS_KEY_ID, etc.) 69 81 # restic/repo.age: Repository URL (e.g., b2:bucket-name:/path) 70 82 # restic/password.age: Repository encryption password 71 - "restic/env.age".publicKeys = [ jsp alastor ]; 72 - "restic/repo.age".publicKeys = [ jsp alastor ]; 73 - "restic/password.age".publicKeys = [ jsp alastor ]; 83 + "restic/env.age".publicKeys = [ 84 + jsp 85 + alastor 86 + ]; 87 + "restic/repo.age".publicKeys = [ 88 + jsp 89 + alastor 90 + ]; 91 + "restic/password.age".publicKeys = [ 92 + jsp 93 + alastor 94 + ]; 74 95 }