Professional-grade physical access control powered by Raspberry Pi
PiDoors is a complete, industrial-grade access control system built on Raspberry Pi hardware. It provides enterprise-level security features while remaining affordable and open source. Designed for small businesses, makerspaces, office buildings, or anyone needing professional access control.
Key Benefits:
- Cost-Effective: 10x cheaper than commercial systems (~$100-150 per door vs $500-2000)
- Secure: TLS database encryption, bcrypt passwords, SQL injection protection, CSRF tokens
- Open Source: Full control over your security system
- Modern Interface: React SPA with TailwindCSS — fast, responsive single-page application
- Offline Capable: 24-hour local caching keeps doors working during network outages
- Extensible: Easy to customize and integrate
| Dashboard | Doors |
|---|---|
![]() |
![]() |
| Cards | Access Logs |
|---|---|
![]() |
![]() |
| Schedules | Access Groups |
|---|---|
![]() |
![]() |
| Reports | Updates |
|---|---|
![]() |
![]() |
- Multi-format Wiegand: 26, 32, 34, 35, 36, 37, 48-bit with auto-detection
- OSDP Readers: RS-485 encrypted readers (planned — reader module included, not yet integrated)
- NFC/RFID: PN532 and MFRC522 support (planned — reader modules included, not yet integrated)
- Time-based access schedules
- Access groups and permissions
- Holiday calendar support
- Card validity date ranges
- Persistent master cards (never expire for emergency access)
- Master card toggle in web UI — promote any card to master with a checkbox
- Toggle any door into gate mode for rolling/sliding gates with motor controllers
- 3 configurable inputs (open / stop / close physical buttons)
- 3 configurable outputs (open / stop / close relays to gate motor)
- Per-output hold duration, configurable polarity, configurable GPIO pins
- Triple-tap any button to enter hold-current-state
- Master card 3-scan = hold open (configurable)
- Stop interrupts the active output immediately
- Reverse direction commands automatically stop and reverse
- Web UI shows live gate state (idle/opening/closing/stopped/open/closed) and dedicated control buttons
- State persists across controller restarts
- Server-side pin conflict checker prevents double-assigning GPIO pins
- Universal configurable LED that lights on access events and during hold states
- Per-door GPIO pin assignment, hi/lo polarity
- Works on both doors and gates
- Modern React SPA with REST API backend
- Login by username or email
- Real-time dashboard with analytics
- Multi-user administration with extended profiles (name, department, company, etc.)
- Extended cardholder details (email, phone, department, employee ID, company, title)
- CSV bulk import/export with all cardholder fields (including master card flag)
- Comprehensive reporting
- Email notifications
- Complete audit trail
- Remote door control
- Instant offline detection via server-initiated polling (no stale heartbeat data)
- Door auto-registration from client heartbeat
- One-click server updates from the web UI
- Remote controller updates via heartbeat signaling
- Pre-flight checks prevent partial updates
- Actionable error messages on failure
- Version tracking across all doors and server
- 24-hour offline operation
- Automatic failover
- Health monitoring
- Auto-reconnection
- Automated backups
- Service redundancy
- TLS database encryption — controller-to-server connections encrypted automatically
- Bcrypt password hashing (cost 12)
- PDO prepared statements (SQL injection proof)
- CSRF protection on all forms
- Secure session management
- Input validation and sanitization
- Security event logging
- Rate limiting on login
- Raspberry Pi 3B+ or newer (Pi 4 recommended for server)
- Raspberry Pi OS (Debian-based, 64-bit recommended)
- Internet connection for initial setup
- Node.js and npm (installed automatically by
install.sh) - For door controllers: card reader hardware and relay module
# Download PiDoors
git clone https://github.com/sybethiesant/pidoors.git
cd pidoors
# Run installer as root
sudo ./install.shThe installer presents three installation modes:
| Mode | Use Case |
|---|---|
| 1) Server | Web interface + MariaDB database (run on your central Pi) |
| 2) Door Controller | GPIO + card reader daemon (run on each door Pi) |
| 3) Full | Both server and controller on one Pi (small deployments) |
- Updates system packages
- Installs dependencies (Nginx, PHP-FPM, MariaDB, Node.js, Python libraries)
- Generates TLS certificates and enables encrypted database connections
- Creates the
usersandaccessdatabases with all required tables - Imports the full database schema (base tables + migration extensions)
- Deploys the PHP API to
/var/www/pidoors/ - Builds the React SPA and deploys to
/var/www/pidoors-ui/ - Configures Nginx to serve the SPA with
/api/*routed to PHP-FPM - Prompts you to create an admin account (username, email + password)
- Sets up log rotation and backup scripts
- Open
https://your-pi-ip/in a browser - Log in with the email (or username
Admin) and password you set during install - Navigate to Doors to see your door controllers as they come online
- Navigate to Cards to add access cards
- Set up Schedules and Access Groups as needed
If you prefer to install manually or need to troubleshoot:
sudo apt-get update && sudo apt-get install -y \
nginx php-fpm php-mysql php-cli php-mbstring php-curl php-json \
mariadb-server nodejs npm \
python3 python3-pip python3-dev python3-venv git curlsudo mysql_secure_installation
sudo mysql -u root -p <<'SQL'
CREATE DATABASE IF NOT EXISTS users;
CREATE DATABASE IF NOT EXISTS access;
CREATE USER IF NOT EXISTS 'pidoors'@'localhost' IDENTIFIED BY 'YOUR_PASSWORD';
CREATE USER IF NOT EXISTS 'pidoors'@'%' IDENTIFIED BY 'YOUR_PASSWORD';
GRANT ALL PRIVILEGES ON users.* TO 'pidoors'@'localhost';
GRANT ALL PRIVILEGES ON access.* TO 'pidoors'@'localhost';
GRANT ALL PRIVILEGES ON users.* TO 'pidoors'@'%';
GRANT ALL PRIVILEGES ON access.* TO 'pidoors'@'%';
FLUSH PRIVILEGES;
SQLNote: The
'pidoors'@'%'user allows door controllers on other Pis to connect remotely. You also need to setbind-address = 0.0.0.0in/etc/mysql/mariadb.conf.d/50-server.cnfand restart MariaDB.
# Create the users table and audit_logs table in the users database
sudo mysql -u root -p users <<'SQL'
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(100) NOT NULL,
`user_email` varchar(255) NOT NULL,
`user_pass` varchar(255) NOT NULL,
`admin` tinyint(1) NOT NULL DEFAULT 0,
`active` tinyint(1) NOT NULL DEFAULT 1,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
`last_login` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_email` (`user_email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `audit_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`event_type` varchar(50) NOT NULL,
`user_id` int(11) DEFAULT NULL,
`ip_address` varchar(45) DEFAULT NULL,
`user_agent` varchar(255) DEFAULT NULL,
`details` text,
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `event_type` (`event_type`),
KEY `user_id` (`user_id`),
KEY `created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SQL
# Import the access database schema and migration extensions
sudo mysql -u root -p access < database_migration.sqlThe migration script creates all access tables, adds extended columns, and also switches to the users database to add profile columns. It is safe to re-run on existing installations.
# Generate a bcrypt hash for your password
HASH=$(php -r "echo password_hash('YOUR_PASSWORD', PASSWORD_BCRYPT);")
sudo mysql -u root -p users -e \
"INSERT INTO users (user_name, user_email, user_pass, admin, active) \
VALUES ('Admin', 'admin@example.com', '$HASH', 1, 1);"sudo mkdir -p /var/www/pidoors
sudo cp -r pidoorserv/* /var/www/pidoors/
sudo cp VERSION /var/www/pidoors/
sudo cp /var/www/pidoors/includes/config.php.example /var/www/pidoors/includes/config.php
sudo nano /var/www/pidoors/includes/config.php # Set your database password and server IP
sudo chown -R www-data:www-data /var/www/pidoors
sudo chmod 640 /var/www/pidoors/includes/config.phpsudo apt-get install -y nodejs npm
cd pidoors-ui
npm install && npm run build
sudo mkdir -p /var/www/pidoors-ui
sudo cp -r dist/* /var/www/pidoors-ui/
sudo chown -R www-data:www-data /var/www/pidoors-ui
cd ..# Detect your PHP version
PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;")
# Install config with correct PHP socket path
sudo sed "s|php-fpm.sock|php${PHP_VERSION}-fpm.sock|g" \
nginx/pidoors.conf > /etc/nginx/sites-available/pidoors
sudo ln -sf /etc/nginx/sites-available/pidoors /etc/nginx/sites-enabled/pidoors
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginxSee the Installation Guide for additional details.
REACT SPA (Browser)
Dashboard | Cards | Doors | Logs | Reports | Settings
|
/api/* REST
|
SERVER RASPBERRY PI
+--------------------------+
| Nginx |
| ├─ / → React |
| └─ /api/* → PHP-FPM |
| MariaDB (TLS) |
| Backups & Logs |
+--------------------------+
| |
HTTPS Push TLS/TCP DB
(instant) (sync/poll)
| |
+----------------+ +----------------+
| Door Pi #1 | | Door Pi #N |
| Push Listener | | Push Listener |
| 24hr Cache | | 24hr Cache |
| Card Reader | | Card Reader |
| Electric Lock | | Electric Lock |
+----------------+ +----------------+
One server Pi runs the React SPA, PHP API, and database. N door Pis control individual access points with 24-hour local caching. The server pushes commands instantly via HTTPS, with database polling as fallback. The server pings controllers on each page load for instant status. Heartbeat runs every 5 minutes as a safety net.
| Component | Requirement |
|---|---|
| Board | Raspberry Pi 3B+ or newer (4GB RAM recommended) |
| Storage | 16GB+ microSD card |
| Network | Ethernet recommended |
| Power | Official Raspberry Pi power supply |
| Component | Requirement |
|---|---|
| Board | Raspberry Pi Zero W or newer |
| Storage | 8GB+ microSD card |
| Reader | See supported readers below |
| Lock | 12V electric strike or magnetic lock |
| Relay | Relay module for lock control |
| Optional | Door sensor, REX button |
Supported Card Readers:
| Type | Interface | Notes |
|---|---|---|
| Wiegand (26/32/34/35/36/37/48-bit) | GPIO | Most common, auto-detection |
| OSDP v2 | RS-485 (UART) | Encrypted, requires USB-RS485 adapter |
| PN532 NFC | I2C or SPI | Mifare Classic, Ultralight, NTAG |
| MFRC522 NFC | SPI | Low-cost Mifare reader |
Total cost per door: ~$100-150
- Copy the configuration template:
cp pidoorserv/includes/config.php.example pidoorserv/includes/config.php- Edit configuration:
nano pidoorserv/includes/config.php- Set your values:
return [
'sqladdr' => '127.0.0.1',
'sqldb' => 'users',
'sqldb2' => 'access',
'sqluser' => 'pidoors',
'sqlpass' => 'your_secure_password',
'url' => 'https://your-pi-ip',
// ... other settings
];- Secure the file:
chmod 640 pidoorserv/includes/config.php- Copy the configuration template:
cp pidoors/conf/config.json.example pidoors/conf/config.json- Edit configuration:
nano pidoors/conf/config.json- Set your values (use your actual door name as the key):
{
"frontdoor": {
"reader_type": "wiegand",
"d0": 24,
"d1": 23,
"wiegand_format": "auto",
"latch_gpio": 18,
"open_delay": 5,
"unlock_value": 1,
"sqladdr": "SERVER_IP_ADDRESS",
"sqluser": "pidoors",
"sqlpass": "your_database_password",
"sqldb": "access"
}
}Important: Most Wiegand readers output 5V logic signals, but Raspberry Pi GPIO pins are 3.3V only. A bi-directional logic level shifter (5V to 3.3V) is recommended on the DATA0 and DATA1 lines to protect the Pi's GPIO pins. Without one, the Pi may work initially but the GPIO pins can be damaged over time.
| Wiegand Reader | Level Shifter | Raspberry Pi |
|---|---|---|
| DATA0 (Green) | HV1 → LV1 | GPIO 24 |
| DATA1 (White) | HV2 → LV2 | GPIO 23 |
| GND (Black) | GND (shared) | GND (Pin 6) |
| 5V+ (Red) | HV | 5V (Pin 2) |
| — | LV | 3V3 (Pin 1) |
| Relay Module | Raspberry Pi |
|---|---|
| IN | GPIO 18 |
| VCC | 5V (Pin 4) |
| GND | GND (Pin 14) |
Connect lock to relay NO/COM terminals with 12V power supply.
For rolling/sliding gates with motor controllers, enable Gate Mode in the door edit page. Each gate has up to 3 inputs (physical buttons) and 3 outputs (relays to the gate motor). All inputs and outputs are optional — only enable the ones your hardware supports. GPIO pins are assigned in the web UI from the available pin list.
Gate Outputs (relays to motor) — typically wire each relay's NO/COM terminals to the corresponding input on your gate motor controller (open, stop, close terminals).
| Gate Output | Purpose |
|---|---|
| Open relay | Held active for the configured duration to open the gate |
| Close relay | Held active for the configured duration to close the gate |
| Stop relay | Optional — fires when stop is triggered, for motor controllers with a dedicated stop input |
Gate Inputs (physical buttons or RF remote relays) — wire the button/relay between the assigned GPIO pin and GND (default pull-up mode).
| Gate Input | Purpose |
|---|---|
| Open button | Triggers the open output (3-tap = hold open) |
| Close button | Triggers the close output (3-tap = hold closed) |
| Stop button | Cuts the active output immediately (3-tap = hold at current position) |
Voltage Warning: Same as Wiegand readers — if your buttons or RF remote relays output 5V signals, use a level shifter to protect the Pi's 3.3V GPIO inputs. For dry-contact switches (relay closures to ground) no level shifter is needed.
In gate mode, the regular Lock Relay is unused — the gate's open/close outputs replace it. The "Unlock Duration" field in the door edit page is automatically hidden when gate mode is enabled. Each gate output has its own configurable hold duration.
PiDoors supports two LED output methods:
Legacy LEDs (hardcoded) — GPIO 22 (red/idle) and GPIO 25 (green/granted) are always set up as outputs. These work automatically for both door and gate mode:
- Idle / locked / denied: GPIO 22 HIGH, GPIO 25 LOW
- Access granted / unlocked / gate opening: GPIO 22 LOW, GPIO 25 HIGH
This is useful for Wiegand keypads with a built-in bicolor LED — wire the LED input to GPIO 22 and the keypad shows red at idle, green on valid access.
Configurable Status LED — For more flexibility, enable the Status LED option in the door edit page. Assign any available GPIO pin and polarity. The LED pulses on access granted and flashes on access denied. Works on both doors and gates, independent of the legacy LEDs.
3V3 (1) (2) 5V
GPIO2 (3) (4) 5V
GPIO3 (5) (6) GND
GPIO4 (7) (8) GPIO14
GND (9) (10) GPIO15
GPIO17 (11) (12) GPIO18 <- Relay (GPIO18)
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23 <- DATA1 (GPIO23)
3V3 (17) (18) GPIO24 <- DATA0 (GPIO24)
GPIO25 <- Green LED (legacy)
GPIO22 <- Red LED (legacy)
Full wiring diagrams available in Installation Guide.
Via Web Interface:
- Navigate to Cards > Add Card
- Enter card details (scan card at reader to get ID)
- Assign access groups and schedules
- Click Add Card
Via CSV Import:
card_id,user_id,firstname,lastname,email,department
12345678,EMP001,John,Smith,john@example.com,Engineering
87654321,EMP002,Jane,Doe,jane@example.com,MarketingUpload at Cards > Import CSV. Optional columns: email, phone, department, employee_id, company, title, notes, group_id, schedule_id, valid_from, valid_until, pin_code.
- Go to Schedules > Add Schedule
- Name the schedule (e.g., "Business Hours")
- Set time windows for each day
- Assign to cards or doors
- Dashboard: Real-time statistics and charts
- Access Logs: Filter by date, door, user; export to CSV
- Audit Log: Track all administrative actions
- Email Alerts: Failed access attempts, door offline notifications
Backups run daily at 2 AM to /var/backups/pidoors/
sudo /usr/local/bin/pidoors-backup.shVia Web UI (Recommended):
- Go to Updates in the admin sidebar
- Click Check for Updates to see the latest release
- Click Update Server to update the web interface
- Use the Doors page to push updates to door controllers
Manual update:
cd ~/pidoors
git pull
sudo cp -r pidoorserv/* /var/www/pidoors/
sudo systemctl restart nginx
sudo systemctl restart pidoors # On door controllersWhen upgrading, run the migration script to add any new columns:
# Backup first
mysqldump -u pidoors -p access > backup_access_$(date +%Y%m%d).sql
mysqldump -u pidoors -p users > backup_users_$(date +%Y%m%d).sql
# Run migration (safe to re-run, uses IF NOT EXISTS checks)
mysql -u root -p access < database_migration.sqlThe migration script handles both the access and users databases automatically.
v2.2.1 Migration (Required if upgrading from v2.2 or earlier):
This migration converts door assignments from space-separated to comma-separated format:
python3 migrations/migrate_doors_format.py --dry-run # Preview
python3 migrations/migrate_doors_format.py # Apply# Door controller logs
sudo journalctl -u pidoors -f
# Web server logs
sudo tail -f /var/log/nginx/pidoors_error.log- TLS encryption for all database connections (auto-configured during install)
- Bcrypt password hashing with automatic MD5 upgrade
- PDO prepared statements (no SQL injection)
- CSRF token protection on all forms
- Secure session handling with timeout
- Comprehensive input validation
- Complete audit logging
- Login rate limiting (5 attempts, 15-minute lockout)
Please report security issues to the repository owner directly, not via public issues.
pidoors/
├── pidoorserv/ # PHP API backend
│ ├── api.php # Unified REST API router
│ ├── includes/ # Core PHP includes
│ │ ├── config.php.example
│ │ ├── security.php
│ │ ├── header.php
│ │ ├── push.php # Push communication & status polling
│ │ ├── notifications.php
│ │ └── smtp.php # Lightweight SMTP sender
│ ├── cron/ # Scheduled tasks (notifications)
│ ├── users/ # User management
│ ├── database/ # Database connection
│ ├── css/ # Stylesheets (legacy)
│ └── js/ # JavaScript (legacy)
├── pidoors-ui/ # React SPA frontend
│ ├── src/
│ │ ├── pages/ # Dashboard, Cards, Doors, Settings, etc.
│ │ ├── components/ # Shared UI components
│ │ ├── contexts/ # Auth context
│ │ ├── api/ # REST API client
│ │ └── types/ # TypeScript type definitions
│ ├── package.json
│ └── vite.config.ts
├── pidoors/ # Door controller
│ ├── pidoors.py # Main daemon
│ ├── pidoors.service # Systemd service
│ ├── pidoors-update.sh # Self-update script (runs as root via sudo)
│ ├── readers/ # Card reader modules
│ │ ├── base.py # Abstract base class
│ │ ├── wiegand.py # Wiegand GPIO reader
│ │ ├── osdp.py # OSDP RS-485 reader
│ │ ├── nfc_pn532.py # PN532 NFC reader
│ │ └── nfc_mfrc522.py # MFRC522 NFC reader
│ ├── formats/ # Card format definitions
│ │ └── wiegand_formats.py
│ └── conf/ # Configuration
│ └── config.json.example
├── nginx/ # Nginx configuration
│ └── pidoors.conf
├── docker/ # Docker dev/test environment
│ ├── docker-compose.yml
│ ├── Dockerfile.server
│ ├── Dockerfile.door
│ ├── server-entrypoint.sh
│ ├── door-entrypoint.sh
│ ├── deploy.sh # Deploy to remote Docker host
│ └── mock_gpio.py # Mock GPIO for containerized door
├── VERSION # Current version number
├── install.sh # Installation script
├── server-update.sh # Server self-update script
├── database_migration.sql
└── README.md
| Document | Description |
|---|---|
| Installation Guide | Complete beginner-friendly setup |
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Requirements:
- Test on actual Raspberry Pi hardware
- Verify database migrations work
- Check security implications
- Update documentation
Current Version: 0.3.9 - Pre-release
Future Enhancements (community contributions welcome):
- Mobile app (iOS/Android)
- Bluetooth Low Energy (BLE) readers
- Biometric integration (fingerprint, face)
- Cloud backup integration
- Multi-site management dashboard
Note: Version numbering was reset from 3.x to 0.x in April 2026. The project had rapidly iterated from v1.0 to v3.2 during initial development. The 0.x series reflects pre-release status as the system matures toward a proper v1.0.0 release.
- React 18 → 19 — major framework migration, built and bundled (users get it automatically via the pre-built SPA tarball, no npm required)
- Vite 5 → 6 — build toolchain upgrade
- @vitejs/plugin-react 4 → 5 — React 19 compatibility
- @tanstack/react-query 5.60 → 5.99 — performance and bug fix patches
- Python requirements safety net — new
requirements.txtin the controller directory.pidoors-update.shnow runspip install -r requirements.txton every update so future new Python dependencies install automatically.install.shalso uses it. - Zero vulnerabilities — npm audit clean after the migration
- Gate auto-close — gates can now auto-close after opening, with a configurable delay (1–3600 seconds). Mandatory clearance sensor input ensures the gate won't auto-close on a person, vehicle, or obstruction.
- Clearance sensor input — new optional 4th gate input for beam-break, IR, or laser clearance sensors at the gate opening. Configurable polarity and "active means clear/blocked" logic.
- Auto-close safety — UI greys out the auto-close toggle until a clearance sensor is configured. The API rejects auto-close enable requests without a sensor. The controller refuses to fire auto-close if the sensor is missing or the path is blocked.
- Auto-close interruption — any manual command (open/close/stop/hold) cancels a pending auto-close timer. The clearance sensor breaking restarts the timer when clear again.
- Dashboard gate controls — the dashboard's door status panel now mirrors the doors page for gates: gate state badge, held badge, and Open/Stop/Close/Hold/Release buttons.
- Session timeout fix —
session_timeout = 0(unlimited) now actually works. Sessions are stored in a custom save path to bypass Debian'sphpsessioncleancron, which would otherwise delete idle sessions after 24 minutes regardless of the app-level setting.
- Gate command errors are actionable — instead of the generic "Could not reach the gate controller" message, the web UI now shows specific reasons: "Gate is held. Release the hold first.", "No output pin is configured for this direction.", "Gate is already opening.", etc.
- Gate state syncs immediately — server-side status polling now reads
gate_stateandgate_heldfrom the controller's/pingresponse, so theHeldbadge and current state update in near-real-time on the doors page - Gate commands update DB immediately — clicking open/close/stop/hold/release reflects on the UI on the next poll (no more waiting for the 5-minute heartbeat)
- Fix:
push_to_controllerfailure now correctly distinguished from "controller reached but command refused"
- Fix:
index.htmlno longer cached by browsers — nginx now sendsCache-Control: no-cachefor the SPA entry point so users don't see stale UI after an update. Hashed JS/CSS assets still cache for a year. - Fix: Web UI server updates now also upgrade the nginx config if it's changed (via a new root-owned helper script + sudoers entry for www-data). This deploys nginx changes like the cache-control fix without requiring shell access.
- Fix: Python
SyntaxErrorin the controller's rename handler (nestedglobal zonedeclaration) that bricked the service after updating to 0.3.5.
- Fix: Door rename could corrupt
config.jsonwhen the controller's in-memoryzonevariable fell out of sync with the on-disk config after a failed restart - Fix: Rename handler updates the in-memory
zoneimmediately so subsequent pushes work even if the service restart fails - Fix: Sudoers now allows
systemctl restart pidoorsso rename can actually restart the service - Fix: Rename refuses to proceed if the old zone key is missing from config.json (prevents creating empty broken entries)
- Fix: Same-name renames are a no-op
- Fix: Update script guards against double self-update in the same chain
- Fix: Update script reads zone from
zone.json(source of truth) and refuses to create a phantom empty zone entry if the zone is missing
- Pin selector dropdowns show all GPIO pins but disable ones already in use — both within the form (other gate I/O, status LED, sensor) and by reserved hardware features (reader, lock relay)
- Disabled options labeled clearly as
GPIO X (in use)orGPIO X (Wiegand DATA0) - Hide
Unlock Durationfield when gate mode is enabled - Documentation: gate mode and status LED sections added to README and INSTALLATION_GUIDE
- Gate mode — toggle on the door edit page to convert a door into a gate with open/close/stop inputs and outputs
- Configurable gate I/O — each input/output is optional with assignable GPIO pin, hi/lo polarity, and (for outputs) hold duration
- Triple-tap hold — physical open/close/stop buttons enter hold-current-state when triple-tapped within the configured window
- Gate state reporting — controller reports current gate state (idle/opening/closing/stopped/open/closed/held) to the web UI
- Gate web UI — dedicated open/close/stop/hold/release buttons on the doors page when in gate mode, with status badge
- Status LED feature — universal configurable LED that lights up on access events and during hold states (works on doors and gates)
- Pin conflict checker — server-side endpoint validates GPIO pin assignments don't conflict with reader, sensor, or other features
- Master scan settings — configurable master scans to hold open / release hold (defaults: 3 / 1)
- Push timeout fix — increased default push timeout from 3s to 5s to accommodate Pi Zero TLS handshake delays
- Door rename push — when renaming a door, server pushes the new name to the controller, which updates its config and restarts
- Door rename protection — doors must be online to rename; server pushes the name change to the controller, which updates its config and restarts
- Reliable controller self-updates — update script detaches from the service cgroup before stopping the service; temp files use
/var/cache/to survive PrivateTmp - Self-update compatibility — patches older release scripts during self-update with cgroup detach, venv python, and temp dir fixes for seamless upgrades from any prior version
- Version transition — update system handles the 3.x → 0.x renumbering so existing installations update seamlessly
- Heartbeat fix —
door_sensor_openmissing global declaration broke heartbeat entirely; doors never auto-registered or showed online - MariaDB TLS — fixed three issues preventing TLS on fresh installs: grep matched commented-out defaults, wrong config section header on newer MariaDB, server cert missing SAN IP
- CA cert signing — fixed serial file permissions and empty tempfile issues that broke the signing API
- Update script — uses venv Python for pymysql, adds curl timeouts, re-signs listener cert on every update
- Installer improvements — socket auth detection, SSL permissions always applied, menu formatting fixed
- Pre-built React SPA included in release tarballs (no Node.js needed for server updates)
- v3.0: React SPA frontend rewrite, REST API, push-based instant controller communication via HTTPS
- v2.6: TLS database encryption, email notifications, self-healing TLS connections
- v2.5: Version tracking, server and controller self-update system, remote door unlock
- v2.4: Master card web UI, GPIO fix for Debian Bookworm (rpi-lgpio), automated installer
- v2.3: Login by username, extended user/cardholder profiles, CSV import
- v2.2: Multi-reader support (Wiegand/OSDP/NFC), security hardening, Bootstrap modals
- v2.0: Security overhaul, 24-hour offline caching, time-based schedules
- v1.0: Initial release — basic Wiegand 26-bit support, simple web interface
- Email notifications
- Automated backups
- Multi-format Wiegand support
- Basic Wiegand 26-bit support
- Simple web interface
- MySQL database
This project is open source and available for free use, modification, and distribution.
- Documentation: Installation Guide
- Bug Reports: GitHub Issues
- Feature Requests: GitHub Issues
Built for the open source community










