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:

  1. Detects Ubuntu via /etc/os-release and refuses EOL releases.
  2. Adds ppa:ondrej/php and the official MariaDB.org repo.
  3. Installs latest available PHP-FPM (auto-picked by apt-cache version sort) plus extensions: mysql redis opcache mbstring xml curl gd zip bcmath intl readline.
  4. Installs MariaDB server + client, Redis 7+, nginx, and certbot.
  5. Configures MariaDB root via unix_socket auth (no password to leak).
  6. Writes server-wide MariaDB tunables to /etc/mysql/mariadb.conf.d/99-tulixhost.cnf.
  7. Hardens Redis: bind 127.0.0.1 ::1, requirepass set to a random 32-char password, CONFIG renamed to a per-install random name, aclfile enabled.
  8. Creates global nginx snippets, a catch-all default vhost returning 444, and patches nginx.conf with server_tokens off plus three rate-limit zones.
  9. Sets a global PHP-FPM hardening pass (disables expose_php, allow_url_include, cgi.fix_pathinfo) and moves the default www.conf pool aside.
  10. Configures ufw (deny incoming except OpenSSH + Nginx Full) and enables unattended security upgrades.
  11. Enables unattended-upgrades.
  12. Writes /etc/tulixhost/tulixhost.conf with the discovered PHP version and the generated Redis admin password.
  13. Symlinks each script into /usr/local/sbin/tulixhost-*.
  14. Installs a nightly backup_all cron 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

flageffect
--ssl=autoRenders 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=./sslLooks 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=noneHTTP only. Logs a warning. Not recommended for public sites.
--demoInstalls the tulixhost demo dashboard into public/ instead of a minimal landing page.
--forceAllow rewriting configs for an existing site. Does not clobber data, the DB, or Redis keys.
--no-wwwSkip 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

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:

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:

  1. Explicit tarball — restores whatever site is named in the tarball's meta.json. Use --site=NAME to override (e.g. for cloning).
  2. 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:

variabledefaultpurpose
PHP_VERSION(auto)The PHP-FPM version managed by tulixhost. Set by setup.sh.
MARIADB_HOST / MARIADB_PORT127.0.0.1 / 3306Used by per-site DSN generation.
REDIS_HOST / REDIS_PORT127.0.0.1 / 6379Same.
REDIS_ADMIN_PASS(generated)Server-wide Redis password. Scripts use it for admin actions (ACL LOAD, etc).
BACKUP_RETENTION_DAYS7Number of tarballs kept per site.
BACKUP_RSYNC_TARGET(empty)Optional rsync destination (e.g. user@offsite:/srv/backups).