- Posted on
- • Artificial Intelligence
Automating User Management with Artificial Intelligence and Bash
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Automating User Management with Artificial Intelligence and Bash
If your “simple” user onboarding/offboarding process still involves copying columns out of an HR spreadsheet and pasting shell commands into a terminal, you’re one typo away from a bad day. The good news: you can turn that brittle human pipeline into a repeatable, auditable system by combining Bash with a small dose of AI to tame messy inputs.
This article shows you how to:
Normalize HR data with an AI assistant or a strict schema
Generate an idempotent plan to create/update/remove users in Bash
Review and safely apply changes
Schedule everything so it runs without you
We’ll keep it distro-friendly and include installation commands for apt, dnf, and zypper wherever tools are introduced.
Why Bash + AI for user management?
Real-world data is messy: HR sends CSVs with inconsistent headers, nicknames, or departments instead of group names. AI can help normalize this to a strict schema.
Bash is battle-tested: useradd/usermod/chpasswd/groupadd are everywhere, easy to audit, and transparent.
Separation of concerns: let AI clean up inputs; let Bash perform exact, controlled changes under least privilege.
The result is a safer process that still fits your Linux workflows.
Prerequisites and installation
We’ll use these tools:
jq: JSON processing in shell
Miller (mlr): robust CSV→JSON handling
curl: HTTP client (for AI APIs or local LLMs)
cron/cronie: scheduling
Core account-management tools (useradd/usermod/chpasswd/etc.)
Install per distro:
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y jq miller curl cron passwd
sudo systemctl enable --now cron
- Fedora/RHEL (dnf):
sudo dnf install -y jq miller curl cronie shadow-utils
sudo systemctl enable --now crond
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y jq miller curl cron shadow
sudo systemctl enable --now cron
Notes:
The package providing useradd/usermod is named differently by family: passwd (Debian/Ubuntu), shadow-utils (Fedora/RHEL), shadow (openSUSE).
cron service name varies: cron (Debian/openSUSE) vs crond (RHEL/Fedora).
Step 1: Model your desired state
Start with a clear schema. A simple CSV works well:
username,full_name,groups,shell,state,temp_password
jdoe,Jane Doe,engineering;docker,/bin/bash,present,
akhan,Ali Khan,contractors;/bin/false,/bin/false,present,temp-P@ss-123
dlee,Dan Lee,engineering,/bin/bash,absent,
groups: semicolon- or comma-separated
state: present or absent
temp_password: leave empty to keep locked or handle via SSO
Prefer JSON for complex cases:
[
{
"username": "jdoe",
"full_name": "Jane Doe",
"groups": ["engineering", "docker"],
"shell": "/bin/bash",
"state": "present",
"temp_password": null
}
]
Optional: Use AI to normalize messy HR exports
If the HR export has inconsistent columns or free-text roles, ask an LLM to transform it into your strict JSON schema. Two portable patterns:
- OpenAI-compatible API (replace endpoint/model/key as appropriate):
export AI_API_ENDPOINT="https://your-llm-endpoint/v1/chat/completions"
export AI_API_KEY="YOUR_API_KEY"
raw_input="$(cat hr_export.txt | jq -Rs .)"
curl -s \
-H "Authorization: Bearer $AI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model",
"temperature": 0,
"messages": [
{"role":"system","content":"You normalize HR user data. Output JSON only: an array of objects with fields: username (lowercase a-z0-9._-), full_name, groups (array of strings), shell, state (present|absent), temp_password (string|null). If missing values, infer conservatively or set null."},
{"role":"user","content": '"$raw_input"'}
]
}' \
"$AI_API_ENDPOINT" \
| jq -r '.choices[0].message.content' > users.json
- Local LLM via Ollama (example; install Ollama per their docs, then run a model like mistral):
prompt="Normalize to JSON array with fields: username, full_name, groups[], shell, state, temp_password (null if missing). Output JSON only.
$(cat hr_export.txt)"
curl -s http://localhost:11434/api/generate -d "{
\"model\": \"mistral\",
\"prompt\": $(jq -Rs '.' <<< \"$prompt\"),
\"options\": {\"temperature\": 0}
}" | jq -r '.response' > users.json
Always review AI output before applying. Treat AI as a helpful parser, not an oracle.
Step 2: Turn the desired state into an idempotent Bash plan
The following Bash script reads a CSV (via Miller) or JSON and prints a plan. By default it runs in dry-run mode; pass --apply to execute. It also:
Creates missing groups
Creates or updates users
Adds users to groups (non-destructively)
Removes users marked absent
Save as ai_user_plan.sh and make executable.
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
usage() {
echo "Usage: $0 (--csv users.csv | --json users.json) [--apply]"
exit 1
}
[[ $# -lt 2 ]] && usage
INPUT_FMT=""
INPUT_FILE=""
APPLY=0
while [[ $# -gt 0 ]]; do
case "$1" in
--csv) INPUT_FMT="csv"; INPUT_FILE="$2"; shift 2;;
--json) INPUT_FMT="json"; INPUT_FILE="$2"; shift 2;;
--apply) APPLY=1; shift;;
*) usage;;
esac
done
need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing: $1" >&2; exit 1; }; }
need jq
need getent
need useradd
need usermod
need userdel
need groupadd
need chpasswd
need logger
to_json() {
if [[ "$INPUT_FMT" == "csv" ]]; then
need mlr
mlr --icsv --ojson cat "$INPUT_FILE"
else
cat "$INPUT_FILE" | jq '.'
fi
}
say() { echo "$@"; logger -t ai-user-mgmt "$*"; }
safe_username() {
# allow a-z0-9._- and strip others
tr '[:upper:]' '[:lower:]' <<<"$1" | sed -E 's/[^a-z0-9._-]+//g'
}
json="$(to_json)"
echo "# Planning user changes (dry-run by default)"
echo "# Source: $INPUT_FILE"
apply() {
if [[ $APPLY -eq 1 ]]; then "$@"; else echo "+ $*"; fi
}
set_password() {
local user="$1" pass="$2"
if [[ -z "$pass" || "$pass" == "null" ]]; then return 0; fi
if [[ $APPLY -eq 1 ]]; then
printf "%s:%s\n" "$user" "$pass" | chpasswd
chage -d 0 "$user" || true
else
echo "+ chpasswd (redacted) | chage -d 0 $user"
fi
}
echo "$json" | jq -c '.[]' | while read -r rec; do
username_raw=$(jq -r '.username // empty' <<<"$rec")
full_name=$(jq -r '.full_name // ""' <<<"$rec")
shell=$(jq -r '.shell // "/bin/bash"' <<<"$rec")
state=$(jq -r '.state // "present"' <<<"$rec")
temp_password=$(jq -r '.temp_password // empty' <<<"$rec")
if [[ -z "$username_raw" ]]; then
echo "Skipping record with empty username: $rec" >&2
continue
fi
username="$(safe_username "$username_raw")"
groups_csv=$(jq -r '[.groups[]?] | join(",")' <<<"$rec")
groups_csv="${groups_csv:-}"
exists=0
getent passwd "$username" >/dev/null 2>&1 || exists=1
if [[ "$state" == "absent" ]]; then
if [[ $exists -eq 0 ]]; then
apply userdel -r "$username" || true
say "User removed: $username"
else
echo "# already absent: $username"
fi
continue
fi
# Ensure groups exist
if [[ -n "$groups_csv" ]]; then
IFS=',' read -r -a glist <<<"$groups_csv"
for g in "${glist[@]}"; do
[[ -z "$g" ]] && continue
if ! getent group "$g" >/dev/null 2>&1; then
apply groupadd -f "$g"
say "Group ensured: $g"
fi
done
fi
if [[ $exists -ne 0 ]]; then
# Create user
if [[ -n "$groups_csv" ]]; then
apply useradd -m -c "$full_name" -s "$shell" -G "$groups_csv" "$username"
else
apply useradd -m -c "$full_name" -s "$shell" "$username"
fi
set_password "$username" "$temp_password"
say "User created: $username"
else
# Update shell/comment; add to groups (non-destructive)
apply usermod -c "$full_name" -s "$shell" "$username"
if [[ -n "$groups_csv" ]]; then
apply usermod -a -G "$groups_csv" "$username"
fi
# Optional: only set password if provided
set_password "$username" "$temp_password"
say "User updated: $username"
fi
done
Try a dry-run first:
chmod +x ai_user_plan.sh
./ai_user_plan.sh --csv users.csv
Then apply:
sudo ./ai_user_plan.sh --csv users.csv --apply
Tip: For JSON input:
./ai_user_plan.sh --json users.json --apply
Step 3: Review and guardrails
Before you ever run with --apply:
Version-control your input files (CSV/JSON) and the script.
Run in dry-run mode and store the plan:
./ai_user_plan.sh --csv users.csv | tee plan.txt
Sanity-check usernames and shells. The script sanitizes usernames to a-z0-9._-, but you should still review group names and shells.
Run it on a staging VM first.
Security hardening tips:
Avoid embedding plaintext passwords in files. If you must, restrict permissions (chmod 600) and consider generating one-time passwords out-of-band.
Prefer locked accounts and SSO where possible; leave temp_password empty.
Use sudo with precise rules so the script can run only the needed commands.
Step 4: Schedule and observe
Use cron/cronie to run nightly with a fresh, normalized file:
- Example cron entry (Debian/openSUSE):
# m h dom mon dow user command
15 1 * * * root /usr/local/sbin/ai_user_plan.sh --json /srv/provision/users.json --apply >> /var/log/ai-user-mgmt.log 2>&1
- Example crontab (RHEL/Fedora: service is crond; same crontab syntax):
15 1 * * * root /usr/local/sbin/ai_user_plan.sh --json /srv/provision/users.json --apply >> /var/log/ai-user-mgmt.log 2>&1
Logs also go to syslog via logger -t ai-user-mgmt for central collection.
Real-world example: From messy HR to executed plan
1) HR sends this (messy) snippet:
Name,Login,Sector,Needs Docker?,Status
"Jane D.","JDoe","Engineering","yes","active"
"Dan Lee","dlee ","Engineering","no","terminated"
2) AI normalization prompt (OpenAI-compatible flow shown; adjust to your API):
export AI_API_ENDPOINT="https://your-llm-endpoint/v1/chat/completions"
export AI_API_KEY="YOUR_API_KEY"
cat > prompt.txt <<'EOF'
Transform the following CSV into a JSON array with fields:
- username (lowercase a-z0-9._-; derive from "Login" trimming spaces)
- full_name (from "Name")
- groups (array; include "engineering" if Sector == Engineering; include "docker" if Needs Docker? == yes)
- shell ("/bin/bash" default)
- state: "present" if Status == active else "absent"
- temp_password: null
Output JSON only.
EOF
raw="$(jq -Rs . < <(cat prompt.txt; echo; cat hr.csv))"
curl -s -H "Authorization: Bearer $AI_API_KEY" -H "Content-Type: application/json" -d '{
"model":"your-model","temperature":0,
"messages":[{"role":"user","content": '"$raw"'}]
}' "$AI_API_ENDPOINT" | jq -r '.choices[0].message.content' > users.json
3) Dry-run the plan:
./ai_user_plan.sh --json users.json
Sample output:
# Planning user changes (dry-run by default)
# Source: users.json
+ groupadd -f engineering
+ groupadd -f docker
+ useradd -m -c "Jane D." -s /bin/bash -G engineering,docker jdoe
+ chpasswd (redacted) | chage -d 0 jdoe
User created: jdoe
+ userdel -r dlee
User removed: dlee
4) Apply when satisfied:
sudo ./ai_user_plan.sh --json users.json --apply
Governance and safety checklist
Backups: snapshot /etc/passwd, /etc/group, and home directories before first run.
Audit: keep inputs, dry-run plans, and logs in version control or an audit bucket.
Idempotence: schedule frequent runs; re-applications won’t harm existing users.
Rollback: for accidental removals, restore from backup and re-run with corrected input.
Conclusion and Call to Action
You don’t need a giant IAM product to get safer, faster Linux user management. Let AI shape messy inputs into a clean desired state, and let Bash execute small, reviewable, idempotent changes.
Next steps:
Install jq, miller, curl, and cron/cronie using your package manager.
Save the ai_user_plan.sh script and run a dry-run against a lab VM.
Add an AI normalization step if your HR data is messy.
Wire it into cron and start capturing logs.
When you’re ready, extend this pattern: enforce exact group membership, add systemd timers, or integrate with your ticketing system. One small script—big impact.