#!/usr/bin/env bash # dev-add.sh - Add a new dev user account # Usage: dev-add.sh # # Creates: # - Unix user account with SSH key # - Adds to devs 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 - Dev'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 devs group for Slack token access usermod -aG devs "$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" # Set up user's shell config (may not exist on NixOS) # .profile = login shells (SSH), .bashrc = interactive non-login local profile="/home/$username/.profile" { echo '# bun global packages (preferred - faster installs)' echo "export PATH=\"\$HOME/.bun/bin:\$PATH\"" echo '' echo '# npm global packages (fallback)' echo "export PATH=\"\$HOME/.npm-global/bin:\$PATH\"" echo '' echo '# Slack bot development tokens' echo '[ -f /etc/slack-dev.env ] && source /etc/slack-dev.env' } > "$profile" chown "$username:users" "$profile" # Pre-create npm global directory and configure npm prefix local npm_global="/home/$username/.npm-global" local npmrc="/home/$username/.npmrc" mkdir -p "$npm_global" echo "prefix=$npm_global" > "$npmrc" chown "$username:users" "$npm_global" "$npmrc" # Create AGENTS.md for AI coding assistants cat > "/home/$username/AGENTS.md" << 'AGENTS_EOF' # AGENTS.md - Dev Server Guide Shared NixOS dev server. This guide helps AI coding agents work effectively here. ## Possible Right Now **Install packages:** ```bash bun install -g @google/gemini-cli # JS tools (fast) nix profile install nixpkgs#go # System tools (go, rust, etc.) uv venv && uv pip install # Python packages ``` **Run services:** ```bash python -m http.server 8080 # Dev servers on high ports bun run app.js # Run JS directly tmux / screen # Persistent sessions ``` **Available tools:** python3, uv, bun, node, npm, zig, git, vim, curl, tmux, opencode, bd ## Not Possible Right Now | Want | Why | Workaround | |------|-----|------------| | sudo / root | Shared server security | Use nix profile or npm install -g | | apt / yum | NixOS uses nix | `nix profile install nixpkgs#` | | Port 80/443 | Needs root | Use high port + SSH tunnel | | Docker | Security isolation | Use nix for dependencies | | systemd system services | Needs root | Use pm2, screen, or tmux | ## Resource Limits Per-user limits to keep the server stable: - **Memory**: ~1GB (50% of system) - **Processes**: 200 max - **Network**: 30 new connections/min Heavy processes may be killed automatically. ## Environment - **OS**: NixOS (not Debian/Ubuntu) - **Shell**: bash - **Home**: ~/ (private, 700) - **Temp**: /tmp (fast, cleared on reboot) AGENTS_EOF chown "$username:users" "/home/$username/AGENTS.md" 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. Install AI tools (pick one or more):" echo " bun install -g @anthropic-ai/claude-code # Claude" echo " bun install -g @google/gemini-cli # Gemini" echo "" echo "3. Authenticate and start coding:" echo " claude # or: gemini" echo " # Follow prompts to authenticate" echo "" echo "## Tools Available" echo " System: python3, uv, git, bun, node, opencode, bd" echo " Install more: bun install -g " echo " nix profile install nixpkgs#" 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" # Check devs group exists (created by NixOS config) if ! getent group devs >/dev/null; then log_error "Required group 'devs' does not exist" log_error "Ensure users.groups.devs = {} is in NixOS config and deployed" exit 1 fi create_user "$username" "$ssh_key" print_onboarding "$username" log_info "Dev user '$username' setup complete!" } main "$@"