#!/bin/sh set -eu # Create a deploy user, generate an ed25519 keypair, copy keys to /root, # update the user's authorized_keys, and (if supported) add an sshd per-user config. # # Usage: # DEPLOY_USER=deploy ./setup_deploy_user.sh # ./setup_deploy_user.sh mydeploy # # Defaults to user "deploy" if not provided via env or argument. info() { printf "[INFO] %s\n" "$*" } error() { printf "[ERROR] %s\n" "$*" 1>&2 } command_exists() { command -v "$1" >/dev/null 2>&1 } require_root() { if [ "$(id -u)" -ne 0 ]; then error "This script must be run as root" exit 1 fi } sanitize_username() { # POSIX-ish username rules (simplified): letters, digits, _ and - # must start with a letter or underscore case "$1" in '' ) return 1 ;; *[!A-Za-z0-9_-]* ) return 1 ;; [!A-Za-z_]* ) return 1 ;; * ) return 0 ;; esac } get_home_dir() { user="$1" home="$(getent passwd "$user" 2>/dev/null | awk -F: 'NR==1 {print $6}')" if [ -z "$home" ]; then home="/home/$user" fi printf '%s' "$home" } create_user_if_needed() { user="$1" if id "$user" >/dev/null 2>&1; then info "User '$user' already exists" return 0 fi if command_exists useradd; then info "Creating user '$user' with useradd" # -m create home, -U create group, -s shell useradd -m -U -s /bin/bash -c "Deployment User" "$user" elif command_exists adduser; then info "Creating user '$user' with adduser" # Non-interactive, no password adduser --disabled-password --gecos "Deployment User" "$user" else error "Neither useradd nor adduser is available" exit 1 fi } backup_if_exists() { p="$1" if [ -e "$p" ]; then ts="$(date +%s)" mv "$p" "$p.bak.$ts" info "Backed up existing $(basename "$p") to $(basename "$p.bak.$ts")" fi } write_user_ssh_client_config() { user="$1" home_dir="$2" ssh_dir="$home_dir/.ssh" cfg="$ssh_dir/config" mkdir -p "$ssh_dir" chmod 700 "$ssh_dir" # Minimal, safe client config if [ ! -f "$cfg" ]; then { echo "Host *" echo " IdentitiesOnly yes" echo " PubkeyAuthentication yes" } > "$cfg" chown "$user":"$user" "$cfg" chmod 600 "$cfg" info "Wrote SSH client config at $cfg" else info "SSH client config already present at $cfg" fi chown "$user":"$user" "$ssh_dir" } configure_sshd_dropin_if_supported() { user="$1" dropin_dir="/etc/ssh/sshd_config.d" if [ -d "$dropin_dir" ]; then conf="$dropin_dir/99-deploy-user-$user.conf" if [ ! -f "$conf" ]; then { echo "Match User $user" echo " PubkeyAuthentication yes" echo " PasswordAuthentication no" echo " AuthorizedKeysFile %h/.ssh/authorized_keys" } > "$conf" chmod 644 "$conf" info "Created sshd drop-in: $conf" else info "sshd drop-in already exists: $conf" fi if command_exists sshd; then if sshd -t >/dev/null 2>&1; then if command_exists systemctl; then systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true else service ssh reload 2>/dev/null || service sshd reload 2>/dev/null || true fi info "Reloaded sshd" else error "sshd configuration test failed; not reloading" fi fi else info "sshd drop-in directory not present; skipping server config" fi } main() { require_root DEPLOY_USER_NAME="${DEPLOY_USER:-${1:-deploy}}" if ! sanitize_username "$DEPLOY_USER_NAME"; then error "Invalid username '$DEPLOY_USER_NAME'. Use lowercase letters, digits, '_' or '-', starting with a letter or '_'" exit 1 fi info "Using deploy user: $DEPLOY_USER_NAME" create_user_if_needed "$DEPLOY_USER_NAME" HOME_DIR="$(get_home_dir "$DEPLOY_USER_NAME")" SSH_DIR="$HOME_DIR/.ssh" AUTH_KEYS="$SSH_DIR/authorized_keys" mkdir -p "$SSH_DIR" chmod 700 "$SSH_DIR" touch "$AUTH_KEYS" chmod 600 "$AUTH_KEYS" chown -R "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$SSH_DIR" # Generate keypair in a temp dir TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT INT HUP TERM KEYBASE="$TMPDIR/id_ed25519" COMMENT="deploy key for $DEPLOY_USER_NAME@$(hostname -f 2>/dev/null || hostname)" info "Generating ed25519 keypair" if ! command_exists ssh-keygen; then error "ssh-keygen not found. Please install OpenSSH client tools." exit 1 fi ssh-keygen -t ed25519 -N "" -C "$COMMENT" -f "$KEYBASE" >/dev/null 2>&1 # Copy keys to /root as deploy.priv and deploy.pub DEST_PRIV="/root/deploy.priv" DEST_PUB="/root/deploy.pub" backup_if_exists "$DEST_PRIV" backup_if_exists "$DEST_PUB" cp "$KEYBASE" "$DEST_PRIV" cp "$KEYBASE.pub" "$DEST_PUB" chown root:root "$DEST_PRIV" "$DEST_PUB" chmod 600 "$DEST_PRIV" chmod 644 "$DEST_PUB" info "Installed private key: $DEST_PRIV" info "Installed public key: $DEST_PUB" # Add public key to authorized_keys if not present PUB_LINE="$(cat "$KEYBASE.pub")" if ! grep -qxF "$PUB_LINE" "$AUTH_KEYS" 2>/dev/null; then printf '%s\n' "$PUB_LINE" >> "$AUTH_KEYS" chown "$DEPLOY_USER_NAME":"$DEPLOY_USER_NAME" "$AUTH_KEYS" chmod 600 "$AUTH_KEYS" info "Added public key to $AUTH_KEYS" else info "Public key already present in $AUTH_KEYS" fi # Minimal client-side SSH config for the deploy user write_user_ssh_client_config "$DEPLOY_USER_NAME" "$HOME_DIR" # Optional: server-side sshd drop-in configuration configure_sshd_dropin_if_supported "$DEPLOY_USER_NAME" printf "\nDone. You can distribute the public key at %s.\n" "$DEST_PUB" } main "$@"