-
Notifications
You must be signed in to change notification settings - Fork 0
/
dev_shell.nix
366 lines (318 loc) · 13.2 KB
/
dev_shell.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
# nixos-22.11 channel
# Apr 9, 2023, 9:59 PM EDT
{ nixpkgs_commit ? "ea96b4af6148114421fda90df33cf236ff5ecf1d"
, project_dir ? builtins.toString ./..
, nix_shell_dir ? "${project_dir}/_nix-shell"
, gunicorn_dir ? "${nix_shell_dir}/gunicorn"
, django_dir ? "${nix_shell_dir}/django"
, nginx_dir ? "${nix_shell_dir}/nginx"
, deploy ? false
, debug ? false
, sops_file ? "${project_dir}/secrets/lynx_settings.sops.json"
, sp_kdbx ? "${project_dir}/secrets/sp.kdbx"
# NOTE Deployment environment: `dev` vs `prod` {{- {{-
#
# It affects
#
# 1. which credentials are used
#
# 2. `dev` enables `DEBUG` in the Django configuration
# }}- }}-
# Possible values: top-level keys of `sops_file`.
, deployment_environment ? "dev"
}:
# HOW TO CALL
# ===========
#
# nix-shell nix/dev_shell.nix
# nix-shell nix/dev_shell.nix --arg "deploy" "true" --arg "debug" "true"
# nix-shell nix/dev_shell.nix --arg "debug" "true" -v
# nix-shell nix/dev_shell.nix --argstr "deployment_environment" "dev" -v
# THE `_nix-shell` TEMPORARY WORKING DIRECTORY {{-
# ============================================
#
# Used to save runtime files (config, logs, pidfiles,
# etc.) during operation in one place instead of being
# scattered all over the system.
#
# Sub-directories are created in `shellHook`
# using `mkdir`, with the exception of
# `_nix-shell/postgres`, which is created using
# `pg_ctl initdb` (both the setup and clean-up shell
# commands are fetched remotely in this case).
# }}-
# NOTE / TODO Incorporate NGINX or not? {{-
#
# Still deliberating whether full deployment should be
# available from this script (i.e., setup -> gunicorn
# -> nginx).
#
# QUESTION Maybe just re-use the Nix shell expression
# by calling it from here using `exec
# nix-shell`?
#
# exec nix-shell -p vim --run "sleep 7"
#
# does exactly one would think it does so it
# seems to be a viable option.
#
# assert start_nginx ->
# }}-
let
pkgs = # {{-
import
# The downloaded archive will be (temporarily?) housed in the Nix store
# e.g., "/nix/store/gk9x7syd0ic6hjrf0fs6y4bsd16zgscg-source"
# (Try any of the `fetchTarball` commands below in `nix repl`, and it
# will print out the path.)
( builtins.fetchTarball nixpkgs_url) { config = {}; overlays = []; }
;
nixpkgs_url = "https://github.com/nixos/nixpkgs/tarball/${nixpkgs_commit}";
# }}-
in
pkgs.mkShell {
# ENVIRONMENT VARIABLE DECLARATIONS
DEPLOY_ENV = deployment_environment;
# TODO pin to specific version (and migrate to flakes)
buildInputs = with pkgs; [ # {{-
postgresql_15
just
openssl # to generate SECRET_KEY on each serving
# `sops` is needed to decrypt `secrets.json` and to
# export its contents as environment variables; the
# the rest is support (see README)
sops
jq
keepassxc
azure-cli
# EXTERNAL DEPENDENCIES FOR PYTHON PACKAGES
# TODO package Python packages and their deps with Nix
systemd
pkg-config
cairo
gobject-introspection
icu
python3
# debug
schemaspy
tmux
mtr # modern traceroute
# TODO Is there an alternative? busybox will also pull in `ps`, and it seems to be an inferior alterntive to the regular one.
# busybox # sed, tr, ...
]; # }}-
# TODO fail if not in the `slate-2` project directory
shellHook =
let
# NOTE Same as the snippets below, but might be {{- {{-
# more readable in certain cases.
#
# See also https://elixirforum.com/t/nix-the-package-manager/23231/3
#
# shellHook = shellHook =
# '' ''
# trap \ trap \
# " "
# '' echo lofa
# + '' sleep 2
# echo lofa echo miez
# sleep 2 " \
# echo miez EXIT
# '' ''
# + '' ;
# " \
# EXIT
# ''
# ;
# }}- }}-
cleanUp = # {{-
shell_commands:
''
trap \
"
${ builtins.concatStringsSep "" shell_commands }
" \
EXIT
''
;
# }}-
in
# NOTE Why the `debug` flag? {{-
# ---------------------
# Because it will output secrets stored in environment
# variables as well, thus it feels safer to make it
# optional.
# }}-
( if (debug)
then ''
set -x
''
else ""
)
# TODO groups & (system) users
#
# The goal is to have each participating service have
# their eponymous system users, and an extra user with
# `sudo` rights to kick things off:
#
# group: lynx_app
# ├── user: (privileged user; e.g., lynx, toraritte)
# ├── user: gunicorn
# ├── user: nginx
# └── user: postgres
#
# sudo usermod -a -G lynx_app <privileged-user>
# sudo usermod -a -G lynx_app gunicorn
# sudo usermod -a -G lynx_app nginx
# sudo usermod -a -G lynx_app postgres
#
# The permissions will be 750 (group: `lynx_app`;
# user: privileged user) throughout the project dir.
#
# sudo chown -R <privileged-user> <project_dir>
# sudo chgrp -R lynx_app <project_dir>
# sudo chmod -R 750 <project_dir>
#
# # Otherwise system users won't be able to create files...
# sudo find <project_dir> -type d -exec chmod 770 {} +
#
# Use `tree -pug` to check the ownerships and permissions.
# TODO 750 permissions: This might need some tweaking, as system users may have to write into files. (Although, the services started with their eponymous system users would create files using their own permissions, so this shouldn't be a problem, right? Also, static files should only need read permissions, given that they are, well, static.)
# TODO There should be a deployment script (or `shell.nix`) to clone the repo, set up group and users, etc.
# QUESTION: Will dir and file ownership be a problem? The plan is to have 750 applied recursively, where the owner system group will be `lynx_app` and the owner user will be the privileged user.
#
# TODO (note from previous commit; couldn't decide if it can be tossed)
# Create system users for nginx, gunicorn, postgresql, and login user (lynx), all of them with their own primary groupgs of the same name. There will be a secondary group (e.g., lynx-services) where these would also be included. The main directory for a lynx deployment will be "lynx-repo" with 750 permissions (owner: lynx, group: lynx-services - this means that service accounts (i.e., system users) will only have read permissions!). Specific service files will be owned the corresponding system user. (Once inside "lynx-repo", most dirs and files have 755/775 and 644, respectively, so this is not strictly necessary - especially in dev - but probably a good idea.)
#
+ ''
# https://stackoverflow.com/a/14811915/1498178
create_system_user_if_none() {
# Only needed to set exit code.
id "$1" &>/dev/null
if [ $? -ne 0 ]
then
sudo adduser --system --no-create-home --disabled-login --disabled-password --group "$1"
fi
}
''
# The privileged user will be created on VM / container creation
+ ''
# create_system_user_if_none postgres
# create_system_user_if_none gunicorn
# create_system_user_if_none nginx
create_system_group_if_none() {
# Only needed to set exit code.
getent group "$1" &>/dev/null
if [ $? -ne 0 ]
then
sudo groupadd --system "$1"
fi
}
# create_system_group_if_none lynx_app
''
+ ''
# Already done in `postgres/shell-hook.sh`, but it doesn't hurt
export NIX_SHELL_DIR="${nix_shell_dir}"
export GUNICORN_DIR="${gunicorn_dir}"
export DJANGO_DIR="${django_dir}"
export NGINX_DIR="${nginx_dir}"
mkdir -p $GUNICORN_DIR
mkdir -p $DJANGO_DIR
mkdir -p $NGINX_DIR
''
# NOTE Why not simply use a `just venv` recipe? {{-
# ----------------------------------------
# Because the first command creates a virtual Python
# environment, and the second one enters into it in a
# new sub-shell. `just` commands run in their own
# sub-shell so any other recipe that depends on this
# `venv` will fail.
#
# Plus, as noted above:
#
# > Also, the whole point of a `shell.nix` is to set up
# > (and tear down) a shell environment after all.
# }}-
+ ''
VENV_DIR="''${DJANGO_DIR}/venv"
python -m venv $VENV_DIR
source "''${VENV_DIR}/bin/activate"
''
# NOTE Why not make this a `just` recipe? {{-
# ----------------------------------
# Because recipes cannot be used in global variables
# and couldn't figure out how to define a shell
# function in a `just` variable that can be used in
# subsequent variable definitions.
#
# For the record, there was a `just s` recipe, but
# these would get evaluated every time, instead of
# simply once per just invocation (e.g., for the
# `add_schema` recipe this meant that both 'USER' and
# 'SCHEMA' would have been extracted 4 times each in
# one call.
# }}-
+ ''
get_db_settings () {
sops --decrypt ${sops_file} \
| jq -r ".[\"${deployment_environment}\"][\"DATABASE\"][\"$1\"]"
}
export -f get_db_settings
''
+ builtins.readFile ./postgres/shell-hook.sh
+ cleanUp
[
( builtins.readFile ./postgres/clean-up.sh )
]
# NOTE Why KeePassXC + SOPS? {{-
# ---------------------
# The dance with KeePassXC is needed beforehand as I
# was not able to use a Azure managed identity (MSI)
# with SOPS (see
# https://github.com/mozilla/sops/issues/1190
# ), so I used a dedicated Azure service principal
# instead - but that SOPS authentication method does
# require secrets in environment variables...
#
# See also https://stackoverflow.com/a/75972492/1498178
# NOTE Why didn't this get moved to Justfile?
# --------------------------------------
# Because the `get_database_setting <name>` recipe
# (more specifically, SOPS) in the Justfile depends
# on the environment variables exported from the
# KeePassXC database. See note "Why KeePassXC + SOPS?"
# above.
#
# `just`, just as `make`, executes each command in a
# new shell, therefore environment variables have to
# be set before hand if the recipes need them. This
# could have been set in the `Justfile`, but then
# running any recipe would probably nagged the user to
# unlock the KeePassXC database.
#
# Also, the whole point of a `shell.nix` is to set up
# (and tear down) a shell environment after all.
# }}-
+ ''
source <(keepassxc-cli attachment-export ${sp_kdbx} az_sp_creds export.sh --stdout)
''
+ ( if (deploy)
then ''
just
''
else ""
)
;
######################################################################
# Without this, almost everything fails with locale issues when #
# using `nix-shell --pure` (at least on NixOS). #
# See #
# + https://github.com/NixOS/nix/issues/318#issuecomment-52986702 #
# + http://lists.linuxfromscratch.org/pipermail/lfs-support/2004-June#023900.html
######################################################################
LOCALE_ARCHIVE =
if pkgs.stdenv.isLinux
then "${pkgs.glibcLocales}/lib/locale/locale-archive"
else ""
;
}
# vim: set foldmethod=marker foldmarker={{-,}}- foldlevelstart=0 tabstop=2 shiftwidth=2 expandtab: