Lab — Build a User Management Script¶
Lab Overview¶
Purpose: Build a small CLI that wraps safe user administration actions for a lab VM.
Scenario: Ops needs a scripted way to create service accounts with a consistent home layout — without pasting ad-hoc commands into Slack.
Expected outcome: A working script under ~/rebash-lab-shell with clear exit codes, stderr logging, and validation steps you can re-run.
This is a lab, not a tutorial
Apply Shell Scripting skills. Prefer small verified steps over rewriting everything at once.
Business Scenario¶
A SaaS team provisions short-lived lab users for contractors. Manual useradd steps drift. You automate create/list/disable with dry-run and audit logging.
Learning Objectives¶
By the end of this lab, you will be able to:
- Parse subcommands with
case - Validate usernames before calling privileged tools
- Log actions and refuse destructive ops without confirmation flags
- Document an exit taxonomy for automation callers
Prerequisites¶
Knowledge¶
Software¶
| Tool | Notes |
|---|---|
| Bash 4.2+ | required |
| sudo | for real user ops — use dry-run if unprivileged |
Estimated cost: £0.
Environment¶
Linux VM where you may use sudo (or dry-run only).
Initial State¶
Privileged actions
Prefer --dry-run first. Only create real users on disposable lab VMs.
Lab Tasks¶
Task 1 — CLI skeleton¶
Create bin/usermgr.sh with subcommands: list, create, disable, help.
cat > bin/usermgr.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${LOG_DIR:-$HOME/rebash-lab-shell/users/logs}"
mkdir -p "$LOG_DIR"
log() { printf '%s %s
' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >>"$LOG_DIR/usermgr.log"; }
usage() {
cat >&2 <<'USAGE'
Usage: usermgr.sh <list|create|disable|help> [options]
create --user NAME [--dry-run]
disable --user NAME [--dry-run]
USAGE
}
cmd="${1:-help}"; shift || true
case "$cmd" in
help|-h|--help) usage; exit 2 ;;
list|create|disable) ;;
*) usage; exit 2 ;;
esac
EOF
chmod +x bin/usermgr.sh
Task 2 — Implement list¶
Print local human users (UID >= 1000) using getent passwd and awk.
Task 3 — Implement create (dry-run first)¶
Accept --user and --dry-run. Validate ^[a-z_][a-z0-9_-]*$. In dry-run, print the useradd command; otherwise run sudo useradd -m -s /bin/bash "$user" on a lab VM.
Task 4 — Implement disable¶
Lock the account with usermod -L / passwd -l (or print the command in dry-run). Log every action.
Task 5 — Exit taxonomy¶
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Usage / validation error |
| 3 | User already exists / not found |
| 4 | Privilege or system failure |
Validation¶
-
listprints UID>=1000 users -
create --dry-runprints intended command and exits 0 - Invalid usernames exit 2 without calling useradd
- Actions append to
logs/usermgr.log
Troubleshooting¶
| Symptom | Possible cause | Resolution |
|---|---|---|
| sudo password prompts break automation | Interactive sudo | Configure NOPASSWD for lab or stay in dry-run |
| Username rejected | Regex too strict/loose | Align with local useradd policy |
| getent missing entries | NSS/LDAP lab | Document scope as local files only |
Cleanup¶
Stretch Goals¶
- Add a
--dry-runmode that prints actions without changing the system - Emit a machine-readable
RESULT status=...line on stdout for CI - Schedule the script with cron or a systemd timer
Production Discussion¶
In production, wrap scripts with lock files, structured logging, explicit PATH, and documented exit codes. Prefer configuration files over hard-coded hosts and thresholds. Never embed secrets in scripts — use environment variables or a secrets manager.
Best Practices¶
- Use
#!/usr/bin/env bashandset -euo pipefail - Quote every expansion that may contain spaces
- Log diagnostics to stderr; keep stdout for data
- Prefer absolute paths in scheduled jobs
- Validate inputs before destructive actions
Common Mistakes¶
| Mistake | Why it happens | Correct approach |
|---|---|---|
| Unquoted paths | Habit from interactive shell | Always "$var" |
Missing pipefail | Default Bash pipeline behaviour | set -o pipefail |
| Interactive-only PATH | Cron/systemd minimal env | Set PATH= at top |
| Skipping dry-run | Time pressure | Default to dry-run for risky ops |
Success Criteria¶
- Script runs under Bash with strict mode
- Validation and failure paths are tested
- Exit codes are documented
- Cleanup leaves no lab artefacts (or documents what remains)
Reflection Questions¶
- What would break if this ran under
/bin/sh(dash) instead of Bash? - How would you make the script idempotent?
- How would you secure credentials and host inventories?
- How would you observe failures in production?
Interview Connection¶
Interviewers often ask about quoting, exit codes, cron environment differences, and how you prevent overlapping jobs. Be ready to walk through a small script and explain failure modes.
Related Tutorials¶
Linux Admin AutomationFunctions Parameters And LocalsControl Flow Conditionals- Cheat sheet: Shell Scripting
- Interview: Shell Scripting
- Quiz: Shell Scripting for DevOps Fundamentals
- Track: Shell Scripting