mirror of
https://github.com/soulis-1256/assemblrr.git
synced 2026-10-19 09:13:33 +00:00
Use .env.example as the env template and cover it with tests
Generate INSTALL_DIR/.env from .env.example via envsubst, drop templates/envsubst.env, and add a unit test that keeps the template keys aligned with setup. Tighten related docs and test helpers.
This commit is contained in:
parent
3ddedb4d15
commit
1fd188a5c4
12 changed files with 167 additions and 148 deletions
52
.env.example
52
.env.example
|
|
@ -1,36 +1,32 @@
|
|||
# Assemblrr Environment Configuration
|
||||
# This file documents all available environment variables.
|
||||
# Actual values are generated by setup.sh using envsubst.
|
||||
# Generated into INSTALL_DIR/.env by setup (envsubst).
|
||||
# Credentials go under secrets/, not here.
|
||||
|
||||
# User/Group IDs — must match the user that owns the media files
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
# Host user/group that owns media files
|
||||
PUID=${PUID}
|
||||
PGID=${PGID}
|
||||
|
||||
# Paths (defaults are home-based; set during setup)
|
||||
MEDIA_DIRECTORY=/home/<user>/assemblrr-media
|
||||
INSTALL_DIRECTORY=/home/<user>/assemblrr
|
||||
MEDIA_DIRECTORY=${MEDIA_DIRECTORY}
|
||||
INSTALL_DIRECTORY=${INSTALL_DIRECTORY}
|
||||
|
||||
# Media service: jellyfin, emby, or plex
|
||||
MEDIA_SERVICE=jellyfin
|
||||
# jellyfin | emby | plex
|
||||
MEDIA_SERVICE=${MEDIA_SERVICE}
|
||||
|
||||
# Timezone (IANA format)
|
||||
TZ=UTC
|
||||
# IANA timezone (e.g. Europe/Athens)
|
||||
TZ=${TZ}
|
||||
|
||||
# VPN Configuration
|
||||
VPN_ENABLED=n
|
||||
VPN_SERVICE=protonvpn
|
||||
VPN_TYPE=openvpn
|
||||
# y/n — secrets under secrets/ when enabled
|
||||
VPN_ENABLED=${VPN_ENABLED}
|
||||
# Gluetun provider id (e.g. protonvpn)
|
||||
VPN_SERVICE=${VPN_SERVICE}
|
||||
# openvpn | wireguard
|
||||
VPN_TYPE=${VPN_TYPE}
|
||||
|
||||
# OpenVPN credentials are stored in secrets/openvpn_user.txt and secrets/openvpn_password.txt
|
||||
# WireGuard private key is stored in secrets/wireguard_private_key.txt
|
||||
# These are NOT in .env — they are mounted as Docker secrets so docker inspect won't expose them.
|
||||
# WireGuard only (not secret)
|
||||
WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
||||
|
||||
# WireGuard addresses (not secret, just an internal IP)
|
||||
WIREGUARD_ADDRESSES=
|
||||
# on/off — only if your provider supports it
|
||||
PORT_FORWARD_ONLY=${PORT_FORWARD_ONLY}
|
||||
VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING}
|
||||
|
||||
# Port forwarding (only supported by some VPN providers)
|
||||
PORT_FORWARD_ONLY=off
|
||||
VPN_PORT_FORWARDING=off
|
||||
|
||||
# API Host for configuration script (advanced users)
|
||||
API_HOST=127.0.0.1
|
||||
# configure.sh API host
|
||||
API_HOST=${API_HOST}
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -1,4 +1,3 @@
|
|||
# Assemblrr developer targets
|
||||
.PHONY: test test-unit test-compose test-integration lint help
|
||||
|
||||
help:
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -32,7 +32,7 @@ A lightweight media server stack via Docker. Cross-platform, VPN-aware, and buil
|
|||
- **Subtitles:** Bazarr
|
||||
- **Update Notifications:** Watchtower
|
||||
|
||||
These are deployed only — no auto-configuration like the core stack. Wire them up in each service’s UI after enabling.
|
||||
Deployed only — not auto-configured. Set them up in each service’s UI.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -90,10 +90,11 @@ See [docs/uninstall.md](docs/uninstall.md) for the complete removal guide, inclu
|
|||
|
||||
Configuration lives in your installation directory (default: `~/assemblrr`).
|
||||
|
||||
- `.env`: Environment overrides (ASSEMBLRR_HOST for remote proxying, PUID/PGID, API variables).
|
||||
- `compose/base.yaml`: Immutable core services.
|
||||
- `compose/vpn.yaml`: Gluetun routing overlays.
|
||||
- `config/`: Persistent data safe from container teardowns.
|
||||
- `.env`: Generated at install from [`.env.example`](.env.example) (all variables are documented there). Credentials live under `secrets/`, not in `.env`.
|
||||
- `compose/base.yaml`: Core services.
|
||||
- `compose/vpn.yaml` / `compose/direct-access.yaml`: Networking overlays.
|
||||
- `compose/custom.yaml`: Optional services (from the example under `compose/examples/`).
|
||||
- `config/`: Persistent service data.
|
||||
|
||||
## Development
|
||||
|
||||
|
|
|
|||
11
bin/setup.sh
11
bin/setup.sh
|
|
@ -238,7 +238,7 @@ copy_configuration_files() {
|
|||
["compose/direct-access.yaml"]="compose/direct-access.yaml"
|
||||
["compose/vpn.yaml"]="compose/vpn.yaml"
|
||||
["compose/examples/custom.yaml.example"]="compose/examples/custom.yaml.example"
|
||||
["templates/envsubst.env"]="templates/envsubst.env"
|
||||
[".env.example"]=".env.example"
|
||||
["templates/recyclarr-ultra_hd.yml"]="templates/recyclarr-ultra_hd.yml"
|
||||
["templates/recyclarr-full_hd.yml"]="templates/recyclarr-full_hd.yml"
|
||||
["branding.conf"]="branding.conf"
|
||||
|
|
@ -322,8 +322,13 @@ generate_env_file() {
|
|||
export PORT_FORWARD_ONLY VPN_PORT_FORWARDING
|
||||
export API_HOST
|
||||
|
||||
# Generate .env from template using envsubst
|
||||
envsubst < "$APP_ROOT/templates/envsubst.env" > "$env_file" || \
|
||||
local env_template
|
||||
if ! env_template=$(resolve_project_file ".env.example"); then
|
||||
log_error "Failed to locate .env.example"
|
||||
fi
|
||||
# Explicit key list so envsubst does not pull in unrelated shell variables
|
||||
envsubst '${PUID} ${PGID} ${MEDIA_DIRECTORY} ${INSTALL_DIRECTORY} ${MEDIA_SERVICE} ${TZ} ${VPN_ENABLED} ${VPN_SERVICE} ${VPN_TYPE} ${WIREGUARD_ADDRESSES} ${PORT_FORWARD_ONLY} ${VPN_PORT_FORWARDING} ${API_HOST}' \
|
||||
< "$env_template" > "$env_file" || \
|
||||
log_error "Failed to generate .env file"
|
||||
|
||||
chmod 600 "$env_file"
|
||||
|
|
|
|||
|
|
@ -249,7 +249,8 @@ cp /tmp/$AppName/lib/*.sh "`$INSTALL_DIR/lib/"
|
|||
cp /tmp/$AppName/compose/base.yaml /tmp/$AppName/compose/vpn.yaml /tmp/$AppName/compose/direct-access.yaml "`$INSTALL_DIR/compose/"
|
||||
mkdir -p "`$INSTALL_DIR/compose/examples"
|
||||
cp /tmp/$AppName/compose/examples/custom.yaml.example "`$INSTALL_DIR/compose/examples/" 2>/dev/null || true
|
||||
cp /tmp/$AppName/templates/envsubst.env /tmp/$AppName/templates/recyclarr-full_hd.yml /tmp/$AppName/templates/recyclarr-ultra_hd.yml "`$INSTALL_DIR/templates/"
|
||||
cp /tmp/$AppName/.env.example "`$INSTALL_DIR/" 2>/dev/null || true
|
||||
cp /tmp/$AppName/templates/recyclarr-full_hd.yml /tmp/$AppName/templates/recyclarr-ultra_hd.yml "`$INSTALL_DIR/templates/"
|
||||
mkdir -p "`$INSTALL_DIR/scripts"
|
||||
cp /tmp/$AppName/scripts/* "`$INSTALL_DIR/scripts/"
|
||||
echo "Files synced to install directory"
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
# Base configuration
|
||||
PUID=${PUID}
|
||||
PGID=${PGID}
|
||||
MEDIA_DIRECTORY=${MEDIA_DIRECTORY}
|
||||
INSTALL_DIRECTORY=${INSTALL_DIRECTORY}
|
||||
MEDIA_SERVICE=${MEDIA_SERVICE}
|
||||
TZ=${TZ}
|
||||
|
||||
# VPN configuration
|
||||
VPN_ENABLED=${VPN_ENABLED}
|
||||
VPN_SERVICE=${VPN_SERVICE}
|
||||
VPN_TYPE=${VPN_TYPE}
|
||||
WIREGUARD_ADDRESSES=${WIREGUARD_ADDRESSES}
|
||||
PORT_FORWARD_ONLY=${PORT_FORWARD_ONLY}
|
||||
VPN_PORT_FORWARDING=${VPN_PORT_FORWARDING}
|
||||
|
||||
# API Host for configuration script
|
||||
API_HOST=${API_HOST}
|
||||
|
||||
|
|
@ -1,55 +1,20 @@
|
|||
# Tests
|
||||
|
||||
Runtime scripts live in `scripts/` (installed with the stack). **All tests live here.**
|
||||
|
||||
## Layout
|
||||
|
||||
```text
|
||||
tests/
|
||||
helpers.sh # assertions, install discovery, qBittorrent helpers
|
||||
run.sh # entrypoint
|
||||
unit/
|
||||
test_compose.sh # lib/compose.sh
|
||||
test_core.sh # expand_path, safe_source, rm guards
|
||||
test_vpn_secrets.sh # validate_vpn_secrets
|
||||
test_compose_config.sh # docker compose config (needs Docker)
|
||||
integration/
|
||||
watchdog_stall.sh # live qBittorrent + vpn-watchdog (opt-in)
|
||||
```
|
||||
|
||||
## Quick start
|
||||
Runtime hooks stay in `scripts/`. Tests live here.
|
||||
|
||||
```bash
|
||||
# From repo root
|
||||
./tests/run.sh # unit tests (no Docker required)
|
||||
./tests/run.sh compose # docker compose config validation
|
||||
./tests/run.sh # unit
|
||||
./tests/run.sh compose # docker compose config
|
||||
./tests/run.sh all # unit + compose + shellcheck (if installed)
|
||||
make test # same as ./tests/run.sh all
|
||||
make test
|
||||
```
|
||||
|
||||
## Live integration (watchdog)
|
||||
## Live integration
|
||||
|
||||
Mutates a **running** qBittorrent (sets `max_connecs=0`, adds a public Ubuntu magnet, runs `vpn-watchdog.sh`). Always restores settings on exit.
|
||||
Hits a running qBittorrent (stalls connections, runs `vpn-watchdog`). Opt-in only:
|
||||
|
||||
```bash
|
||||
ASSEMBLRR_ALLOW_LIVE_TEST=1 ./tests/run.sh integration
|
||||
|
||||
# Or point at a non-default install:
|
||||
ASSEMBLRR_DIR=~/assemblrr ASSEMBLRR_ALLOW_LIVE_TEST=1 \
|
||||
./tests/integration/watchdog_stall.sh
|
||||
```
|
||||
|
||||
Without `ASSEMBLRR_ALLOW_LIVE_TEST=1` the script exits with code 2 and does nothing.
|
||||
|
||||
Config resolution order:
|
||||
|
||||
1. `ASSEMBLRR_DIR`
|
||||
2. `ASSEMBLRR_CONFIG` (path to `.assemblrr-config`)
|
||||
3. `find_install_directory` from `lib/core.sh`
|
||||
4. `$HOME/assemblrr`
|
||||
|
||||
## Notes
|
||||
|
||||
- Unit tests use a tiny bash assertion helper (no BATS dependency).
|
||||
- Compose checks create a temporary fixture with dummy secret files so `docker compose config` succeeds.
|
||||
- Install [shellcheck](https://www.shellcheck.net/) for `./tests/run.sh lint`.
|
||||
Install dir: `ASSEMBLRR_DIR`, `ASSEMBLRR_CONFIG`, then `~/assemblrr`.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
#!/bin/bash
|
||||
# Shared helpers for Assemblrr unit and integration tests.
|
||||
# shellcheck shell=bash
|
||||
|
||||
# Repo root (parent of tests/)
|
||||
TESTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$TESTS_DIR/.." && pwd)"
|
||||
|
||||
# --- Assertion helpers ---
|
||||
|
||||
_TEST_FAILURES=0
|
||||
_TEST_PASSES=0
|
||||
_TEST_CURRENT=""
|
||||
|
|
@ -124,8 +120,7 @@ assert_false() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# Soft log_error for unit tests — core.sh's log_error exits the process.
|
||||
# Call after sourcing lib/core.sh so we can exercise guard paths.
|
||||
# core.sh log_error exits; unit tests need a non-fatal stub
|
||||
stub_log_error_no_exit() {
|
||||
log_error() {
|
||||
echo -e "${RED:-}$1${NC:-}" >&2
|
||||
|
|
@ -145,10 +140,6 @@ test_summary() {
|
|||
return 0
|
||||
}
|
||||
|
||||
# --- Install / config discovery (integration) ---
|
||||
|
||||
# Resolve Assemblrr install directory.
|
||||
# Priority: ASSEMBLRR_DIR, ASSEMBLRR_CONFIG parent, find_install_directory, ~/assemblrr
|
||||
resolve_install_dir() {
|
||||
if [ -n "${ASSEMBLRR_DIR:-}" ]; then
|
||||
echo "$ASSEMBLRR_DIR"
|
||||
|
|
@ -195,8 +186,6 @@ load_assemblrr_config() {
|
|||
source "$config_file"
|
||||
}
|
||||
|
||||
# --- qBittorrent API helpers (integration) ---
|
||||
|
||||
QBIT_HOST="${QBIT_HOST:-127.0.0.1:8081}"
|
||||
QBIT_API="http://${QBIT_HOST}/api/v2"
|
||||
|
||||
|
|
@ -219,7 +208,6 @@ qbit_api() {
|
|||
curl -s -b "$cookie_jar" -H "Referer: http://${QBIT_HOST}" "$@"
|
||||
}
|
||||
|
||||
# Create a temporary install-like tree for compose validation.
|
||||
make_compose_fixture() {
|
||||
local dest="$1"
|
||||
mkdir -p "$dest/compose" "$dest/config" "$dest/scripts" "$dest/secrets" "$dest/media"
|
||||
|
|
@ -228,7 +216,7 @@ make_compose_fixture() {
|
|||
mkdir -p "$dest/compose/examples"
|
||||
cp "$REPO_ROOT/compose/examples/"* "$dest/compose/examples/" 2>/dev/null || true
|
||||
fi
|
||||
# Secret files must exist for `docker compose config` with vpn overlay
|
||||
# vpn overlay expects these files to exist
|
||||
: >"$dest/secrets/openvpn_user.txt"
|
||||
: >"$dest/secrets/openvpn_password.txt"
|
||||
: >"$dest/secrets/wireguard_private_key.txt"
|
||||
|
|
|
|||
|
|
@ -1,20 +1,13 @@
|
|||
#!/bin/bash
|
||||
# Live integration test: stall qBittorrent connections and verify vpn-watchdog reacts.
|
||||
#
|
||||
# SAFETY: mutates a running qBittorrent instance (max connections → 0, test torrent).
|
||||
# Requires ASSEMBLRR_ALLOW_LIVE_TEST=1 and a configured Assemblrr install.
|
||||
#
|
||||
# Usage:
|
||||
# ASSEMBLRR_ALLOW_LIVE_TEST=1 ./tests/integration/watchdog_stall.sh
|
||||
# ASSEMBLRR_DIR=~/assemblrr ASSEMBLRR_ALLOW_LIVE_TEST=1 ./tests/integration/watchdog_stall.sh
|
||||
#
|
||||
# Live test: stall qBittorrent and check vpn-watchdog.
|
||||
# Mutates a real install — requires ASSEMBLRR_ALLOW_LIVE_TEST=1.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$SCRIPT_DIR/../helpers.sh"
|
||||
|
||||
# Ubuntu 22.04.4 desktop ISO infohash + public trackers (no content hosted by us)
|
||||
# Ubuntu 22.04 desktop infohash + public trackers
|
||||
TARGET_HASH="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
|
||||
MAGNET="magnet:?xt=urn:btih:${TARGET_HASH}&dn=ubuntu-22.04.4-desktop-amd64.iso&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A6969%2Fannounce"
|
||||
|
||||
|
|
@ -46,7 +39,6 @@ EOF
|
|||
fi
|
||||
}
|
||||
|
||||
# Fail fast before any trap/cleanup noise
|
||||
require_live_opt_in
|
||||
|
||||
cleanup() {
|
||||
|
|
@ -78,7 +70,6 @@ resolve_watchdog_script() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Watchdog in containers reads /run/secrets; for host runs, stage secrets or env.
|
||||
prepare_watchdog_env() {
|
||||
local install_dir="$1"
|
||||
local secrets="$install_dir/secrets"
|
||||
|
|
@ -87,7 +78,6 @@ prepare_watchdog_env() {
|
|||
export AUTH_PASSWORD_FILE="$secrets/auth_password.txt"
|
||||
return 0
|
||||
fi
|
||||
# Fallback: materialize from config credentials into temp secret files
|
||||
if [ -n "${AUTH_USERNAME:-}" ] && [ -n "${AUTH_PASSWORD:-}" ]; then
|
||||
local staged
|
||||
staged=$(mktemp -d)
|
||||
|
|
@ -117,7 +107,6 @@ install_dir=$(resolve_install_dir) || die "Could not resolve install directory.
|
|||
load_assemblrr_config "$install_dir" || die "Could not load config from $install_dir"
|
||||
|
||||
if [ -z "${AUTH_USERNAME:-}" ] || [ -z "${AUTH_PASSWORD:-}" ]; then
|
||||
# Try secret files
|
||||
if [ -f "$install_dir/secrets/auth_username.txt" ]; then
|
||||
AUTH_USERNAME=$(cat "$install_dir/secrets/auth_username.txt")
|
||||
AUTH_PASSWORD=$(cat "$install_dir/secrets/auth_password.txt")
|
||||
|
|
@ -152,7 +141,6 @@ echo " $(torrent_summary || echo 'torrent not listed yet')"
|
|||
echo "[4] Sabotaging max_connecs=0..."
|
||||
qbit_api "$COOKIE_JAR" -d 'json={"max_connecs": 0}' "${QBIT_API}/app/setPreferences" >/dev/null
|
||||
|
||||
# Bounce torrent to drop existing peer connections quickly
|
||||
qbit_api "$COOKIE_JAR" -d "hashes=${TARGET_HASH}" "${QBIT_API}/torrents/stop" >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
qbit_api "$COOKIE_JAR" -d "hashes=${TARGET_HASH}" "${QBIT_API}/torrents/start" >/dev/null 2>&1 || true
|
||||
|
|
@ -176,19 +164,15 @@ failures=0
|
|||
if [ "$watchdog_rc" -ne 0 ]; then
|
||||
echo " PASS: watchdog exited non-zero (rc=$watchdog_rc) — expected under stall"
|
||||
else
|
||||
# Watchdog only restarts gluetun when specific tracker-failure symptoms match.
|
||||
# If symptoms are not severe enough yet, surface a soft failure with diagnostics.
|
||||
if echo "$watchdog_out" | grep -qiE 'DETECTED|routing failure|VPN routing'; then
|
||||
echo " PASS: watchdog reported routing failure"
|
||||
else
|
||||
echo " FAIL: watchdog exited 0 without a detection message"
|
||||
echo " Symptoms may not have matched (needs has_metadata=false, 0 connections,"
|
||||
echo " and ≥3 failed trackers with Operation not permitted / timed out / Host not found)."
|
||||
echo " (needs stalled torrent + failed trackers — see vpn-watchdog.sh)"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# API still reachable after watchdog
|
||||
if qbit_api "$COOKIE_JAR" "${QBIT_API}/app/version" >/dev/null 2>&1 \
|
||||
|| qbit_login "$COOKIE_JAR" "$AUTH_USERNAME" "$AUTH_PASSWORD"; then
|
||||
echo " PASS: qBittorrent API still reachable after watchdog"
|
||||
|
|
|
|||
14
tests/run.sh
14
tests/run.sh
|
|
@ -1,14 +1,5 @@
|
|||
#!/bin/bash
|
||||
# Assemblrr test runner
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/run.sh # unit tests only
|
||||
# ./tests/run.sh unit
|
||||
# ./tests/run.sh compose # docker compose config checks
|
||||
# ./tests/run.sh integration # live stack (requires ASSEMBLRR_ALLOW_LIVE_TEST=1)
|
||||
# ./tests/run.sh all # unit + compose (not live integration)
|
||||
# ./tests/run.sh lint # shellcheck if installed
|
||||
#
|
||||
# Usage: ./tests/run.sh [unit|compose|integration|lint|all]
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
|
@ -44,10 +35,10 @@ run_scripts() {
|
|||
|
||||
run_unit() {
|
||||
echo "Running unit tests..."
|
||||
# Pure unit tests first (no docker required)
|
||||
for f in \
|
||||
"$SCRIPT_DIR/unit/test_compose.sh" \
|
||||
"$SCRIPT_DIR/unit/test_core.sh" \
|
||||
"$SCRIPT_DIR/unit/test_env_example.sh" \
|
||||
"$SCRIPT_DIR/unit/test_vpn_secrets.sh"
|
||||
do
|
||||
echo ""
|
||||
|
|
@ -101,7 +92,6 @@ run_lint() {
|
|||
return 0
|
||||
fi
|
||||
|
||||
# SC1091: sourced paths resolved at runtime; SC2034: libs export for callers
|
||||
if shellcheck -x -e SC1091,SC2034 "${files[@]}"; then
|
||||
echo "shellcheck: OK"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -44,10 +44,8 @@ run_config "base + vpn" \
|
|||
-f "$tmp/compose/base.yaml" \
|
||||
-f "$tmp/compose/vpn.yaml"
|
||||
|
||||
# examples/custom.yaml.example is documentation (all services commented), so
|
||||
# `services:` is empty and invalid for compose. Test a minimal real overlay instead.
|
||||
# example custom.yaml has only commented services; use a valid empty overlay
|
||||
cat >"$tmp/compose/custom.yaml" <<'EOF'
|
||||
# Minimal custom overlay for config validation
|
||||
services: {}
|
||||
EOF
|
||||
run_config "base + direct-access + custom overlay" \
|
||||
|
|
|
|||
111
tests/unit/test_env_example.sh
Executable file
111
tests/unit/test_env_example.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
#!/bin/bash
|
||||
# .env.example stays aligned with setup's envsubst key list and actually substitutes
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck disable=SC1091
|
||||
source "$SCRIPT_DIR/../helpers.sh"
|
||||
|
||||
example="$REPO_ROOT/.env.example"
|
||||
setup="$REPO_ROOT/bin/setup.sh"
|
||||
|
||||
test_suite ".env.example exists"
|
||||
assert_true "repo has .env.example" "[ -f \"$example\" ]"
|
||||
assert_true "setup.sh exists" "[ -f \"$setup\" ]"
|
||||
|
||||
mapfile -t template_keys < <(
|
||||
grep -E '^[A-Za-z_][A-Za-z0-9_]*=\$\{[A-Za-z_][A-Za-z0-9_]*\}$' "$example" \
|
||||
| sed 's/=.*//' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
test_suite "template has expected keys"
|
||||
if [ "${#template_keys[@]}" -gt 0 ]; then
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: found ${#template_keys[@]} KEY=\${KEY} lines"
|
||||
else
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: no KEY=\${KEY} lines in .env.example"
|
||||
fi
|
||||
|
||||
# First envsubst '...' argument in setup (literal key list)
|
||||
envsubst_arg=$(
|
||||
grep -E "envsubst '" "$setup" | head -1 \
|
||||
| sed -n "s/.*envsubst '\\([^']*\\)'.*/\\1/p"
|
||||
)
|
||||
|
||||
test_suite "setup envsubst list"
|
||||
if [ -n "$envsubst_arg" ]; then
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: found envsubst key list in setup.sh"
|
||||
else
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: no envsubst '...' list in setup.sh"
|
||||
test_summary
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t envsubst_keys < <(
|
||||
# Parse ${NAME} tokens without expanding them
|
||||
printf '%s\n' "$envsubst_arg" \
|
||||
| grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' \
|
||||
| sed 's/^\${//;s/}$//' \
|
||||
| sort -u
|
||||
)
|
||||
|
||||
test_suite "template keys match envsubst list"
|
||||
for key in "${template_keys[@]}"; do
|
||||
if printf '%s\n' "${envsubst_keys[@]}" | grep -qx "$key"; then
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: $key in envsubst list"
|
||||
else
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: $key missing from setup envsubst list"
|
||||
fi
|
||||
done
|
||||
|
||||
for key in "${envsubst_keys[@]}"; do
|
||||
if printf '%s\n' "${template_keys[@]}" | grep -qx "$key"; then
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: $key present in .env.example"
|
||||
else
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: envsubst lists $key but .env.example has no matching assignment"
|
||||
fi
|
||||
done
|
||||
|
||||
test_suite "envsubst leaves no placeholders"
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
|
||||
export PUID=1000 PGID=1000 \
|
||||
MEDIA_DIRECTORY=/data/media INSTALL_DIRECTORY=/opt/assemblrr \
|
||||
MEDIA_SERVICE=jellyfin TZ=UTC \
|
||||
VPN_ENABLED=n VPN_SERVICE= VPN_TYPE=openvpn \
|
||||
WIREGUARD_ADDRESSES= \
|
||||
PORT_FORWARD_ONLY=off VPN_PORT_FORWARDING=off \
|
||||
API_HOST=127.0.0.1
|
||||
|
||||
envsubst "$envsubst_arg" < "$example" > "$tmp"
|
||||
|
||||
if grep -E '\$\{[A-Za-z_][A-Za-z0-9_]*\}' "$tmp" >/dev/null; then
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: unsubstituted placeholders remain:"
|
||||
grep -E '\$\{[A-Za-z_][A-Za-z0-9_]*\}' "$tmp" | sed 's/^/ /' || true
|
||||
else
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: no leftover placeholders"
|
||||
fi
|
||||
|
||||
for key in PUID INSTALL_DIRECTORY MEDIA_SERVICE API_HOST; do
|
||||
val=$(grep -E "^${key}=" "$tmp" | head -1 | cut -d= -f2- || true)
|
||||
if [ -n "$val" ]; then
|
||||
_TEST_PASSES=$((_TEST_PASSES + 1))
|
||||
echo " PASS: $key set ($val)"
|
||||
else
|
||||
_TEST_FAILURES=$((_TEST_FAILURES + 1))
|
||||
echo " FAIL: $key empty or missing in generated .env"
|
||||
fi
|
||||
done
|
||||
|
||||
test_summary
|
||||
Loading…
Add table
Add a link
Reference in a new issue