Skip to content
BILLETKnowledge Base

Install the portal

Unpack the server release, set up PostgreSQL, the portal's account, folders and secrets, create its database and run it as a systemd service.

For
Administrators
Applies to
Billet 1.0
Updated

These steps are for Ubuntu 24.04 LTS, with the portal's database in PostgreSQL. Run them as a user who can use sudo.

The portal listens on 127.0.0.1:5150 only. Nothing reaches it except through the reverse proxy you set up next, which decides which addresses are public.

Note: Use PostgreSQL for every deployment people rely on. The portal can also run on a SQLite file, but only for a lab or test deployment: SQLite cannot be shared by a second portal node, and it is not what Billet supports for real use. See SQLite, for a lab or test only.

1. Install the packages it needs

sudo apt-get update
sudo apt-get install -y nginx openssl ca-certificates libxmlsec1t64-openssl postgresql

The portal links the xmlsec1 library for SAML sign-in and will not start without it. Leave out postgresql if your database runs on a server of its own.

2. Create the database

On the PostgreSQL server, create an account and a database for the portal. Use a long random password made of letters and digits, so it needs no escaping in a connection address:

openssl rand -hex 24          # copy this, it is the database password
sudo -u postgres createuser --pwprompt billet
sudo -u postgres createdb --owner=billet billet

createuser asks for the password twice. Paste the one you just made.

If PostgreSQL is on another server, allow the portal's address to connect to the billet database in that server's pg_hba.conf, and use TLS between them.

3. Check and unpack the release

sha256sum -c SHA256SUMS
tar -xzf Billet-Server-1.0.0-linux-x64.tar.gz

sha256sum must print OK for the archive. If it does not, download it again.

4. Create the service account and folders

sudo useradd --system --no-create-home --home-dir /opt/billet/portal \
  --shell /usr/sbin/nologin billet

sudo install -d -o billet -g billet -m 0755 /opt/billet/portal
sudo install -d -o billet -g billet -m 0750 /var/lib/billet
sudo install -d -o billet -g billet -m 0700 /var/lib/billet-portal
sudo install -d -o root   -g billet -m 0750 /etc/billet
Folder Holds
/opt/billet/portal The program, its pages and its configuration
/var/lib/billet The portal's working files
/var/lib/billet-portal The certificate authority and this node's install ID
/etc/billet The portal's secrets

5. Copy the portal into place

sudo cp -r Billet-Server-1.0.0-linux-x64/portal/. /opt/billet/portal/
sudo cp /opt/billet/portal/config/production.yaml.example \
        /opt/billet/portal/config/production.yaml
sudo install -d /opt/billet/portal/downloads
sudo install -m 0644 Billet-Server-1.0.0-linux-x64/agent/billet-stats-agent \
        /opt/billet/portal/downloads/
sudo chown -R billet:billet /opt/billet/portal

production.yaml holds settings that are not secret, and reads the database address and pool size from the environment. Secrets never go in it.

The portal hands the Linux stats agent in downloads to the one-command installer for a Linux host. Copy it again after every upgrade, so new desktops get the agent from the same release as the portal.

6. Write the secrets

Replace <database password> with the password from step 2, and localhost with the database server's name if it is elsewhere:

sudo tee /etc/billet/portal.env > /dev/null <<EOF
BILLET_SESSION_SECRET=$(openssl rand -hex 32)
BILLET_BROKER_SECRET=$(openssl rand -hex 32)
DATABASE_URL=postgres://billet:<database password>@localhost:5432/billet
DB_MAX_CONNECTIONS=10
EOF
sudo chown root:billet /etc/billet/portal.env
sudo chmod 0640 /etc/billet/portal.env

DB_MAX_CONNECTIONS sets the portal's connection pool. Its default is 1, which suits only a SQLite lab or test portal.

Warning: Generate the two secrets once and keep them, with your backups. Changing BILLET_SESSION_SECRET signs everybody out. Changing BILLET_BROKER_SECRET ends sessions in progress, and the portal can no longer read the license key it stored, so you would have to enter it again.

7. Add a helper for portal commands

Several later steps run the portal's own command line as the billet account with the service's secrets loaded. This short script does that, so no secret is ever typed on a command line:

sudo tee /usr/local/sbin/billet-portal-task > /dev/null <<'EOF'
#!/bin/sh
# Run billet_portal-cli as the billet account, with the portal service's
# environment. Usage: sudo billet-portal-task <arguments>
set -e
set -a
. /etc/billet/portal.env
set +a
cd /opt/billet/portal
exec runuser -u billet -- env LOCO_ENV=production \
  BILLET_CA_DIR=/var/lib/billet-portal/ca ./billet_portal-cli "$@"
EOF
sudo chmod 0750 /usr/local/sbin/billet-portal-task

8. Create the tables

The portal does not change its database by itself when it starts, so that two nodes starting at once never race to alter it. Create the tables now, and run the same command after every upgrade:

sudo billet-portal-task db migrate -e production

9. Run it as a service

Create /etc/systemd/system/billet-portal.service, replacing portal.example.com with your portal's name:

[Unit]
Description=Billet portal
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=billet
Group=billet
WorkingDirectory=/opt/billet/portal
ExecStart=/opt/billet/portal/billet_portal-cli start -e production -b 127.0.0.1 -p 5150
Environment=LOCO_ENV=production
EnvironmentFile=/etc/billet/portal.env

# The name people and machines reach the portal at, without https://.
Environment=BILLET_PUBLIC_HOST=portal.example.com
# Redirect plain HTTP to HTTPS and mark sign-in cookies Secure.
Environment=BILLET_FORCE_HTTPS=1
# The deployment's own certificate authority, which the portal trusts when it
# talks to a relay. Created in a later step.
Environment=BILLET_TLS_ROOTS=/var/lib/billet-portal/ca/ca.crt
# Where the agent for Linux DCV desktops is (step 5).
Environment=BILLET_AGENT_DIST_DIR=/opt/billet/portal/downloads

Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/billet/portal /var/lib/billet -/var/lib/billet-portal

[Install]
WantedBy=multi-user.target

Then start it:

sudo systemctl daemon-reload
sudo systemctl enable --now billet-portal
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5150/_health

The last command prints 200 when the portal is up. If it does not, read the log:

journalctl -u billet-portal -b --no-pager | tail -50

Settings without editing the unit

Every BILLET_ setting can also go in /etc/billet/settings.toml, as a key without the prefix: public_host = "portal.example.com" is BILLET_PUBLIC_HOST. The service's environment wins over the file. The portal's start-up log lists each setting it took from the file.

Back up

Back up the PostgreSQL database, /etc/billet and /var/lib/billet-portal together. The database holds every tenant, desktop and assignment; /etc/billet holds the secrets that read parts of it; /var/lib/billet-portal/ca is the certificate authority every host and relay trusts.

SQLite, for a lab or test only

For a lab or a test deployment that nobody depends on, the portal can keep its database in a SQLite file instead. Skip step 2, and in step 6 use these two lines in place of the PostgreSQL ones:

DATABASE_URL=sqlite:///var/lib/billet/billet.sqlite?mode=rwc
DB_MAX_CONNECTIONS=1

DB_MAX_CONNECTIONS must stay 1 for a SQLite lab or test portal: with more, a new database cannot be created. A lab portal on SQLite cannot have a second node. Start any deployment people will rely on with PostgreSQL, rather than planning to move a lab across later.

Next

Put the portal behind a reverse proxy.