- shell-system.sh (runs last, step 6): rewrite .profile with agent launcher, full .bashrc with sudo() shell function that fire-and-forget snapshots zroot@cli-<ts> before mdo -u root. PATH, prompt, history, aliases (ll/la/lt). Agent fallback for non-login shells via ~/.ssh-agent-env. - shell-ssh.sh (step 4): strip to ~/.ssh/config only. Agent/profile/tmux seeding moved to shell-system.sh so it is not overwritten. - AGENTS.md: document sudo→mdo decision with rationale table (ISO size, audit surface, single privilege path, ZFS rollback safety).
307 lines
9.8 KiB
Bash
307 lines
9.8 KiB
Bash
#!/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)
|
|
# 4. Seed SSH client defaults (~/.ssh/config)
|
|
|
|
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
|
|
clawdie_shell_ssh_seed_client_config
|
|
log_msg "[ssh] SSH public key installed, password auth disabled, client config seeded"
|
|
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 CLIENT CONFIGURATION (seed ~/.ssh/config)
|
|
# ============================================================================
|
|
|
|
clawdie_shell_ssh_seed_client_config() {
|
|
# Seed SSH client defaults for the clawdie user.
|
|
# .profile / .bashrc / .tmux.conf are handled by shell-system.sh
|
|
# (which runs after this module).
|
|
# Run after clawdie_shell_ssh_install_pubkey (requires user to exist).
|
|
|
|
local ssh_config="/home/clawdie/.ssh/config"
|
|
|
|
# --- ~/.ssh/config: auto-load key on first use, no agent forwarding ---
|
|
if [ ! -f "$ssh_config" ]; then
|
|
cat > "$ssh_config" <<'SSHEOF'
|
|
Host *
|
|
AddKeysToAgent yes
|
|
ForwardAgent no
|
|
SSHEOF
|
|
chmod 600 "$ssh_config"
|
|
chown clawdie:clawdie "$ssh_config" 2>/dev/null || true
|
|
log_msg "[ssh] Seeded ~/.ssh/config with AddKeysToAgent yes"
|
|
else
|
|
if ! grep -q 'AddKeysToAgent' "$ssh_config" 2>/dev/null; then
|
|
printf '\nHost *\n AddKeysToAgent yes\n ForwardAgent no\n' >> "$ssh_config"
|
|
log_msg "[ssh] Appended AddKeysToAgent to existing ~/.ssh/config"
|
|
fi
|
|
fi
|
|
|
|
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. Prefer bash when the package is present;
|
|
# fall back to /bin/sh during partial recovery states.
|
|
local clawdie_shell="/bin/sh"
|
|
if [ -x /usr/local/bin/bash ]; then
|
|
clawdie_shell="/usr/local/bin/bash"
|
|
if ! grep -qx '/usr/local/bin/bash' /etc/shells 2>/dev/null; then
|
|
echo '/usr/local/bin/bash' >> /etc/shells 2>/dev/null || true
|
|
fi
|
|
fi
|
|
if [ -x /usr/local/bin/zsh ] && ! grep -qx '/usr/local/bin/zsh' /etc/shells 2>/dev/null; then
|
|
echo '/usr/local/bin/zsh' >> /etc/shells 2>/dev/null || true
|
|
fi
|
|
if ! id clawdie >/dev/null 2>&1; then
|
|
pw useradd -n clawdie -u 1000 -g wheel -m -s "$clawdie_shell" 2>/dev/null || {
|
|
log_msg "[ssh] ERROR: Failed to create clawdie user"
|
|
return 1
|
|
}
|
|
log_msg "[ssh] Created clawdie user"
|
|
elif [ "$clawdie_shell" = "/usr/local/bin/bash" ]; then
|
|
pw usermod clawdie -s /usr/local/bin/bash 2>/dev/null || true
|
|
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@<domain>"
|
|
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
|