Fix: Resolve password hash corruption in Authelia users_database.yml

Critical fix for argon2 password hash preservation:
- Root cause: Bash variable expansion of $ characters in argon2id hashes
- Solution: Write hash directly from Docker output to file, bypass bash variables entirely
- setup-homelab.sh: Stream Docker output directly to /tmp/authelia_password_hash.tmp
- deploy-homelab.sh: Read hash file in Python to avoid any bash expansion
- Result: Password hash correctly preserved with full $argon2id$v=19$m=... format

Other changes:
- Added DOCKER_API_VERSION=1.44 env var for watchtower (API compatibility)
- Watchtower still has issues with Docker 29.1.4 - keeping version pinned for investigation

Tested on Debian 12 with Docker 29.1.4:
 All 11 critical containers healthy
 Authelia authentication working correctly
 Password hash preserved through entire deployment workflow
⚠️  Watchtower restart loop (non-critical, under investigation)
This commit is contained in:
2026-01-13 21:02:49 -05:00
parent 659d580d14
commit cf061f35d2
4 changed files with 71 additions and 50 deletions

View File

@@ -179,11 +179,11 @@ log_info "Configuring Authelia for domain: $DOMAIN..."
sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml
# Configure Authelia admin user from setup script
if [ -f /tmp/authelia_admin_credentials.tmp ]; then
if [ -f /tmp/authelia_admin_credentials.tmp ] && [ -f /tmp/authelia_password_hash.tmp ]; then
log_info "Loading Authelia admin credentials from setup script..."
source /tmp/authelia_admin_credentials.tmp
if [ -n "$PASSWORD_HASH" ] && [ -n "$ADMIN_USER" ] && [ -n "$ADMIN_EMAIL" ]; then
if [ -n "$ADMIN_USER" ] && [ -n "$ADMIN_EMAIL" ]; then
log_success "Using credentials: $ADMIN_USER ($ADMIN_EMAIL)"
# Create users_database.yml with credentials from setup
@@ -203,13 +203,36 @@ users:
- users
EOF
# Now safely replace placeholders
# Escape special characters in PASSWORD_HASH for sed
ESCAPED_HASH=$(printf '%s\n' "$PASSWORD_HASH" | sed 's:[\/&]:\\&:g;$!s/$/\\/')
ESCAPED_HASH="${ESCAPED_HASH%\\}"
sed -i "s/ADMIN_USER_PLACEHOLDER/${ADMIN_USER}/g" /opt/stacks/core/authelia/users_database.yml
sed -i "s|PASSWORD_HASH_PLACEHOLDER|${ESCAPED_HASH}|g" /opt/stacks/core/authelia/users_database.yml
sed -i "s/ADMIN_EMAIL_PLACEHOLDER/${ADMIN_EMAIL}/g" /opt/stacks/core/authelia/users_database.yml
# Read hash from file (not bash variable) to avoid shell expansion
# The hash file was written directly from Docker output in setup script
export ADMIN_USER
export ADMIN_EMAIL
python3 << 'PYTHON_EOF'
# Read password hash from file to completely avoid bash variable expansion
with open('/tmp/authelia_password_hash.tmp', 'r') as f:
password_hash = f.read().strip()
import os
admin_user = os.environ['ADMIN_USER']
admin_email = os.environ['ADMIN_EMAIL']
content = f"""###############################################################
# Users Database #
###############################################################
users:
{admin_user}:
displayname: "Admin User"
password: "{password_hash}"
email: {admin_email}
groups:
- admins
- users
"""
with open('/opt/stacks/core/authelia/users_database.yml', 'w') as f:
f.write(content)
PYTHON_EOF
log_success "Authelia admin user configured from setup script"
echo ""

View File

@@ -313,8 +313,11 @@ fi
echo ""
log_info "Generating password hash..."
# Generate hash with timeout and better error capture
HASH_OUTPUT=$(timeout 60 docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1)
# Generate hash and write DIRECTLY to file to avoid bash variable expansion of $ characters
# The argon2 hash contains multiple $ characters that bash would try to expand as variables
timeout 60 docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | \
grep -oP 'Digest: \K\$argon2.*' > /tmp/authelia_password_hash.tmp
HASH_EXIT_CODE=$?
if [ $HASH_EXIT_CODE -eq 124 ]; then
@@ -324,27 +327,15 @@ if [ $HASH_EXIT_CODE -eq 124 ]; then
log_info " 2. Docker status: docker ps"
log_info " 3. Try manually: docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2"
exit 1
elif [ $HASH_EXIT_CODE -ne 0 ]; then
log_error "Failed to generate password hash (exit code: $HASH_EXIT_CODE)"
log_info "Error output:"
echo "$HASH_OUTPUT"
exit 1
fi
# Extract hash - format is "Digest: $argon2id$..."
PASSWORD_HASH=$(echo "$HASH_OUTPUT" | grep -oP 'Digest: \K\$argon2.*' || echo "$HASH_OUTPUT" | grep '^\$argon2')
if [ -z "$PASSWORD_HASH" ]; then
log_error "Failed to extract password hash from output"
log_info "Command output:"
echo "$HASH_OUTPUT"
log_info ""
elif [ $HASH_EXIT_CODE -ne 0 ] || [ ! -s /tmp/authelia_password_hash.tmp ]; then
log_error "Failed to generate password hash"
log_info "You can generate the hash manually after setup:"
log_info " docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2"
log_info " Then edit: /opt/stacks/core/authelia/users_database.yml"
exit 1
fi
chmod 600 /tmp/authelia_password_hash.tmp
log_success "Password hash generated successfully"
# Read admin email from .env or prompt
@@ -358,13 +349,13 @@ log_success "Admin user configured: $ADMIN_USER"
log_success "Password hash generated and will be applied during deployment"
# Store the admin credentials for the deployment script
# Include both password and hash so deploy can show the password to user
cat > /tmp/authelia_admin_credentials.tmp << EOF
ADMIN_USER=$ADMIN_USER
ADMIN_EMAIL=$ADMIN_EMAIL
ADMIN_PASSWORD=$ADMIN_PASSWORD
PASSWORD_HASH=$PASSWORD_HASH
EOF
# Password hash is already in /tmp/authelia_password_hash.tmp (written directly from Docker)
# This avoids bash variable expansion issues with $ characters in argon2 hashes
{
echo "ADMIN_USER=$ADMIN_USER"
echo "ADMIN_EMAIL=$ADMIN_EMAIL"
echo "ADMIN_PASSWORD=$ADMIN_PASSWORD"
} > /tmp/authelia_admin_credentials.tmp
chmod 600 /tmp/authelia_admin_credentials.tmp
log_info "Credentials saved for deployment script"