forked from kclejeune/system
-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.sh
executable file
·337 lines (302 loc) · 8.44 KB
/
build.sh
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
#!/usr/bin/env bash
set -eu
###
# settings
# set cachix user
CACHIX_USER="bri"
DEFAULT_ARCH=x86_64
DEFAULT_TARGET=server
DEFAULT_FORMAT=raw-efi
DEFAULT_OS=linux
# too lazy to write something better rn
_THISFILE() {
printf %s/%s "${HOME}/system" 'build.sh'
}
# server to scp artifacts to
UPLOAD_SERVER=ci-upload.ibeep.com
UPLOAD_USER=ci-upload
# hashtable of destdirs
declare -A DESTDIRS=(
["proxmox"]="dump/"
["proxmox-lxc"]="template/cache/"
["raw-efi"]="images/"
["iso"]="template/iso/"
)
DEFAULT_TARGETS=(
"server"
"bri"
)
DEFAULT_FORMATS=(
"proxmox"
"proxmox-lxc"
"raw-efi"
# "iso"
)
# DEFAULT_EXCLUDES contains pairs of "TARGET.FORMAT" to exclude from builds
DEFAULT_EXCLUDES=(
"bri.proxmox-lxc"
"bri.proxmox"
"server.raw-efi"
)
# unused
# shellcheck disable=SC2034
declare -A EXTENSIONS=(
["raw-efi"]="img"
["proxmox"]="vma.zst"
["iso"]="iso"
)
# filename prefixes (only proxmox needs this)
_PREFIX() {
case "${FORMAT}" in
"proxmox")
local PREFIX="vzdump-qemu-"
;;
*)
local PREFIX="" # null
;;
esac
printf '%s' "${PREFIX}"
}
###
# Helper functions
# _STDERR (command_or_function)
# Runs (command_or_function) and redirects 1>&2
_STDERR() {
"${@}" 1>&2
}
# INVOCATION_DATE returns `(date -I)-(date +%H-%M)`
INVOCATION_DATE() {
printf %s "${INVOCATION_DATE-$(date -I)}"
}
# _NAME outputs a formatted filename for (_TARGET) (_ARCH) (_OS) (_FORMAT)
_NAME() {
printf 'build_image_%s@%s-%s_%s' \
"$(_TARGET)" \
"$(_ARCH)" \
"$(_OS)" \
"$(_FORMAT)" \
;
}
# _TARGET helper function to output the TARGET to build.
# Returns either $TARGET or $DEFAULT_TARGET
_TARGET() {
printf '%s' "${TARGET-${DEFAULT_TARGET}}"
}
# _ARCH helper function to output the ARCH to build.
# Returns either $ARCH or $DEFAULT_ARCH
_ARCH() {
printf '%s' "${ARCH-${DEFAULT_ARCH}}"
}
# _OS helper function to output the OS to build.
# Returns either $OS or $DEFAULT_OS
_OS() {
printf '%s' "${OS-${DEFAULT_OS}}"
}
# _FORMAT helper function to output the FORMAT to build.
# Returns either $FORMAT or $DEFAULT_format
_FORMAT() {
printf '%s' "${FORMAT-${DEFAULT_FORMAT}}"
}
# Usage: _println <message>
_println() {
printf "%s\n" "${*}"
}
# Usage: _die [exit code] <message>
_die() {
set +x
exit_code=254
if [[ ${1} =~ ^[0-9]+$ ]]; then
exit_code="${1}"
shift
fi
exit "${exit_code}"
}
# LISTs the main functions in this script
_LIST() {
KEEP_FUNCS='/() {/!d' # bug in bash lsp syntax highlighting
FMT_FUNCS='s/() {//'
LIST="$(sed <"$(_THISFILE)" -e '/^_/d' -e '/^ /d' -e "${KEEP_FUNCS}" -e "${FMT_FUNCS}")"
_println "Targets:"
_println "${LIST}" | sed -e's/^/ /'
}
# Installs cachix
INSTALL_CACHIX() { # INSTALL_CACHIX Installs cachix
nix profile install github:nixos/nixpkgs/nixpkgs-unstable#cachix --impure && cachix use $CACHIX_USER
}
WITH_CACHIX() { # Run a command with `cachix
DEFAULT_CACHIX_USER=bri
cachix watch-exec "${CACHIX_USER-${DEFAULT_CACHIX_USER}}" -- "${@}"
}
# BUILD_IMAGE
# Builds an image $TARGET@$ARCH-linux.$FORMAT
# Usage: BUILD_IMAGE
# Returns the built filename to stdout and redirects
# all other output to stderr.
BUILD_IMAGE() { # Build image $TARGET@$ARCH-linux.$FORMAT
IMAGE_BUILD_FLAGS=(-v --show-trace --accept-flake-config)
local NIX_BUILD_TARGET BUILT_FILE ARCH BASE_FILE HASH CUT_FILE
mkdir -p build
ARCH="$(_ARCH)"
# NIX_BUILD_TARGET e.g., `.#nixosConfigurations.server@x86_64-linux.config.formats.raw-efi`
NIX_BUILD_TARGET="$(
printf .#nixosConfigurations.%s@%s-%s.config.formats.%s \
"$(_TARGET)" \
"${ARCH//arm/aarch}" \
"$(_OS)" \
"$(_FORMAT)" \
;
)"
# $BUILT_FILE is the path to the build artifact inside /nix store.
# `nix build --print-out-paths` sends all build output to stderr and outputs only the built path(s) to stdout
if [[ -n ${DRY_RUN-} ]]; then
for _ in {1..10}; do
sleep 0.05s
_STDERR _println "Blah blah blah sample build output..."
done
BUILT_FILE="/tmp/x9s4kb8sip304fbggdbf9ibjqmdz9c6l-proxmox-nixos-24.05.20240211.f9d39fb.vma.zst"
touch "${BUILT_FILE}"
else
BUILT_FILE="$(nix build --print-out-paths "${NIX_BUILD_TARGET}" "${IMAGE_BUILD_FLAGS[@]}")"
fi
# BASE_FILE basename of $BUILT_FILE
BASE_FILE="$(basename "${BUILT_FILE}")"
# HASH first five bytes of $BASE_FILE
# e.g., `cHaRs`
HASH=$(cut -b-5 <<<"${BASE_FILE}")
# CUT_FILE the rest of $BASE_FILE after the full $HASH
CUT_FILE="$(cut -d- -f2- <<<"${BASE_FILE}")"
# OUTNAME the final filename of our linked build artifact
OUTNAME="build/$(_PREFIX)$(INVOCATION_DATE)-${HASH}_${TARGET}_${CUT_FILE}"
export OUTNAME
_STDERR ln -fvs "${BUILT_FILE}" "${OUTNAME}"
# return "${OUTNAME}"
printf %s "${OUTNAME}"
}
# SAVE_SSH_KEY
# Usage: SAVE_SSH_KEY [var]
# Saves the (base64-encoded) SSH private key from .env variable $UPLOAD_SSH_KEY_BASE64
SAVE_SSH_KEY() {
if [[ ${UPLOAD_SSH_KEY_BASE64-"NO_SSH_KEY"} == "NO_SSH_KEY" ]]; then
# if no upload key in the env or /tmp then
if [[ ! -f /tmp/ci-upload.key ]]; then
_die 253 "No upload key found."
fi
else
base64 <<<"${UPLOAD_SSH_KEY_BASE64// /$'\n'}" -d >/tmp/ci-upload.key
# ssh refuses to use a key with open permissions
chmod 600 /tmp/ci-upload.key
fi
}
# UPLOAD_ARTIFACT
# Usage: UPLOAD_ARTIFACT <file> [<file> [<file>]]...
# Saves SSH key and uploads <file>s via rsync
UPLOAD_ARTIFACT() {
SSH_OPTIONS=(
"-oIdentityFile=/tmp/ci-upload.key" # private key
"-oStrictHostKeyChecking=no" # Disable prompting on unknown host key TODO: add host public key instead
"-oPort=222"
"-oIdentitiesOnly=true" # Don't use any other private keys.
"-oPasswordAuthentication=no" # Don't attempt password authentication.
"-oUser=${UPLOAD_USER}" # Probably not necessary unless you use a bastion host
)
RSYNC_OPTIONS=(
#"-t" # timestamps
"-u" # update (skip newer files)
#"-v" # verbose
"-L" # traverse symlinks
"-z" # compress data during transfer
"--chmod=D2775,F664" "-p" # file modes
"-e" "ssh ${SSH_OPTIONS[*]}" # ssh flags
"--info=progress2" # show progress
)
[[ -n ${DRY_RUN-} ]] && RSYNC_OPTIONS+=("--dry-run")
rsync \
"${RSYNC_OPTIONS[@]}" \
"${@}" \
"${UPLOAD_USER}@${UPLOAD_SERVER}:${DESTDIRS["$(_FORMAT)"]}"
}
###
# META TARGETS
# CLEAN cleans built artifacts
# Usage: CLEAN
CLEAN() {
set -x
rm -fR build result
}
# BUILD_IMAGE_TASKS
# Usage: BUILD_IMAGE_TASKS
# Builds an image for $FORMAT and $TARGET
# runs (BUILD_IMAGE)
BUILD_IMAGE_TASKS() {
#INSTALL_CACHIX
#WITH_CACHIX BUILD_IMAGE
BUILD_IMAGE 2> >(tee "build_${INVOCATION_NAME}.log" >&2)
}
# UPLOAD_TASKS
# Usage: UPLOAD_TASKS
# runs (SAVE_SSH_KEY) and (UPLOAD_ARTIFACT)
UPLOAD_TASKS() {
SAVE_SSH_KEY
UPLOAD_ARTIFACT "${@}"
}
# BUILD_AND_UPLOAD
# Usage: BUILD_AND_UPLOAD
# runs (BUILD_IMAGE_TASKS) while saving output to "build_${INVOCATION_NAME}.log".
BUILD_AND_UPLOAD() {
INVOCATION_NAME="${BUILD_NAME-$(_TARGET).$(_FORMAT).$(INVOCATION_DATE)}"
# Build image, then set BUILT_ARTIFACT to built image filename
BUILT_ARTIFACT="$(
# and tee build log (from stderr) to build_${INVOCATION_NAME}.log
BUILD_IMAGE_TASKS
)"
# upload built image
cp "build_${INVOCATION_NAME}.log" "${BUILT_ARTIFACT}.log"
UPLOAD_TASKS "${BUILT_ARTIFACT}"{,.log} 2>&1 | tee "upload_${INVOCATION_NAME}.log"
}
# BUILD_MATRIX
# Runs (CLEAN) and (BUILD_AND_UPLOAD) for each combination of the arrays ${TARGETS[@]} and ${FORMATS[@]}.
# If ${TARGETS[@]} or ${FORMATS[@]} are unset, the ${DEFAULT_TARGETS[@]} and ${DEFAULT_FORMATS[@]} arrays are used instead.
BUILD_MATRIX() {
TARGETS=("${TARGETS[@]-${DEFAULT_TARGETS[@]}}")
FORMATS=("${FORMATS[@]-${DEFAULT_FORMATS[@]}}")
EXCLUDES=("${EXCLUDES[@]-${DEFAULT_EXCLUDES[@]}}")
for FORMAT in "${FORMATS[@]}"; do
for TARGET in "${TARGETS[@]}"; do
EXCLUDING=0
for EXCLUDE in "${EXCLUDES[@]}"; do
if [[ "${TARGET}.${FORMAT}" == "${EXCLUDE}" ]]; then
EXCLUDING=1
break
fi
done
if [[ ${EXCLUDING} -eq 0 ]]; then
# not excluding
if [[ -n ${NOCLEAN-} ]]; then
CLEAN
fi
BUILD_NAME="$(_TARGET).$(_FORMAT).$(INVOCATION_DATE)"
export BUILD_NAME
_println ""
_println " *** Starting build ${BUILD_NAME} ***"
time BUILD_AND_UPLOAD
_println " *** Finished build ${BUILD_NAME} ***"
else
_println " *** Skipping excluded TARGET.FORMAT ${TARGET}.${FORMAT} ***"
fi
done
done
_println " *** DONE! ***"
}
CI_BUILD() {
export INVOCATION_DATE
INVOCATION_DATE="$(INVOCATION_DATE)" # save the INVOCATION_DATE so we can upload it
time BUILD_MATRIX 2>&1 | tee "ci-build.${INVOCATION_DATE}.log"
}
_main() {
if [[ -z ${1-} ]]; then
_LIST
_die ''
fi
"${@}"
}
_main "${@}"