#!/bin/sh # Clawdie Shell — Environment Configuration Module # Purpose: Generate .env file with secrets and configuration # POSIX-compliant (no bash-isms) set -eu # Configuration (can be overridden for testing) CLAWDIE_HOME="${CLAWDIE_HOME:-/home/clawdie}" ENV_FILE="${ENV_FILE:-$CLAWDIE_HOME/.env}" LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}" PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}" # Subnet base (default 10.0.0) AGENT_SUBNET_BASE="${AGENT_SUBNET_BASE:-10.0.0}" # ============================================================================ # MAIN ENTRY POINT # ============================================================================ clawdie_shell_env_generate() { # Main orchestrator: generate .env file with all required variables. # In upgrade mode, secrets are NEVER regenerated — only new keys are appended. # This is the fix for the PC-BSD class of upgrade bug where new install # passwords orphan the existing database. log_msg "[env] Starting .env generation (mode: ${CLAWDIE_BOOT_MODE:-install})" # ── Upgrade path: preserve secrets, append only missing keys ────────────── if [ "${CLAWDIE_BOOT_MODE:-install}" = "upgrade" ] && [ -f "$ENV_FILE" ]; then clawdie_shell_env_append_new_keys || { log_msg "[env] ERROR: Failed to append new .env keys" return 1 } clawdie_shell_env_validate || log_msg "[env] WARNING: .env validation found issues (upgrade)" echo "[ENV] SUCCESS" >> "$PROGRESS_FILE" log_msg "[env] Upgrade .env complete — existing secrets preserved" return 0 fi # ── Fresh install path ───────────────────────────────────────────────────── # Validate required inputs if [ -z "${ASSISTANT_NAME:-}" ]; then log_msg "[env] ERROR: ASSISTANT_NAME not set" return 1 fi if [ -z "${AGENT_DOMAIN:-}" ]; then log_msg "[env] ERROR: AGENT_DOMAIN not set" return 1 fi if [ -z "${TZ:-}" ]; then TZ="UTC" log_msg "[env] WARNING: TZ not set, defaulting to UTC" fi if [ -z "${SYSTEM_LOCALE:-}" ]; then SYSTEM_LOCALE="en_US.UTF-8" log_msg "[env] WARNING: SYSTEM_LOCALE not set, defaulting to ${SYSTEM_LOCALE}" fi DISPLAY_LOCALE="${DISPLAY_LOCALE:-$SYSTEM_LOCALE}" ASSISTANT_LOCALE="${ASSISTANT_LOCALE:-$SYSTEM_LOCALE}" KEYMAP="${KEYMAP:-us}" # Step 1: Create directory mkdir -p "$CLAWDIE_HOME" chown clawdie:clawdie "$CLAWDIE_HOME" 2>/dev/null || true # Step 2: Generate all .env variables clawdie_shell_env_write_file || { log_msg "[env] ERROR: Failed to write .env file" return 1 } log_msg "[env] .env file generated successfully" # Step 3: Validate clawdie_shell_env_validate || { log_msg "[env] ERROR: .env validation failed" return 1 } echo "[ENV] SUCCESS" >> "$PROGRESS_FILE" log_msg "[env] Environment configuration complete" } clawdie_shell_env_append_new_keys() { # Upgrade-only: read the existing .env and add any keys introduced in the new # version that are not yet present. Never touches existing values. # Pattern: if key is absent → append with safe default; if present → skip. log_msg "[env] Upgrade mode: appending missing keys to existing .env" _env_append_if_missing() { _key="$1"; _val="$2" if ! grep -q "^${_key}=" "$ENV_FILE" 2>/dev/null; then printf '%s=%s\n' "$_key" "$_val" >> "$ENV_FILE" log_msg "[env] Appended new key: $_key" fi } # Keys added in v0.10.x / v1.0.0 that older installs may not have. # Do not append provider/model defaults here; Clawdie-AI resolves those at # runtime unless the operator explicitly configures them. _env_append_if_missing "DB_RUNTIME" '"jail"' _env_append_if_missing "EMBED_DIMENSIONS" '"1024"' _env_append_if_missing "LOCAL_LLM_PROVIDER" '"none"' _env_append_if_missing "FEATURE_OLLAMA" '"NO"' _env_append_if_missing "FEATURE_LLAMA_CPP" '"NO"' _env_append_if_missing "CLAWDIE_PROFILE" '"core"' _env_append_if_missing "CONTROLPLANE_SHARED_SECRET" '""' _env_append_if_missing "CONTROLPLANE_BIND_HOST" '"127.0.0.1"' _env_append_if_missing "CONTROLPLANE_HOST_IP" "\"${AGENT_SUBNET_BASE}.1\"" _env_append_if_missing "CONTROLPLANE_EXPOSURE" '"internal"' _env_append_if_missing "CONTROLPLANE_AUTH_MODE" '"local_trusted"' _env_append_if_missing "CONTROLPLANE_PORT" '"3100"' _env_append_if_missing "BETTER_AUTH_URL" '"http://127.0.0.1:3100"' _env_append_if_missing "BETTER_AUTH_SECRET" '""' _env_append_if_missing "GIT_LOCAL_URL" "\"git@${AGENT_SUBNET_BASE}.6:/srv/git/${ASSISTANT_NAME:-clawdie}-ai.git\"" chmod 600 "$ENV_FILE" 2>/dev/null || true chown clawdie:clawdie "$ENV_FILE" 2>/dev/null || true log_msg "[env] Append complete ($(wc -l < "$ENV_FILE") lines total)" } # ============================================================================ # .ENV FILE GENERATION # ============================================================================ clawdie_shell_env_write_file() { # Write a minimal .env seed file. # # This file is copied into the Clawdie-AI repo by the deploy module and then # completed by Clawdie-AI onboarding (secrets, derived defaults, URLs, etc). # Derive agent name from assistant name (lowercase, strip non-alnum) local agent_name agent_name=$(echo "$ASSISTANT_NAME" | tr 'A-Z' 'a-z' | sed 's/[^a-z0-9]//g') # Generate controlplane auth secrets (one-time at install) local cp_secret auth_secret cp_secret=$(openssl rand -base64 32) auth_secret=$(openssl rand -base64 32) # Provider/model are intentionally unset by default; Clawdie-AI resolves the # recommended runtime profile and post-install setup writes the operator's # chosen provider. Embedding base URL is left empty when no OpenRouter key # exists so config.ts can dynamically pick OpenRouter or local at runtime. local pi_tui_provider pi_tui_model embed_base_url embed_model embed_api_key pi_tui_provider="${PI_TUI_PROVIDER:-}" pi_tui_model="${PI_TUI_MODEL:-}" if [ -n "${EMBED_BASE_URL:-}" ]; then embed_base_url="$EMBED_BASE_URL" elif [ -n "${OPENROUTER_API_KEY:-}" ]; then embed_base_url="https://openrouter.ai/api/v1" else embed_base_url="" fi embed_model="${EMBED_MODEL:-BAAI/bge-m3}" if [ -n "${EMBED_API_KEY:-}" ]; then embed_api_key="$EMBED_API_KEY" elif [ "$embed_base_url" = "https://openrouter.ai/api/v1" ]; then embed_api_key="${OPENROUTER_API_KEY:-}" else embed_api_key="" fi # Remove existing .env if present rm -f "$ENV_FILE" 2>/dev/null || true # Create new .env with restricted permissions touch "$ENV_FILE" chmod 600 "$ENV_FILE" chown clawdie:clawdie "$ENV_FILE" 2>/dev/null || true # Write .env file cat > "$ENV_FILE" </dev/null || stat -c "%a" "$ENV_FILE" 2>/dev/null || echo "") if [ "$perms" != "600" ] && [ -n "$perms" ]; then log_msg "[env] WARNING: .env permissions are $perms (should be 600)" fi # Check for required variables local required_vars="ASSISTANT_NAME AGENT_NAME AGENT_DOMAIN AGENT_INTERNAL_DOMAIN TZ SYSTEM_LOCALE DISPLAY_LOCALE ASSISTANT_LOCALE KEYMAP" local missing=0 for var in $required_vars; do if ! grep -q "^$var=" "$ENV_FILE" 2>/dev/null; then log_msg "[env] ERROR: Missing required variable: $var" missing=$((missing + 1)) fi done if [ $missing -gt 0 ]; then return 1 fi log_msg "[env] .env validation passed ($(wc -l < "$ENV_FILE") lines)" return 0 } # ============================================================================ # LOGGING HELPER # ============================================================================ log_msg() { echo "$(date '+%H:%M:%S') $1" | tee -a "$LOG_FILE" 2>/dev/null || true } # Only run if sourced directly (not during test) if [ "${SHELL_ENV_TEST:-0}" -eq 0 ]; then clawdie_shell_env_generate fi