feat: extract .env validators to common.sh and add validate_env()

Move 10 validation functions from configure_env.sh to lib/common.sh as
shared utilities. Define variable-to-validator mapping using parallel
arrays (bash 3.2 compatible). validate_env() checks all ~50 .env
variables against their expected format and reports all failures at once.

Wired into preflight.sh (Check 6b) and bitwarden_to_env.sh (post-restore).
configure_env.sh now sources validators from common.sh instead of
defining its own copies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
S
2026-02-28 22:08:01 -05:00
parent 743f1281e6
commit 0e0aeda658
7 changed files with 192 additions and 45 deletions

View File

@@ -123,6 +123,167 @@ require_vars() {
done
}
# ---------------------------------------------------------------------------
# .env value validators — shared by configure_env.sh, preflight.sh,
# bitwarden_to_env.sh. Each returns 0 (valid) or 1 (invalid).
# ---------------------------------------------------------------------------
validate_ip() {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
}
validate_port() {
[[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -ge 1 ]] && [[ "$1" -le 65535 ]]
}
validate_email() {
[[ "$1" == *@* ]]
}
validate_path() {
[[ "$1" == /* ]]
}
validate_url() {
[[ "$1" =~ ^https?:// ]]
}
validate_bool() {
[[ "$1" == "true" ]] || [[ "$1" == "false" ]]
}
validate_integer() {
[[ "$1" =~ ^[0-9]+$ ]]
}
validate_nonempty() {
[[ -n "$1" ]]
}
validate_password() {
[[ ${#1} -ge 8 ]]
}
validate_ssl_mode() {
[[ "$1" == "letsencrypt" ]] || [[ "$1" == "existing" ]]
}
# ---------------------------------------------------------------------------
# .env format validation — validate_env()
# Checks every .env variable against its expected type.
# Requires .env to be loaded first (variables in environment).
# Returns 0 if all pass, 1 if any fail. Does NOT exit — caller decides.
# ---------------------------------------------------------------------------
# Parallel arrays: variable name → validator type (bash 3.2 compatible).
# Order matches configure_env.sh prompt_var calls.
_ENV_VAR_NAMES=(
UNRAID_IP UNRAID_SSH_USER UNRAID_SSH_PORT
UNRAID_GITEA_PORT UNRAID_GITEA_SSH_PORT UNRAID_GITEA_DATA_PATH
FEDORA_IP FEDORA_SSH_USER FEDORA_SSH_PORT
FEDORA_GITEA_PORT FEDORA_GITEA_SSH_PORT FEDORA_GITEA_DATA_PATH
GITEA_ADMIN_USER GITEA_ADMIN_PASSWORD GITEA_ADMIN_EMAIL
GITEA_ORG_NAME GITEA_INSTANCE_NAME GITEA_DB_TYPE GITEA_VERSION ACT_RUNNER_VERSION
GITEA_DOMAIN GITEA_INTERNAL_URL
GITEA_BACKUP_INTERNAL_URL GITEA_BACKUP_MIRROR_INTERVAL
BACKUP_STORAGE_PATH BACKUP_RETENTION_COUNT
GITHUB_USERNAME GITHUB_TOKEN
REPO_1_NAME REPO_2_NAME REPO_3_NAME
MIGRATE_ISSUES MIGRATE_LABELS MIGRATE_MILESTONES MIGRATE_WIKI
GITHUB_MIRROR_TOKEN GITHUB_MIRROR_INTERVAL
NGINX_CONTAINER_NAME NGINX_CONF_PATH SSL_MODE
PROTECTED_BRANCH REQUIRE_PR_REVIEW REQUIRED_APPROVALS
SEMGREP_VERSION TRIVY_VERSION GITLEAKS_VERSION SECURITY_FAIL_ON_ERROR
)
_ENV_VAR_TYPES=(
ip nonempty port
port port path
ip nonempty port
port port path
nonempty password email
nonempty nonempty nonempty nonempty nonempty
nonempty url
url nonempty
path integer
nonempty nonempty
nonempty nonempty nonempty
bool bool bool bool
nonempty nonempty
nonempty path ssl_mode
nonempty bool integer
nonempty nonempty nonempty bool
)
# Conditional variables — validated only when SSL_MODE matches.
_ENV_CONDITIONAL_NAMES=(SSL_EMAIL SSL_CERT_PATH SSL_KEY_PATH)
_ENV_CONDITIONAL_TYPES=(email path path)
_ENV_CONDITIONAL_WHEN=( letsencrypt existing existing)
# Human-readable format hints for error messages.
_validator_hint() {
case "$1" in
ip) echo "expected: x.x.x.x" ;;
port) echo "expected: 1-65535" ;;
email) echo "must contain @" ;;
path) echo "must start with /" ;;
url) echo "must start with http:// or https://" ;;
bool) echo "must be true or false" ;;
integer) echo "must be a number" ;;
nonempty) echo "cannot be empty" ;;
password) echo "must be at least 8 characters" ;;
ssl_mode) echo "must be letsencrypt or existing" ;;
*) echo "invalid" ;;
esac
}
validate_env() {
local errors=0
local i var_name var_type value
# Validate main variables
for ((i = 0; i < ${#_ENV_VAR_NAMES[@]}; i++)); do
var_name="${_ENV_VAR_NAMES[$i]}"
var_type="${_ENV_VAR_TYPES[$i]}"
value="${!var_name:-}"
if [[ -z "$value" ]]; then
log_error "$var_name is empty ($(_validator_hint "$var_type"))"
errors=$((errors + 1))
elif ! "validate_${var_type}" "$value"; then
log_error "$var_name='$value' ($(_validator_hint "$var_type"))"
errors=$((errors + 1))
fi
done
# Validate conditional variables (SSL_MODE-dependent)
local ssl_mode="${SSL_MODE:-}"
for ((i = 0; i < ${#_ENV_CONDITIONAL_NAMES[@]}; i++)); do
var_name="${_ENV_CONDITIONAL_NAMES[$i]}"
var_type="${_ENV_CONDITIONAL_TYPES[$i]}"
local required_when="${_ENV_CONDITIONAL_WHEN[$i]}"
if [[ "$ssl_mode" != "$required_when" ]]; then
continue
fi
value="${!var_name:-}"
if [[ -z "$value" ]]; then
log_error "$var_name is empty (required when SSL_MODE=$required_when, $(_validator_hint "$var_type"))"
errors=$((errors + 1))
elif ! "validate_${var_type}" "$value"; then
log_error "$var_name='$value' ($(_validator_hint "$var_type"))"
errors=$((errors + 1))
fi
done
if [[ $errors -gt 0 ]]; then
log_error "$errors .env variable(s) failed format validation"
return 1
fi
return 0
}
# ---------------------------------------------------------------------------
# OS compatibility checks
# ---------------------------------------------------------------------------