Chapter 16
Installation and hosting
This chapter is for whoever runs the panel: 2plus1 for the hosted service, or your own IT for a self-hosted installation. Access Controller is one binary with an embedded database and MQTT broker, so hosting is deliberately simple. Where this chapter describes a layout that is conventional rather than fixed by the software, it is marked typical.
Note: hosted customers can skip this chapter. If 2plus1 hosts your panel, there is nothing here for you to do. The panel is installed, backed up, monitored and kept up to date by 2plus1; new releases are applied for you, and the only thing that ever needs your attention is updating the firmware on your own controllers, which is done from the Controllers page (chapter 5) at a time that suits the site. The rest of this chapter is for self-hosters.
What you get
- One executable,
accesscontroller(Linux) oraccesscontroller.exe(Windows), containing the web interface, the MQTT broker, the keyset compiler and the database engine. No runtime, no separate database server, no message broker to install. - A data directory holding the SQLite database file
acs.db(plus its-waland-shmcompanions while running), atlsfolder for the self-signed certificate, and afirmwarefolder with the controller images used by Flash & adopt and over-the-air updates.
Requirements: a small Linux virtual machine or on-premise box (1 vCPU, 512 MB RAM and a few GB of disk are ample), a hostname with an A record pointing at it, and the ability to open three inbound ports.
Quick start (any platform)
accesscontroller create-admin admin YOURPASSWORD
accesscontroller
The first command creates (or resets) a super-admin login in the provider tenant. The second starts the server: web interface on http://localhost:8080 and https://localhost:8443 (self-signed), MQTT broker on port 1883, database at data/acs.db relative to the working directory. Sign in and follow chapter 2.
Run accesscontroller help to see the options.
Environment variables
All configuration is by environment variable; there is no config file.
| Variable | Default | Meaning |
|---|---|---|
ACS_DB | data/acs.db | Path of the SQLite database file. The directory is created if missing. The TLS certificate is kept in a tls folder beside it. |
ACS_ADDR | :8080 | Web interface listen address (plain HTTP). Behind a reverse proxy bind it to localhost, for example 127.0.0.1:8081. |
ACS_TLS_ADDR | :8443 | HTTPS listen address with a self-signed certificate. Set to off when a reverse proxy provides TLS. |
ACS_MQTT_ADDR | :1883 | MQTT broker listen address for controllers. Must be reachable from every site. |
ACS_TZ | Europe/London | The panel timezone used for schedules, "today" counts and displayed times. |
ACS_DEVICE_USER / ACS_DEVICE_PASS | empty | Username and password controllers must present to the broker. Empty means an open broker, acceptable only on an isolated LAN. Also written into controllers at provisioning. |
ACS_SYNC_HOURS | 3 | How often keysets are re-pushed to every controller as a safety net (also carries daylight-saving offsets). |
ACS_FW_DIR | data/firmware | Where controller firmware images live: <kind>/<chip>/ with four .bin files each, for example networked/esp32/ and networked/esp32s3/. |
Typical Linux layout (systemd)
The following is how 2plus1 deploys the hosted service and is a sensible template for self-hosting. Adjust paths and names freely.
- Binary and data under
/opt/accesscontroller/, owned by a dedicated system useracs. - Environment in
/etc/accesscontroller.env(mode 0600, root-owned):
ACS_ADDR=127.0.0.1:8081
ACS_TLS_ADDR=off
ACS_MQTT_ADDR=:1883
ACS_DB=/opt/accesscontroller/data/acs.db
ACS_FW_DIR=/opt/accesscontroller/data/firmware
ACS_TZ=Europe/London
ACS_DEVICE_USER=doorctl
ACS_DEVICE_PASS=<long random secret>
- A unit file
/etc/systemd/system/accesscontroller.service(typical):
[Unit]
Description=Access Controller panel
After=network-online.target
Wants=network-online.target
[Service]
User=acs
Group=acs
WorkingDirectory=/opt/accesscontroller
EnvironmentFile=/etc/accesscontroller.env
ExecStart=/opt/accesscontroller/accesscontroller
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
Then:
sudo systemctl daemon-reload
sudo systemctl enable --now accesscontroller
sudo journalctl -u accesscontroller -f
The log line web ui on 127.0.0.1:8081 confirms the panel is up. A warning "no admin accounts" means you still need create-admin (run it as the acs user with the same ACS_DB so it opens the same database).
Reverse proxy and certificates (Caddy)
Put a TLS reverse proxy in front of the panel so that users get HTTPS with an automatically issued Let's Encrypt certificate. Caddy does this in two lines (typical), in /etc/caddy/Caddyfile:
doors.example.com {
reverse_proxy 127.0.0.1:8081
}
Caddy obtains and renews the certificate, redirects HTTP to HTTPS, and passes X-Forwarded-Proto: https, which the panel uses to know it is on a secure page (needed for Flash & adopt and webcam capture). Prerequisite: the DNS A record doors.example.com must already point at the server before Caddy can issue the certificate. Nginx or Apache with certbot work equally well; forward to the panel's ACS_ADDR and pass the forwarded-proto header.
After the proxy is up, set Public HTTPS URL on the Settings page to https://doors.example.com so that over-the-air firmware downloads use it, and set the Adoption address to doors.example.com (chapter 13).
Ports and firewall
| Port | Protocol | Who connects | Notes |
|---|---|---|---|
| 443 | HTTPS | Staff browsers, the reception screen, the PBX, controllers fetching OTA images | Via the reverse proxy. |
| 80 | HTTP | Let's Encrypt validation, redirect to 443 | Via the reverse proxy. |
| 1883 | MQTT (TCP) | Controllers at every site | The panel's embedded broker binds this itself. Restrict to site addresses where possible. Plain text. |
| 22 | SSH | Administrators | Standard. |
Outbound from the panel host: HTTPS (443) to api.telegram.org if any tenant uses Telegram alerts (chapter 4). Nothing else leaves the panel.
The panel binary binds ACS_ADDR (localhost behind a proxy) and ACS_MQTT_ADDR (all interfaces) itself; the proxy binds 80 and 443. If the panel is on a site LAN with no proxy, users connect to 8080 or 8443 directly instead.
Note: at each site the controllers need outbound TCP to the panel on 1883 (and 443 for updates). Most routers allow outbound traffic by default. On a filtering firewall, add a pass rule for the door network to the panel address on 1883. If a rule appears correct but shows zero traffic, check that the firewall actually loaded its rule set: one deployment lost days to a pfSense box whose filter reload was silently failing because of an oversized block list, so every new rule sat idle and the default deny won. Chapter 17 has the diagnosis steps.
Firmware images
Flash & adopt and OTA serve images from ACS_FW_DIR. Copy the built images for each chip into networked/esp32/ and networked/esp32s3/ (and standalone/... if you offer standalone units): bootloader.bin, partitions.bin, boot_app0.bin and firmware.bin. The Flash & adopt page also accepts uploads per chip, and shows per chip whether a build is complete. 2plus1 supplies the images with each release.
Backups
The whole system state is the SQLite database file. To back it up:
- Simplest: stop the service, copy
acs.db, start the service. Stopping first guarantees a consistent file and folds the write-ahead log (acs.db-wal) into the main file. - Without downtime: use SQLite's online backup, for example
sqlite3 acs.db ".backup /backups/acs-$(date +%F).db", which is safe while the panel runs.
Keep the environment file (it holds the device credentials) and the data/firmware folder with the backup. Photos are stored inside the database, so the one file really is everything.
Restore: stop the service, remove acs.db, acs.db-wal and acs.db-shm, copy the backup in as acs.db, make sure it is owned by the service user, start the service. Run create-admin if you need to reset a login. After a restore the panel re-pushes keysets to every controller on its next connection, so doors pick up the restored state automatically.
Updates
On the hosted service, 2plus1 applies updates; nothing below applies to you.
Self-hosting, replace the binary and restart:
sudo systemctl stop accesscontroller
sudo install -m 0755 -o acs -g acs ./accesscontroller /opt/accesscontroller/accesscontroller
sudo systemctl start accesscontroller
Database schema changes are applied automatically at start-up and are additive, so a newer binary opens an older database. Take a backup before updating anyway. Copy any new firmware images into ACS_FW_DIR at the same time, then use Update on the Controllers page to roll firmware out to controllers one at a time (chapter 5).
Moving to a new server
- Install the new panel, restore the database backup onto it, set the same
ACS_DEVICE_USERandACS_DEVICE_PASS(or be ready to type the new ones), and put the proxy and DNS in place. - If the hostname stays the same and DNS is switched over, controllers simply reconnect to the new address when their existing connection drops.
- If the address changes, use Re-point on the old panel for each controller while it is still online, giving the new address and, if they differ, the new MQTT credentials. Chapter 5 and chapter 17 cover the credential pitfall.
Housekeeping queries (optional)
The database is standard SQLite and can be inspected with the sqlite3 tool while the service runs (read-only queries are safe). Two examples for data-protection requests and retention (typical; take a backup first before any write):
-- everything logged about one fob
SELECT datetime(ts,'unixepoch'), door_name, decision, reason FROM events WHERE fob_code='A1B2C3' ORDER BY ts;
-- trim access events older than a year for one tenant
DELETE FROM events WHERE tenant_id=3 AND kind='access' AND ts < strftime('%s','now','-1 year');
Windows and development use
The binary also runs on Windows (accesscontroller.exe) with the same variables, which is handy for a bench set-up or a single-site installation on an existing office PC. Open port 1883 in Windows Firewall for the door network and use the PC's LAN IP as the adoption address. For anything more than a bench, host it on Linux behind a proxy as above.