#!/bin/bash set -euo pipefail # .env is mode 600. The secrets directory is mode 755 and the files are mode 644. # compose mounts the whole directory at /run/secrets, so the container user (uid 33) # must be able to traverse it. A mode 700 directory would hide the files from that user. umask 077 # Set FILEDPAPERS_VERSION to a released tag to pin the downloaded compose.yaml, for example # FILEDPAPERS_VERSION=6.0.18 ./install.sh if [ -n "${FILEDPAPERS_VERSION:-}" ]; then FILEDPAPERS_REF="refs/tags/${FILEDPAPERS_VERSION}" else FILEDPAPERS_REF="refs/heads/main" fi BASE_URL="https://raw.githubusercontent.com/svenkubiak/filedpapers/${FILEDPAPERS_REF}" COMPOSE_URL="${BASE_URL}/compose.yaml" SECRETS_DIR="secrets" # The file name is the configuration key. Mangoo reads file{} from /run/secrets/. # smtp.password is handled separately: it is the operator's mailbox password, not a generated key. SECRET_KEYS=( "application.secret" "session.cookie.secret" "session.cookie.key" "authentication.cookie.secret" "authentication.cookie.key" "flash.cookie.secret" "flash.cookie.key" "api.accessToken.secret" "api.accessToken.key" "api.refreshToken.secret" "api.refreshToken.key" "api.challengeToken.secret" "api.challengeToken.key" "persistence.mongo.password" ) # LC_ALL=C, because tr fails with "Illegal byte sequence" on systems without a C.UTF-8 locale # (macOS, BSD) and would then silently produce an empty secret. # The pipeline is guarded, as head closes the pipe and tr exits with SIGPIPE, which would abort # the script through pipefail. generate_secret() { local secret secret="$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom 2>/dev/null | head -c 64)" || true if [ "${#secret}" -ne 64 ]; then echo "Failed to generate a random secret. Is /dev/urandom readable?" >&2 exit 1 fi printf '%s' "$secret" } # Fails on HTTP errors instead of silently writing the error page to disk download() { curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 --output "$2" "$1" } read_env() { local key="$1" local file="$2" local line value line="$(grep -E "^${key}=" "$file" 2>/dev/null | tail -n 1 || true)" value="${line#"${key}"=}" value="${value%$'\r'}" printf '%s' "$value" } # An existing installation kept these values in .env. Reuse them so cookie, # API and database secrets stay valid across the move to secret files. migrated_secret() { local key="$1" if [ ! -f .env ]; then printf '' return fi case "$key" in application.secret) read_env APPLICATION_SECRET .env ;; session.cookie.secret) read_env SESSION_COOKIE_SECRET .env ;; session.cookie.key) read_env SESSION_COOKIE_KEY .env ;; authentication.cookie.secret) read_env AUTHENTICATION_COOKIE_SECRET .env ;; authentication.cookie.key) read_env AUTHENTICATION_COOKIE_KEY .env ;; flash.cookie.secret) read_env FLASH_COOKIE_SECRET .env ;; flash.cookie.key) read_env FLASH_COOKIE_KEY .env ;; api.accessToken.secret) read_env API_ACCESSTOKEN_SECRET .env ;; api.accessToken.key) read_env API_ACCESSTOKEN_KEY .env ;; api.refreshToken.secret) read_env API_REFRESHTOKEN_SECRET .env ;; api.refreshToken.key) read_env API_REFRESHTOKEN_KEY .env ;; api.challengeToken.secret) read_env API_CHALLENGETOKEN_SECRET .env ;; api.challengeToken.key) read_env API_CHALLENGETOKEN_KEY .env ;; persistence.mongo.password) local password password="$(read_env MONGODB_INITDB_ROOT_PASSWORD .env)" if [ -z "$password" ]; then password="$(read_env PERSISTENCE_MONGO_PASSWORD .env)" fi printf '%s' "$password" ;; *) echo "Unknown secret key: ${key}" >&2 exit 1 ;; esac } write_secret_file() { local path="$1" local value="$2" if [ -z "$value" ]; then echo "Refusing to write an empty secret to ${path}" >&2 exit 1 fi printf '%s' "$value" > "$path" chmod 644 "$path" } read_secret_file() { local path="$1" printf '%s' "$(cat "$path")" } ensure_env() { local key="$1" local value="$2" local comment="$3" if grep -qE "^${key}=" .env; then return fi if [ -n "$comment" ]; then printf '\n%s\n%s=%s\n' "$comment" "$key" "$value" >> .env else printf '%s=%s\n' "$key" "$value" >> .env fi echo " Added ${key}=${value} to .env" } echo "1/5 Creating secret files..." mkdir -p "$SECRETS_DIR" chmod 755 "$SECRETS_DIR" created=0 migrated=0 for key in "${SECRET_KEYS[@]}"; do path="${SECRETS_DIR}/${key}" if [ -s "$path" ]; then chmod 644 "$path" continue fi existing="$(migrated_secret "$key")" if [ -n "$existing" ]; then write_secret_file "$path" "$existing" echo " ${key} copied from .env" migrated=1 else write_secret_file "$path" "$(generate_secret)" echo " ${key} generated" fi created=1 done smtp_path="${SECRETS_DIR}/smtp.password" if [ -s "$smtp_path" ]; then chmod 644 "$smtp_path" else smtp_password="" if [ -f .env ]; then smtp_password="$(read_env SMTP_PASSWORD .env)" fi if [ -n "$smtp_password" ]; then write_secret_file "$smtp_path" "$smtp_password" echo " smtp.password copied from .env" migrated=1 created=1 else # Same placeholder the previous .env shipped. The operator replaces it before sending mail. write_secret_file "$smtp_path" "password" echo " smtp.password set to the placeholder 'password'. Replace it with the mailbox password." created=1 fi fi if [ "$created" -eq 0 ]; then echo " [Skipping] secret files already exist." elif [ "$migrated" -eq 1 ]; then echo " Existing values were copied from .env into ${SECRETS_DIR}/." echo " The application reads those files. MONGODB_INITDB_ROOT_PASSWORD in .env is still used by the database container." fi if [ -f .env ] && [ -s "${SECRETS_DIR}/persistence.mongo.password" ]; then env_password="$(read_env MONGODB_INITDB_ROOT_PASSWORD .env)" file_password="$(read_secret_file "${SECRETS_DIR}/persistence.mongo.password")" if [ -n "$env_password" ] && [ "$env_password" != "$file_password" ]; then echo " Warning: MONGODB_INITDB_ROOT_PASSWORD in .env differs from ${SECRETS_DIR}/persistence.mongo.password." >&2 echo " The database container uses .env and the application reads the secret file. They must match." >&2 fi fi if [ -f .env ] && [ -s "${SECRETS_DIR}/smtp.password" ]; then env_smtp="$(read_env SMTP_PASSWORD .env)" file_smtp="$(read_secret_file "${SECRETS_DIR}/smtp.password")" if [ -n "$env_smtp" ] && [ "$env_smtp" != "$file_smtp" ]; then echo " Warning: SMTP_PASSWORD in .env differs from ${SECRETS_DIR}/smtp.password." >&2 echo " The application reads the secret file. SMTP_PASSWORD in .env is not used." >&2 fi fi if [ -f ".env" ]; then echo "2/5 [Skipping] .env already exists - not regenerating it." echo " Remove it manually if you really want a fresh installation." ensure_env "PERSISTENCE_MONGO_HOST" "filedpapers-database" "# Hostname of MongoDB. filedpapers-database is the service in compose.yaml." ensure_env "CONNECTOR_HTTP_PORT" "9090" "# Port the application listens on inside the container. It must match the container port in compose.yaml." else echo "2/5 Creating .env file..." mongo_password="$(read_secret_file "${SECRETS_DIR}/persistence.mongo.password")" if [ -z "$mongo_password" ]; then echo "Failed to read the MongoDB password from ${SECRETS_DIR}/persistence.mongo.password" >&2 exit 1 fi cat > .env <