Scripts
All scripts live in the install directory and are also reachable as
tulixhost-* in /usr/local/sbin. They all share lib/common.sh
for logging, validation, and templating. All write to /var/log/tulixhost/audit.log
when run as root.
setup.sh
Installs and hardens nginx, PHP-FPM, MariaDB, and Redis. Idempotent — safe to re-run.
sudo ./setup.sh
What it does:
- Detects Ubuntu via
/etc/os-releaseand refuses EOL releases. - Adds
ppa:ondrej/phpand the official MariaDB.org repo. - Installs latest available PHP-FPM (auto-picked by
apt-cacheversion sort) plus extensions:mysql redis opcache mbstring xml curl gd zip bcmath intl readline. - Installs MariaDB server + client, Redis 7+, nginx, and certbot.
- Configures MariaDB root via
unix_socketauth (no password to leak). - Writes server-wide MariaDB tunables to
/etc/mysql/mariadb.conf.d/99-tulixhost.cnf. - Hardens Redis:
bind 127.0.0.1 ::1, requirepass set to a random 32-char password,CONFIGrenamed to a per-install random name,aclfileenabled. - Creates global nginx snippets, a catch-all default vhost returning 444, and patches
nginx.confwithserver_tokens offplus three rate-limit zones. - Sets a global PHP-FPM hardening pass (disables expose_php, allow_url_include,
cgi.fix_pathinfo) and moves the default
www.confpool aside. - Configures ufw (deny incoming except OpenSSH + Nginx Full) and enables unattended security upgrades.
- Enables unattended-upgrades.
- Writes
/etc/tulixhost/tulixhost.confwith the discovered PHP version and the generated Redis admin password. - Symlinks each script into
/usr/local/sbin/tulixhost-*. - Installs a nightly
backup_allcron at 03:17 UTC and a logrotate policy.
create_vhost.sh
sudo ./create_vhost.sh SITE_NAME [--ssl=auto|./ssl|none]
[--demo]
[--force]
[--no-www]
[--server-name="alt1 alt2"]
Flags
| flag | effect |
|---|---|
--ssl=auto | Renders HTTP-only vhost, then runs certbot --nginx
to obtain a Let's Encrypt cert. On success, re-renders a managed HTTPS vhost pointing at the
certbot symlinks. |
--ssl=./ssl | Looks for ./ssl/SITE_NAME.crt and
./ssl/SITE_NAME.key in the cwd at create time. Installs them into
conf/ssl/. Also picks up SITE_NAME.fullchain/chain/bundle
if present. |
--ssl=none | HTTP only. Logs a warning. Not recommended for public sites. |
--demo | Installs the tulixhost demo dashboard into public/
instead of a minimal landing page. |
--force | Allow rewriting configs for an existing site. Does not clobber data, the DB, or Redis keys. |
--no-www | Skip adding www.SITE_NAME to server_name. |
--server-name="..." | Extra hostnames added to server_name and
included in the certbot request. |
Site-name rules
Site names must match ^[a-z][a-z0-9.-]{1,62}$, with no leading, trailing, or adjacent
. / -. The derived unix username is web_ + the site name with
. and - replaced by _ and must fit in 32 characters. Pick
shorter site names if you hit the limit.
What gets created
- System user
web_<name> - Directory tree under
/data/web/<name>/(see Architecture) - Per-site
.envtulixwith freshly generated DB and Redis passwords plus anAPP_KEY - Per-site
php.ini,my.cnf,redis.conf - PHP-FPM pool at
/etc/php/<ver>/fpm/pool.d/<site>.conf(symlinked intoconf/php-fpm.conf) - nginx vhost at
/etc/nginx/sites-available/<site>.conf+ enabled symlink - MariaDB database + user (granted only on its own DB)
- Redis ACL user restricted to one DB number and one key prefix
- Per-site cron stub at
/data/web/<site>/cron/crontaband an empty/etc/cron.d/tulixhost-<site>
Examples
sudo ./create_vhost.sh example.com --ssl=auto
sudo ./create_vhost.sh app.example.com --ssl=auto --demo
sudo ./create_vhost.sh internal.lan --ssl=./ssl
sudo ./create_vhost.sh dev.local --ssl=none --no-www
sudo ./create_vhost.sh example.com --ssl=auto --server-name="static.example.com cdn.example.com"
remove_vhost.sh
sudo ./remove_vhost.sh SITE_NAME [--no-backup] [--yes]
Asks you to type the site name to confirm (skip with --yes). Takes a final backup unless
you pass --no-backup, then removes: nginx vhost, FPM pool, cron entry, MariaDB database +
user, Redis ACL user + the site's keyspace, Let's Encrypt cert (via certbot delete), the
/data/web/<site> tree, and the per-site system user.
backup_site.sh
sudo ./backup_site.sh SITE_NAME [--reason=tag] [--no-rotate]
Produces /data/backups/<site>/<site>-<UTC>-<reason>.tar.gz containing:
site/— full/data/web/<site>tree (excludingtmp/and rotated logs)nginx/<site>.conf— server-side vhostphp-fpm/<site>.conf— server-side FPM poolcron/tulixhost-<site>— materialized cron filedb/<site>.sql.gz—mysqldump --single-transaction --routines --triggers --events --hex-blobredis/<site>.txt.gz— SCAN + DUMP of every key under the site's prefix, base64'dmeta.json— site identity, versions, sizes
Rotation keeps the newest BACKUP_RETENTION_DAYS tarballs (default 7). Disable rotation
with --no-rotate when you want to preserve a specific backup permanently.
If BACKUP_RSYNC_TARGET is set in /etc/tulixhost/tulixhost.conf, each
tarball is also rsynced offsite to $BACKUP_RSYNC_TARGET/<site>/.
backup_all.sh
sudo ./backup_all.sh [--reason=tag] [--parallel=N]
Iterates every directory under /data/web/ and runs backup_site.sh on each.
Default reason is scheduled. With --parallel=4, runs up to 4 backups
concurrently via xargs -P — be careful on busy DBs.
restore_site.sh
sudo ./restore_site.sh /path/to/backup.tar.gz [--site=NAME] [--no-data] [--yes]
sudo ./restore_site.sh SITE_NAME --latest [--no-data] [--yes]
Two invocation styles:
- Explicit tarball — restores whatever site is named in the tarball's
meta.json. Use--site=NAMEto override (e.g. for cloning). SITE_NAME --latest— picks the newest tarball in/data/backups/<site>/and restores it.
By default, restores everything: filesystem, configs, DB schema + rows, and Redis keys. Pass
--no-data to restore only the structure (configs + empty DB + no Redis replay) — useful
when you want to migrate the shell of a site without its data.
restore_all.sh
sudo ./restore_all.sh [--from=/path/to/dir] [--no-data] [--yes]
Walks every per-site subdirectory under the backup root and restores each site from its newest
tarball. Use this for full host recovery on a fresh box: run setup.sh, copy your
/data/backups/ into place, then run restore_all.sh --yes.
Configuration
All scripts read /etc/tulixhost/tulixhost.conf. Editable values:
| variable | default | purpose |
|---|---|---|
PHP_VERSION | (auto) | The PHP-FPM version managed by tulixhost. Set by setup.sh. |
MARIADB_HOST / MARIADB_PORT | 127.0.0.1 / 3306 | Used by per-site DSN generation. |
REDIS_HOST / REDIS_PORT | 127.0.0.1 / 6379 | Same. |
REDIS_ADMIN_PASS | (generated) | Server-wide Redis password. Scripts use it for admin actions (ACL LOAD, etc). |
BACKUP_RETENTION_DAYS | 7 | Number of tarballs kept per site. |
BACKUP_RSYNC_TARGET | (empty) | Optional rsync destination (e.g. user@offsite:/srv/backups). |