Skip to content

Installation

Varun Karthic edited this page Oct 10, 2026 · 5 revisions

Installation

How to install the imPress backend, from a five-minute trial on a laptop to a production server behind TLS. Reference: docs/guides/deployment.md.

Contents


Requirements

Item Requirement
Operating system Linux (recommended for servers), macOS or Windows
Python 3.12 or newer (CI uses 3.12; 3.14 is tested locally)
Disk Small: the SQLite database grows with answers; firmware images are about 1 MB each
Network Reachable from every classroom gateway over the school's Wi-Fi network; reachable from teachers' browsers
Processes One backend process: WebSocket rooms live in its memory

Database software is not needed: SQLite is bundled with Python.

Quick trial on a laptop

git clone https://github.com/Kush-Kelaiya22/imPress.git
cd imPress
scripts/install.sh
IMPRESS_INITIAL_ADMIN_PASSWORD=change-me scripts/start.sh

Open http://127.0.0.1:8000 and sign in as admin / change-me. The interactive API documentation is at http://127.0.0.1:8000/docs.

To let devices on your network reach it, bind to all interfaces:

IMPRESS_HOST=0.0.0.0 IMPRESS_PORT=8000 scripts/start.sh

Gateways then use your machine's LAN IP as BACKEND_HOST, never localhost.

From a release download

Each release has a bundle, impress-<version>.zip, with the source at the release commit, the prebuilt firmware and the release notes. Use it instead of git clone when you want an exact, verified version:

sha256sum -c SHA256SUMS.txt          # every line must say OK
unzip impress-varun-2.1.5.zip && cd impress-varun-2.1.5
scripts/install.sh
IMPRESS_HOST=0.0.0.0 scripts/start.sh

The rest of this page applies unchanged. Check the release's status badge first: a Beta release has open hardware test issues.

Container (Docker)

The backend is also published as a container image, built from the same hash-locked dependencies that CI tests:

docker run -d --name impress --restart unless-stopped \
  -p 8000:8000 -v impress-data:/data \
  ghcr.io/kush-kelaiya22/impress-backend:varun-2.1
docker logs impress         # the admin password (first start only) and the device API key
Property Value
Platform linux/amd64
User impress (UID 10001), not root
Data the /data volume: impress.db, firmware_bins/, and .env with the secrets generated on the first start (mode 600)
Health check GET /health every 30 s: docker inspect -f '{{.State.Health.Status}}' impress
Settings the usual IMPRESS_* variables (-e ...); they override /data/.env
TLS mount the certificate and key, and set IMPRESS_SSL_CERTFILE and IMPRESS_SSL_KEYFILE
Tag Meaning
varun-2.1.5 (<owner>-<version>) that release; never moved
varun-2.1 (<owner>-<MAJOR.MINOR>) the newest release of that line
varun-latest / varun-beta the owner's newest Stable / newest Beta release
sha-<commit> one exact commit

Upgrade: stop the container and start the new tag with the same volume. Migrations run at startup and back up the database first. Back up the impress-data volume, for example docker run --rm -v impress-data:/data -v "$PWD":/b alpine tar czf /b/impress-data.tgz -C /data ..

If docker pull asks for credentials, the package is still private; the repository owner can make it public in the package settings.

What install.sh does

scripts/install.sh is safe to run again at any time, for example after git pull. Step by step:

  1. Checks Python: stops with a message if the interpreter is older than 3.12. Set PYTHON=python3.12 (or similar) to choose one.
  2. Creates backend/.venv if it doesn't exist, and upgrades pip.
  3. Installs dependencies:
    • on Linux x86_64, the hash-locked set that CI tests (backend/requirements-lock.txt with --require-hashes), so every package is verified against a recorded hash;
    • elsewhere, backend/requirements.txt (the lock is resolved for Linux).
  4. Creates or completes backend/.env (through scripts/init_env.py):
    • copies backend/.env.example if there is no .env;
    • an empty or missing IMPRESS_JWT_SECRET or IMPRESS_DEVICE_API_KEY gets a random value (secrets.token_urlsafe(32));
    • it never writes the published default secrets, and it keeps every value already set;
    • the file is made readable by its owner only (mode 600).
  5. Checks the app imports, and prints the version and the schema version it will migrate to.
  6. Prints the next steps, including where to put the device key.

First start

scripts/start.sh starts uvicorn from backend/ with one worker.

Variable Default Meaning
IMPRESS_HOST 127.0.0.1 address to listen on
IMPRESS_PORT 8000 port
IMPRESS_SSL_CERTFILE, IMPRESS_SSL_KEYFILE unset set both to serve HTTPS/WSS directly (see TLS)

On the very first start, with an empty database:

  1. The schema is created and every migration is recorded.
  2. A super admin named admin is created:
    • with IMPRESS_INITIAL_ADMIN_PASSWORD set, that is its password;
    • otherwise a random password is generated and printed once in the server log. Copy it then: it is never shown again.
  3. The presence, deployment and session-cleanup loops start.

The server refuses to start in production mode (IMPRESS_DEBUG=false) while IMPRESS_JWT_SECRET or IMPRESS_DEVICE_API_KEY still have their published defaults. The error names the variables to set. install.sh already generated real values, so this only happens with a hand-made .env.

After the first sign-in: change the admin password (Settings → Change password), then create other users (Administrator Guide).

Checking an installation

curl http://127.0.0.1:8000/health
# {"status":"healthy","version":"2.1.0","schema_version":8}

scripts/smoke_test.py --base http://127.0.0.1:8000 --password <admin password>

The smoke test runs the whole system end to end:

Step Checks
1 /health reports the repository's VERSION and a schema
2 admin login works
3 it can create a course
4 it can create a section with a room code
5 a gateway registers and is linked to that room's class
6 the gateway's heartbeat with diagnostics is accepted
7 a student joins through the gateway
8 the class's device view lists the gateway and the module
9 the gateway's health is ONLINE
10 the firmware registry lists
11 random bytes are refused as firmware
12 the web app is served

It reads the device key from backend/.env (--device-key to override). Every run uses fresh random names, so it can be re-run against the same database. Add --cafile ca.pem to check an HTTPS server that uses a private CA.

CI runs exactly this install, start and smoke test on a fresh machine for every change, over both HTTP and HTTPS.

Production server (Linux, systemd)

  1. Create a user and install:
    sudo useradd --system --create-home --home-dir /opt/imPress impress
    sudo -u impress git clone https://github.com/Kush-Kelaiya22/imPress.git /opt/imPress
    cd /opt/imPress && sudo -u impress scripts/install.sh
  2. Edit backend/.env:
    IMPRESS_DEBUG=false
    IMPRESS_JWT_SECRET=…                    # generated by install.sh
    IMPRESS_DEVICE_API_KEY=…                # generated; this also goes into every device
    IMPRESS_INITIAL_ADMIN_PASSWORD=…        # or leave empty and read it from the log once
    IMPRESS_CORS_ORIGINS=["https://impress.example.edu"]
  3. Create /etc/systemd/system/impress.service:
    [Unit]
    Description=imPress backend
    After=network.target
    
    [Service]
    WorkingDirectory=/opt/imPress
    User=impress
    ExecStart=/opt/imPress/scripts/start.sh
    Environment=IMPRESS_HOST=127.0.0.1 IMPRESS_PORT=8000
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    start.sh changes into backend/ itself and reads .env there. The database path, .env and firmware_bins/ are all relative to backend/.
  4. Start it:
    sudo systemctl daemon-reload
    sudo systemctl enable --now impress
    journalctl -u impress -f          # the first admin password appears here if you left it empty

TLS

There are two options. Pick one.

Option A: a reverse proxy (recommended)

The proxy handles certificates and serves the WebSocket upgrade:

server {
    listen 443 ssl;
    server_name impress.example.edu;
    ssl_certificate     /etc/letsencrypt/live/impress.example.edu/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/impress.example.edu/privkey.pem;

    location /ws/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        access_log off;            # teacher tokens travel in the WebSocket query string
    }
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

access_log off on /ws/ matters: browsers can't send headers on a WebSocket, so the teacher's session token is in the URL.

Option B: uvicorn serves TLS itself

IMPRESS_SSL_CERTFILE=/etc/impress/server.crt IMPRESS_SSL_KEYFILE=/etc/impress/server.key \
  IMPRESS_HOST=0.0.0.0 IMPRESS_PORT=443 scripts/start.sh

start.sh refuses to start if only one of the two variables is set, or if a file is unreadable.

Devices over TLS

Classroom devices use plain HTTP unless their firmware is built with Backend over TLS. Turning it on is covered in Security → Device traffic over TLS.

Windows and manual installs

install.sh and start.sh are bash scripts. On Windows (or to do it by hand anywhere):

cd backend
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env      # then set IMPRESS_JWT_SECRET and IMPRESS_DEVICE_API_KEY
uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 1 --proxy-headers

Generate a secret:

python -c "import secrets; print(secrets.token_urlsafe(32))"

Start uvicorn from backend/, and with one worker.

Backups

What How Notes
Database sqlite3 backend/impress.db ".backup '/backups/impress-$(date +%F).db'" safe while the server runs
Firmware images copy backend/firmware_bins/ each file is named by its SHA-256
Configuration copy backend/.env store it separately: it holds secrets
Firmware signing key if you use signing: ~/.impress/firmware-signing/signing_key.pem offline, in two places. Losing it means devices can only be updated over USB

Migrations also write automatic backups before changing the database (impress.db.bak-<from>-to-<to>-<time>). See Database and Migrations.

Upgrading

  1. Back up the database.

  2. Get the new code: git pull.

  3. Update dependencies and .env: scripts/install.sh (it keeps your values).

  4. Restart: sudo systemctl restart impress. Migrations run at startup:

    • they back up the database first;
    • they stop the server with instructions if a step can't apply.

    Then check GET /health: version and schema_version.

  5. Update firmware:

    Read the release's upgrade notes, which list any one-time serial flashes: Release Notes.

Uninstalling

Stop and disable the service, then delete the checkout:

sudo systemctl disable --now impress
sudo rm /etc/systemd/system/impress.service

/opt/imPress contains backend/impress.db, backend/firmware_bins/ and backend/.env. Back them up first if the data matters.

Go-live checklist

  • GET /health works through the proxy, over valid HTTPS.
  • The admin password is changed; IMPRESS_DEBUG=false; no default secrets.
  • Each room's gateway shows online and is linked to the right class.
  • A test quiz appears on a student module, and an answer raises the live count.
  • Backups are scheduled, and a restore has been tried once.
  • Signed firmware is enabled (Security).
  • Device TLS is enabled (Security).
  • Once every device has its own key, IMPRESS_DEVICE_KEYS_REQUIRED=true (Security).
  • If the repository is public: its git history is purged of the databases committed before v2, and the passwords that were in them are rotated. See Security.

Clone this wiki locally