forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
1{ 2 description = "atproto github"; 3 4 inputs = { 5 nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11"; 6 indigo = { 7 url = "github:oppiliappan/indigo"; 8 flake = false; 9 }; 10 htmx-src = { 11 url = "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"; 12 flake = false; 13 }; 14 lucide-src = { 15 url = "https://github.com/lucide-icons/lucide/releases/download/0.483.0/lucide-icons-0.483.0.zip"; 16 flake = false; 17 }; 18 ia-fonts-src = { 19 url = "github:iaolo/iA-Fonts"; 20 flake = false; 21 }; 22 gitignore = { 23 url = "github:hercules-ci/gitignore.nix"; 24 inputs.nixpkgs.follows = "nixpkgs"; 25 }; 26 }; 27 28 outputs = { 29 self, 30 nixpkgs, 31 indigo, 32 htmx-src, 33 lucide-src, 34 gitignore, 35 ia-fonts-src, 36 }: let 37 supportedSystems = ["x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"]; 38 forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 39 nixpkgsFor = forAllSystems (system: 40 import nixpkgs { 41 inherit system; 42 overlays = [self.overlays.default]; 43 }); 44 inherit (gitignore.lib) gitignoreSource; 45 in { 46 overlays.default = final: prev: let 47 goModHash = "sha256-2vljseczrvsl2T0P9k69ro72yU59l5fp9r/sszmXYY4="; 48 buildCmdPackage = name: 49 final.buildGoModule { 50 pname = name; 51 version = "0.1.0"; 52 src = gitignoreSource ./.; 53 subPackages = ["cmd/${name}"]; 54 vendorHash = goModHash; 55 CGO_ENABLED = 0; 56 }; 57 in { 58 indigo-lexgen = final.buildGoModule { 59 pname = "indigo-lexgen"; 60 version = "0.1.0"; 61 src = indigo; 62 subPackages = ["cmd/lexgen"]; 63 vendorHash = "sha256-pGc29fgJFq8LP7n/pY1cv6ExZl88PAeFqIbFEhB3xXs="; 64 doCheck = false; 65 }; 66 67 appview = with final; 68 final.pkgsStatic.buildGoModule { 69 pname = "appview"; 70 version = "0.1.0"; 71 src = gitignoreSource ./.; 72 postUnpack = '' 73 pushd source 74 mkdir -p appview/pages/static/{fonts,icons} 75 cp -f ${htmx-src} appview/pages/static/htmx.min.js 76 cp -rf ${lucide-src}/*.svg appview/pages/static/icons/ 77 cp -f ${ia-fonts-src}/"iA Writer Quattro"/Static/*.ttf appview/pages/static/fonts/ 78 cp -f ${ia-fonts-src}/"iA Writer Mono"/Static/*.ttf appview/pages/static/fonts/ 79 ${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o appview/pages/static/tw.css 80 popd 81 ''; 82 doCheck = false; 83 subPackages = ["cmd/appview"]; 84 vendorHash = goModHash; 85 CGO_ENABLED = 1; 86 stdenv = pkgsStatic.stdenv; 87 }; 88 89 knotserver = with final; 90 final.pkgsStatic.buildGoModule { 91 pname = "knotserver"; 92 version = "0.1.0"; 93 src = gitignoreSource ./.; 94 nativeBuildInputs = [final.makeWrapper]; 95 subPackages = ["cmd/knotserver"]; 96 vendorHash = goModHash; 97 installPhase = '' 98 runHook preInstall 99 100 mkdir -p $out/bin 101 cp $GOPATH/bin/knotserver $out/bin/knotserver 102 103 wrapProgram $out/bin/knotserver \ 104 --prefix PATH : ${pkgs.git}/bin 105 106 runHook postInstall 107 ''; 108 CGO_ENABLED = 1; 109 }; 110 knotserver-unwrapped = final.pkgsStatic.buildGoModule { 111 pname = "knotserver"; 112 version = "0.1.0"; 113 src = gitignoreSource ./.; 114 subPackages = ["cmd/knotserver"]; 115 vendorHash = goModHash; 116 CGO_ENABLED = 1; 117 }; 118 repoguard = buildCmdPackage "repoguard"; 119 keyfetch = buildCmdPackage "keyfetch"; 120 }; 121 packages = forAllSystems (system: { 122 inherit 123 (nixpkgsFor."${system}") 124 indigo-lexgen 125 appview 126 knotserver 127 knotserver-unwrapped 128 repoguard 129 keyfetch 130 ; 131 }); 132 defaultPackage = forAllSystems (system: nixpkgsFor.${system}.appview); 133 formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 134 devShells = forAllSystems (system: let 135 pkgs = nixpkgsFor.${system}; 136 staticShell = pkgs.mkShell.override { 137 stdenv = pkgs.pkgsStatic.stdenv; 138 }; 139 in { 140 default = staticShell { 141 nativeBuildInputs = [ 142 pkgs.go 143 pkgs.air 144 pkgs.gopls 145 pkgs.httpie 146 pkgs.indigo-lexgen 147 pkgs.litecli 148 pkgs.websocat 149 pkgs.tailwindcss 150 pkgs.nixos-shell 151 ]; 152 shellHook = '' 153 mkdir -p appview/pages/static/{fonts,icons} 154 cp -f ${htmx-src} appview/pages/static/htmx.min.js 155 cp -rf ${lucide-src}/*.svg appview/pages/static/icons/ 156 cp -f ${ia-fonts-src}/"iA Writer Quattro"/Static/*.ttf appview/pages/static/fonts/ 157 cp -f ${ia-fonts-src}/"iA Writer Mono"/Static/*.ttf appview/pages/static/fonts/ 158 ''; 159 }; 160 }); 161 apps = forAllSystems (system: let 162 pkgs = nixpkgsFor."${system}"; 163 air-watcher = name: 164 pkgs.writeShellScriptBin "run" 165 '' 166 ${pkgs.air}/bin/air -c /dev/null \ 167 -build.cmd "${pkgs.tailwindcss}/bin/tailwindcss -i input.css -o ./appview/pages/static/tw.css && ${pkgs.go}/bin/go build -o ./out/${name}.out ./cmd/${name}/main.go" \ 168 -build.bin "./out/${name}.out" \ 169 -build.include_ext "go,html,css" 170 ''; 171 in { 172 watch-appview = { 173 type = "app"; 174 program = ''${air-watcher "appview"}/bin/run''; 175 }; 176 watch-knotserver = { 177 type = "app"; 178 program = ''${air-watcher "knotserver"}/bin/run''; 179 }; 180 }); 181 182 nixosModules.appview = { 183 config, 184 pkgs, 185 lib, 186 ... 187 }: 188 with lib; { 189 options = { 190 services.tangled-appview = { 191 enable = mkOption { 192 type = types.bool; 193 default = false; 194 description = "Enable tangled appview"; 195 }; 196 port = mkOption { 197 type = types.int; 198 default = 3000; 199 description = "Port to run the appview on"; 200 }; 201 cookie_secret = mkOption { 202 type = types.str; 203 default = "00000000000000000000000000000000"; 204 description = "Cookie secret"; 205 }; 206 }; 207 }; 208 209 config = mkIf config.services.tangled-appview.enable { 210 systemd.services.tangled-appview = { 211 description = "tangled appview service"; 212 wantedBy = ["multi-user.target"]; 213 214 serviceConfig = { 215 ListenStream = "0.0.0.0:${toString config.services.tangled-appview.port}"; 216 ExecStart = "${self.packages.${pkgs.system}.appview}/bin/appview"; 217 Restart = "always"; 218 }; 219 220 environment = { 221 TANGLED_DB_PATH = "appview.db"; 222 TANGLED_COOKIE_SECRET = config.services.tangled-appview.cookie_secret; 223 }; 224 }; 225 }; 226 }; 227 228 nixosModules.knotserver = { 229 config, 230 pkgs, 231 lib, 232 ... 233 }: let 234 cfg = config.services.tangled-knotserver; 235 in 236 with lib; { 237 options = { 238 services.tangled-knotserver = { 239 enable = mkOption { 240 type = types.bool; 241 default = false; 242 description = "Enable a tangled knotserver"; 243 }; 244 245 appviewEndpoint = mkOption { 246 type = types.str; 247 default = "https://tangled.sh"; 248 description = "Appview endpoint"; 249 }; 250 251 gitUser = mkOption { 252 type = types.str; 253 default = "git"; 254 description = "User that hosts git repos and performs git operations"; 255 }; 256 257 openFirewall = mkOption { 258 type = types.bool; 259 default = true; 260 description = "Open port 22 in the firewall for ssh"; 261 }; 262 263 stateDir = mkOption { 264 type = types.path; 265 default = "/home/${cfg.gitUser}"; 266 description = "Tangled knot data directory"; 267 }; 268 269 repo = { 270 scanPath = mkOption { 271 type = types.path; 272 default = cfg.stateDir; 273 description = "Path where repositories are scanned from"; 274 }; 275 276 mainBranch = mkOption { 277 type = types.str; 278 default = "main"; 279 description = "Default branch name for repositories"; 280 }; 281 }; 282 283 server = { 284 listenAddr = mkOption { 285 type = types.str; 286 default = "0.0.0.0:5555"; 287 description = "Address to listen on"; 288 }; 289 290 internalListenAddr = mkOption { 291 type = types.str; 292 default = "127.0.0.1:5444"; 293 description = "Internal address for inter-service communication"; 294 }; 295 296 secretFile = mkOption { 297 type = lib.types.path; 298 example = "KNOT_SERVER_SECRET=<hash>"; 299 description = "File containing secret key provided by appview (required)"; 300 }; 301 302 dbPath = mkOption { 303 type = types.path; 304 default = "${cfg.stateDir}/knotserver.db"; 305 description = "Path to the database file"; 306 }; 307 308 hostname = mkOption { 309 type = types.str; 310 example = "knot.tangled.sh"; 311 description = "Hostname for the server (required)"; 312 }; 313 314 dev = mkOption { 315 type = types.bool; 316 default = false; 317 description = "Enable development mode (disables signature verification)"; 318 }; 319 }; 320 }; 321 }; 322 323 config = mkIf cfg.enable { 324 environment.systemPackages = with pkgs; [git]; 325 326 system.activationScripts.gitConfig = '' 327 mkdir -p "${cfg.repo.scanPath}" 328 chown -R ${cfg.gitUser}:${cfg.gitUser} \ 329 "${cfg.repo.scanPath}" 330 331 mkdir -p "${cfg.stateDir}/.config/git" 332 cat > "${cfg.stateDir}/.config/git/config" << EOF 333 [user] 334 name = Git User 335 email = git@example.com 336 EOF 337 chown -R ${cfg.gitUser}:${cfg.gitUser} \ 338 "${cfg.stateDir}" 339 ''; 340 341 users.users.${cfg.gitUser} = { 342 isSystemUser = true; 343 useDefaultShell = true; 344 home = cfg.stateDir; 345 createHome = true; 346 group = cfg.gitUser; 347 }; 348 349 users.groups.${cfg.gitUser} = {}; 350 351 services.openssh = { 352 enable = true; 353 extraConfig = '' 354 Match User ${cfg.gitUser} 355 AuthorizedKeysCommand /etc/ssh/keyfetch_wrapper 356 AuthorizedKeysCommandUser nobody 357 ''; 358 }; 359 360 environment.etc."ssh/keyfetch_wrapper" = { 361 mode = "0555"; 362 text = '' 363 #!${pkgs.stdenv.shell} 364 ${self.packages.${pkgs.system}.keyfetch}/bin/keyfetch \ 365 -repoguard-path ${self.packages.${pkgs.system}.repoguard}/bin/repoguard \ 366 -internal-api "http://${cfg.server.internalListenAddr}" \ 367 -git-dir "${cfg.repo.scanPath}" \ 368 -log-path /tmp/repoguard.log 369 ''; 370 }; 371 372 systemd.services.knotserver = { 373 description = "knotserver service"; 374 after = ["network.target" "sshd.service"]; 375 wantedBy = ["multi-user.target"]; 376 serviceConfig = { 377 User = cfg.gitUser; 378 WorkingDirectory = cfg.stateDir; 379 Environment = [ 380 "KNOT_REPO_SCAN_PATH=${cfg.repo.scanPath}" 381 "KNOT_REPO_MAIN_BRANCH=${cfg.repo.mainBranch}" 382 "APPVIEW_ENDPOINT=${cfg.appviewEndpoint}" 383 "KNOT_SERVER_INTERNAL_LISTEN_ADDR=${cfg.server.internalListenAddr}" 384 "KNOT_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}" 385 "KNOT_SERVER_DB_PATH=${cfg.server.dbPath}" 386 "KNOT_SERVER_HOSTNAME=${cfg.server.hostname}" 387 ]; 388 EnvironmentFile = cfg.server.secretFile; 389 ExecStart = "${self.packages.${pkgs.system}.knotserver}/bin/knotserver"; 390 Restart = "always"; 391 }; 392 }; 393 394 networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [22]; 395 }; 396 }; 397 398 nixosConfigurations.knotVM = nixpkgs.lib.nixosSystem { 399 system = "x86_64-linux"; 400 modules = [ 401 self.nixosModules.knotserver 402 ({ 403 config, 404 pkgs, 405 ... 406 }: { 407 virtualisation.memorySize = 2048; 408 virtualisation.cores = 2; 409 services.getty.autologinUser = "root"; 410 environment.systemPackages = with pkgs; [curl vim git]; 411 systemd.tmpfiles.rules = [ 412 "w /var/lib/knotserver/secret 0660 git git - KNOT_SERVER_SECRET=6995e040e80e2d593b5e5e9ca611a70140b9ef8044add0a28b48b1ee34aa3e85" 413 ]; 414 services.tangled-knotserver = { 415 enable = true; 416 server = { 417 secretFile = "/var/lib/knotserver/secret"; 418 hostname = "localhost:6000"; 419 listenAddr = "0.0.0.0:6000"; 420 }; 421 }; 422 }) 423 ]; 424 }; 425 }; 426}