#!/usr/bin/env bash # learner-add.sh - Add a new dev user account # Usage: learner-add.sh # # Creates: # - Unix user account with SSH key # - Adds to learners group (Slack token access) # - Outputs onboarding instructions set -euo pipefail # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; } usage() { echo "Usage: $0 " echo "" echo "Arguments:" echo " username - Learner's username (alphanumeric, 3-16 chars)" echo " ssh-pubkey - SSH public key (starts with ssh-ed25519, ssh-rsa, etc.)" echo "" echo "Example:" echo " $0 alice 'ssh-ed25519 AAAA... alice@laptop'" exit 1 } validate_username() { local username="$1" if [[ ! "$username" =~ ^[a-z][a-z0-9_-]{2,15}$ ]]; then log_error "Invalid username: must be 3-16 chars, start with letter, alphanumeric/underscore/dash only" exit 1 fi # Check if user already exists if id "$username" &>/dev/null; then log_error "User '$username' already exists" exit 1 fi } validate_ssh_key() { local key="$1" if [[ ! "$key" =~ ^ssh-(ed25519|rsa|ecdsa) ]]; then log_error "Invalid SSH key: must start with ssh-ed25519, ssh-rsa, or ssh-ecdsa" exit 1 fi } create_user() { local username="$1" local ssh_key="$2" log_info "Creating user '$username'..." # Create user with home directory # NixOS: don't specify shell (uses default), group is 'users' useradd -m -g users "$username" # Make home directory private (not world-readable) chmod 700 "/home/$username" # Add to learners group for Slack token access usermod -aG learners "$username" # Set up SSH key local ssh_dir="/home/$username/.ssh" mkdir -p "$ssh_dir" echo "$ssh_key" > "$ssh_dir/authorized_keys" chmod 700 "$ssh_dir" chmod 600 "$ssh_dir/authorized_keys" chown -R "$username:users" "$ssh_dir" # Add Slack env vars to bashrc { echo '' echo '# Slack bot development tokens' echo 'source /etc/slack-learner.env' } >> "/home/$username/.bashrc" log_info "User created with SSH access" } print_onboarding() { local username="$1" local server_ip # NixOS: use ip command instead of hostname -I server_ip=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1) echo "" echo "==========================================" echo " Dev Environment Ready: $username" echo "==========================================" echo "" echo "## SSH Config (~/.ssh/config on your laptop)" echo "" echo " Host dev-server" echo " HostName ${server_ip:-}" echo " User $username" echo " LocalForward 8080 127.0.0.1:8080" echo "" echo "## Quick Start" echo "" echo "1. SSH in:" echo " ssh dev-server" echo "" echo "2. Authenticate Claude (first time only):" echo " claude" echo " # Opens localhost URL - paste in your local browser" echo " # Complete OAuth, token flows back automatically" echo "" echo "3. Start coding:" echo " mkdir mybot && cd mybot" echo " claude 'create a slack bot that responds to hello'" echo "" echo "## Tools Available" echo " python3, uv, go (nix profile install nixpkgs#go)" echo " Slack tokens: \$SLACK_BOT_TOKEN, \$SLACK_APP_TOKEN" echo "" echo "==========================================" } main() { if [[ $# -lt 2 ]]; then usage fi local username="$1" local ssh_key="$2" # Must run as root if [[ $EUID -ne 0 ]]; then log_error "This script must be run as root" exit 1 fi validate_username "$username" validate_ssh_key "$ssh_key" create_user "$username" "$ssh_key" print_onboarding "$username" log_info "Dev user '$username' setup complete!" } main "$@"