1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 11:16:47 +00:00

feat(installer): idempotent secret-key, settings, perms and nginx primitives

This commit is contained in:
catborise 2026-10-07 15:01:59 +03:00
parent e84db7542d
commit 3e4f3f339c
2 changed files with 130 additions and 0 deletions

60
conf/install-lib.sh Executable file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Idempotent installer primitives. Sourcing this file has NO side effects;
# it only defines functions, so it can be unit-tested (conf/test_install_lib.sh).
# Create <data_dir>/secret_key (mode 600) only if missing or empty; keep an
# existing key. A blank key would make Django fail to boot, so an empty file is
# treated as missing.
wvc_ensure_secret_key() {
local data_dir="$1" python="${2:-python3}" key_file
key_file="$data_dir/secret_key"
mkdir -p "$data_dir"
chmod 700 "$data_dir"
if [ ! -s "$key_file" ]; then
# umask 077 so the key is never group/world readable, even briefly
( umask 077; "$python" -c 'import secrets; print(secrets.token_urlsafe(50))' > "$key_file" )
fi
chmod 600 "$key_file"
}
# Copy settings.py from the template only when it does not already exist, so a
# re-run never overwrites local edits. 0 + "created" on first copy; 1 + "kept"
# when it already exists.
wvc_ensure_settings() {
local template="$1" target="$2"
if [ -f "$target" ]; then
echo "kept"
return 1
fi
cp "$template" "$target"
echo "created"
return 0
}
# Restrict modes so the secret, settings, and db are not world-readable.
wvc_harden_modes() {
local settings="$1" db="$2" data_dir="$3"
[ -e "$settings" ] && chmod 600 "$settings"
[ -e "$db" ] && chmod 600 "$db"
[ -d "$data_dir" ] && chmod 700 "$data_dir"
[ -e "$data_dir/secret_key" ] && chmod 600 "$data_dir/secret_key"
return 0
}
# Install the nginx main config only on first run (keeping a one-time backup of
# whatever was there), and always refresh the app's conf.d snippet. A re-run
# therefore never clobbers an operator's edited main nginx.conf.
wvc_install_nginx() {
local main_template="$1" snippet="$2" etc="$3" marker
marker="$etc/.wvc-nginx-installed"
mkdir -p "$etc/conf.d"
# Use a dedicated marker (always created on first install) rather than the
# backup file, which is absent when the host had no nginx.conf to begin with.
if [ ! -f "$marker" ]; then
[ -f "$etc/nginx.conf" ] && cp "$etc/nginx.conf" "$etc/nginx.conf.wvc-orig"
cp "$main_template" "$etc/nginx.conf"
: > "$marker"
fi
cp "$snippet" "$etc/conf.d/webvirtcloud.conf"
return 0
}

70
conf/test_install_lib.sh Executable file
View file

@ -0,0 +1,70 @@
#!/usr/bin/env bash
# Unit tests for conf/install-lib.sh. Run: bash conf/test_install_lib.sh
set -u
here="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=/dev/null
. "$here/install-lib.sh"
fail=0
check() { if eval "$2"; then echo "ok - $1"; else echo "NOT ok - $1"; fail=1; fi; }
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
# wvc_ensure_secret_key: creates, keeps, mode 600, regenerates when empty
wvc_ensure_secret_key "$tmp/data"
check "secret key created" "[ -s '$tmp/data/secret_key' ]"
check "secret key mode 600" "[ \"\$(stat -c %a '$tmp/data/secret_key')\" = 600 ]"
first="$(cat "$tmp/data/secret_key")"
wvc_ensure_secret_key "$tmp/data"
check "existing key kept" "[ \"\$(cat '$tmp/data/secret_key')\" = \"$first\" ]"
: > "$tmp/data/secret_key" # truncate
wvc_ensure_secret_key "$tmp/data"
check "empty key regenerated" "[ -s '$tmp/data/secret_key' ]"
check "data dir is 700" "[ \"\$(stat -c %a '$tmp/data')\" = 700 ]"
# wvc_ensure_settings: creates once, keeps on re-run
echo "TEMPLATE" > "$tmp/tpl"
check "ensure_settings returns 0 on create" "wvc_ensure_settings '$tmp/tpl' '$tmp/settings.py'"
echo "EDITED" > "$tmp/settings.py"
check "ensure_settings returns 1 when present" "! wvc_ensure_settings '$tmp/tpl' '$tmp/settings.py'"
check "existing settings kept" "[ \"\$(cat '$tmp/settings.py')\" = EDITED ]"
# wvc_harden_modes
mkdir -p "$tmp/app/webvirtcloud" "$tmp/app/data"
echo x > "$tmp/app/webvirtcloud/settings.py"; echo x > "$tmp/app/db.sqlite3"
echo k > "$tmp/app/data/secret_key"
wvc_harden_modes "$tmp/app/webvirtcloud/settings.py" "$tmp/app/db.sqlite3" "$tmp/app/data"
check "settings 600" "[ \"\$(stat -c %a '$tmp/app/webvirtcloud/settings.py')\" = 600 ]"
check "db 600" "[ \"\$(stat -c %a '$tmp/app/db.sqlite3')\" = 600 ]"
check "data 700" "[ \"\$(stat -c %a '$tmp/app/data')\" = 700 ]"
check "secret 600" "[ \"\$(stat -c %a '$tmp/app/data/secret_key')\" = 600 ]"
# wvc_install_nginx: first run installs main + snippet, keeps backup; re-run keeps main
etc="$tmp/etc/nginx"; mkdir -p "$etc"
echo "ORIGINAL" > "$etc/nginx.conf"
echo "MAINTPL" > "$tmp/main_tpl"; echo "SNIPPET" > "$tmp/snippet"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc"
check "main replaced on first run" "[ \"\$(cat '$etc/nginx.conf')\" = MAINTPL ]"
check "original backed up" "[ \"\$(cat '$etc/nginx.conf.wvc-orig')\" = ORIGINAL ]"
check "snippet installed" "[ \"\$(cat '$etc/conf.d/webvirtcloud.conf')\" = SNIPPET ]"
echo "HANDEDITED" > "$etc/nginx.conf"
echo "SNIPPET2" > "$tmp/snippet"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc"
check "main NOT replaced on re-run" "[ \"\$(cat '$etc/nginx.conf')\" = HANDEDITED ]"
check "snippet refreshed on re-run" "[ \"\$(cat '$etc/conf.d/webvirtcloud.conf')\" = SNIPPET2 ]"
# fresh host: no existing main nginx.conf must not error
etc2="$tmp/etc2/nginx"; mkdir -p "$etc2"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc2"
check "fresh host installs main" "[ \"\$(cat '$etc2/nginx.conf')\" = MAINTPL ]"
# fresh host (no original config): a second run must NOT clobber operator edits
etc3="$tmp/etc3/nginx"; mkdir -p "$etc3"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc3"
echo "OP_EDIT" > "$etc3/nginx.conf"
wvc_install_nginx "$tmp/main_tpl" "$tmp/snippet" "$etc3"
check "no-orig second run keeps operator edit" "[ \"\$(cat '$etc3/nginx.conf')\" = OP_EDIT ]"
[ "$fail" = 0 ] && echo "ALL PASS" || echo "FAILURES"
exit "$fail"