Stitch any CI into Tangled
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

nixOS module

+175 -1
+22 -1
flake.nix
··· 7 7 }; 8 8 9 9 outputs = { 10 + self, 10 11 nixpkgs, 11 12 flake-utils, 12 13 ... 13 14 }: 15 + # Per-system outputs (packages, apps, devShells). nixosModules is 16 + # system-independent and is merged in below. 14 17 flake-utils.lib.eachDefaultSystem ( 15 18 system: let 16 19 pkgs = nixpkgs.legacyPackages.${system}; ··· 58 61 }; 59 62 }; 60 63 } 61 - ); 64 + ) 65 + // { 66 + # NixOS module. Operators add `imports = [ tack.nixosModules.default ];` 67 + # and configure via `services.tangled.tack`. Mirrors the upstream 68 + # Tangled module shape (services.tangled.spindle, .knot, …) so the 69 + # config namespace is consistent. 70 + nixosModules.default = { 71 + lib, 72 + pkgs, 73 + ... 74 + }: { 75 + imports = [./nix/modules/tack.nix]; 76 + 77 + services.tangled.tack.package = 78 + lib.mkDefault 79 + self.packages.${pkgs.stdenv.hostPlatform.system}.tack; 80 + }; 81 + nixosModules.tack = self.nixosModules.default; 82 + }; 62 83 }
+153
nix/modules/tack.nix
··· 1 + # NixOS module for the tack spindle. 2 + { 3 + config, 4 + lib, 5 + ... 6 + }: let 7 + cfg = config.services.tangled.tack; 8 + in 9 + with lib; { 10 + options.services.tangled.tack = { 11 + enable = mkOption { 12 + type = types.bool; 13 + default = false; 14 + description = "Enable the tack Tangled spindle."; 15 + }; 16 + 17 + package = mkOption { 18 + type = types.package; 19 + description = "Package providing the `tack` binary."; 20 + }; 21 + 22 + listenAddr = mkOption { 23 + type = types.str; 24 + default = ":8080"; 25 + description = "HTTP listen address (TACK_LISTEN_ADDR)."; 26 + }; 27 + 28 + hostname = mkOption { 29 + type = types.str; 30 + example = "tack.example.com"; 31 + description = '' 32 + Public hostname this spindle is registered under in Tangled 33 + (matches `sh.tangled.repo.spindle`). Required. 34 + ''; 35 + }; 36 + 37 + ownerDid = mkOption { 38 + type = types.str; 39 + example = "did:plc:qfpnj4og54vl56wngdriaxug"; 40 + description = "DID of the spindle operator (TACK_OWNER_DID). Required."; 41 + }; 42 + 43 + dbPath = mkOption { 44 + type = types.path; 45 + default = "/var/lib/tack/tack.db"; 46 + description = "Path to the local SQLite store (TACK_DB_PATH)."; 47 + }; 48 + 49 + jetstreamUrl = mkOption { 50 + type = types.str; 51 + default = "wss://jetstream1.us-west.bsky.network/subscribe"; 52 + description = "Tangled Jetstream WebSocket URL (TACK_JETSTREAM_URL)."; 53 + }; 54 + 55 + dev = mkOption { 56 + type = types.bool; 57 + default = false; 58 + description = '' 59 + Use `ws://` instead of `wss://` for knot event-streams 60 + (TACK_DEV). Useful when running against a local knot. 61 + ''; 62 + }; 63 + 64 + # Buildkite provider. Token + webhook secret are sensitive and 65 + # should be supplied via `environmentFile` so they don't end up 66 + # world-readable in the Nix store. 67 + buildkite = { 68 + org = mkOption { 69 + type = types.nullOr types.str; 70 + default = null; 71 + example = "my-org"; 72 + description = '' 73 + Default Buildkite org for workflows that don't specify one 74 + (TACK_BUILDKITE_ORG). Required when the Buildkite token is 75 + supplied via `environmentFile`. 76 + ''; 77 + }; 78 + 79 + webhookMode = mkOption { 80 + type = types.enum ["token" "signature"]; 81 + default = "token"; 82 + description = '' 83 + How tack authenticates incoming Buildkite webhooks 84 + (TACK_BUILDKITE_WEBHOOK_MODE). 85 + ''; 86 + }; 87 + }; 88 + 89 + environmentFile = mkOption { 90 + type = with types; nullOr path; 91 + default = null; 92 + example = "/etc/tack.env"; 93 + description = '' 94 + Additional environment file as defined in {manpage}`systemd.exec(5)`. 95 + 96 + Sensitive values such as {env}`TACK_BUILDKITE_TOKEN` and 97 + {env}`TACK_BUILDKITE_WEBHOOK_SECRET` belong here so they 98 + don't get baked into the world-readable Nix store. 99 + ''; 100 + }; 101 + }; 102 + 103 + config = mkIf cfg.enable { 104 + systemd.services.tack = { 105 + description = "Tack Tangled spindle"; 106 + after = ["network.target"]; 107 + wantedBy = ["multi-user.target"]; 108 + 109 + serviceConfig = { 110 + # StateDirectory creates /var/lib/tack with the right 111 + # ownership; the default dbPath lives there. 112 + StateDirectory = "tack"; 113 + LogsDirectory = "tack"; 114 + EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; 115 + 116 + # Only the non-secret env vars go inline here. Anything 117 + # sensitive (token, webhook secret) must come from 118 + # `environmentFile` to stay out of the Nix store. 119 + Environment = 120 + [ 121 + "TACK_LISTEN_ADDR=${cfg.listenAddr}" 122 + "TACK_HOSTNAME=${cfg.hostname}" 123 + "TACK_OWNER_DID=${cfg.ownerDid}" 124 + "TACK_DB_PATH=${cfg.dbPath}" 125 + "TACK_JETSTREAM_URL=${cfg.jetstreamUrl}" 126 + "TACK_BUILDKITE_WEBHOOK_MODE=${cfg.buildkite.webhookMode}" 127 + ] 128 + ++ optional cfg.dev "TACK_DEV=1" 129 + ++ optional (cfg.buildkite.org != null) 130 + "TACK_BUILDKITE_ORG=${cfg.buildkite.org}"; 131 + 132 + ExecStart = "${cfg.package}/bin/tack -addr ${cfg.listenAddr}"; 133 + Restart = "always"; 134 + 135 + # Light hardening. Tack only needs network access plus its 136 + # state directory, so we lock the rest down. 137 + DynamicUser = true; 138 + NoNewPrivileges = true; 139 + ProtectSystem = "strict"; 140 + ProtectHome = true; 141 + PrivateTmp = true; 142 + PrivateDevices = true; 143 + ProtectKernelTunables = true; 144 + ProtectKernelModules = true; 145 + ProtectControlGroups = true; 146 + RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; 147 + RestrictNamespaces = true; 148 + LockPersonality = true; 149 + MemoryDenyWriteExecute = true; 150 + }; 151 + }; 152 + }; 153 + }