From ae11ae03ff13fae781ededc94d7cfefa87bcb32f Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Tue, 24 Mar 2026 07:45:50 +0000 Subject: [PATCH] firstboot: Add shell-ssh.sh module and restore shell-system.sh - Create new shell-ssh.sh module for SSH key installation and password setup * Install SSH public keys to authorized_keys (root + clawdie) * Configure sshd: disable password auth if key provided, enable if not * Set system user passwords (auto-generate or use provided) * Save emergency root password to root/.firstboot-emergency-password - Restore shell-system.sh (was accidentally overwritten during rename) Enables secure SSH-key-first access with password fallback. Co-Authored-By: Claude Haiku 4.5 --- firstboot/shell-ssh.sh | 260 +++++++++++++++++++++++++ firstboot/shell-system.sh | 399 +++++++++++++++++++------------------- 2 files changed, 460 insertions(+), 199 deletions(-) create mode 100644 firstboot/shell-ssh.sh diff --git a/firstboot/shell-ssh.sh b/firstboot/shell-ssh.sh new file mode 100644 index 00000000..48c33cdf --- /dev/null +++ b/firstboot/shell-ssh.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# Clawdie Shell — SSH & System User Password Module +# Purpose: Configure SSH keys and system user passwords +# Author: Clawdie Project +# POSIX-compliant (no bash-isms) + +set -eu + +# Configuration (can be overridden for testing) +LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}" +PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}" +EMERGENCY_PASSWORD_FILE="${EMERGENCY_PASSWORD_FILE:-/root/.firstboot-emergency-password}" + +# Derived from wizard/build inputs (caller sets these) +# SSH_PUBLIC_KEY - Optional SSH public key (ssh-ed25519 or ssh-rsa) +# ROOT_PASSWORD - Optional root password (if empty, auto-generate) +# CLAWDIE_USER_PASSWORD - Optional clawdie user password (if empty, auto-generate) + +# ============================================================================ +# MAIN ENTRY POINT +# ============================================================================ + +clawdie_shell_ssh_setup() { + # Main orchestrator + # 1. Configure SSH keys (if provided) + # 2. Set system passwords (if provided or auto-generate) + # 3. Configure SSH auth methods (key-only or key+password) + + log_msg "[ssh] Starting SSH and password setup" + + # Generate emergency root password if not provided + if [ -z "${ROOT_PASSWORD:-}" ]; then + ROOT_PASSWORD=$(gen_secret) + fi + + # Generate clawdie user password if not provided + if [ -z "${CLAWDIE_USER_PASSWORD:-}" ]; then + CLAWDIE_USER_PASSWORD=$(gen_secret) + fi + + # Setup SSH keys if provided + if [ -n "${SSH_PUBLIC_KEY:-}" ]; then + clawdie_shell_ssh_install_pubkey + clawdie_shell_ssh_disable_password_auth + log_msg "[ssh] SSH public key installed, password auth disabled" + else + clawdie_shell_ssh_enable_password_auth + log_msg "[ssh] No SSH key provided, password auth enabled (less secure)" + fi + + # Set system user passwords + clawdie_shell_ssh_set_passwords "$ROOT_PASSWORD" "$CLAWDIE_USER_PASSWORD" + log_msg "[ssh] System user passwords set" + + # Save emergency root password to file + clawdie_shell_ssh_save_emergency_password "$ROOT_PASSWORD" + log_msg "[ssh] Emergency root password saved" + + echo "[SSH] COMPLETE" >> "$PROGRESS_FILE" + log_msg "[ssh] SSH setup complete" +} + +# ============================================================================ +# SSH PUBLIC KEY INSTALLATION +# ============================================================================ + +clawdie_shell_ssh_install_pubkey() { + # Install SSH_PUBLIC_KEY to authorized_keys for both root and clawdie user + + local root_ssh_dir="/root/.ssh" + local clawdie_ssh_dir="/home/clawdie/.ssh" + + # Setup root authorized_keys + if [ ! -d "$root_ssh_dir" ]; then + mkdir -p "$root_ssh_dir" + chmod 700 "$root_ssh_dir" + fi + + echo "$SSH_PUBLIC_KEY" >> "$root_ssh_dir/authorized_keys" 2>/dev/null || { + log_msg "[ssh] ERROR: Failed to install SSH key for root" + return 1 + } + + chmod 600 "$root_ssh_dir/authorized_keys" + log_msg "[ssh] Installed SSH key for root user" + + # Setup clawdie authorized_keys + if [ ! -d "$clawdie_ssh_dir" ]; then + mkdir -p "$clawdie_ssh_dir" + chmod 700 "$clawdie_ssh_dir" + fi + + echo "$SSH_PUBLIC_KEY" >> "$clawdie_ssh_dir/authorized_keys" 2>/dev/null || { + log_msg "[ssh] ERROR: Failed to install SSH key for clawdie" + return 1 + } + + chmod 600 "$clawdie_ssh_dir/authorized_keys" + chown -R clawdie:clawdie "$clawdie_ssh_dir" + log_msg "[ssh] Installed SSH key for clawdie user" + + return 0 +} + +# ============================================================================ +# SSH AUTH METHOD CONFIGURATION +# ============================================================================ + +clawdie_shell_ssh_disable_password_auth() { + # Disable password authentication in sshd_config + # Keep PubkeyAuthentication enabled + + local sshd_config="/etc/ssh/sshd_config" + + if [ ! -f "$sshd_config" ]; then + log_msg "[ssh] WARNING: sshd_config not found" + return 1 + fi + + # Ensure PubkeyAuthentication is enabled + if grep -q "^#*PubkeyAuthentication" "$sshd_config"; then + sed -i '' 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' "$sshd_config" + else + echo "PubkeyAuthentication yes" >> "$sshd_config" + fi + + # Disable PasswordAuthentication + if grep -q "^#*PasswordAuthentication" "$sshd_config"; then + sed -i '' 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' "$sshd_config" + else + echo "PasswordAuthentication no" >> "$sshd_config" + fi + + # Disable PermitRootLogin with password (allow pubkey only) + if grep -q "^#*PermitRootLogin" "$sshd_config"; then + sed -i '' 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' "$sshd_config" + else + echo "PermitRootLogin prohibit-password" >> "$sshd_config" + fi + + log_msg "[ssh] Disabled password auth in sshd_config" + return 0 +} + +clawdie_shell_ssh_enable_password_auth() { + # Enable password authentication in sshd_config (fallback) + # Less secure but prevents lockout if user loses SSH key + + local sshd_config="/etc/ssh/sshd_config" + + if [ ! -f "$sshd_config" ]; then + log_msg "[ssh] WARNING: sshd_config not found" + return 1 + fi + + # Enable PasswordAuthentication + if grep -q "^#*PasswordAuthentication" "$sshd_config"; then + sed -i '' 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' "$sshd_config" + else + echo "PasswordAuthentication yes" >> "$sshd_config" + fi + + # Allow root login with password + if grep -q "^#*PermitRootLogin" "$sshd_config"; then + sed -i '' 's/^#*PermitRootLogin.*/PermitRootLogin yes/' "$sshd_config" + else + echo "PermitRootLogin yes" >> "$sshd_config" + fi + + log_msg "[ssh] WARNING: Password auth enabled (less secure)" + return 0 +} + +# ============================================================================ +# PASSWORD MANAGEMENT +# ============================================================================ + +clawdie_shell_ssh_set_passwords() { + local root_pwd="$1" + local clawdie_pwd="$2" + + # Set root password using echo | pw usermod + echo "$root_pwd" | pw usermod root -h 0 2>/dev/null || { + log_msg "[ssh] ERROR: Failed to set root password" + return 1 + } + + log_msg "[ssh] Root password set" + + # Set clawdie user password + # Create user if it doesn't exist + if ! id clawdie >/dev/null 2>&1; then + pw useradd -n clawdie -u 1000 -g wheel -m -s /bin/sh 2>/dev/null || { + log_msg "[ssh] ERROR: Failed to create clawdie user" + return 1 + } + log_msg "[ssh] Created clawdie user" + fi + + echo "$clawdie_pwd" | pw usermod clawdie -h 0 2>/dev/null || { + log_msg "[ssh] ERROR: Failed to set clawdie password" + return 1 + } + + log_msg "[ssh] Clawdie user password set" + return 0 +} + +clawdie_shell_ssh_save_emergency_password() { + local root_pwd="$1" + + # Save emergency password to file (root-only, readable only by root) + { + echo "EMERGENCY ROOT PASSWORD" + echo "==============================" + echo "" + echo "Generated at: $(date)" + echo "Password: $root_pwd" + echo "" + echo "Access:" + echo " ssh root@" + echo "" + echo "WARNING: Store this securely." + echo "This file should be moved to secure storage and deleted from this system." + } > "$EMERGENCY_PASSWORD_FILE" 2>/dev/null || { + log_msg "[ssh] WARNING: Could not save emergency password file" + return 1 + } + + chmod 600 "$EMERGENCY_PASSWORD_FILE" + log_msg "[ssh] Emergency password saved to $EMERGENCY_PASSWORD_FILE" + return 0 +} + +# ============================================================================ +# UTILITY: Logging & Secret Generation +# ============================================================================ + +log_msg() { + local msg="$1" + echo "$msg" >> "$LOG_FILE" 2>/dev/null || true +} + +gen_secret() { + openssl rand -base64 32 | tr -d '\n/+=' | head -c 32 +} + +# ============================================================================ +# Export for use by firstboot.sh +# ============================================================================ + +case "${0##*/}" in + shell-ssh.sh) + # Direct execution (for testing) + clawdie_shell_ssh_setup + ;; + *) + # Sourced from another script — functions available + ;; +esac diff --git a/firstboot/shell-system.sh b/firstboot/shell-system.sh index 36fcc40c..624a4406 100755 --- a/firstboot/shell-system.sh +++ b/firstboot/shell-system.sh @@ -1,232 +1,233 @@ #!/bin/sh -# Unit tests for clawdie-shell-system.sh -# Run: sh test-clawdie-shell-system.sh -# POSIX-compliant +# Clawdie Shell — System Configuration Module +# Purpose: System-level config (rc.conf, hostname, services, environment) +# Author: Clawdie Project +# POSIX-compliant (no bash-isms) -set -u +set -eu +# FreeBSD /bin/sh doesn't support trap ERR -TESTDIR="/tmp/clawdie-test-system-$$" -mkdir -p "$TESTDIR" -cd "$TESTDIR" +# Configuration (can be overridden for testing) +RC_CONF="${RC_CONF:-/etc/rc.conf}" +HOSTNAME_FILE="${HOSTNAME_FILE:-/etc/hostname}" +PROFILE_DIR="${PROFILE_DIR:-/etc/profile.d}" +LOG_FILE="${LOG_FILE:-/var/log/clawdie-firstboot.log}" +PROGRESS_FILE="${PROGRESS_FILE:-/var/log/clawdie-firstboot.progress}" -# Setup test environment -mkdir -p "$TESTDIR/etc/profile.d" -mkdir -p "$TESTDIR/var/log" +# Derived from wizard inputs (caller sets these) +# TZ - Timezone (e.g., "Europe/Ljubljana") +# AGENT_DOMAIN - FQDN (e.g., "clawdie.local") +# DETECTED_GPU - GPU vendor from gpu module (intel, amd, nvidia, vmware, vesa) -# Create test rc.conf -touch "$TESTDIR/etc/rc.conf" +# ============================================================================ +# MAIN ENTRY POINT +# ============================================================================ -# Environment overrides -export RC_CONF="$TESTDIR/etc/rc.conf" -export HOSTNAME_FILE="$TESTDIR/etc/hostname" -export PROFILE_DIR="$TESTDIR/etc/profile.d" -export LOG_FILE="$TESTDIR/var/log/clawdie-system-test.log" -export PROGRESS_FILE="$TESTDIR/var/log/clawdie-system-progress-test" +clawdie_shell_system_config() { + # Main orchestrator -# Test inputs -export TZ="Europe/Ljubljana" -export AGENT_DOMAIN="clawdie.local" -export DETECTED_GPU="intel" + log_msg "[system] Starting system configuration" -# Initialize log/progress files -touch "$LOG_FILE" -touch "$PROGRESS_FILE" - -# Source the module -. "$(dirname "$0")/clawdie-shell-system.sh" - -# Color codes -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -# Test counter -TESTS_PASSED=0 -TESTS_FAILED=0 - -# Helper: assert -assert_eq() { - local name="$1" - local expected="$2" - local actual="$3" - - if [ "$expected" = "$actual" ]; then - echo "${GREEN}✓${NC} $name" - TESTS_PASSED=$((TESTS_PASSED + 1)) - else - echo "${RED}✗${NC} $name" - echo " Expected: $expected" - echo " Actual: $actual" - TESTS_FAILED=$((TESTS_FAILED + 1)) + if [ -z "${TZ:-}" ]; then + echo "ERROR: TZ not set" >&2 + exit 1 fi + + if [ -z "${AGENT_DOMAIN:-}" ]; then + echo "ERROR: AGENT_DOMAIN not set" >&2 + exit 1 + fi + + # Write rc.conf with timezone and services + clawdie_shell_system_write_rcconf + log_msg "[system] Updated rc.conf" + + # Set hostname + clawdie_shell_system_set_hostname + log_msg "[system] Set hostname" + + # Setup environment + clawdie_shell_system_setup_env + log_msg "[system] Setup environment" + + # Enable services + clawdie_shell_system_enable_services + log_msg "[system] Enabled services" + + echo "[SYSTEM] COMPLETE" >> "$PROGRESS_FILE" + log_msg "[system] System configuration complete" } -assert_file_exists() { - local name="$1" - local file="$2" +# ============================================================================ +# RC.CONF CONFIGURATION +# ============================================================================ - if [ -f "$file" ]; then - echo "${GREEN}✓${NC} $name" - TESTS_PASSED=$((TESTS_PASSED + 1)) - else - echo "${RED}✗${NC} $name (file not found: $file)" - TESTS_FAILED=$((TESTS_FAILED + 1)) +clawdie_shell_system_write_rcconf() { + # Update /etc/rc.conf with: + # - timezone + # - service configurations (dbus, hald, seatd, lightdm) + # - Lumina desktop settings + + if [ ! -f "$RC_CONF" ]; then + log_msg "[system] Creating $RC_CONF" + touch "$RC_CONF" fi + + # Helper to set or update rc.conf variable + clawdie_shell_system_sysrc "timezone=$TZ" + clawdie_shell_system_sysrc "dbus_enable=YES" + clawdie_shell_system_sysrc "hald_enable=YES" + clawdie_shell_system_sysrc "seatd_enable=YES" + clawdie_shell_system_sysrc "display_manager=lightdm" + clawdie_shell_system_sysrc "lightdm_enable=YES" + + log_msg "[system] Wrote rc.conf configuration" } -assert_file_contains() { - local name="$1" - local file="$2" - local pattern="$3" +clawdie_shell_system_sysrc() { + # Add or update a variable in rc.conf + # Input: VAR=VALUE - if [ -f "$file" ] && grep -q "$pattern" "$file"; then - echo "${GREEN}✓${NC} $name" - TESTS_PASSED=$((TESTS_PASSED + 1)) + local var_assignment="$1" + local var_name var_value + + var_name=$(echo "$var_assignment" | cut -d= -f1) + var_value=$(echo "$var_assignment" | cut -d= -f2-) + + # Check if var already set (idempotence) + if grep -q "^${var_name}=" "$RC_CONF" 2>/dev/null; then + # Update existing (use | as delimiter to avoid issues with / in values) + sed -i '' "s|^${var_name}=.*|${var_assignment}|" "$RC_CONF" else - echo "${RED}✗${NC} $name" - echo " File: $file" - echo " Pattern: $pattern" - TESTS_FAILED=$((TESTS_FAILED + 1)) + # Append new + echo "$var_assignment" >> "$RC_CONF" fi } # ============================================================================ -# TEST SUITE +# HOSTNAME CONFIGURATION # ============================================================================ -echo "=== Clawdie Shell System Module Tests ===" -echo "" +clawdie_shell_system_set_hostname() { + # Set /etc/hostname and apply live -# Test 1: RC.CONF Configuration -echo "Test Group: RC.CONF Configuration" -clawdie_shell_system_write_rcconf + if [ ! -f "$HOSTNAME_FILE" ]; then + touch "$HOSTNAME_FILE" + fi -assert_file_contains "rc.conf contains timezone" "$RC_CONF" "timezone=Europe/Ljubljana" -assert_file_contains "rc.conf contains dbus_enable" "$RC_CONF" "dbus_enable=YES" -assert_file_contains "rc.conf contains hald_enable" "$RC_CONF" "hald_enable=YES" -assert_file_contains "rc.conf contains seatd_enable" "$RC_CONF" "seatd_enable=YES" -assert_file_contains "rc.conf contains display_manager" "$RC_CONF" "display_manager=lightdm" -assert_file_contains "rc.conf contains lightdm_enable" "$RC_CONF" "lightdm_enable=YES" -echo "" + # Write to file + echo "$AGENT_DOMAIN" > "$HOSTNAME_FILE" -# Test 2: RC.CONF Idempotence -echo "Test Group: RC.CONF Idempotence" -rm -f "$TESTDIR/etc/rc.conf" -touch "$TESTDIR/etc/rc.conf" + # Apply live (if not in chroot) + if command -v hostname >/dev/null 2>&1; then + hostname "$AGENT_DOMAIN" 2>/dev/null || true + fi -# Write multiple times -clawdie_shell_system_write_rcconf -clawdie_shell_system_write_rcconf - -# Should only have one timezone line -timezone_count=$(grep -c "^timezone=" "$RC_CONF" || true) -assert_eq "Timezone appears only once" "1" "$timezone_count" - -dbus_count=$(grep -c "^dbus_enable=" "$RC_CONF" || true) -assert_eq "dbus_enable appears only once" "1" "$dbus_count" -echo "" - -# Test 3: Hostname Configuration -echo "Test Group: Hostname Configuration" -clawdie_shell_system_set_hostname - -assert_file_exists "Hostname file created" "$HOSTNAME_FILE" -assert_file_contains "Hostname file contains domain" "$HOSTNAME_FILE" "clawdie.local" -echo "" - -# Test 4: Environment Setup -echo "Test Group: Environment Setup" -clawdie_shell_system_setup_env - -assert_file_exists "clawdie.sh profile created" "$PROFILE_DIR/clawdie.sh" -assert_file_contains "Profile has npm_config_prefix" "$PROFILE_DIR/clawdie.sh" "npm_config_prefix" -assert_file_contains "Profile has PATH export" "$PROFILE_DIR/clawdie.sh" "PATH" -echo "" - -# Test 5: Full Setup Flow -echo "Test Group: Full Setup Flow" -rm -f "$TESTDIR/etc/rc.conf" "$TESTDIR/etc/hostname" "$PROGRESS_FILE" -touch "$TESTDIR/etc/rc.conf" -touch "$PROGRESS_FILE" - -clawdie_shell_system_config 2>/dev/null - -assert_file_exists "rc.conf exists" "$RC_CONF" -assert_file_exists "hostname file exists" "$HOSTNAME_FILE" -assert_file_exists "profile exists" "$PROFILE_DIR/clawdie.sh" - -if grep -q "\[SYSTEM\] COMPLETE" "$PROGRESS_FILE"; then - echo "${GREEN}✓${NC} Progress checkpoint logged" - TESTS_PASSED=$((TESTS_PASSED + 1)) -else - echo "${RED}✗${NC} Progress checkpoint not found" - TESTS_FAILED=$((TESTS_FAILED + 1)) -fi -echo "" - -# Test 6: Validation -echo "Test Group: Validation" -if clawdie_shell_system_validate 2>/dev/null; then - echo "${GREEN}✓${NC} Validation passes" - TESTS_PASSED=$((TESTS_PASSED + 1)) -else - echo "${RED}✗${NC} Validation failed" - TESTS_FAILED=$((TESTS_FAILED + 1)) -fi -echo "" - -# Test 7: SYSRC Update Behavior -echo "Test Group: SYSRC Update Behavior" -# Already tested above, but verify update specifically -rm -f "$TESTDIR/etc/rc.conf" -touch "$TESTDIR/etc/rc.conf" - -clawdie_shell_system_sysrc "update_test=oldvalue" -clawdie_shell_system_sysrc "update_test=newvalue" - -# Ensure only one line exists -update_count=$(grep -c "^update_test=" "$TESTDIR/etc/rc.conf" || true) -if [ "$update_count" = "1" ]; then - echo "${GREEN}✓${NC} SYSRC properly updates existing vars" - TESTS_PASSED=$((TESTS_PASSED + 1)) -else - echo "${RED}✗${NC} SYSRC created duplicate entries ($update_count lines)" - TESTS_FAILED=$((TESTS_FAILED + 1)) -fi -echo "" - -# Test 8: SYSRC Helper Function -echo "Test Group: SYSRC Helper" -rm -f "$TESTDIR/etc/rc.conf" -touch "$TESTDIR/etc/rc.conf" - -clawdie_shell_system_sysrc "test_var=value1" -assert_file_contains "New var added" "$RC_CONF" "test_var=value1" - -clawdie_shell_system_sysrc "test_var=value2" -test_count=$(grep -c "^test_var=" "$RC_CONF" || true) -assert_eq "SYSRC updates existing var" "1" "$test_count" - -actual_value=$(grep "^test_var=" "$RC_CONF" | cut -d= -f2) -assert_eq "SYSRC sets correct value" "value2" "$actual_value" -echo "" + log_msg "[system] Set hostname to $AGENT_DOMAIN" +} # ============================================================================ -# SUMMARY +# ENVIRONMENT SETUP # ============================================================================ -echo "=== Test Results ===" -echo "${GREEN}Passed: $TESTS_PASSED${NC}" -echo "${RED}Failed: $TESTS_FAILED${NC}" -echo "" +clawdie_shell_system_setup_env() { + # Create /etc/profile.d/clawdie.sh for environment variables + # Sets up npm global paths and other Clawdie-specific variables -# Cleanup -rm -rf "$TESTDIR" + if [ ! -d "$PROFILE_DIR" ]; then + mkdir -p "$PROFILE_DIR" + fi -if [ $TESTS_FAILED -eq 0 ]; then - echo "${GREEN}✓ All tests passed!${NC}" - exit 0 -else - echo "${RED}✗ Some tests failed${NC}" - exit 1 -fi + local clawdie_profile="$PROFILE_DIR/clawdie.sh" + + cat > "$clawdie_profile" <<'EOF' +# Clawdie-AI environment setup +# Adds npm global bin directory to PATH + +export npm_config_prefix="${HOME}/.npm-global" +export PATH="${HOME}/.npm-global/bin:${PATH}" +EOF + + chmod 644 "$clawdie_profile" + log_msg "[system] Created $clawdie_profile" +} + +# ============================================================================ +# SERVICE ENABLEMENT +# ============================================================================ + +clawdie_shell_system_enable_services() { + # Enable and start required services + # Safe to fail if running in chroot (first boot) + + local services="dbus hald seatd lightdm" + + for service in $services; do + if command -v service >/dev/null 2>&1; then + # Try to start service + service "$service" onestart 2>/dev/null || { + log_msg "[system] Could not start $service (expected in chroot)" + } + fi + done + + log_msg "[system] Service enablement complete" +} + +# ============================================================================ +# VALIDATION +# ============================================================================ + +clawdie_shell_system_validate() { + # Verify system configuration completed + + if [ ! -f "$RC_CONF" ]; then + echo "ERROR: rc.conf not found" >&2 + return 1 + fi + + # Check timezone is set + if ! grep -q "^timezone=" "$RC_CONF"; then + echo "ERROR: timezone not set in rc.conf" >&2 + return 1 + fi + + # Check hostname file exists + if [ ! -f "$HOSTNAME_FILE" ]; then + echo "ERROR: $HOSTNAME_FILE not created" >&2 + return 1 + fi + + # Check environment profile exists + if [ ! -f "$PROFILE_DIR/clawdie.sh" ]; then + echo "ERROR: $PROFILE_DIR/clawdie.sh not created" >&2 + return 1 + fi + + log_msg "[system] Validation passed" + return 0 +} + +# ============================================================================ +# UTILITY: Logging +# ============================================================================ + +log_msg() { + local msg="$1" + echo "$msg" >> "$LOG_FILE" 2>/dev/null || true +} + +# ============================================================================ +# Export for use by firstboot.sh +# ============================================================================ + +case "${0##*/}" in + clawdie-shell-system.sh) + # Direct execution (for testing) + clawdie_shell_system_config + clawdie_shell_system_validate + ;; + *) + # Sourced from another script — functions available + ;; +esac