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