Skip to content

Repository files navigation

Net Repository

A central, community-maintained directory of amateur radio and GMRS nets.
Companion project to Netcontrol-Online.

License: GPL v3


What It Is

Net Repository is a lightweight FastAPI service that maintains a curated public database of amateur radio and GMRS nets — their frequencies, schedules, modes, and geographic coverage. It provides a public read API that any tool (including Netcontrol-Online) can query to discover known nets, and a submission API that allows trusted instances to contribute net listings for moderation.

This is the missing piece from Netcontrol-Online's issue #8: a community-run, API-accessible net directory that other tools can publish to and consume.


Features

  • Public directory — browse and search published nets with no login required
  • Manual submissions — anyone can add their net via a public form (/nets/new), no Netcontrol-Online integration or API key required; enters the same moderation queue as an API submission
  • Submission API — Netcontrol-Online (or any trusted tool) can submit nets via a long-lived API key; submissions enter a moderation queue before publish
  • Verified Netcontrol-Online badge — an API submitter can claim its net runs on Netcontrol-Online, but the badge only shows once an admin verifies the claim
  • Admin moderation — approve, reject, or edit submissions; retire stale entries
  • Duplicate prevention — submissions from the same source instance + net ID update the existing net instead of creating a duplicate
  • Net session stats — a source can log a session (e.g. when a net closes) via POST /nets/stats; the directory shows each net's session count, average check-ins, and last-session date
  • API key management — admins create/revoke nr_-prefixed keys for submitters; keys are stored as SHA-256 hashes, shown raw only at creation
  • Self-service key requests — prospective submitters request a key via POST /keys/request, an admin approves it through a browser dashboard, and the requester automatically retrieves the issued key by polling — no manual key-copying required
  • Admin accounts — named username/password logins (bcrypt-hashed) protect the browser admin UI, separate from the ADMIN_TOKEN used by the JSON API
  • SEO — every published net has its own crawlable page (/nets/{id}), with robots.txt, a dynamic sitemap.xml, canonical URLs, meta descriptions, and Open Graph/Twitter Card tags throughout
  • Same tech stack as Netcontrol-Online — FastAPI + SQLAlchemy + PostgreSQL, same deployment patterns (systemd + Apache), so operators of both projects share familiar tooling

Tech Stack

  • Backend: Python 3.11+, FastAPI, SQLAlchemy (sync), PostgreSQL
  • Auth: Admin Bearer token for the JSON API (env var); session-cookie login (bcrypt-hashed passwords) for the browser admin UI; submitter API keys (SHA-256 hashed)
  • Deployment: systemd + Apache reverse proxy + Let's Encrypt

Installation

1. Clone the repository

git clone https://github.com/LadyHwesta/Net-Repository.git
cd Net-Repository

2. Create a virtual environment and install dependencies

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

3. Create the database

sudo -u postgres psql
CREATE USER netrepo WITH PASSWORD 'yourpassword';
CREATE DATABASE netrepo OWNER netrepo;
\q

4. Configure the environment

cp .env.example .env
nano .env

Set DATABASE_URL, ADMIN_TOKEN, SESSION_SECRET_KEY, and PORT. Keep SESSION_COOKIE_SECURE=true in production (behind TLS); set it to false only for local HTTP development. Also set APP_BASE_URL (e.g. https://nets.example.com) — it's used for canonical URLs, sitemap.xml, and Open Graph tags; without it they fall back to the request's own URL, which can report http:// instead of https:// behind a reverse proxy.

5. Initialize the schema

python3 -c "from database import init_db; init_db()"

6. Create your first admin account

python3 create_admin.py --username YOUR_CALLSIGN

See Admin Accounts below.

7. Run the application

Development:

uvicorn main:app --host 127.0.0.1 --port 8001 --reload

Production: see Deployment below.


Database Migrations

Fresh installs create the full schema automatically via init_db().

For existing installs, run migrations manually or let deploy.sh handle it:

python3 migrate.py

migrate.py is the single source of truth for schema changes. Every statement is idempotent and safe to re-run. When adding a column to models.py, add the corresponding ALTER TABLE … ADD COLUMN IF NOT EXISTS to MIGRATIONS in migrate.py.

New tables (like admin_users or api_key_requests) don't need a migration entry — init_db() creates any missing table automatically on startup.


Admin Accounts

The browser admin UI at /admin is protected by named username/password accounts (separate from the ADMIN_TOKEN used by the JSON /admin/* API, which keeps working unchanged for scripts and the Swagger UI).

Create or reset an account:

python3 create_admin.py --username YOUR_CALLSIGN

You'll be prompted for a password (not echoed). Running it again for an existing username updates that account's password — this is also how you reset a forgotten password.

Log in at /admin/login. Sessions last 12 hours and are signed with SESSION_SECRET_KEY — keep that value private and stable across restarts (if unset, a random key is generated at startup, which logs everyone out on every restart).


API Reference

Interactive docs are available at /docs (Swagger UI) when the service is running.

Public Endpoints (no authentication)

Method Path Description
GET / Health check / service info
GET /nets List/search published nets
GET /nets/{id} Get a single net by ID
GET /directory Alias for GET /nets
GET /robots.txt Crawler rules
GET /sitemap.xml Sitemap of /, /nets, and every published net
GET /nets/new Manual net submission form
POST /nets/new Submit the manual form

A browser hitting /, /nets, or /nets/{id} gets a server-rendered HTML page (with its own <title>, meta description, canonical URL, and Open Graph/Twitter Card tags) instead of raw JSON — same content-negotiation pattern as the rest of the site: send Accept: application/json for the API response instead.

Query parameters for GET /nets:

Parameter Type Description
q string Full-text search on name, description, region
net_type ham | gmrs Filter by type
state string Filter by US state
band string Filter by band (e.g. 2m, 70cm)
mode string Filter by mode (e.g. FM, DMR)
is_ares bool Filter ARES/ACES nets
skip int Pagination offset (default 0)
limit int Page size, 1–200 (default 50)

Manual Net Submission (no authentication)

GET /nets/new shows a public form for anyone to add their net — no Netcontrol-Online integration or API key needed. POST /nets/new accepts the standard form submission (name, type, frequency, up to 3 recurring schedule slots, etc.) and enters the same moderation queue as an API submission; once approved it publishes with source: "manual". There's no way to set claims_netcontrol_online through this form — that's API-only (see below).

Submission Endpoint (API key required)

POST /nets/submit
Authorization: Bearer nr_<your-key>
Content-Type: application/json

Request body:

{
  "name": "Monday Night Net",
  "net_type": "ham",
  "frequency": "146.520 MHz",
  "band": "2m",
  "mode": "FM",
  "ctcss_tone": "100.0",
  "description": "Weekly check-in net for the valley.",
  "region": "Snohomish County",
  "state": "WA",
  "country": "US",
  "website": "https://example.club",
  "contact_callsign": "W7XYZ",
  "is_ares": false,
  "submitted_by_callsign": "W7XYZ",
  "source_net_id": 12,
  "claims_netcontrol_online": true,
  "schedules": [
    { "day_of_week": 0, "start_time": "19:30", "timezone": "America/Los_Angeles" }
  ]
}

source_net_id is the net's ID in the submitting system, used to match this submission against anything already sent from the same source. Returns 202 Accepted with a submission_id for tracking.

claims_netcontrol_online is optional (default false) and only meaningful here — the public manual form (below) has no way to set it. A true claim doesn't show a badge by itself: an admin has to verify it first (see Admin Endpoints — JSON API).

Re-submitting the same source_net_id (e.g. after editing a net's schedule or frequency) behaves differently depending on where it is:

  • If that net is already published here, the update is applied directly — no new moderation, since the API key already proves the source's identity and this exact net was approved once already.
  • If it's still pending review, the queued submission's payload is updated in place rather than creating a second one to review.
  • Otherwise, it's a normal new submission that enters the moderation queue.

Net Session Stats (API key required)

Log a session against a net this source already has published here — e.g. call this when the net closes:

POST /nets/stats
Authorization: Bearer nr_<your-key>
Content-Type: application/json

{
  "source_net_id": 12,
  "checkin_count": 9,
  "session_date": "2026-08-18T19:30:00Z"
}

The net is matched the same way as /nets/submit — by this API key's instance_url + source_net_id — and must already be published (404 if not found). No moderation needed; returns 201 with the logged session's id. The directory's GET /nets response includes each net's rolled-up session_count, avg_checkins, and last_session_at, and the public directory page displays them on each net's card.

API Key Requests (no authentication to request)

Instead of an admin manually creating a key via POST /admin/keys, a prospective submitter can request one and retrieve it automatically once approved:

POST /keys/request
Content-Type: application/json

{
  "name": "My Netcontrol-Online Instance",
  "contact_callsign": "W7XYZ",
  "instance_url": "https://mytracker.example.com",
  "request_notes": "Would like to publish nets from our club."
}

Returns 201 with a claim_token — shown once, store it. Poll for status (and, once approved, the issued key) with:

GET /keys/request/status
Authorization: Bearer <claim_token>

Response status is one of pending, rejected, or claimed. The first poll after an admin approves the request transitions it to claimed and includes the raw api_key (nr_...) in that one response only — subsequent polls report claimed with no key. If the key is lost in transit, submit a new request.

Admin Endpoints — JSON API

All admin JSON endpoints require Authorization: Bearer <ADMIN_TOKEN>.

Method Path Description
GET /admin/submissions List submissions (?status=pending)
GET /admin/submissions/{id} Get a single submission
POST /admin/submissions/{id}/approve Approve → publish net
POST /admin/submissions/{id}/reject Reject submission
PATCH /admin/nets/{id} Edit a published net
DELETE /admin/nets/{id} Retire a net (soft-delete)
GET /admin/nets/{id}/sessions List a net's raw logged sessions
GET /admin/nets/netcontrol-online-claims Nets claiming Netcontrol-Online, unverified
POST /admin/nets/{id}/verify-netcontrol-online Verify the claim (shows the badge)
POST /admin/nets/{id}/dismiss-netcontrol-online-claim Reject the claim
GET /admin/keys List API keys
POST /admin/keys Create a new API key directly
DELETE /admin/keys/{id} Revoke an API key
GET /admin/key-requests List key requests (?status=pending)
GET /admin/key-requests/{id} Get a single key request
POST /admin/key-requests/{id}/approve Approve a key request
POST /admin/key-requests/{id}/reject Reject a key request

Admin Endpoints — Browser UI

Session-cookie login, separate from ADMIN_TOKEN — see Admin Accounts.

Method Path Description
GET /admin/login Login form
POST /admin/login Authenticate
POST /admin/logout Clear session
GET /admin Dashboard: submissions, Netcontrol-Online claims, key requests, API keys
POST /admin/ui/submissions/{id}/approve Approve a submission (from dashboard)
POST /admin/ui/submissions/{id}/reject Reject a submission (from dashboard)
POST /admin/ui/nets/{id}/verify-netcontrol-online Verify a claim (from dashboard)
POST /admin/ui/nets/{id}/dismiss-netcontrol-online-claim Reject a claim (from dashboard)
POST /admin/ui/key-requests/{id}/approve Approve a key request (from dashboard)
POST /admin/ui/key-requests/{id}/reject Reject a key request (from dashboard)
POST /admin/ui/keys/{id}/revoke Revoke an API key (from dashboard)

Netcontrol-Online Integration

Net Repository is designed to work alongside Netcontrol-Online. The integration has two directions:

Netcontrol-Online → Net Repository (publish):
When a net owner checks "List in Public Net Directory" in Netcontrol-Online, the app can optionally submit that net to Net Repository via POST /nets/submit. This requires a Net Repository API key configured in Netcontrol-Online's .env.

Net Repository → Netcontrol-Online (discover):
When creating a new net, Netcontrol-Online can query GET /nets to let the operator find and import an existing listing rather than typing everything from scratch.

Both integration hooks are tracked in Netcontrol-Online issue #8.


Deployment

systemd service

Create /etc/systemd/system/netrepo.service:

[Unit]
Description=Net Repository
After=network.target postgresql.service

[Service]
Type=simple
User=netrepo
WorkingDirectory=/opt/net-repository
EnvironmentFile=/opt/net-repository/.env
ExecStart=/opt/net-repository/venv/bin/uvicorn main:app --host 127.0.0.1 --port ${PORT}
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=netrepo

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable netrepo
sudo systemctl start netrepo

Apache reverse proxy

See apache/net-repository.example.conf for a ready-to-use vhost with HTTPS.

sudo cp apache/net-repository.example.conf /etc/apache2/sites-available/net-repository.conf
# Edit ServerName, then:
sudo a2enmod proxy proxy_http headers rewrite
sudo a2ensite net-repository
sudo systemctl reload apache2
sudo certbot --apache -d nets.example.com

Deploying updates

sudo -u netrepo bash deploy.sh

deploy.sh pulls the latest code, installs dependencies, runs migrations, and restarts the systemd service.


Running Tests

Tests run against an in-memory SQLite database — no PostgreSQL required.

pip install -r requirements-dev.txt
python -m pytest tests/

Contributing

Pull requests are welcome. For major changes please open an issue first.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes
  4. Open a pull request

License

GNU General Public License v3.0 — see LICENSE for the full text.

73

Built for the ham radio community as a companion to Netcontrol-Online.

About

A central repository for amateur radio and GMRS nets

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages