Shell Scripts
Bash/shell scripts for automation, backup operations, and system administration tasks.
Prerequisites
- Bash 4.0+ or Zsh
curlfor downloading scriptskubectl(for Kubernetes scripts)jq(optional, for JSON processing)
Quick Start
# Run directly (one-time use)
curl -s https://raw.githubusercontent.com/this-is-tobi/tools/main/shell/<script_name> | bash -s -- --help
# Download and run (repeated use)
curl -fsSL https://raw.githubusercontent.com/this-is-tobi/tools/main/shell/<script_name> -o script.sh
chmod +x script.sh
./script.sh --helpAvailable Scripts
| Script | Description |
|---|---|
| backup-kube-mariadb.sh | backup / restore mariadb database from / to a kubernetes pod. |
| backup-kube-pg.sh | backup / restore postgres database from / to a kubernetes pod. |
| backup-kube-qdrant.sh | backup / restore qdrant collection snapshots from / to a kubernetes pod. |
| backup-kube-vault.sh | backup / restore vault raft cluster from / to a kubernetes pod. |
| clone-subdir.sh | clone a subdirectory from a git repository. |
| compose-to-matrix.sh | parse docker-compose file to create github matrix. |
| delete-ghcr-image.sh | delete image and subsequent manifests from ghcr. |
| eol-infos.sh | get package end of life infos. |
| export-argocd-resources.sh | export ready-to-apply argocd resources. |
| export-kube-resources.sh | export ready-to-apply kubernetes resources. |
| github-create-app.sh | create a github application. |
| github-create-ruleset.sh | create a github rulesets for a given repository. |
| github-configure-repo.sh | configure github repository security best practices. |
| helm-template.sh | generate helm template. |
| init-env-files.sh | init '.env' and '.yaml' example files by copying them without 'example'. |
| keycloak-add-clients.sh | add keycloak clients for a given keycloak realm. |
| keycloak-add-users.sh | add keycloak users for a given keycloak realm. |
| keycloak-check-tac.sh | check how many users have accepted terms and conditions in a keycloak realm. |
| keycloak-extract-users.sh | extract all users from a keycloak realm to a CSV file. |
| keycloak-get-token.sh | display keycloak token for the given infos. |
| keycloak-list-users.sh | list keycloak users for a given keycloak realm. |
| keycloak-required-tac.sh | add terms and conditions required action to all realm users. |
| kube-generate-token.sh | generate a kubernetes token / kubeconfig with a given service account / RBAC. |
| manage-etc-hosts.sh | add or update host ip adress in /etc/hosts. |
| monitor-kube-cnpg.sh | generate and print cnpg monitoring report. |
| monitor-kube-qdrant.sh | generate and print qdrant monitoring report. |
| monitor-kube-redis.sh | generate and print redis monitoring report. |
| monitor-kube-vault.sh | generate and print vault monitoring report. |
| purge-ghcr-tags.sh | purge ghcr tags older than a given date. |
| setup-ai-agent.sh | bootstrap Claude Code / Copilot instructions, skills, agents, and safely run rtk init -g --copilot. |
| trivy-report.sh | parse trivy json reports to create a markdown summary. |
| update-zsh-completions.sh | update zsh-completions sources. |
TIP
Using a script directly from a curl command:
curl -s https://raw.githubusercontent.com/this-is-tobi/tools/main/shell/<script_name> | bash -s -- -hUsage Examples
Backup Operations
The backup scripts never put a password or token on a command line (where ps and the Kubernetes API audit log would show it): it reaches the pod on stdin, or the local client through its usual environment variable. Pass it through that variable rather than the -p / -a flag, which stays visible in the local process list. The *_forward modes forward a random free local port, so a database running on your machine is never touched, and every failure (dump, copy, port forward) makes the script exit non-zero instead of reporting success.
| Script | Secret variable | Flag |
|---|---|---|
backup-kube-pg.sh | PGPASSWORD | -p |
backup-kube-mariadb.sh | MYSQL_PWD | -p |
backup-kube-vault.sh | VAULT_TOKEN | -p |
backup-kube-qdrant.sh | QDRANT_API_KEY | -a |
PostgreSQL:
read -rs PGPASSWORD && export PGPASSWORD # prompts without echo, stays out of the shell history
./backup-kube-pg.sh -m dump -n my-namespace -t my-postgres-pod -d mydatabase -u myuser -o ./backups
./backup-kube-pg.sh -m restore_forward -n my-namespace -t svc/my-postgres -d mydatabase -u myuser -f ./backups/20240101T1200-mydatabase.dump -zMariaDB:
read -rs MYSQL_PWD && export MYSQL_PWD
./backup-kube-mariadb.sh -m dump_forward -n my-namespace -t my-mariadb-pod -d mydatabase -o ./backupsVault:
read -rs VAULT_TOKEN && export VAULT_TOKEN
./backup-kube-vault.sh -m dump -n vault -t vault-0 -o ./backups
./backup-kube-vault.sh -m restore_forward -n vault -t vault-0 -f ./backups/20240101T1200-vault.snapQdrant (snapshots go through the HTTP API over a port forward, the former dump / restore modes are gone):
read -rs QDRANT_API_KEY && export QDRANT_API_KEY
./backup-kube-qdrant.sh -m dump_forward -n my-namespace -t qdrant-0 -o ./backups
./backup-kube-qdrant.sh -m restore_forward -n my-namespace -t qdrant-0 -f ./backups/20240101T1200-qdrantMonitoring
# PostgreSQL (CloudNativePG) cluster report
./monitor-kube-cnpg.sh -n database-namespace -c my-cluster
# Vault health check
./monitor-kube-vault.sh -n vault -v vault
# Redis cluster status
./monitor-kube-redis.sh -n redis-namespace -r my-redis
# Qdrant cluster status
./monitor-kube-qdrant.sh -n qdrant-namespace -q my-qdrantResource Management
# Export Kubernetes resources (files are created readable by you only: they can hold secrets)
./export-kube-resources.sh -k all -n my-namespace -o ./exported
# Export ArgoCD applications
./export-argocd-resources.sh -k all -n argocd -o ./argocd-backup
# Generate a service account token (24h by default, read-only role without access to secrets)
./kube-generate-token.sh -n default -s my-service-account
# Write a kubeconfig file (mode 600); add -S to also grant read access to secrets
./kube-generate-token.sh -n default -s my-service-account -d 8h -f kubeconfig -o ~/.kube/config.d/my-service-accountkube-generate-token.sh prints only the token or kubeconfig on stdout, progress goes to stderr.
Helm Chart Scaffolding
# Generate a chart with a base service and two more; pin the template with HELM_CHARTS_REF=<branch|tag|sha>
./helm-template.sh -c my-chart -s api -a worker -a client -o ./my-chartGitHub Repository Settings
The GitHub scripts read the token from GH_TOKEN, then GITHUB_TOKEN, then gh auth token. The -t flag still works but exposes the token in the process list and the shell history.
# Merge strategy, Dependabot alerts, secret scanning, Actions policy (all actions allowed,
# pinned to a full commit SHA), read-only workflow token, tag protection for every tag
./github-configure-repo.sh -o owner -r repo -T '*'
# Only allow GitHub-owned actions and some patterns instead
./github-configure-repo.sh -o owner -r repo -a selected -L 'docker/*,owner/github-workflows/*'
# Classic branch protection on 'main'
./github-create-ruleset.sh -o owner -r repo -b main
# Repository ruleset on the default branch: required checks, pull requests without
# mandatory approval, repository admins may bypass
./github-create-ruleset.sh -o owner -r repo -m ruleset -c 'ci / lint,ci / test' -A 0 -a false
# Register a GitHub App (manifest flow: the browser opens, paste back the address GitHub redirects to)
./github-create-app.sh -n my-app -o owner -p '{"contents":"read"}' -w https://example.com/webhook -e pushgithub-configure-repo.sh leaves Dependabot security pull requests off by default (-x true turns them on when Renovate does not handle vulnerabilities). With -H true (the default), every action and reusable workflow must be referenced by a full commit SHA; it is not documented whether GitHub also enforces this for actions called from inside a composite action, so turn it off with -H false if a run fails on such an action. Both scripts update existing rulesets in place, so they can be run again.
GitHub Container Registry
# Delete the version carrying exactly this tag and the untagged manifests it references
# (-n: dry run; -f: also when the version carries other tags, such as 'latest')
./delete-ghcr-image.sh -o owner -i image-name -t tag -n
# Purge versions older than a date (dry run first). Release tags ('latest', '1.2.3',
# 'api-v1.2.3', ...), what they reference and their signatures are kept unless -a is given
./purge-ghcr-tags.sh -u owner -i image-name -d 2024-01-01 -nBoth scripts need docker buildx to list the manifests an image references (docker login ghcr.io for private images) and follow the API pagination.
Keycloak Management
The admin password is read from KC_PASSWORD (and the client secret of keycloak-get-token.sh from KC_CLIENT_SECRET); -p and -s still work but expose the value in the process list and the shell history. Passwords and tokens are handed to curl through stdin or a file descriptor, never as arguments.
read -rsp 'Password: ' KC_PASSWORD && export KC_PASSWORD
# Get access token
read -rsp 'Client secret: ' KC_CLIENT_SECRET && export KC_CLIENT_SECRET
./keycloak-get-token.sh -k https://keycloak.example.com -r my-realm -i client-id -u admin
# List users
./keycloak-list-users.sh -k https://keycloak.example.com -r my-realm -u admin
# Extract users to CSV (file created with mode 600, formula-like cells neutralized)
./keycloak-extract-users.sh -k https://keycloak.example.com -r my-realm -u admin -b 100 -o users.csv
# Add users or clients: inline JSON, '@file' or '-' (stdin) when they carry credentials
./keycloak-add-users.sh -k https://keycloak.example.com -r my-realm -u admin -d @users.json
./keycloak-add-clients.sh -k https://keycloak.example.com -r my-realm -u admin -c @clients.json
# Check terms acceptance
./keycloak-check-tac.sh -k https://keycloak.example.com -r my-realm -u adminDevelopment Tools
# Clone a subdirectory (or a single file): './output/dir' is created, nothing else is written
./clone-subdir.sh -u https://github.com/owner/repo -b main -s path/to/dir -o ./output
# Generate GitHub Actions matrix
./compose-to-matrix.sh -f docker-compose.yml -n owner/repo -t 1.0.0 -a
# Map a host name in /etc/hosts (uses sudo only when needed; -f targets another file)
./manage-etc-hosts.sh -i 192.168.1.100 -n example.localTroubleshooting
Permission Errors
# Make script executable
chmod +x script.sh
# Some scripts need sudo
sudo ./script.shKubernetes Connection
# Verify kubectl config
kubectl config current-context
kubectl cluster-info
# Check permissions
kubectl auth can-i get pods --all-namespacesGitHub Token Issues
# Reuse the gh CLI session (nothing to export)
gh auth login
# Or set the token in the environment without leaving it in the shell history
read -rs GH_TOKEN && export GH_TOKEN
# Required access: repository admin (configure-repo, create-ruleset),
# read:packages + delete:packages (ghcr scripts)Best Practices
- Always test with
--helpfirst - Log output for audit trails:
./script.sh 2>&1 | tee log.txt - Use absolute paths in cron jobs
- Never hardcode credentials
- Store tokens securely