-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdefault.nix
412 lines (364 loc) · 18.5 KB
/
default.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
{ lib, pkgs, ... }:
let
# unsetup the systemd service
# inspired by setup-systemd-service
unsetup-systemd-service =
{ units # : AttrSet String (Either Path { path : Path, wanted-by : [ String ] })
# ^ A set whose names are unit names and values are
# either paths to the corresponding unit files or a set
# containing the path and the list of units this unit
# should be wanted-by (none by default).
#
# The names should include the unit suffix
# (e.g. ".service")
, namespace # : String
# The namespace for the unit files, to allow for
# multiple independent unit sets managed by
# `setupSystemdUnits`.
}:
let
remove-unit-snippet = name: file: ''
oldUnit=$(readlink -f "$unitDir/${name}" || echo "$unitDir/${name}")
if [ -f "$oldUnit" ]; then
unitsToStop+=("${name}")
unitFilesToRemove+=("$unitDir/${name}")
${
lib.concatStringsSep "\n" (map (unit: ''
unitWantsToRemove+=("$unitDir/${unit}.wants/${name}")
'') file.wanted-by or [ ])
}
fi
'';
in pkgs.writeScriptBin "unsetup-systemd-units" ''
#!${pkgs.bash}/bin/bash -e
export PATH=${pkgs.coreutils}/bin:${pkgs.systemd}/bin
unitDir=/etc/systemd/system
if [ ! -w "$unitDir" ]; then
unitDir=/nix/var/nix/profiles/default/lib/systemd/system
fi
declare -a unitsToStop unitFilesToRemove unitWantsToRemove
${lib.concatStringsSep "\n"
(lib.mapAttrsToList remove-unit-snippet units)}
if [ ''${#unitsToStop[@]} -ne 0 ]; then
echo "Stopping unit(s) ''${unitsToStop[@]}" >&2
systemctl stop "''${unitsToStop[@]}"
if [ ''${#unitWantsToRemove[@]} -ne 0 ]; then
echo "Removing unit wants-by file(s) ''${unitWantsToRemove[@]}" >&2
rm -fr "''${unitWantsToRemove[@]}"
fi
echo "Removing unit file(s) ''${unitFilesToRemove[@]}" >&2
rm -fr "''${unitFilesToRemove[@]}"
fi
if [ -e /etc/systemd-static/${namespace} ]; then
echo "removing systemd static namespace ${namespace}"
rm -fr /etc/systemd-static/${namespace}
fi
systemctl daemon-reload
'';
# define some utility function for release packing ( code adapted from setup-systemd-units.nix )
mk-release-packer = { referencePath # : Path
# paths to the corresponding reference file
, component # : String
# The name for the deployed component
# e.g., "my-postgresql", "my-postgrest"
, site # : String
# The name for the deployed target site
# e.g., "my-site", "local"
, phase # : String
# The name for the deployed target phase
# e.g., "local", "test", "production"
, innerTarballName # : String
# The name for the deployed inner tarball
# e.g., "component"+"site"+"phase".tar.gz
, deployScript # : Path
# the deploy script path
, cleanupScript # : Path
# the cleanup script path
}:
let
namespace = lib.concatStringsSep "-" [ component site phase ];
referenceKey = lib.concatStringsSep "." [ namespace "reference" ];
reference = lib.attrsets.setAttrByPath [ referenceKey ] referencePath;
static = pkgs.runCommand "${namespace}-reference-file-static" { } ''
mkdir -p $out
${lib.concatStringsSep "\n"
(lib.mapAttrsToList (nm: file: "ln -sv ${file} $out/${nm}") reference)}
'';
gen-changed-pkgs-list = name: file: ''
oldReference=$(readlink -f "$referenceDir/${name}" || echo "$referenceDir/${name}")
if [ -f "$oldReference" -a "$oldReference" != "${file}" ]; then
echo "$oldReference <-> ${file}"
LC_ALL=C comm -13 <(LC_ALL=C sort -u $oldReference) <(LC_ALL=C sort -u "${file}") > "$referenceDir/${name}.delta"
fileListToPack="$referenceDir/${name}.delta"
else
fileListToPack="${file}"
fi
ln -sf "/nix/var/reference-file-static/${namespace}/${name}" \
"$referenceDir/.${name}.tmp"
mv -T "$referenceDir/.${name}.tmp" "$referenceDir/${name}"
'';
in pkgs.writeScriptBin "mk-release-packer-for-${site}-${phase}" ''
#!${pkgs.bash}/bin/bash -e
export PATH=${pkgs.coreutils}/bin:${pkgs.gnutar}/bin:${pkgs.gzip}/bin:${pkgs.gawk}/bin:${pkgs.findutils}/bin:${pkgs.gnused}/bin:${pkgs.makeself}/bin
fileListToPack="${referencePath}"
referenceDir=/nix/var/reference-file
mkdir -p "$referenceDir"
oldStatic=$(readlink -f /nix/var/reference-file-static/${namespace} || true)
if [ "$oldStatic" != "${static}" ]; then
${
lib.concatStringsSep "\n"
(lib.mapAttrsToList gen-changed-pkgs-list reference)
}
mkdir -p /nix/var/reference-file-static
ln -sfT ${static} /nix/var/reference-file-static/.${namespace}.tmp
mv -T /nix/var/reference-file-static/.${namespace}.tmp /nix/var/reference-file-static/${namespace}
else
echo "Dependence reference file not exist or unchanged, will do a full release pack" >&2
fi
# pack the systemd service or executable sh and dependencies with full path
tar zPcf ./${innerTarballName} -T "$fileListToPack"
# pack the previous tarball and the two scripts for distribution
packDirTemp=$(mktemp -d)
# add timestamp to the directory name and final tarball name
# to avoid overwrite, that would make it eay to rollback to
# any previous deployed version
currentTimeStamp=$(date "+%Y%m%d%H%M%S")
packDirWithTS="${namespace}-dist-$currentTimeStamp"
packDirWithTSFull="$packDirTemp/$packDirWithTS"
mkdir -p "$packDirWithTSFull"
cp "${deployScript}/mk-deploy-sh" "$packDirWithTSFull/deploy-${component}-to-${site}-${phase}"
cp "${cleanupScript}/mk-cleanup-sh" "$packDirWithTSFull/cleanup-${component}-on-${site}-${phase}"
mv "./${innerTarballName}" "$packDirWithTSFull"
# tar zcf "./$packDirWithTS.tar.gz" \
# -C "$packDirTemp" \
# "packDirWithTS"
# use makeself instead
makeself --gzip --current "$packDirTemp" "./$packDirWithTS.sh" \
"Deploy ${component} to ${site} ${phase} environment" \
"$packDirWithTS/deploy-${component}-to-${site}-${phase}"
rm -fr "$packDirTemp"
'';
# following script running at the target machine during deployment while nix store not available yet
# so switch to writeTextFile to use target host bash
mk-deploy-sh =
{ env # : AttrsSet the environment for the deployment target machine
, payloadPath # : Path the nix path to the script which sets up the systemd service or wrapping script
, innerTarballName # : String the tarball file name for the inner package tar
, execName # : String the executable file name
, startCmd ? "" # : String command line to start the program, default ""
, stopCmd ? "" # : String command line to stop the program, default ""
}:
pkgs.writeTextFile rec {
name = "mk-deploy-sh";
executable = true;
destination = "/${name}";
text = ''
#!/usr/bin/env bash
# this script need to be run with root or having sudo permission
[ $EUID -ne 0 ] && ! sudo echo >/dev/null 2>&1 && echo "need to run with root or sudo without password" && exit 127
# some command fix up for systemd service, especially web server
getent group nogroup > /dev/null || sudo groupadd nogroup
# create user and group
getent group "${env.processUser}" > /dev/null || sudo groupadd "${env.processUser}"
getent passwd "${env.processUser}" > /dev/null || sudo useradd -m -p Passw0rd -g "${env.processUser}" "${env.processUser}"
# create directories
for dirToMk in "${env.runDir}" "${env.dataDir}"
do
FIRST_C=$(echo "$dirToMk" | cut -c1)
if [ "X$FIRST_C" != "X/" ]; then
echo "the path must be a absolute path."
exit 111
fi
if [ -d "$dirToMk" ]; then
# directory exists
if [ $(stat --format '%U' "$dirToMk") != "${env.processUser}" ]; then
# but belongs to other users, that should be an error
echo "the path $dirToMk exists but owner is not the process user ${env.processUser}, abort."
exit 110
else
# directory already exists and belongs to the process user
# check if we could create files under this directory
if ! sudo su - "${env.processUser}" -c "touch "$dirToMk"/.check.if.we.could.create.files.under.this.directory > /dev/null 2>&1"; then
echo "Directory $dirToMk exists, owned by ${env.processUser}, however, ${env.processUser} could not create files under this dir."
echo "Please check the mode of the whole directory tree."
exit 111
else
# clean up the check
rm -fr "$dirToMk"/.check.if.we.could.create.files.under.this.directory
fi
fi
else
# directory not exists
NONEXIST_TOP_PATH="$dirToMk"
NONEXIST_LAST_PATH=""
while [ "X$NONEXIST_TOP_PATH" != "X" ] && [ ! -d "$NONEXIST_TOP_PATH" ]
do
NONEXIST_LAST_PATH=$(echo "$NONEXIST_TOP_PATH" | awk -F'/' '{print $NF}')
NONEXIST_TOP_PATH=$(echo "$NONEXIST_TOP_PATH" | sed 's:\(.*\)/\(.*\)$:\1:g')
done
[[ "X$NONEXIST_LAST_PATH" != "X" ]] && NONEXIST_TOP_PATH=$(echo "$NONEXIST_TOP_PATH/$NONEXIST_LAST_PATH")
sudo mkdir -p "$dirToMk"
sudo chown -R ${env.processUser}:${env.processUser} "$NONEXIST_TOP_PATH"
sudo chmod -R 755 "$NONEXIST_TOP_PATH"
# Even after having changed the owner to the user from the top non-exist directory
# We still cannot make sure the user can create files under the given directory
# check if we could create files under this directory
if ! sudo su - "${env.processUser}" -c "touch "$dirToMk"/.check.if.we.could.create.files.under.this.directory > /dev/null 2>&1"; then
echo "Directory $dirToMk created and change the owner to {env.processUser}, however, ${env.processUser} could not create files under this dir."
echo "Please check the mode of the whole directory tree from $NONEXIST_TOP_PATH and make sure the user has write permission to all directories"
exit 111
else
# clean up the check
rm -fr "$dirToMk"/.check.if.we.could.create.files.under.this.directory
fi
fi
done
# now unpack(note we should preserve the /nix/store directory structure)
# determine the PWD
[ "X$USER_PWD" != "X" ] && MYPWD="$USER_PWD/$(dirname $0)" || MYPWD="$(dirname $0)"
sudo tar zPxf "$MYPWD"/${innerTarballName}
# the /nix should belong to root
sudo chown -R root:root /nix
sudo chmod 555 /nix
sudo chmod 555 /nix/store
# save the user name of the current process
MY_CURRENT_USER=$(id -nu)
# setup the systemd service or create a link to the executable
${lib.concatStringsSep "\n" (if env.isSystemdService then
[ "sudo ${payloadPath}/bin/setup-systemd-units" ]
else [''
# there is a previous version here, stop it first
if [ -e ${env.runDir}/stop.sh ]; then
# do not do any output, because the app may rely on its output to function properly
# echo "stopping ${execName}"
sudo su - "${env.processUser}" -c '${env.runDir}/stop.sh "$@"'
fi
# since the payload path changed for every deployment,
# the start/stop scripts must be generated each deployment
{
echo "#!/usr/bin/env bash"
echo "MY_CURRENT_USER=\$(id -nu)"
echo "[[ \"\$MY_CURRENT_USER\" != \"${env.processUser}\" ]] && echo \"this script should be run with the user name ${env.processUser}\" && exit 127"
echo "exec ${payloadPath}/bin/${execName} ${startCmd} \"\$@\""
} > ${env.runDir}/start.sh
{
echo "#!/usr/bin/env bash"
echo "MY_CURRENT_USER=\$(id -nu)"
echo "[[ \"\$MY_CURRENT_USER\" != \"${env.processUser}\" ]] && echo \"this script should be run with the user name ${env.processUser}\" && exit 127"
echo "exec ${payloadPath}/bin/${execName} ${stopCmd} \"\$@\""
} > ${env.runDir}/stop.sh
sudo chown -R ${env.processUser}:${env.processUser} "${env.runDir}"
chmod +x ${env.runDir}/start.sh ${env.runDir}/stop.sh
# do not do any output, because the app may rely on its output to function properly
# echo "starting the program ${execName}"
sudo su - "${env.processUser}" -c '${env.runDir}/start.sh "$@"'
# do not do any output, because the app may rely on its output to function properly
# echo "check the scripts under ${env.runDir} to start or stop the program."''])}
'';
};
# following script running at the target machine during deployment while nix store not available yet
# so switch to writeTextFile to use target host bash
mk-cleanup-sh = { env # the environment for the deployment target machine
, payloadPath # the nix path to the script which unsets up the systemd service or wrapping script
, innerTarballName # : String the tarball file name for the inner package tar
, execName # : String the executable file name
}:
pkgs.writeTextFile rec {
name = "mk-cleanup-sh";
executable = true;
destination = "/${name}";
text = ''
#!/usr/bin/env bash
# this script need to be run with root or having sudo permission
[ $EUID -ne 0 ] && ! sudo echo >/dev/null 2>&1 && echo "need to run with root or sudo without password" && exit 127
# check to make sure we are running this cleanup script after deploy script
alreadyDeployed=""
${lib.concatStringsSep "\n" (if env.isSystemdService then [''
if [ -e ${payloadPath}/bin/unsetup-systemd-units ]; then
alreadyDeployed="true"
else
alreadyDeployed="false"
fi''] else [''
if [ -e ${env.runDir}/start.sh ] && [ -e ${env.runDir}/stop.sh ]; then
newBinSh=$(awk '/exec/ {print $2}' "${env.runDir}/start.sh")
if [ -e "$newBinSh" ]; then
alreadyDeployed="true"
else
alreadyDeployed="false"
fi
else
alreadyDeployed="false"
fi
''])}
[ $alreadyDeployed == "false" ] && echo "service not installed yet or installed with a previous version. please run the deploy script first." && exit 126
# ok, the deploy script had been run now we can run the cleanup script
echo "BIG WARNING!!!"
echo "This script will also ERASE all data generated during the program running."
echo "That means all data generated during the program running will be lost and cannot be restored."
echo "Think twice before you answer Y nad hit ENTER. You have been warned."
echo "If your are looking for how to start/stop the program,"
echo "Refer to the following command"
${lib.concatStringsSep "\n" (if env.isSystemdService then [''
serviceNames=$(awk 'BEGIN { FS="\"" } /unitsToStop\+=\(/ {print $2}' ${payloadPath}/bin/unsetup-systemd-units)
echo "To stop - sudo systemctl stop <service-name>"
echo "To start - sudo systemctl start <service-name>"
echo "Where <service-name> is $serviceNames"
''] else [''
echo "To stop - under the user name ${env.processUser}, run ${env.runDir}/stop.sh"
echo "To start - under the user name ${env.processUser}, run ${env.runDir}/start.sh"
''])}
read -p "Continue? (Y/N): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 129
# how do we unsetup the systemd unit? we do not unsetup the systemd service for now
# we just stop it before doing the cleanup
${lib.concatStringsSep "\n" (if env.isSystemdService then [''
sudo ${payloadPath}/bin/unsetup-systemd-units
''] else [''
sudo su - "${env.processUser}" -c '${env.runDir}/stop.sh "$@"'
''])}
for dirToRm in "${env.runDir}" "${env.dataDir}"
do
FIRST_C=$(echo "$dirToRm" | cut -c1)
if [ "X$FIRST_C" != "X/" ]; then
echo "the path must be a absolute path."
exit 111
fi
if [ -d "$dirToRm" ]; then
if [ $(stat --format '%U' "$dirToRm") != "${env.processUser}" ]; then
echo "the path $dirToRm exists but owner is not the process user ${env.processUser}, abort."
exit 110
else
EXIST_TOP_PATH="$dirToRm"
EXIST_LAST_PATH=""
while [ "X$EXIST_TOP_PATH" != "X" ] && [ -d "$EXIST_TOP_PATH" ] && [ $(stat --format '%U' "$EXIST_TOP_PATH") == "${env.processUser}" ]
do
EXIST_LAST_PATH=$(echo "$EXIST_TOP_PATH" | awk -F'/' '{print $NF}')
EXIST_TOP_PATH=$(echo "$EXIST_TOP_PATH" | sed 's:\(.*\)/\(.*\)$:\1:g')
done
[[ "X$EXIST_LAST_PATH" != "X" ]] && EXIST_TOP_PATH=$(echo "$EXIST_TOP_PATH/$EXIST_LAST_PATH")
sudo rm -fr "$EXIST_TOP_PATH"
fi
else
echo "the path $dirToRm not exists, skip."
fi
done
# FOLLOWING IS REALLY DANGEROUS!!! DO NOT DO THAT!!!
# BECAUSE SOME DEPENDENCIES ARE SHARED BY MORE THAN ONE PACKAGE!!!
# YOU HAVE BEEN WARNED!!!
# do we need to delete the program and all its dependencies in /nix/store?
# we will not do that for now
# determine the PWD
# [ "X$USER_PWD" != "X" ] && MYPWD="$USER_PWD/$(dirname $0)" || MYPWD="$(dirname $0)"
# if [ -f "$MYPWD/${innerTarballName}" ]; then
# tar zPtvf "$MYPWD/${innerTarballName}"|awk '{print $NF}'|grep '/nix/store/'|awk -F'/' '{print "/nix/store/" $4}'|sort|uniq|xargs sudo rm -fr
# else
# echo "cannot find the release tarball $MYPWD/${innerTarballName}, skip cleaning the distribute files."
# fi
# well, shall we remove the user and group? maybe not
# we will do that for now.
getent passwd "${env.processUser}" > /dev/null && sudo userdel -fr "${env.processUser}"
getent group "${env.processUser}" > /dev/null && sudo groupdel -f "${env.processUser}"
'';
};
in {
inherit unsetup-systemd-service mk-release-packer mk-deploy-sh mk-cleanup-sh;
}