Monorepo for Tangled
tangled.org
1{
2 config,
3 pkgs,
4 lib,
5 ...
6}: let
7 cfg = config.services.tangled.spindle;
8in
9 with lib; {
10 options = {
11 services.tangled.spindle = {
12 enable = mkOption {
13 type = types.bool;
14 default = false;
15 description = "Enable a tangled spindle";
16 };
17 package = mkOption {
18 type = types.package;
19 description = "Package to use for the spindle";
20 };
21 tap-package = mkOption {
22 type = types.package;
23 description = "Package to use for the spindle";
24 };
25
26 atpRelayUrl = mkOption {
27 type = types.str;
28 default = "https://relay1.us-east.bsky.network";
29 description = "atproto relay";
30 };
31
32 server = {
33 listenAddr = mkOption {
34 type = types.str;
35 default = "0.0.0.0:6555";
36 description = "Address to listen on";
37 };
38
39 stateDir = mkOption {
40 type = types.path;
41 default = "/var/lib/spindle";
42 description = "Tangled spindle data directory";
43 };
44
45 hostname = mkOption {
46 type = types.str;
47 example = "my.spindle.com";
48 description = "Hostname for the server (required)";
49 };
50
51 plcUrl = mkOption {
52 type = types.str;
53 default = "https://plc.directory";
54 description = "atproto PLC directory";
55 };
56
57 dev = mkOption {
58 type = types.bool;
59 default = false;
60 description = "Enable development mode (disables signature verification)";
61 };
62
63 owner = mkOption {
64 type = types.str;
65 example = "did:plc:qfpnj4og54vl56wngdriaxug";
66 description = "DID of owner (required)";
67 };
68
69 maxJobCount = mkOption {
70 type = types.int;
71 default = 2;
72 example = 5;
73 description = "Maximum number of concurrent jobs to run";
74 };
75
76 queueSize = mkOption {
77 type = types.int;
78 default = 100;
79 example = 100;
80 description = "Maximum number of jobs queue up";
81 };
82
83 secrets = {
84 provider = mkOption {
85 type = types.str;
86 default = "sqlite";
87 description = "Backend to use for secret management, valid options are 'sqlite', and 'openbao'.";
88 };
89
90 openbao = {
91 proxyAddr = mkOption {
92 type = types.str;
93 default = "http://127.0.0.1:8200";
94 };
95 mount = mkOption {
96 type = types.str;
97 default = "spindle";
98 };
99 };
100 };
101 };
102
103 pipelines = {
104 nixery = mkOption {
105 type = types.str;
106 default = "nixery.tangled.sh"; # note: this is *not* on tangled.org yet
107 description = "Nixery instance to use";
108 };
109
110 workflowTimeout = mkOption {
111 type = types.str;
112 default = "5m";
113 description = "Timeout for each step of a pipeline";
114 };
115 };
116 };
117 };
118
119 config = mkIf cfg.enable {
120 virtualisation.docker.enable = true;
121
122 systemd.services.spindle-tap = {
123 description = "spindle tap service";
124 after = ["network.target" "docker.service"];
125 wantedBy = ["multi-user.target"];
126 serviceConfig = {
127 LogsDirectory = "spindle-tap";
128 StateDirectory = "spindle-tap";
129 Environment = [
130 "TAP_BIND=:2480"
131 "TAP_PLC_URL=${cfg.server.plcUrl}"
132 "TAP_RELAY_URL=${cfg.atpRelayUrl}"
133 "TAP_DATABASE_URL=sqlite:///var/lib/spindle-tap/tap.db"
134 "TAP_RETRY_TIMEOUT=3s"
135 "TAP_COLLECTION_FILTERS=${concatStringsSep "," [
136 "sh.tangled.repo"
137 "sh.tangled.repo.collaborator"
138 "sh.tangled.spindle.member"
139 "sh.tangled.repo.pull"
140 ]}"
141 # temporary hack to listen for repo.pull from non-tangled users
142 "TAP_SIGNAL_COLLECTION=sh.tangled.repo.pull"
143 ];
144 ExecStart = "${getExe cfg.tap-package} run";
145 };
146 };
147
148 systemd.services.spindle = {
149 description = "spindle service";
150 after = ["network.target" "docker.service" "spindle-tap.service"];
151 wantedBy = ["multi-user.target"];
152 path = [
153 pkgs.git
154 ];
155 serviceConfig = {
156 LogsDirectory = "spindle";
157 StateDirectory = "spindle";
158 Environment = [
159 "SPINDLE_SERVER_LISTEN_ADDR=${cfg.server.listenAddr}"
160 "SPINDLE_SERVER_DATA_DIR=${cfg.server.stateDir}"
161 "SPINDLE_SERVER_HOSTNAME=${cfg.server.hostname}"
162 "SPINDLE_SERVER_PLC_URL=${cfg.server.plcUrl}"
163 "SPINDLE_SERVER_DEV=${lib.boolToString cfg.server.dev}"
164 "SPINDLE_SERVER_OWNER=${cfg.server.owner}"
165 "SPINDLE_SERVER_MAX_JOB_COUNT=${toString cfg.server.maxJobCount}"
166 "SPINDLE_SERVER_QUEUE_SIZE=${toString cfg.server.queueSize}"
167 "SPINDLE_SERVER_SECRETS_PROVIDER=${cfg.server.secrets.provider}"
168 "SPINDLE_SERVER_SECRETS_OPENBAO_PROXY_ADDR=${cfg.server.secrets.openbao.proxyAddr}"
169 "SPINDLE_SERVER_SECRETS_OPENBAO_MOUNT=${cfg.server.secrets.openbao.mount}"
170 "SPINDLE_SERVER_TAP_URL=http://localhost:2480"
171 "SPINDLE_NIXERY_PIPELINES_NIXERY=${cfg.pipelines.nixery}"
172 "SPINDLE_NIXERY_PIPELINES_WORKFLOW_TIMEOUT=${cfg.pipelines.workflowTimeout}"
173 ];
174 ExecStart = "${cfg.package}/bin/spindle";
175 Restart = "always";
176 };
177 };
178 };
179 }