Installation

Get FD Commander running on your server in about 5 minutes. Works on any Linux machine with 2 GB of RAM, including a Raspberry Pi 4 running off a battery pack at your Field Day site.

System Requirements

FD Commander is designed to run on modest hardware, the kind of gear you'd actually bring to a field site. The deploy script installs everything it needs, so you're starting from a bare OS.

ComponentMinimumRecommended
Operating SystemUbuntu 22.04 / Debian 12Ubuntu 24.04 LTS or Raspberry Pi OS
RAM2 GB4 GB+
Storage16 GB32 GB+
NetworkLAN accessGigabit Ethernet

You do not need to install PHP, MariaDB, Node.js, or any other dependencies yourself. The deploy script handles all of it. It installs PHP 8.4, MariaDB, Node.js 20, Composer, and FrankenPHP automatically.

No Internet Required at Runtime
FD Commander is fully air-gapped once deployed. The install script downloads dependencies, so you need internet during setup. After that, zero external connectivity is needed. Do the install at home before the event.

Install Steps

1

Clone the Repository

Pull down the source code from GitHub.

2

Run the Deploy Script

One command. The script installs PHP 8.4, MariaDB, Node.js, Composer, and FrankenPHP. It compiles front-end assets, runs database migrations, seeds reference data (bands, modes, ARRL sections, bonus types), and creates systemd services for the web server, queue worker, scheduler, and WebSocket server.

3

Open It in a Browser

Navigate to your server's address from any device on the network. Register the first account (it becomes the System Administrator), create your event, and start configuring.

1. Clone the Repository

bash Copy
$ git clone https://github.com/ckoval7/fd-commander.git
$ cd fd-commander

2. Run the Deploy Script

At minimum, you need to pass your server's domain name or IP address. The script will use sensible defaults for everything else.

bash Copy
$ sudo ./deploy.sh --domain fd.local

The --domain flag tells the server what hostname or IP address it should respond to. This is the address your operators will type into their browsers to reach FD Commander. A few common choices:

You can change this later by re-running the deploy script with a different --domain value.

The script runs through 9 phases: detecting your OS, installing system packages, cloning and building the application, creating the database, configuring FrankenPHP, setting up SSL (if requested), creating systemd services, opening firewall ports, and caching the configuration. It logs everything to /var/log/fd-commander-deploy.log.

Raspberry Pi Users
On a Raspberry Pi 4, the install may take longer due to Composer dependency resolution and npm asset compilation. This is normal. Make sure you have at least a 16 GB SD card, though a USB SSD is recommended for better database performance.

3. Open It Up

Once the script finishes, open a browser on any device connected to your LAN and navigate to the domain or IP you specified. By default the server runs on port 80 (HTTP). The first account you register automatically gets the System Administrator role.

You're Live
From here, create an event, set your callsign and ARRL section, define stations, and share the registration link with your operators. The dashboard will guide you through the setup checklist.

Deploy Script Flags

The deploy script accepts several optional flags to customize the installation. Only --domain is required.

FlagDefaultDescription
--domain(required)Domain name or IP address for the server
--port80 (HTTP) / 443 (SSL)Port for the web server
--ssloffEnable HTTPS. Uses Let's Encrypt by default
--email(none)Email for Let's Encrypt certificate registration
--ssl-cert(none)Path to a custom SSL certificate file
--ssl-key(none)Path to a custom SSL private key file
--db-namefd_commanderMariaDB database name
--db-userfd_commanderMariaDB username
--db-password(generated)MariaDB password. Auto-generated if omitted
--app-path/var/www/fd-commanderInstallation directory on the server
--reverb-port8080Port for the WebSocket server (Laravel Reverb)
--branchmainGit branch to deploy
--repo-url(GitHub)Custom Git repository URL
--no-seedersoffSkip database seeders (bands, modes, sections, etc.)
--dry-runoffPrint what would be done without making changes

Example: Raspberry Pi on your LAN

bash Copy
$ sudo ./deploy.sh --domain fd.local

This deploys on port 80 with HTTP. Operators connect by opening http://fd.local in any browser. If your network doesn't resolve .local hostnames, use the Pi's IP address instead.

Example: HTTPS with Let's Encrypt

bash Copy
$ sudo ./deploy.sh --domain fd.yourclub.org --ssl --email admin@yourclub.org

Caddy (the web server bundled with FrankenPHP) handles Let's Encrypt certificate provisioning and auto-renewal. This requires the server to be reachable from the internet on ports 80 and 443 during setup.

First Event Setup

After installation, you'll register the first user account (which becomes the System Administrator), then set up your Field Day event. The dashboard shows a setup checklist that guides you through the process.

Create an Event

Go to Administration > Events and click Create Event. You'll set the event type (Field Day or Winter Field Day), your club callsign, ARRL section, operating class, number of transmitters, and the power sources you'll be using. The start and end times are pre-filled based on the official ARRL schedule.

Setup Checklist

The dashboard tracks four setup milestones:

1

Event configuration created

Your event exists with callsign, section, class, and power sources defined.

2

Equipment inventoried

At least one piece of equipment has been added to the system.

3

Stations set up

At least one station has been defined with band and mode assignments.

4

Shifts scheduled

Operator shifts have been created for your stations.

Invite Operators

The dashboard shows a registration link (e.g. http://fd.local/register) that you can share with your club. Operators visit the link, register with their callsign, and they're in the system. By default, new registrations get the Operator role. You can change registration mode and default roles in Settings.

User Roles

FD Commander ships with four built-in roles. Each role has a specific set of permissions that control what parts of the system a user can access. Roles are managed under Settings > Role Management.

RoleWhat They Can Do
System AdministratorFull access to all settings, events, users, roles, stations, equipment, scheduling, safety, reports, and audit logs. Cannot log contacts by default (to keep the admin role distinct from operators).
Event ManagerManage events, stations, equipment, schedules, bonuses, guestbook, safety checklists, photos, and view reports. The person running your Field Day operation.
Station CaptainLog and edit contacts, view and manage stations, manage equipment. Runs a single operating position and keeps their operators on task.
OperatorLog contacts, view stations, manage their own personal equipment catalog. The default role for new registrations.

Permissions are fully customizable. You can modify what each role can do, or adjust individual user permissions to fit how your club actually operates.

Stations

Stations represent individual operating positions. Go to Event Management > Stations to create them. Each station gets a name, band/mode assignments, and equipment. During the event, operators select which station they're logging for, and FD Commander tracks contacts per station.

If you're running the same station setup from a previous event, use the Clone from Event button to copy your station definitions forward.

Volunteer Scheduling

The scheduling system lets you define shift blocks for each station and role, then lets operators self-sign-up for open slots. Go to Event Management > Schedule to see the operator view, or Administration > Manage Schedule to create and assign shifts.

Fill-status indicators show which shifts have gaps so you can spot coverage problems before they happen. Each operator sees a "My Shifts" view so they know when they're up. You can schedule any role, not just operators: safety officers, public information table, station captains.

Equipment Tracking

Every operator has a personal equipment catalog under Equipment > My Catalog where they add their own gear (radios, antennas, power supplies, etc.) with photos, make/model, serial numbers, and tags. Equipment can then be committed to an upcoming event and assigned to specific stations.

The Equipment > All User Catalogs view shows everything across all users. Equipment > Event Commitments shows what's been committed for the current event. Equipment moves through four statuses: committed, delivered, returned, and damaged.

Safety Checklists

FD Commander includes the 15-item ARRL Field Day safety checklist, which is worth 100 bonus points when completed. Go to Event Management > Site Safety to work through the checklist during setup, or Administration > Manage Safety Checklist to customize the items.

Each item tracks who completed it and when. The scoring page automatically reflects whether the safety bonus has been earned.

Contact Logging

Operators log contacts under Logging > View Log after selecting their station. The logging interface is a single-field entry flow: type the callsign, the exchange fields populate, and hit Enter. Dupe checks run in real time against every contact logged by every station on every band. Section and class validation catches typos before they're saved.

All contacts go into a shared database via WebSocket (Laravel Reverb), so every connected browser sees updates as they happen. The scoring dashboard, logbook, and band activity all reflect new contacts instantly.

Scoring

The scoring page (Event Management > Scoring) shows your live score using the ARRL Field Day formula:

Scoring Formula
(QSO Base Points × Power Multiplier) + Bonus Points = Final Score

QSO points are weighted by mode: CW and digital contacts are worth 2 points each, phone contacts are worth 1. Power multiplier is determined automatically from your configured power sources (5x for ≤5W natural power, 2x for ≤5W or 6-100W with commercial/generator, 1x for over 100W). Bonus points come from 12 ARRL categories, each worth 100 points (except NTS Messages Handled at 10 points each).

Bonus Categories

The scoring page lists all 12 categories with their status: verified, claimed, or unclaimed. Some bonuses are tracked automatically (like the safety checklist completion or visitor guestbook count). Others are claimed manually by the event manager (like media publicity or satellite QSO).

BonusPointsTracking
Emergency Power100Based on configured power sources
Media Publicity100Manual claim
Public Location100Manual claim
Information Booth100Manual claim
Social Media100Manual claim
Safety Officer100Automatic (safety checklist completion)
Natural Power QSOs100Manual claim
Agency/Official Visit100Manual claim
Satellite QSO100Manual claim
Section Manager Message100Automatic (NTS message tracking)
W1AW Field Day Bulletin100Automatic (W1AW bulletin form)
NTS Messages Handled10 eachAutomatic (per radiogram logged)

Visitor Guestbook

Field Day is a public event. The guestbook (Event Management > Guestbook) lets visitors sign in with just a name and optionally a callsign. No account or password needed. Hand someone a tablet and they sign themselves in.

Licensed hams and general public are tracked separately. Guest counts feed into the Public Information bonus point tracking. The event manager can view and manage all entries under Administration > Manage Guestbook.

NTS Messages

FD Commander tracks NTS radiogram handling: originated, relayed, and received. Each message logged counts toward the NTS Messages Handled bonus (10 points each). There are also dedicated forms for capturing the Section Manager message and the W1AW Field Day bulletin, which are separate 100-point bonuses.

ICS-213 General Message format is also supported and can be enabled under Settings > System Preferences > Enable ICS-213 Message Format.

Photos

Operators can upload photos from any connected device during the event. The gallery (Event Management > Gallery) stores everything on the local server with no cloud dependency. Photos are tagged with who uploaded them and when.

Cabrillo Export

When the event ends, go to Reports to generate a Cabrillo file ready to upload to the ARRL submission page. The export includes all your QSO records formatted to the Cabrillo specification for Field Day.

Offline Resilience

FD Commander has two layers of resilience. First, the entire platform runs on your LAN with zero internet dependency, fully air-gapped. Second, if a browser temporarily loses its connection to the server (someone trips over the ethernet cable, Wi-Fi drops out), contacts queue locally on the device. When the connection comes back, they sync automatically. A status indicator shows how many contacts are queued and when they've been synced.

Paper Log Transcription

If the server goes down and you fall back to paper for a while, FD Commander has a transcription feature that lets you enter those contacts after the fact with the correct timestamps. This way nothing is lost and your Cabrillo export stays complete. There is a configurable post-event grace period (default: 30 days) during which operators can still enter late contacts.

Network Setup

FD Commander runs on your local network. Operators connect by opening the server's address in any web browser. A typical field setup looks like this:

1

Server

A Raspberry Pi, laptop, or any Linux box connected to your field network via ethernet. Runs FrankenPHP on port 80 and Laravel Reverb (WebSocket) on port 8080.

2

Network

A router or access point providing your field LAN. Can be Wi-Fi, ethernet, or both. No internet connection needed.

3

Client Devices

Any device with a web browser: laptops, tablets, phones. Windows, Mac, Linux, Android, iOS. Connect to the field network and open the server's address.

Firewall Ports
The deploy script opens ports automatically via UFW (Debian/Ubuntu) or firewalld (RHEL). If you're managing your firewall manually, open port 80 (or 443 for HTTPS) for the web interface and port 8080 for WebSocket connections.

Docker

FD Commander includes a Docker Compose setup for containerized deployment. The stack runs two services: an application container (web server, WebSockets, queue worker, scheduler) and a MySQL database container.

Quick Start

bash Copy
# Generate .env with secure defaults (DB password, Reverb keys, etc.)
$ bash docker/setup.sh
 
# Build and start
$ docker compose up -d
 
# Check logs (first run will migrate and seed the database)
$ docker compose logs -f app

The setup script creates .env from .env.example and auto-generates all secrets (DB_PASSWORD, REVERB_APP_KEY, etc.). Run it again on an existing .env to fill in any missing secrets without overwriting existing values. The app will be available at http://localhost (or whichever port you set with APP_PORT).

Configuration

These environment variables in .env are used by docker-compose.yml:

VariableDefaultDescription
APP_PORT80Host port mapped to the container
DB_DATABASEfd_commanderMySQL database name
DB_USERNAMEfd_commanderMySQL user
DB_PASSWORD(required)MySQL password (auto-generated by setup.sh)
DB_ROOT_PASSWORDrootsecretMySQL root password

To run on a custom port:

bash Copy
$ APP_PORT=8080 docker compose up -d

Architecture

The app container runs four processes via supervisord:

ProcessPurpose
Octane (FrankenPHP)HTTP server on port 80
ReverbWebSocket server (port 8080, internal)
Queue WorkerBackground job processing
SchedulerScheduled tasks (runs every 60s)

WebSocket connections reach Reverb through Caddy's reverse proxy at the /app path, so only port 80 is exposed to the network.

First Run

On first startup, the entrypoint script automatically waits for the database, runs all migrations, runs production seeders (event types, bands, modes, sections, roles, permissions, etc.), generates an APP_KEY if not set, generates Reverb WebSocket credentials if not set, and caches configuration, routes, views, and events. Seeders only run once. A marker file in the persistent storage volume prevents re-seeding on subsequent starts.

Data Persistence

Two named volumes store persistent data:

VolumeContents
app-storageUploaded files, logs, framework cache
mysql-dataDatabase files

These survive docker compose down. Use docker compose down -v to remove them (this destroys all data).

HTTPS with a Reverse Proxy

The container serves plain HTTP on port 80. For HTTPS, place a reverse proxy (like Nginx) in front of it. The app includes trustProxies(at: '*') so X-Forwarded-Proto and X-Forwarded-For headers are respected automatically. When behind a TLS proxy, update your .env:

env Copy
APP_URL=https://your-domain.com
REVERB_HOST=your-domain.com
REVERB_PORT=443
REVERB_SCHEME=https

Managing Services

bash Copy
# View all process statuses
$ docker compose exec app supervisorctl status
 
# Restart a specific process
$ docker compose exec app supervisorctl restart queue-worker
 
# View logs
$ docker compose logs -f app
 
# Stop everything
$ docker compose down

Rebuilding After Updates

bash Copy
$ docker compose build
$ docker compose up -d

The entrypoint runs php artisan migrate --force on every start, so new migrations are applied automatically.

Updating

FD Commander includes an update.sh script that pulls the latest code, updates dependencies, runs new database migrations, and restarts services.

bash Copy
$ sudo ./update.sh

Run this at home when preparing for the next event, not during the event itself.