Files

237 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
ENV_FILE="${ENV_FILE:-./.env}"
MASTODON_IMAGE="lscr.io/linuxserver/mastodon:latest"
LEGACY_DB_PASSWORD="mastodon-change-me"
LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY="fGWkJDBwyRYhILyO7akZGLSSz0gAjPpo"
LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY="Z8BE3tc3XnmUr0MbRexRiPN7vcP52VX0"
LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT="3hRwOaLRCikUCsxd19cUcO5EgXCN6ig1"
LEGACY_SECRET_KEY_BASE="ae3ab77b9a041a4d760126dba8c4e24a33bdfcec7fdbadf3d973baf92017ee0d0b988d62c040aca882ee8030ea22adb04d9c26a9541c918a434029840c8719b0"
LEGACY_OTP_SECRET="b3d6069b87df718c733a1b17a9adcbd7705b76ba0b48dd3f3ce58870dd52bed0d32d947a43a4eadac4230d508e11df0cee6f59ed85e40670134a52545f1131ac"
LEGACY_VAPID_PRIVATE_KEY="1eF670mQsgs_-W_eb06ZZ46apD4qVDYNvFWu9eGWc7E="
LEGACY_VAPID_PUBLIC_KEY="BOOqXKvCVA9tb8Bas05sdez6fGavnA5SrDJe88s7FMeIu7txiQ5sOqOZqKIpTSS_xmm5Wkd_MKHqj2UCQIwl9_8="
read_env_value() {
local key="$1"
if [[ ! -f "$ENV_FILE" ]] || ! grep -qE "^${key}=" "$ENV_FILE"; then
return
fi
local current
current="$(sed -n -E "s/^${key}=//p" "$ENV_FILE" | tail -n 1)"
current="${current%\"}"
current="${current#\"}"
current="${current%\'}"
current="${current#\'}"
printf '%s\n' "$current"
}
read_cache_value() {
local cache_file="$1"
if [[ ! -s "$cache_file" ]]; then
return
fi
sed -n '1p' "$cache_file" | tr -d '\r\n'
}
write_cache_value() {
local cache_file="$1"
local value="$2"
mkdir -p "$(dirname "$cache_file")"
printf '%s\n' "$value" > "$cache_file"
chmod 600 "$cache_file" 2>/dev/null || true
}
escape_sed_replacement() {
printf '%s' "$1" | sed -e 's/[\/&|]/\\&/g'
}
set_env_value() {
local key="$1"
local value="$2"
if [[ ! -f "$ENV_FILE" ]]; then
echo "$ENV_FILE not found; skipped $key generation"
return
fi
local replacement
replacement="$(escape_sed_replacement "$value")"
if grep -qE "^${key}=" "$ENV_FILE"; then
sed -i -E "s|^${key}=.*|${key}=${replacement}|" "$ENV_FILE"
echo "Updated $key"
return
fi
printf '%s=%s\n' "$key" "$value" >> "$ENV_FILE"
echo "Added $key"
}
generate_alnum_secret() {
local secret=""
local chunk=""
while [[ ${#secret} -lt 32 ]]; do
chunk="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c $((32 - ${#secret})) || true)"
secret="${secret}${chunk}"
done
printf '%s\n' "$secret"
}
run_mastodon_generator() {
docker run --rm --entrypoint /bin/bash "$MASTODON_IMAGE" "$1"
}
extract_generator_value() {
local output="$1"
local key="$2"
printf '%s\n' "$output" | sed -n -E "s/^${key}=//p" | tail -n 1
}
is_active_record_secret_valid() {
local value="$1"
[[ "$value" =~ ^[A-Za-z0-9]{32}$ ]]
}
is_hex_secret_valid() {
local value="$1"
[[ "$value" =~ ^[a-f0-9]{128}$ ]]
}
is_vapid_key_valid() {
local value="$1"
[[ ${#value} -ge 40 && "$value" =~ ^[A-Za-z0-9_-]+=*$ ]]
}
CONFIG_PATH_VALUE="${CONFIG_PATH:-$(read_env_value CONFIG_PATH || true)}"
CONFIG_PATH_VALUE="${CONFIG_PATH_VALUE:-./data/config}"
SECRET_CACHE_DIR="${SECRET_CACHE_DIR:-${CONFIG_PATH_VALUE}/.mastodon_secret_cache}"
DB_PASSWORD_CACHE="${DB_PASSWORD_CACHE:-${SECRET_CACHE_DIR}/db_password}"
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_primary_key}"
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE="${ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_deterministic_key}"
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE="${ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE:-${SECRET_CACHE_DIR}/active_record_encryption_key_derivation_salt}"
SECRET_KEY_BASE_CACHE="${SECRET_KEY_BASE_CACHE:-${SECRET_CACHE_DIR}/secret_key_base}"
OTP_SECRET_CACHE="${OTP_SECRET_CACHE:-${SECRET_CACHE_DIR}/otp_secret}"
VAPID_PRIVATE_KEY_CACHE="${VAPID_PRIVATE_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_private_key}"
VAPID_PUBLIC_KEY_CACHE="${VAPID_PUBLIC_KEY_CACHE:-${SECRET_CACHE_DIR}/vapid_public_key}"
if [[ -f "$ENV_FILE" ]]; then
db_password="$(read_env_value DB_PASSWORD || true)"
cached_db_password="$(read_cache_value "$DB_PASSWORD_CACHE" || true)"
if [[ -n "$db_password" && "$db_password" != "$LEGACY_DB_PASSWORD" ]]; then
write_cache_value "$DB_PASSWORD_CACHE" "$db_password"
elif [[ -n "$cached_db_password" ]]; then
set_env_value "DB_PASSWORD" "$cached_db_password"
else
db_password="$(generate_alnum_secret)"
set_env_value "DB_PASSWORD" "$db_password"
write_cache_value "$DB_PASSWORD_CACHE" "$db_password"
fi
active_primary="$(read_env_value ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY || true)"
active_deterministic="$(read_env_value ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY || true)"
active_salt="$(read_env_value ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT || true)"
cached_active_primary="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" || true)"
cached_active_deterministic="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" || true)"
cached_active_salt="$(read_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" || true)"
if is_active_record_secret_valid "$active_primary" \
&& is_active_record_secret_valid "$active_deterministic" \
&& is_active_record_secret_valid "$active_salt" \
&& [[ "$active_primary" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \
&& [[ "$active_deterministic" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \
&& [[ "$active_salt" != "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]]; then
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary"
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic"
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt"
elif is_active_record_secret_valid "$cached_active_primary" \
&& is_active_record_secret_valid "$cached_active_deterministic" \
&& is_active_record_secret_valid "$cached_active_salt"; then
set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$cached_active_primary"
set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$cached_active_deterministic"
set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$cached_active_salt"
elif [[ "$active_primary" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" ]] \
|| [[ "$active_deterministic" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" ]] \
|| [[ "$active_salt" == "$LEGACY_ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" ]] \
|| ! is_active_record_secret_valid "$active_primary" \
|| ! is_active_record_secret_valid "$active_deterministic" \
|| ! is_active_record_secret_valid "$active_salt"; then
active_record_output="$(run_mastodon_generator generate-active-record)"
active_primary="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY")"
active_deterministic="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY")"
active_salt="$(extract_generator_value "$active_record_output" "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT")"
set_env_value "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY" "$active_primary"
set_env_value "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY" "$active_deterministic"
set_env_value "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT" "$active_salt"
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY_CACHE" "$active_primary"
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY_CACHE" "$active_deterministic"
write_cache_value "$ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT_CACHE" "$active_salt"
fi
secret_key_base="$(read_env_value SECRET_KEY_BASE || true)"
if [[ "$secret_key_base" == "$LEGACY_SECRET_KEY_BASE" ]] || ! is_hex_secret_valid "$secret_key_base"; then
cached_secret_key_base="$(read_cache_value "$SECRET_KEY_BASE_CACHE" || true)"
if is_hex_secret_valid "$cached_secret_key_base"; then
set_env_value "SECRET_KEY_BASE" "$cached_secret_key_base"
else
secret_key_base="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')"
set_env_value "SECRET_KEY_BASE" "$secret_key_base"
write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base"
fi
else
write_cache_value "$SECRET_KEY_BASE_CACHE" "$secret_key_base"
fi
otp_secret="$(read_env_value OTP_SECRET || true)"
if [[ "$otp_secret" == "$LEGACY_OTP_SECRET" ]] || ! is_hex_secret_valid "$otp_secret"; then
cached_otp_secret="$(read_cache_value "$OTP_SECRET_CACHE" || true)"
if is_hex_secret_valid "$cached_otp_secret"; then
set_env_value "OTP_SECRET" "$cached_otp_secret"
else
otp_secret="$(run_mastodon_generator generate-secret | tail -n 1 | tr -d '\r\n')"
set_env_value "OTP_SECRET" "$otp_secret"
write_cache_value "$OTP_SECRET_CACHE" "$otp_secret"
fi
else
write_cache_value "$OTP_SECRET_CACHE" "$otp_secret"
fi
vapid_private_key="$(read_env_value VAPID_PRIVATE_KEY || true)"
vapid_public_key="$(read_env_value VAPID_PUBLIC_KEY || true)"
cached_vapid_private_key="$(read_cache_value "$VAPID_PRIVATE_KEY_CACHE" || true)"
cached_vapid_public_key="$(read_cache_value "$VAPID_PUBLIC_KEY_CACHE" || true)"
if is_vapid_key_valid "$vapid_private_key" \
&& is_vapid_key_valid "$vapid_public_key" \
&& [[ "$vapid_private_key" != "$LEGACY_VAPID_PRIVATE_KEY" ]] \
&& [[ "$vapid_public_key" != "$LEGACY_VAPID_PUBLIC_KEY" ]]; then
write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$vapid_private_key"
write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$vapid_public_key"
elif is_vapid_key_valid "$cached_vapid_private_key" && is_vapid_key_valid "$cached_vapid_public_key"; then
set_env_value "VAPID_PRIVATE_KEY" "$cached_vapid_private_key"
set_env_value "VAPID_PUBLIC_KEY" "$cached_vapid_public_key"
elif [[ "$vapid_private_key" == "$LEGACY_VAPID_PRIVATE_KEY" ]] \
|| [[ "$vapid_public_key" == "$LEGACY_VAPID_PUBLIC_KEY" ]] \
|| ! is_vapid_key_valid "$vapid_private_key" \
|| ! is_vapid_key_valid "$vapid_public_key"; then
vapid_output="$(run_mastodon_generator generate-vapid)"
set_env_value "VAPID_PRIVATE_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")"
set_env_value "VAPID_PUBLIC_KEY" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")"
write_cache_value "$VAPID_PRIVATE_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PRIVATE_KEY")"
write_cache_value "$VAPID_PUBLIC_KEY_CACHE" "$(extract_generator_value "$vapid_output" "VAPID_PUBLIC_KEY")"
fi
fi
paths=(
"${CONFIG_PATH:-./data/config}"
"${DB_DATA_PATH:-./data/db}"
"${REDIS_DATA_PATH:-./data/redis}"
)
mkdir -p "${paths[@]}"
for path in "${paths[@]}"; do
case "$path" in
./*|../*) chown -R 1000:1000 "$path" 2>/dev/null || true ;;
esac
done