Documentation

Install & Usage Guide

From a fresh Linux server to your first live visitor in about 15 minutes. Everything runs in Docker; you host it on your own infrastructure.

1. Requirements

You need a Linux host (a small VM or on-prem server is fine) with:

  • Docker Engine 24+ and the Docker Compose plugin
  • 2 vCPU / 4 GB RAM minimum (8 GB recommended for busy sites)
  • 10 GB free disk for the database and stored selfies
  • An SMS gateway account (MSG91, Twilio, or your in-house Nimbus API) for OTP
  • Optionally, an SMTP account for feedback emails and a domain name for TLS
No internet access is required by the app itself except to reach your chosen SMS/email gateway. All visitor data stays on your server.

2. Installation

Step 1 — Get the code onto the server

Copy the solution folder to your server (e.g. via git clone or scp), then enter it:

# from your server
git clone <your-repo-url> visitor-management
cd visitor-management
Step 2 — Create your environment file

Copy the template and open it in an editor:

cp .env.example .env
nano .env

Set strong values for the items below. Generate the encryption key with the one-liner shown:

# passwords & secrets — use long random strings
MARIADB_PASSWORD=...
MARIADB_ROOT_PASSWORD=...
MINIO_ACCESS_KEY=...
MINIO_SECRET_KEY=...
SECRET_KEY=...

# field encryption key — generate this exact way:
# python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
FIELD_ENCRYPTION_KEY=...

# first admin account (created automatically on first start)
BOOTSTRAP_ADMIN_EMAIL=admin@yourcompany.com
BOOTSTRAP_ADMIN_PASSWORD=...

# public URL used in gate-pass & feedback links
PUBLIC_BASE_URL=https://vms.yourcompany.com
Important: keep .env off version control. It already appears in .gitignore.
Step 3 — Build & start the stack
docker compose up --build -d

This launches MariaDB, Redis, MinIO, the API, the background worker, the web app and the nginx proxy. On first start the backend automatically runs database migrations and creates your admin account and seed data.

Step 4 — Verify it’s running
docker compose ps
curl http://localhost:8080/health

You should see all services running and a {"status":"ok"} response.

✓ Open http://your-server:8080 — the visitor screen. The API docs are at /api/docs, and the MinIO console at :9001.

3. First configuration

Sign in at /login with your BOOTSTRAP_ADMIN_* credentials, then open Admin and complete these one-time steps in order:

3.1 — Configure the SMS / OTP provider

Go to Admin → Notifications. Choose your gateway and enter its credentials:

  • MSG91 — auth key, sender ID, OTP template ID
  • Twilio — account SID, auth token, from-number (and WhatsApp from-number if used)
  • Nimbus / Generic REST — endpoint, auth header & value, and the field names your API expects

Credentials are encrypted at rest. Switching providers later is just a dropdown — no redeploy.

3.2 — Add locations, departments & team members

Under Admin → Team & Departments you can add people three ways:

  1. By form — one person at a time (name, email, mobile, role, department).
  2. Bulk Excel upload — a .xlsx with headers: full_name, email, mobile, role, designation, department_id, location_id, external_id.
  3. REST API syncPOST /api/admin/users/sync with a JSON array; records upsert by external_id.

Assign a coordinator to each department — they receive department-level visits and diversions.

3.3 — Set categories & purposes

Under Admin → Categories, add visit categories (e.g. “Demo equipment / tools” — tick requires asset details) and purposes (e.g. “Business meeting”, “Interview”). These populate the visitor’s dropdowns.

3.4 — Choose visit rules

Set the default visit validity window (DEFAULT_VISIT_VALIDITY_HOURS in .env) and decide whether returning visitors may skip the selfie (toggle in settings). Restart the stack after editing .env: docker compose up -d.

4. Using the system

For the visitor

  1. Open the app on the kiosk or scan the QR at reception on their phone.
  2. Enter name, mobile, company and give consent; tap Send OTP.
  3. Enter the OTP received by SMS.
  4. Take a selfie (skipped automatically for returning visitors if you enabled it).
  5. Choose a person or a department, the purpose, a category, and note any equipment carried.
  6. Submit now, or tick future appointment and pick a date/time.
  7. Receive the gate pass — download it, add to calendar, or add to Apple Wallet on iPhone.

For the host / coordinator / reception

  1. Sign in and open Staff. Set your presence (available, in-meeting, DND, not available).
  2. Pending requests appear as cards. Approve or Decline.
  3. When the visitor arrives, Check in; when they leave, Check out (this records engagement time).
  4. Optionally add minutes of meeting to the visit.
  5. Diverted requests arrive with a note explaining who was unavailable and why.
If a host is in a meeting, on DND or out of office (per the in/out register), the request is diverted to the department coordinator; if they’re also unavailable, it goes to reception with a note — so a visitor is never stranded.

For the manager

Open Admin → Reports for live insight: total visits, average engagement time, total team time spent with visitors, and the feedback rating distribution. A feedback link is sent to each visitor after checkout and its results roll straight into these reports.

5. Access-control webhook (in/out register)

Keep the in/out register live by having your badge/access-control system POST events to the app whenever someone badges in or out.

Step 1 — Create the webhook

In Admin → Webhooks, create a config with a slug, a bearer secret, and the allowed source IPs of your access-control server. You’ll get a URL like https://vms.yourcompany.com/hooks/acs.

Step 2 — Point your access-control system at it

Configure it to send, on each badge event:

# POST https://vms.yourcompany.com/hooks/acs
# Header: Authorization: Bearer <your-secret>
{
  "employee_id": "E1234",
  "direction": "in"        // or "out"
}
Step 3 — Whitelist at the proxy

The webhook page generates an nginx snippet for you. Paste it into your proxy so only the access-control server’s IPs can reach the endpoint:

location /hooks/acs {
    allow 203.0.113.10;   # your access-control server
    deny  all;
    proxy_pass http://backend:8000;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Every attempt — success or failure — is recorded. The last 1,000 attempts (time, source IP, result, message) are viewable on the same page for troubleshooting.

6. Branding & theme

Under Admin → Branding, upload your company logo. The interface keeps its neutral slate base and automatically derives an accent colour from the logo, applied across the app. You can fine-tune the accent value manually if you prefer.

7. Apple Wallet passes (optional)

Downloadable image passes and calendar (.ics) files work out of the box on every device. To enable native Add to Apple Wallet on iPhone, supply your Apple signing assets in .env:

WALLET_PASS_TYPE_ID=pass.com.yourcompany.visitor
WALLET_TEAM_ID=XXXXXXXXXX
WALLET_CERT_PATH=/certs/pass.pem
WALLET_KEY_PATH=/certs/pass.key
WALLET_WWDR_PATH=/certs/wwdr.pem

Mount your certificate files into the backend container and restart. Until configured, iPhone users simply use Add to calendar and the downloadable pass.

8. Production notes

  • TLS: terminate HTTPS at the nginx proxy (or your load balancer) and set PUBLIC_BASE_URL to your https:// domain.
  • Backups: back up the MariaDB volume and the MinIO volume regularly.
  • Secrets: rotate SECRET_KEY and provider credentials per your policy; never commit .env.
  • Retention: set SELFIE_RETENTION_DAYS to match your data-protection policy — old selfies are purged automatically.
  • Updates: git pull && docker compose up --build -d — migrations run automatically on start.
  • Scaling: increase worker replicas for high OTP/notification volume.

9. FAQ

Do visitors need to install an app?

No. Everything runs in the browser — on a reception kiosk or the visitor’s own phone.

Where are selfies stored?

In on-prem MinIO object storage on your server, with an automatic retention limit. Nothing is sent to any cloud unless you choose a cloud SMS gateway.

Can we sync staff from our HR system?

Yes — use the Excel bulk upload for a one-off, or the REST sync endpoint for ongoing automation (upsert by external_id).

What happens if nobody is available?

The routing engine falls back host → coordinator → reception, and the message that reaches reception explains that the team member and coordinator were both unavailable.

Is face matching included?

The selfie is captured and stored as the pass photo. Biometric face-matching is an optional future upgrade: the schema and a provider interface are already in place for an on-prem face service, disabled by default.