#!/usr/bin/env bash set -euo pipefail # Restore .env from a Bitwarden JSON export (vault export or single-item JSON). SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" source "$PROJECT_ROOT/lib/common.sh" ITEM_NAME="gitea-migration-env" INPUT_FILE="" OUTPUT_FILE="$PROJECT_ROOT/.env" TEMPLATE_FILE="$PROJECT_ROOT/.env.example" USE_BW_CLI=false usage() { cat >&2 </dev/null; then log_error "jq is required but not installed" exit 1 fi # ── Retrieve the Bitwarden item ────────────────────────────────────────────── item_json="" if $USE_BW_CLI; then if ! command -v bw &>/dev/null; then log_error "Bitwarden CLI (bw) not found — install it or use -f with a JSON export" exit 1 fi log_info "Fetching '$ITEM_NAME' from Bitwarden CLI..." if ! item_json=$(bw get item "$ITEM_NAME" 2>/dev/null); then log_error "Failed to fetch '$ITEM_NAME'. Is your vault unlocked? (bw unlock)" exit 1 fi elif [[ -n "$INPUT_FILE" ]]; then if [[ ! -f "$INPUT_FILE" ]]; then log_error "File not found: $INPUT_FILE" exit 1 fi # Full vault export (has .items array) vs single-item JSON (has .fields directly) if jq -e '.items' "$INPUT_FILE" >/dev/null 2>&1; then if ! item_json=$(jq -e --arg name "$ITEM_NAME" \ '.items[] | select(.name == $name)' "$INPUT_FILE" 2>/dev/null); then log_error "Item '$ITEM_NAME' not found in $INPUT_FILE" log_info "Available items:" jq -r '.items[].name' "$INPUT_FILE" >&2 || true exit 1 fi elif jq -e '.fields' "$INPUT_FILE" >/dev/null 2>&1; then item_json=$(cat "$INPUT_FILE") else log_error "Unrecognised JSON format — expected a Bitwarden export or single-item JSON" exit 1 fi else log_error "Specify -f FILE or --bw. Run with -h for help." exit 1 fi # ── Extract fields ─────────────────────────────────────────────────────────── fields_json=$(printf '%s' "$item_json" | jq -e '.fields // empty') || { log_error "No custom fields found in the Bitwarden item" exit 1 } field_count=$(printf '%s' "$fields_json" | jq 'length') log_info "Found $field_count fields in '$ITEM_NAME'" # ── Reconstruct .env ───────────────────────────────────────────────────────── if [[ -f "$TEMPLATE_FILE" ]]; then log_info "Using .env.example as template (preserves comments and section structure)" output="" while IFS= read -r line || [[ -n "$line" ]]; do # Pass through comments and blank lines unchanged if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then output+="$line"$'\n' continue fi key="${line%%=*}" # Look up this key in the Bitwarden fields bw_value=$(printf '%s' "$fields_json" | \ jq -r --arg k "$key" '.[] | select(.name == $k) | .value // empty') if [[ -n "$bw_value" ]]; then # Quote values that contain shell-sensitive characters if [[ "$bw_value" =~ [[:space:]\#\$\|\&\;\(\)\<\>] ]]; then output+="${key}=\"${bw_value}\""$'\n' else output+="${key}=${bw_value}"$'\n' fi else # No value in Bitwarden — keep the template line as-is (placeholder) output+="$line"$'\n' fi done < "$TEMPLATE_FILE" # Append any Bitwarden fields that aren't in the template (custom additions) extra_header_written=false while IFS= read -r key; do if ! grep -q "^${key}=" "$TEMPLATE_FILE" 2>/dev/null; then if ! $extra_header_written; then output+=$'\n'"# --- Additional fields (not in .env.example) ---"$'\n' extra_header_written=true fi value=$(printf '%s' "$fields_json" | \ jq -r --arg k "$key" '.[] | select(.name == $k) | .value') output+="${key}=${value}"$'\n' fi done < <(printf '%s' "$fields_json" | jq -r '.[].name') printf '%s' "$output" > "$OUTPUT_FILE" else log_warn "No .env.example found — writing flat key=value list (no comments)" printf '%s' "$fields_json" | jq -r '.[] | "\(.name)=\(.value)"' > "$OUTPUT_FILE" fi log_success "Restored .env to $OUTPUT_FILE ($field_count variables)" # Validate restored values against expected formats log_info "Validating restored .env values..." set -a # shellcheck source=/dev/null source "$OUTPUT_FILE" set +a if validate_env; then log_success "All .env values pass format validation" else log_warn "Some .env values failed validation — review and fix before running preflight" fi