1
+ # Partner Chains Node NixOS module
2
+ { self , ...} :
3
+ {
4
+ flake . nixosModules . partner-chains =
5
+ { config , lib , pkgs , ... } :
6
+
7
+ with lib ;
8
+
9
+ let
10
+ cfg = config . services . partner-chains ;
11
+ in {
12
+ options . services . partner-chains = {
13
+ enable = mkEnableOption "Partner Chains Node" ;
14
+
15
+ # Common environment variables
16
+ environment = mkOption {
17
+ type = types . attrsOf types . str ;
18
+ default = {
19
+ CARDANO_SECURITY_PARAMETER = "432" ;
20
+ CARDANO_ACTIVE_SLOTS_COEFF = "0.05" ;
21
+ DB_SYNC_POSTGRES_CONNECTION_STRING = "postgresql://cexplorer:password@localhost:5432/cexplorer" ;
22
+ MC__FIRST_EPOCH_TIMESTAMP_MILLIS = "1666656000000" ;
23
+ MC__EPOCH_DURATION_MILLIS = "86400000" ;
24
+ MC__SLOT_DURATION_MILLIS = "1000" ;
25
+ MC__FIRST_EPOCH_NUMBER = "0" ;
26
+ MC__FIRST_SLOT_NUMBER = "0" ;
27
+ BLOCK_STABILITY_MARGIN = "0" ;
28
+ } ;
29
+ description = "Environment variables for the partner chains node" ;
30
+ } ;
31
+
32
+ # Node-specific options
33
+ nodeName = mkOption {
34
+ type = types . nullOr types . str ;
35
+ default = null ;
36
+ example = "alice" ;
37
+ description = "Node name flag (--alice, --bob, --charlie, etc.)" ;
38
+ } ;
39
+
40
+ nodeKey = mkOption {
41
+ type = types . str ;
42
+ example = "0a04cb23cff606facb13ddd43655840e9f6f32bd7b432809620d461596e188e9" ;
43
+ description = "Node key for identification" ;
44
+ } ;
45
+
46
+ chainSpecPath = mkOption {
47
+ type = types . path ;
48
+ default = "/var/lib/partner-chains/chain-spec.json" ;
49
+ description = "Path to the chain specification file" ;
50
+ } ;
51
+
52
+ keystorePath = mkOption {
53
+ type = types . path ;
54
+ default = "/var/lib/partner-chains/keystore" ;
55
+ description = "Path to the keystore directory" ;
56
+ } ;
57
+
58
+ listenAddr = mkOption {
59
+ type = types . str ;
60
+ default = "/ip4/0.0.0.0/tcp/30333" ;
61
+ description = "Address to listen on" ;
62
+ } ;
63
+
64
+ prometheusPort = mkOption {
65
+ type = types . int ;
66
+ default = 9615 ;
67
+ description = "Prometheus metrics port" ;
68
+ } ;
69
+
70
+ rpcPort = mkOption {
71
+ type = types . int ;
72
+ default = 9944 ;
73
+ description = "RPC server port" ;
74
+ } ;
75
+
76
+ logLevel = mkOption {
77
+ type = types . str ;
78
+ default = "runtime=debug" ;
79
+ description = "Log level configuration" ;
80
+ } ;
81
+
82
+ reservedNodes = mkOption {
83
+ type = types . listOf types . str ;
84
+ default = [ ] ;
85
+ example = [ "/dns/dave.node.sc.iog.io/tcp/30333/p2p/12D3KooWH4LhgJDUbYbXsksQef4jTpDjA64ecUBjBVJprNzF64hE" ] ;
86
+ description = "List of reserved nodes" ;
87
+ } ;
88
+
89
+ bootNodes = mkOption {
90
+ type = types . listOf types . str ;
91
+ default = [ ] ;
92
+ example = [ "/dns/eve.node.sc.iog.io/tcp/30333/p2p/12D3KooWN3YiYbk9nMZJ2VG7uk9iKfFWb1Kwrj7PoMdadfnAsRJm" ] ;
93
+ description = "List of boot nodes" ;
94
+ } ;
95
+
96
+ extraArgs = mkOption {
97
+ type = types . listOf types . str ;
98
+ default = [ ] ;
99
+ example = [ "--rpc-methods=unsafe" "--rpc-max-connections" "1000" ] ;
100
+ description = "Additional command line arguments" ;
101
+ } ;
102
+
103
+ enableRpc = mkOption {
104
+ type = types . bool ;
105
+ default = false ;
106
+ description = "Enable RPC server" ;
107
+ } ;
108
+
109
+ rpcCors = mkOption {
110
+ type = types . str ;
111
+ default = "all" ;
112
+ description = "RPC CORS setting" ;
113
+ } ;
114
+
115
+ blockBeneficiary = mkOption {
116
+ type = types . str ;
117
+ example = "0a04cb23cff606facb13ddd43655840e9f6f32bd7b432809620d461596e188e9" ;
118
+ description = "Sidechain block beneficiary address" ;
119
+ } ;
120
+
121
+ package = mkOption {
122
+ type = types . package ;
123
+ default = pkgs . partner-chains . packages . x86_64-linux . partner-chains ;
124
+ description = "The partner-chains package to use" ;
125
+ } ;
126
+
127
+ # New options
128
+ enableValidator = mkOption {
129
+ type = types . bool ;
130
+ default = false ;
131
+ description = "Enable validator mode" ;
132
+ } ;
133
+
134
+ pruning = mkOption {
135
+ type = types . enum [ "default" "archive" ] ;
136
+ default = "default" ;
137
+ description = "Pruning mode for the blockchain data" ;
138
+ } ;
139
+ } ;
140
+
141
+ config = mkIf cfg . enable {
142
+ systemd . services . partner-chains = {
143
+ description = "Partner Chains Node" ;
144
+ wantedBy = [ "multi-user.target" ] ;
145
+ after = [ "network.target" ] ;
146
+
147
+ # Combine the default environment with any overrides
148
+ environment = cfg . environment // {
149
+ SIDECHAIN_BLOCK_BENEFICIARY = cfg . blockBeneficiary ;
150
+ } ;
151
+
152
+ preStart = ''
153
+ # Ensure directories exist
154
+ mkdir -p ${ dirOf cfg . chainSpecPath }
155
+ mkdir -p ${ cfg . keystorePath }
156
+
157
+ # Create a default chain spec if it doesn't exist
158
+ if [ ! -f "${ cfg . chainSpecPath } " ]; then
159
+ echo "Chain spec file does not exist at ${ cfg . chainSpecPath } "
160
+ echo "Creating a minimal placeholder. Please ensure a proper chain spec is configured."
161
+ echo '{"name": "partner-chains", "id": "partner-chains"}' > ${ cfg . chainSpecPath }
162
+ fi
163
+
164
+ # Set proper permissions
165
+ chown -R partner-chains:partner-chains ${ dirOf cfg . chainSpecPath }
166
+ chown -R partner-chains:partner-chains ${ cfg . keystorePath }
167
+ chmod 750 ${ dirOf cfg . chainSpecPath }
168
+ chmod 750 ${ cfg . keystorePath }
169
+ chmod 640 ${ cfg . chainSpecPath }
170
+
171
+ # Create data directories with proper permissions
172
+ DATA_DIR=$(dirname ${ cfg . keystorePath } )
173
+ if [ ! -d "$DATA_DIR" ]; then
174
+ mkdir -p $DATA_DIR
175
+ chown -R partner-chains:partner-chains $DATA_DIR
176
+ chmod -R 750 $DATA_DIR
177
+ fi
178
+ '' ;
179
+
180
+ serviceConfig = let
181
+ # Node name flag (--alice, --bob, etc.)
182
+ nodeNameFlag = if cfg . nodeName != null then "--${ cfg . nodeName } " else "" ;
183
+
184
+ # Reserved nodes flags
185
+ reservedNodesFlags = if cfg . reservedNodes != [ ]
186
+ then [ "--reserved-only" ] ++ ( flatten ( map ( node : [ "--reserved-nodes" node ] ) cfg . reservedNodes ) )
187
+ else [ ] ;
188
+
189
+ # Boot nodes flags
190
+ bootNodesFlags = flatten ( map ( node : [ "--bootnodes" node ] ) cfg . bootNodes ) ;
191
+
192
+ # RPC flags
193
+ rpcFlags = if cfg . enableRpc
194
+ then [ "--rpc-external" "--rpc-cors=${ cfg . rpcCors } " "--rpc-port" ( toString cfg . rpcPort ) ]
195
+ else [ ] ;
196
+
197
+ # Pruning flags
198
+ pruningFlags = if cfg . pruning == "archive"
199
+ then [ "--state-pruning" "archive" "--blocks-pruning" "archive" ]
200
+ else [ ] ;
201
+
202
+ # Combine all arguments
203
+ allArgs =
204
+ [ nodeNameFlag ]
205
+ ++ ( optional cfg . enableValidator "--validator" )
206
+ ++ [ "--node-key" cfg . nodeKey ]
207
+ ++ [ "--chain" cfg . chainSpecPath ]
208
+ ++ reservedNodesFlags
209
+ ++ bootNodesFlags
210
+ ++ [ "-llibp2p=debug" ]
211
+ ++ [ "--listen-addr" cfg . listenAddr ]
212
+ ++ [ "--keystore-path" cfg . keystorePath ]
213
+ ++ [ "--log" cfg . logLevel ]
214
+ ++ [ "--prometheus-port" ( toString cfg . prometheusPort ) ]
215
+ ++ [ "--prometheus-external" ]
216
+ ++ rpcFlags
217
+ ++ pruningFlags
218
+ ++ cfg . extraArgs ;
219
+
220
+ # Filter out empty strings (from nodeNameFlag if it's empty)
221
+ cleanArgs = filter ( arg : arg != "" ) allArgs ;
222
+ in {
223
+ ExecStart = "${ cfg . package } /bin/partner-chains-node ${ concatStringsSep " " cleanArgs } " ;
224
+
225
+ # Important! Create a StateDirectory for persistent storage
226
+ StateDirectory = "partner-chains" ;
227
+
228
+ # This ensures systemd captures all stdout/stderr
229
+ StandardOutput = "journal" ;
230
+ StandardError = "journal" ;
231
+
232
+ # Security hardening
233
+ User = "partner-chains" ;
234
+ Group = "partner-chains" ;
235
+ Restart = "always" ;
236
+ RestartSec = "10s" ;
237
+ LimitNOFILE = 65535 ;
238
+ } ;
239
+ } ;
240
+
241
+ # Create the user/group
242
+ users . users . partner-chains = {
243
+ isSystemUser = true ;
244
+ group = "partner-chains" ;
245
+ home = "/var/lib/partner-chains" ;
246
+ createHome = true ;
247
+ } ;
248
+ users . groups . partner-chains = { } ;
249
+
250
+ # Open firewall ports
251
+ networking . firewall . allowedTCPPorts = [
252
+ 30333 # P2P port
253
+ cfg . prometheusPort
254
+ ] ++ ( optionals cfg . enableRpc [ cfg . rpcPort ] ) ;
255
+ } ;
256
+ } ;
257
+ }
0 commit comments