XTQ/Web/DocsDownload

Install

The installer for systemd and Docker, manual install, Windows, upgrades, uninstalling and the systemd unit.

Releases are published at https://xtq.ro/web/download/. Each version has its own folder with these files:

File What it is
xtq-web-<version>-linux-amd64.tar.gz Static Linux binary for x86_64
xtq-web-<version>-linux-arm64.tar.gz Static Linux binary for ARM64
xtq-web-<version>-windows-amd64.zip Windows binary for x86_64
SHA256SUMS SHA-256 sums of the packages and the installer

https://xtq.ro/web/download/latest.txt holds the newest version number.

A Linux package unpacks into a folder named xtq-web-<version>-linux-<arch> with xtq-web, xtq-web.service, xtq-web.conf.example, README.txt and LICENSE. The Windows zip has xtq-web.exe, xtq-web.conf.example, README.txt and LICENSE.

The installer

curl -fsSL https://xtq.ro/web/install.sh | sh

Options go after sh -s --:

Option Effect
--docker Install as a Docker container instead of a systemd service
--version <x.y.z> Install that version instead of the latest
-h, --help Print the usage and exit
curl -fsSL https://xtq.ro/web/install.sh | sh -s -- --version 1.0.1
curl -fsSL https://xtq.ro/web/install.sh | sh -s -- --docker

The installer runs only on Linux, on x86_64 or ARM64. It needs curl or wget, and root or sudo.

What it does in both modes

It reads the latest version from latest.txt, unless you passed --version. It downloads the package for your architecture and SHA256SUMS, and refuses to go on if the sum does not match (checksum mismatch, download refused).

It creates /etc/xtq-web. If /etc/xtq-web/xtq-web.conf does not exist, it copies the example config there. If it exists, it is left alone.

If /etc/xtq-web/env does not exist, it creates it with mode 600 and a single comment line. This file holds settings such as XTQW_ACME=1 and XTQW_ADMIN_TOKEN, one NAME=value per line, and both modes read it. An existing file is left alone.

It runs the new binary's xtq-web check against /etc/xtq-web/xtq-web.conf, on the host in native mode and inside the new container image in Docker mode. If the check fails, the installer stops before it touches the running service or container.

Native mode (the default)

It installs the binary to /usr/local/bin/xtq-web with mode 755.

If systemd is running, it also:

  • creates the system user xtq-web (no home directory, no login shell) if it doesn't exist yet,
  • installs xtq-web.service to /etc/systemd/system/xtq-web.service, unless a different file is already there (see below),
  • runs systemctl daemon-reload, enables the service and restarts it.

If /etc/systemd/system/xtq-web.service exists and you changed it, the installer keeps it and writes the new default next to it as /etc/systemd/system/xtq-web.service.new, with the message kept your /etc/systemd/system/xtq-web.service, the new default is in xtq-web.service.new. Compare the two with diff, and if you want the new one, move it in place and run systemctl daemon-reload. A unit exactly as an earlier release shipped it is recognised and simply replaced.

Without systemd it installs only the binary and prints the command to start it:

XTQW_CONFIG=/etc/xtq-web/xtq-web.conf xtq-web

Docker mode

With --docker, the installer installs Docker with Docker's official script from get.docker.com if docker is missing. It then builds a local image tagged xtq/web:<version> and xtq/web:latest. The image is built FROM scratch and holds only the binary and a CA certificate bundle. It sets XTQW_CONFIG=/etc/xtq-web/xtq-web.conf and XTQW_ACME_DIR=/var/lib/xtq-web/acme.

It runs xtq-web check inside the new image, with the same folders mounted as the container gets, so the paths in your config are checked as the server will see them. If that fails, it stops and leaves the running container alone.

It then stops any container named xtq-web with docker stop -t 20, which gives it time to finish its open requests, removes it, and starts a new one like this:

docker run -d --name xtq-web \
  --restart unless-stopped \
  --network host \
  --ulimit nofile=1048576:1048576 \
  --read-only \
  --cap-drop ALL --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --log-opt max-size=10m --log-opt max-file=3 \
  --stop-timeout 20 \
  --env-file /etc/xtq-web/env \
  -v /etc/xtq-web:/etc/xtq-web:ro \
  -v /srv:/srv:ro \
  -v /var/www:/var/www:ro \
  -v /run/xtq:/run/xtq \
  -v xtq-web-acme:/var/lib/xtq-web/acme \
  xtq/web:<version>

/srv, /var/www and /run/xtq are mounted only when they exist on the host when the installer runs. /srv and /var/www are read-only. /run/xtq is writable, since connecting to the xTQ PHP socket there needs it.

Host networking means no NAT between clients and xTQ Web, so it sees the real client address. If the container is not up a second later, the installer prints its logs and fails.

Things to know about the Docker setup:

  • Inside the container you see /etc/xtq-web and whichever of /srv, /var/www and /run/xtq were mounted, at the same paths as on the host. Files you serve from anywhere else, Unix sockets outside /run/xtq, and certificate files or IP list files outside /etc/xtq-web must be mounted too, at the same paths your config uses.
  • Variables in /etc/xtq-web/env are read when the container is created. After changing the file, run the installer again with --docker, or recreate the container yourself. docker restart keeps the old values.
  • Certificates from Let's Encrypt live in the Docker volume xtq-web-acme.
  • Reload with docker kill -s HUP xtq-web.

A container with your own flags, here with another site folder mounted:

sudo docker stop -t 20 xtq-web
sudo docker rm xtq-web
sudo docker run -d --name xtq-web \
  --restart unless-stopped \
  --network host \
  --ulimit nofile=1048576:1048576 \
  --read-only \
  --cap-drop ALL --cap-add NET_BIND_SERVICE \
  --security-opt no-new-privileges \
  --log-opt max-size=10m --log-opt max-file=3 \
  --stop-timeout 20 \
  --env-file /etc/xtq-web/env \
  -v /etc/xtq-web:/etc/xtq-web:ro \
  -v /opt/sites:/opt/sites:ro \
  -v xtq-web-acme:/var/lib/xtq-web/acme \
  xtq/web:latest

/etc/xtq-web/env holds one NAME=value per line, for example XTQW_ACME=1. Lines starting with # are ignored.

Manual install from the tarball

Pick the version and architecture, then download and verify:

V=1.0.1
A=amd64
curl -fsSLO https://xtq.ro/web/download/$V/xtq-web-$V-linux-$A.tar.gz
curl -fsSLO https://xtq.ro/web/download/$V/SHA256SUMS
grep " xtq-web-$V-linux-$A.tar.gz\$" SHA256SUMS | sha256sum -c -
tar -xzf xtq-web-$V-linux-$A.tar.gz
cd xtq-web-$V-linux-$A

Install the binary and a config:

sudo install -m 755 xtq-web /usr/local/bin/xtq-web
sudo mkdir -p /etc/xtq-web
sudo cp -n xtq-web.conf.example /etc/xtq-web/xtq-web.conf
xtq-web check /etc/xtq-web/xtq-web.conf

Then the user and the service:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin xtq-web
sudo install -m 644 xtq-web.service /etc/systemd/system/xtq-web.service
sudo systemctl daemon-reload
sudo systemctl enable --now xtq-web

Windows

Unzip xtq-web-<version>-windows-amd64.zip, copy xtq-web.conf.example to a config file of your own and start the binary with XTQW_CONFIG pointing at it.

In Command Prompt:

set XTQW_CONFIG=C:\xtq-web\xtq-web.conf
xtq-web.exe

In PowerShell:

$env:XTQW_CONFIG = "C:\xtq-web\xtq-web.conf"
.\xtq-web.exe

Ctrl-C stops it. The Windows build proxies and serves files like the Linux one. The differences:

  • There is no SIGHUP on Windows, so a config change needs a restart.
  • php sites and unix: backends need Linux or macOS. On Windows the config loads, but requests to them fail.
  • XTQW_ACME_DIR defaults to /var/lib/xtq-web/acme. Set it to a real Windows folder if you use automatic HTTPS.
  • The zip has no service wrapper. Use a service manager of your choice to run it in the background.

xtq-web.exe check C:\xtq-web\xtq-web.conf validates a config the same way as on Linux.

Upgrading

Run the installer again. It downloads the new version, checks your existing config with the new binary, replaces /usr/local/bin/xtq-web, installs the new unit file unless you have a different one (see Native mode), and restarts the service. Your /etc/xtq-web/xtq-web.conf, /etc/xtq-web/env and everything else in /etc/xtq-web is kept.

The restart lets requests in flight finish first, for up to XTQW_DRAIN_SECS seconds (default 10), see Stop. New connections are refused for the moment between the old process and the new one. Open WebSockets are cut, and so is a download that takes longer than the drain time. Clients reconnect.

The simplest way to keep your own service settings is a drop-in, made with systemctl edit xtq-web. Drop-ins live in /etc/systemd/system/xtq-web.service.d/, are never touched by the installer, and the unit file itself stays as shipped.

In Docker mode, running the installer again with --docker builds the new image, stops the old container with docker stop -t 20 so its requests can finish, and recreates it with the installer's own docker run flags. If you run the container with your own flags, rebuild and recreate it yourself.

To install a specific version, or go back to one:

curl -fsSL https://xtq.ro/web/install.sh | sh -s -- --version 1.0.1

Uninstalling

Native install:

sudo systemctl disable --now xtq-web
sudo rm /etc/systemd/system/xtq-web.service
sudo systemctl daemon-reload
sudo rm /usr/local/bin/xtq-web
sudo userdel xtq-web

That keeps your config and certificates. To remove them too:

sudo rm -r /etc/xtq-web
sudo rm -r /var/lib/xtq-web

Docker install:

sudo docker rm -f xtq-web
sudo docker image rm xtq/web:latest xtq/web:1.0.1
sudo docker volume rm xtq-web-acme

The systemd unit

This is the xtq-web.service from the package, line by line.

Setting What it does
Documentation=https://xtq.ro/web/docs/ Where systemctl status points for help
After=network-online.target, Wants=network-online.target Start once the network is up
Type=simple xtq-web runs in the foreground
User=xtq-web, Group=xtq-web Runs as the unprivileged xtq-web user
Environment=XTQW_CONFIG=/etc/xtq-web/xtq-web.conf The config file
Environment=XTQW_ACME_DIR=/var/lib/xtq-web/acme Where Let's Encrypt certificates are stored
EnvironmentFile=-/etc/xtq-web/env Optional file with more variables, such as XTQW_ADMIN_TOKEN. The - means a missing file is fine
ExecStartPre=/usr/local/bin/xtq-web check ... Refuses to start on a config with a mistake in it
ExecStart=/usr/local/bin/xtq-web The server
ExecReload=... check ... then ExecReload=/bin/kill -HUP $MAINPID systemctl reload checks the config first and sends SIGHUP only if it passes
Restart=always, RestartSec=1 Restarted one second after it exits for any reason
TimeoutStopSec=20 On stop or restart, systemd waits up to 20 seconds for the process to exit before it kills it. That leaves room for open requests to finish within the default XTQW_DRAIN_SECS of 10
LimitNOFILE=1048576 Room for many open connections
StateDirectory=xtq-web Creates /var/lib/xtq-web, owned by the service user and writable
AmbientCapabilities=CAP_NET_BIND_SERVICE, CapabilityBoundingSet=CAP_NET_BIND_SERVICE May bind ports below 1024, and nothing else that needs root
NoNewPrivileges=true Can never gain privileges
ProtectSystem=strict The whole filesystem is read-only for the service, except /var/lib/xtq-web
ProtectHome=true /home, /root and /run/user are invisible
PrivateTmp=true The service gets its own empty /tmp
PrivateDevices=true, ProtectKernelTunables=true, ProtectKernelModules=true, ProtectControlGroups=true, RestrictSUIDSGID=true, LockPersonality=true Further sandboxing

What the sandbox means in practice:

  • Files you serve must be readable by the xtq-web user and must not live under /home or /root.
  • A backend socket in /tmp is not visible to the service, because of PrivateTmp. Put sockets under /run instead.
  • The service can't write anywhere except /var/lib/xtq-web. xTQ Web only needs to write there for certificates.

Secrets such as XTQW_ADMIN_TOKEN go in /etc/xtq-web/env, readable only by root. systemd reads it before dropping privileges. The installer creates the file. After a manual install, create it first with sudo install -m 600 /dev/null /etc/xtq-web/env. Then add a line and restart:

echo "XTQW_ADMIN_TOKEN=$(openssl rand -hex 32)" | sudo tee -a /etc/xtq-web/env >/dev/null
sudo systemctl restart xtq-web

Environment variables are read when the process starts, so a change to them needs systemctl restart, not reload.