Server Tools v2 is a modular Bash toolkit for managing Debian/Ubuntu servers. It replaces the monolithic predecessor (v1, single file with ~900 lines) with a clean, testable architecture consisting of 15 specialized libraries and over 5,900 lines of code.
Server Tools v2 automates recurring server administration tasks - from database management to virtual host creation to SSL certificates and SSH user management. Saves an average of 15-30 minutes per administration task.
The tool offers two interfaces:
server-tools db create --name mydb --user myuser)server-tools without arguments)| Aspect | v1 | v2 |
|---|---|---|
| Architecture | Single file (~900 lines) | 15 modular libraries (~5,900 LOC) |
| Language | German (output) | English |
| Input validation | Minimal | Comprehensive (13 types) |
| Audit logging | None | Complete |
| Backups | None | Auto-backup before delete operations |
| Tests | None | 315 BATS tests |
| CI/CD | None | ShellCheck + shfmt + BATS |
| Security | Basic | Comprehensive (escaping, path traversal protection, ACLs) |
| Rollback | None | On failures in multi-step operations |
.sql, .sql.gz, .sql.zip)/etc/shellsSSH User Management uses POSIX ACLs for access control: The user owns the
html/files,www-datagets rwX access. Forlogs/it's reversed -www-dataowns the logs, the user gets read access.
/etc/cron.d/ (not crontab)Server Tools v2 follows the Composable Building Blocks pattern: Each library consists of atomic functions (building blocks) that are composed by high-level operations.
| Layer | Modules | Purpose |
|---|---|---|
| Foundation | core, config, security | Logging, configuration, input validation |
| Infrastructure | backup | Backup creation and management |
| Services | database, vhost, ssl, cron, firewall, fail2ban, log, status, user | Business operations |
| Interface | cli, menu | User interaction (CLI or TUI) |
Using database.sh as an example:
Building blocks are pure functions without side effects (no logging, no validation). High-level operations add validation, logging, and rollback. This makes the blocks individually testable and reusable.
| Requirement | Version | Notes |
|---|---|---|
| Operating System | Debian 11+, Ubuntu 20.04+ | Other distros not tested |
| Bash | 5.0+ | Check with bash --version |
| Root access | Required | All operations require root |
| Apache | 2.4+ | apache2ctl -v |
| MySQL/MariaDB | 5.7+ / 10.3+ | mysql --version |
| OpenSSL | 1.1+ | For password generation |
| Certbot | Optional | Auto-installed when using SSL features |
| UFW | Optional | Only for firewall management |
| Fail2Ban | Optional | Only for Fail2Ban management |
| acl (setfacl/getfacl) | Optional | Only for SSH user management |
Root access is mandatory. The tool checks
EUID == 0at startup and exits if insufficient privileges are detected.
# Clone repository and checkout latest version
git clone https://github.com/markus-michalski/server-tools-v2.git
cd server-tools-v2
git checkout "$(git tag -l 'v*' | sort -V | tail -1)"
# Install as root
sudo ./bin/server-tools install
The install command:
/etc/server-tools/ with default config (permissions 600)/root/db-credentials/ (umask 077)/root/server-tools-backups/ (chmod 700)/usr/local/lib/server-tools//usr/local/bin/server-tools (chmod 700)st and servertools# Clone repository
git clone https://github.com/markus-michalski/server-tools-v2.git
cd server-tools-v2
# Run directly (auto-detects local libs)
sudo ./bin/server-tools
After installation, the tool is accessible via three commands:
server-tools,st(shorthand) andservertools.
cd server-tools-v2
git fetch --tags
git checkout "$(git tag -l 'v*' | sort -V | tail -1)"
sudo ./bin/server-tools install
# Edit config file
sudo nano /etc/server-tools/config
# Create MySQL root credentials
sudo nano /root/.my.cnf
Contents of /root/.my.cnf:
[client]
password=your_mysql_root_password
# Set permissions
sudo chmod 600 /root/.my.cnf
server-tools [--yes|-y] [--quiet|-q] <command> <subcommand> [options]
| Flag | Short | Description |
|---|---|---|
--yes |
-y |
Auto-accept confirmations |
--quiet |
-q |
Only output errors |
--version |
-v |
Show version number |
--config |
-c |
Show active configuration |
--help |
-h |
Show help |
# Full setup: DB + user + password
server-tools db create --name shop_production --user shop_user
# With custom password
server-tools db create --name shop_production --user shop_user --password 'MyS3cur3Pass!'
# DB for existing user
server-tools db create-for-user --name shop_staging --user shop_user
# Simple VHost with PHP 8.3
server-tools vhost create --domain example.com --php 8.3
# With aliases and custom document root
server-tools vhost create --domain example.com \
--aliases "www.example.com cdn.example.com" \
--php 8.2 \
--docroot /var/www/example.com/public
# Create user for domain
server-tools user create --domain example.com --username exampledev
# Add SSH key
server-tools user add-key --username exampledev \
--key "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@laptop"
# Set password (or let it auto-generate)
server-tools user set-password --username exampledev
# Standard certificate
server-tools ssl create --domain example.com --email admin@example.com
# Wildcard via Cloudflare DNS
server-tools ssl wildcard --domain example.com --provider cloudflare
# 1. Create VHost
server-tools vhost create --domain shop.example.com --php 8.3
# 2. Enable SSL
server-tools ssl create --domain shop.example.com
# 3. Create database
server-tools db create --name shop_db --user shop_user
# 4. Set up SSH user for developer
server-tools user create --domain shop.example.com --username shopdev
server-tools user add-key --username shopdev --key "ssh-ed25519 AAAA..."
# 5. Open firewall
server-tools firewall allow --port 443
| Command | Description |
|---|---|
db create --name <db> --user <user> [--password <pass>] |
Create DB + user |
db create-for-user --name <db> --user <user> |
Create DB for existing user |
db delete --name <db> [--user <user>] [--drop-user] |
Delete DB (optionally with user) |
db backup --name <db> |
Backup single DB |
db backup-all |
Backup all user databases |
db restore --name <db> --file <path> |
Restore DB from dump |
db import --name <db> --file <path> |
Import SQL file (.sql, .sql.gz, .sql.zip) |
db export --name <db> [--output <path>] |
Export DB to file |
db list |
List all databases and users |
db info --name <db> |
DB details (size, tables, users) |
db grant --name <db> --user <user> [--level all\|readonly\|readwrite] |
Assign permissions |
db grants --user <user> |
Show all grants for a user |
| Command | Description |
|---|---|
vhost create --domain <d> [--php <v>] [--aliases <list>] [--docroot <p>] |
Create VHost |
vhost delete --domain <domain> |
Delete VHost |
vhost list |
List all VHosts |
vhost php --domain <domain> --version <php> |
Change PHP version |
vhost info --domain <domain> |
Show VHost details |
vhost redirect --from <domain> --to <url> [--code 301\|302] |
Create redirect |
VHost deletion is blocked when an SSH user is assigned to the domain. Delete the user first, then the VHost.
| Command | Description |
|---|---|
user create --domain <d> --username <u> |
Create SSH user for domain |
user delete --username <u> |
Delete user (ownership reverts to www-data) |
user list |
List all domain users |
user add-key --username <u> --key "ssh-ed25519 ..." |
Add SSH key |
user set-password --username <u> [--password <p>] |
Set/generate password |
user info --username <u> |
Show user details |
| Command | Description |
|---|---|
ssl create --domain <domain> [--email <email>] |
Create certificate |
ssl delete --domain <domain> |
Delete certificate |
ssl wildcard --domain <domain> [--provider <name>] |
Wildcard SSL via DNS |
ssl list |
List all certificates |
ssl check [--days <n>] |
Check expiring certificates |
ssl renew |
Set up renewal cronjob |
| Command | Description |
|---|---|
cron add --schedule '<cron>' --command '<cmd>' --name '<name>' |
Add cronjob |
cron remove --pattern <search> |
Remove cronjob |
cron list |
List all cronjobs |
| Command | Description |
|---|---|
firewall status |
Show UFW status |
firewall allow --port <port> [--proto tcp\|udp] |
Allow port |
firewall deny --port <port> [--proto tcp\|udp] |
Deny port |
Alias
fwcan be used instead offirewall, e.g.server-tools fw status.
| Command | Description |
|---|---|
fail2ban status |
Show all jails with statistics |
fail2ban banned |
List all banned IPs |
fail2ban unban --ip <address> |
Unban IP from all jails |
Alias
f2bcan be used instead offail2ban.
| Command | Description |
|---|---|
logs apache [--domain <d>] [--lines <n>] |
Apache access log |
logs apache-errors [--domain <d>] [--lines <n>] |
Apache error log |
logs mysql [--lines <n>] |
MySQL error log |
logs audit [--lines <n>] [--filter <text>] |
Audit log |
logs search --pattern <text> [--lines <n>] |
Cross-log search |
server-tools status
Shows: services, system resources, versions, configuration, backups.
Without arguments, server-tools launches the interactive TUI menu:
+=======================================+
| Server Tools v2.2.0 |
+=======================================+
1) Database Management
2) Virtual Host Management
3) SSL Certificate Management
4) Cron Job Management
5) Firewall Management
6) Fail2Ban Management
7) Log Viewer
8) SSH User Management
9) System Status
10) Exit
Select option [1-10]:
Each menu item opens a submenu with all available operations. The Database menu, for example, offers 17 options for all database operations.
The interactive menu is ideal for manual administration. For scripting and automation, use CLI mode.
Path: /etc/server-tools/config (or ST_CONFIG_FILE environment variable)
All variables start with the ST_ prefix and have sensible defaults:
| Variable | Default | Description |
|---|---|---|
ST_CONFIG_FILE |
/etc/server-tools/config |
Config file path |
ST_AUDIT_LOG |
/var/log/server-tools-audit.log |
Audit log file |
ST_AUDIT_LOGGING |
true |
Enable audit logging |
| Variable | Default | Description |
|---|---|---|
ST_CREDENTIAL_DIR |
/root/db-credentials |
Credential storage location |
ST_CREDENTIAL_FILE_PERMISSIONS |
600 |
File permissions for credentials (600 or 400) |
ST_MYSQL_CONFIG_FILE |
/root/.my.cnf |
MySQL root credentials |
ST_DEFAULT_CHARSET |
utf8mb4 |
Default character set |
ST_DEFAULT_COLLATION |
utf8mb4_unicode_ci |
Default collation |
ST_PASSWORD_LENGTH |
25 |
Generated password length (12-64) |
ST_PASSWORD_MIN_LENGTH |
12 |
Minimum password length (min. 8) |
| Variable | Default | Description |
|---|---|---|
ST_BACKUP_DIR |
/root/server-tools-backups |
General backup directory |
ST_DB_BACKUP_DIR |
/root/db-backups |
DB dump directory |
ST_AUTO_BACKUP |
true |
Auto-backup before delete operations |
ST_BACKUP_RETENTION_DAYS |
30 |
Retention period in days (1-365) |
| Variable | Default | Description |
|---|---|---|
ST_DEFAULT_PHP_VERSION |
8.3 |
Default PHP version |
ST_PHP_VERSIONS_TO_SCAN |
7.4 8.0 8.1 8.2 8.3 8.4 |
FPM socket scan |
ST_APACHE_SERVER_ADMIN |
webmaster@localhost |
ServerAdmin entry |
ST_ALLOWED_DOCROOT_PATHS |
/var/www:/srv/www |
Allowed document root paths |
ST_APACHE_LOG_DIR |
/var/log/apache2 |
Apache log directory |
ST_LOGROTATE_DAYS |
14 |
Max log age |
ST_LOGROTATE_ROTATE |
52 |
Rotation count (~1 year) |
| Variable | Default | Description |
|---|---|---|
ST_CERTBOT_EMAIL |
(empty) | Let's Encrypt email |
ST_DNS_PROVIDER |
(empty) | DNS provider for wildcard SSL |
ST_DNS_CREDENTIALS_FILE |
(empty) | DNS API credentials file |
| Variable | Default | Description |
|---|---|---|
ST_USER_DEFAULT_SHELL |
/bin/bash |
Default shell for new users |
| Variable | Default | Description |
|---|---|---|
ST_LOG_LINES |
50 |
Default number of log lines |
ST_MYSQL_LOG_FILE |
/var/log/mysql/error.log |
MySQL error log path |
# /etc/server-tools/config
# Database
ST_DEFAULT_CHARSET="utf8mb4"
ST_DEFAULT_COLLATION="utf8mb4_unicode_ci"
ST_PASSWORD_LENGTH=30
# Apache
ST_DEFAULT_PHP_VERSION="8.3"
ST_APACHE_SERVER_ADMIN="admin@example.com"
# Backup
ST_AUTO_BACKUP=true
ST_BACKUP_RETENTION_DAYS=60
# SSL
ST_CERTBOT_EMAIL="admin@example.com"
ST_DNS_PROVIDER="cloudflare"
# SSH Users
ST_USER_DEFAULT_SHELL="/bin/bash"
The config file is loaded via
source. When running as root, Server Tools checks owner and permission: The file must be owned by root and must not be world-readable or group-writable.
All user input is validated before processing. 13 validation types are available:
| Type | Validation | Example |
|---|---|---|
domain |
RFC 1035, labels 1-63 chars, TLD 2+ letters | example.com |
database |
Max 64 chars, [a-zA-Z0-9_-] |
shop-production |
username |
Max 32 chars, [a-zA-Z0-9_-] |
shop-user |
linux_username |
POSIX-compliant, lowercase, max 32, no leading digit | shopdev |
ssh_public_key |
No newlines, 40-8192 chars, ssh-rsa/ed25519/ecdsa/sk-* | ssh-ed25519 AAAA... |
path |
Path traversal protection via realpath + whitelist check |
/var/www/shop |
email |
Basic RFC 5322 | admin@example.com |
password |
Length check (configurable, min 8) | |
php_version |
Whitelist: 7.4, 8.0-8.4 | 8.3 |
cron_schedule |
5-field cron regex, max 500 chars | 0 3 * * * |
port |
1-65535, numeric | 443 |
protocol |
tcp or udp | tcp |
ip_address |
IPv4 with octet validation (0-255) | 192.168.1.1 |
url |
http/https with valid host | https://example.com |
mysql_escape() + backtick quotingrealpath + whitelist (ST_ALLOWED_DOCROOT_PATHS)safe_write_file() (mktemp + mv, TOCTOU-safe, symlink check).my.cnf permissionsopenssl rand -base64 (~6 bits/char entropy)[2026-02-18 14:32:15] [INFO] user=root action=create_database db=shop_production user=shop_user
[2026-02-18 14:33:01] [INFO] user=root action=create_vhost domain=shop.example.com php=8.3
[2026-02-19 10:15:42] [INFO] user=root action=create_user username=shopdev domain=shop.example.com
[2026-02-18 14:35:22] [WARNING] user=root action=delete_database db=old_staging backup=true
All destructive operations (delete, modify) automatically create a backup when
ST_AUTO_BACKUP=true(default). If the backup fails, you are asked whether to proceed.
Symptom: bash: ./bin/server-tools: Permission denied
Check:
ls -la bin/server-tools
Solution:
chmod +x bin/server-tools
Symptom: Script exits immediately
Solution:
sudo server-tools <command>
# or
sudo su -
server-tools <command>
Symptom: ERROR: MySQL connection failed
Check:
# MySQL running?
systemctl status mysql
# Credentials present?
cat /root/.my.cnf
# Manual test
mysql -e "SELECT 1"
Solution:
# Create credentials
echo -e "[client]\npassword=YOUR_ROOT_PASSWORD" > /root/.my.cnf
chmod 600 /root/.my.cnf
Symptom: ERROR: Apache configuration test failed
Check:
apache2ctl configtest
Solution: The configtest output shows the error. Common causes:
# Check PHP-FPM
systemctl status php8.3-fpm
# Enable modules
a2enmod headers proxy_fcgi
systemctl reload apache2
Symptom: ERROR: ACL support not available or setfacl: command not found
Check:
# ACL tools installed?
which setfacl getfacl
# Filesystem supports ACLs?
mount | grep acl
Solution:
# Install ACL package
apt-get install acl
# If ACLs not enabled in mount
mount -o remount,acl /var/www
Symptom: WARNING: Config file is world-readable
Solution:
chmod 600 /etc/server-tools/config
If your problem isn't listed here: The audit log at
/var/log/server-tools-audit.logcontains details about all executed operations and any errors encountered.
server-tools-v2/
├── bin/
│ └── server-tools # Entry point (~300 LOC)
├── lib/
│ ├── core.sh # Logging, error handling, dependencies (122 LOC)
│ ├── config.sh # Configuration, defaults, validation (161 LOC)
│ ├── security.sh # Input validation, escaping, audit (254 LOC)
│ ├── backup.sh # Backup creation and cleanup (106 LOC)
│ ├── database.sh # MySQL CRUD (885 LOC)
│ ├── vhost.sh # Apache VHosts, logrotate, redirects (666 LOC)
│ ├── ssl.sh # Let's Encrypt, wildcard SSL (455 LOC)
│ ├── cron.sh # Cronjob management via /etc/cron.d (165 LOC)
│ ├── firewall.sh # UFW firewall (189 LOC)
│ ├── fail2ban.sh # Fail2Ban jails (158 LOC)
│ ├── log.sh # Log viewer (241 LOC)
│ ├── status.sh # Service status, system resources (167 LOC)
│ ├── user.sh # Per-domain SSH user management (464 LOC)
│ ├── cli.sh # CLI interface (916 LOC)
│ └── menu.sh # TUI interface (712 LOC)
├── conf/
│ └── server-tools.conf.example # Config template
├── tests/
│ ├── test_helper.bash # BATS helper with mock system
│ └── unit/ # 13 test files, 315 tests
├── .github/workflows/
│ └── ci.yml # CI: ShellCheck + shfmt + BATS
├── Makefile
├── README.md
├── CHANGELOG.md
└── CONTRIBUTING.md
Each module protects against multiple loading:
[[ -n "${_DATABASE_SOURCED:-}" ]] && return
_DATABASE_SOURCED=1
| Exit Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error / validation failed |
Server Tools uses
set -euo pipefailin the entry point. All errors go throughdie()which exits with code 1 and logs the error.
Tests use BATS (Bash Automated Testing System) with:
bats-support - Base assertionsbats-assert - Extended assertionsbats-file - File assertions315 tests in 13 files cover all modules:
| Test File | Topic |
|---|---|
cli.bats |
CLI argument parsing, routing, help texts |
database.bats |
MySQL building blocks, credentials, validation |
security.bats |
Input validation (all 13 types), escaping, audit, password |
vhost.bats |
Config generation, redirects, logrotate, validation |
ssl.bats |
Certbot integration, wildcard, email fallback |
config.bats |
Config loading, validation, defaults |
backup.bats |
Backup creation, cleanup, auto-backup |
core.bats |
Logging, error handling, root check |
cron.bats |
Cron name sanitizing, content generation, validation |
log.bats |
tail/grep, log paths, cross-log search |
firewall.bats |
UFW wrapper, port/protocol validation |
fail2ban.bats |
Jail parsing, ban/unban, IP validation |
status.bats |
Service checks, system resources |
# Run all tests
make test
# Test single module
bats tests/unit/database.bats
# With verbose output
bats --verbose-run tests/unit/
Each test runs in an isolated TEST_TMPDIR with mocked system commands. No test touches the real system.
The GitHub Actions pipeline runs on every push and pull request:
.sh files-bn -ci)| Target | Description |
|---|---|
make help |
Show help |
make install |
System-wide installation (root) |
make uninstall |
Uninstall (root) |
make setup-tests |
Install BATS dependencies |
make test |
Run tests |
make test-verbose |
Run tests with trace output |
make lint |
Run ShellCheck |
make format |
Auto-format with shfmt |
make format-check |
Format check without changes |
make check |
lint + format-check + test |
make clean |
Remove test artifacts |
Can I use Server Tools on CentOS/RHEL?
No, currently Debian/Ubuntu only. Package manager calls (apt-get) and paths (/etc/apache2) are Debian-specific.
Will existing VHost configurations be overwritten?
No. create_vhost checks if the VHost exists beforehand and aborts if it does.
What happens to my data if I uninstall Server Tools?
Nothing. Server Tools only manages configurations and creates backups. Databases, VHosts, and SSL certificates remain intact.
Can I run v1 and v2 side by side?
Yes, the binary names are compatible. After installing v2, the symlinks overwrite the v1 installation. The old script can still be called directly.
How do I back up all databases automatically?
# Manual
server-tools db backup-all
# As daily cronjob
server-tools cron add \
--schedule '0 2 * * *' \
--command '/usr/local/bin/server-tools db backup-all --yes --quiet' \
--name 'db-daily-backup'
Does Server Tools support PostgreSQL?
No, MySQL/MariaDB only.
Can multiple developers access the same domain user?
Yes. SSH User Management uses a 1:1 mapping (one user per domain). Multiple developers can add separate SSH keys to the same user and thus share access to the domain.
Do I need chroot for SSH user isolation?
No. Server Tools uses POSIX ACLs instead of chroot. This is simpler to manage and sufficient for most use cases. The ACLs ensure that users can only access their assigned domain.
License: MIT | Current Version: 2.2.0 (February 19, 2026)
The complete changelog with all versions is available on GitHub:
CHANGELOG.md on GitHub