Stitch any CI into Tangled
3

Configure Feed

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

at main 6.1 kB View raw
1# NixOS module for the tack spindle. 2{ 3 config, 4 lib, 5 ... 6}: let 7 cfg = config.services.tangled.tack; 8in 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 # Escape hatch for arbitrary systemd `[Service]` settings (e.g. 103 # `MemoryMax`, `CPUQuota`, additional sandboxing knobs). These 104 # are merged into `serviceConfig` and override the defaults 105 # below on conflict, so callers can both add new settings and 106 # tweak the ones we set out of the box. 107 extraServiceConfig = mkOption { 108 type = types.attrsOf types.unspecified; 109 default = {}; 110 example = literalExpression '' 111 { 112 MemoryMax = "512M"; 113 CPUQuota = "50%"; 114 } 115 ''; 116 description = '' 117 Extra settings merged into the systemd service's 118 `[Service]` section. See {manpage}`systemd.exec(5)` and 119 {manpage}`systemd.resource-control(5)` for available 120 options. Values here take precedence over the module's 121 defaults. 122 ''; 123 }; 124 }; 125 126 config = mkIf cfg.enable { 127 systemd.services.tack = { 128 description = "Tack Tangled spindle"; 129 after = ["network.target"]; 130 wantedBy = ["multi-user.target"]; 131 132 # `extraServiceConfig` is merged in last so user-supplied 133 # settings override our defaults on conflict. 134 serviceConfig = 135 { 136 # StateDirectory creates /var/lib/tack with the right 137 # ownership; the default dbPath lives there. 138 StateDirectory = "tack"; 139 LogsDirectory = "tack"; 140 EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile; 141 142 # Only the non-secret env vars go inline here. Anything 143 # sensitive (token, webhook secret) must come from 144 # `environmentFile` to stay out of the Nix store. 145 Environment = 146 [ 147 "TACK_LISTEN_ADDR=${cfg.listenAddr}" 148 "TACK_HOSTNAME=${cfg.hostname}" 149 "TACK_OWNER_DID=${cfg.ownerDid}" 150 "TACK_DB_PATH=${cfg.dbPath}" 151 "TACK_JETSTREAM_URL=${cfg.jetstreamUrl}" 152 "TACK_BUILDKITE_WEBHOOK_MODE=${cfg.buildkite.webhookMode}" 153 ] 154 ++ optional cfg.dev "TACK_DEV=1" 155 ++ optional (cfg.buildkite.org != null) 156 "TACK_BUILDKITE_ORG=${cfg.buildkite.org}"; 157 158 ExecStart = "${cfg.package}/bin/tack -addr ${cfg.listenAddr}"; 159 Restart = "always"; 160 161 # Light hardening. Tack only needs network access plus its 162 # state directory, so we lock the rest down. 163 DynamicUser = true; 164 NoNewPrivileges = true; 165 ProtectSystem = "strict"; 166 ProtectHome = true; 167 PrivateTmp = true; 168 PrivateDevices = true; 169 ProtectKernelTunables = true; 170 ProtectKernelModules = true; 171 ProtectControlGroups = true; 172 RestrictAddressFamilies = ["AF_INET" "AF_INET6" "AF_UNIX"]; 173 RestrictNamespaces = true; 174 LockPersonality = true; 175 MemoryDenyWriteExecute = true; 176 } 177 // cfg.extraServiceConfig; 178 }; 179 }; 180 }