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