# Phryktor observer contributor: complete setup and operations guide

Updated 2026-10-03. The signed HTTPS intake, local review console, USB receiver uploader, and map/archive integration are implemented. A physical remote installation still needs its contributor to connect a compatible radio and follow these steps; no remote receiver is presumed online before it sends genuine observations.

This guide can be requested later as “the Phryktor observer contributor guide.” the operator can download this guide and the uploader from his local admin console and send those files directly to a contributor. No Colorado Mesh API, broker, feed, credentials, or observer data is used.

## What this contributes

```text
Your USB companion radio → your Linux computer → signed HTTPS metadata upload
                                                       ↓
                               private validation and initial review queue
                                                       ↓
                              the operator approves → attributed map / packet archive
```

The receiver reports traffic it actually hears. Unknown or ambiguous route prefixes remain unresolved. Contributions cannot prove that other nodes received a message, provide delivery acknowledgements, or measure the speed of a packet animation. These uploads contain packet metadata and optionally publicly advertised contacts, not message bodies or direct messages. the operator’s live room display continues to use his own locally collected public-channel messages.

## 1. Gather the equipment

You need:

- A supported MeshCore radio with **USB companion firmware** for its exact board. A Heltec V3, XIAO, and other boards need their own board-specific firmware; a Wi-Fi observer or repeater image is not interchangeable with a USB companion image.
- An appropriate antenna connected before powering the radio.
- A USB data cable, stable power, and a Linux machine or Raspberry Pi with internet access.
- Permission to share the public observations you receive.
- From the operator: this guide, `observer-uploader.py`, your private `phryktor-observer-private.json`, and the RF settings used by the local mesh.

An existing Wi-Fi/MQTT observer cannot send directly to this HTTPS endpoint with its default firmware. Use the supported USB route below, or arrange a separately reviewed metadata adapter with the operator. No public MQTT port or arbitrary broker connection is needed for this setup.

## 2. Prepare the radio

1. Identify the exact board model and current firmware role/version.
2. Follow the official [MeshCore project instructions](https://github.com/meshcore-dev/MeshCore) for that board. If flashing is necessary, choose the matching **USB companion** build and follow the project's flashing instructions. Preserve any settings you need before flashing.
3. Attach the antenna and USB data cable.
4. Using the MeshCore application, match the frequency, bandwidth, spreading factor, and coding rate the operator supplies. Do not copy an assumed frequency from a video or another region.
5. Use a nonidentifying observer alias. Disable location sharing and automatic advertisements in the firmware/application if supported and unwanted. The uploader does not send adverts or change radio settings.
6. Give the operator the observer's **public** key if you want it suppressed from his maps. Never provide its private key. the operator can store the self public key when adding your observer.
7. Close applications using the same USB port before starting the uploader. Only one collector should own this serial connection.

the operator’s existing radios are not modified or restarted by this setup.

## 3. the operator creates your source and credential

the operator does this on his workstation:

1. Open `http://127.0.0.1:8100/` and select **Observer uploads**. This console is local; the contributor does not receive admin access.
2. Enter the agreed observer nickname and, optionally, the observer's own public key. Choose **Add observer**.
3. Select that observer under **Destination observer**.
4. Choose **Create / rotate upload credential**, then **Download private receiver configuration**. Rotation invalidates the old signing credential and returns the source to review mode.
5. Download the complete setup guide and receiver uploader.
6. Send those files privately to the contributor. Do not post the configuration in chat rooms, GitHub, public screenshots, or the public site. The configuration contains a private signing secret scoped to that one source.
7. Leave **Trust this source for automatic publication** unchecked for the first test.

Revoking upload access stops future uploads. Disabling the observer also excludes its records and nodes from active exports. Previously downloaded public records cannot be recalled.

## 4. Install the uploader on the contributor's Linux computer

The example uses your own Linux account, with files in your home directory. Run one command line at a time.

```bash
sudo apt update
sudo apt install python3 python3-venv
mkdir -p "$HOME/phryktor-observer"
chmod 700 "$HOME/phryktor-observer"
cd "$HOME/phryktor-observer"
python3 -m venv .venv
.venv/bin/python -m pip install 'meshcore==2.3.9.1'
```

Copy `observer-uploader.py` and the private configuration into this directory using your normal file-transfer method. The MeshCore version is pinned to the version used by the operator’s working receiver integration. If installation fails on your OS/Python version, resolve that compatibility with the operator before replacing the pin.

```bash
chmod 600 phryktor-observer-private.json
ls -l /dev/serial/by-id/
```

Identify the serial path belonging to your connected radio. Use that stable `/dev/serial/by-id/...` path rather than guessing `/dev/ttyUSB0`, which can change after a reboot. If no device appears, check your cable, USB port and firmware role.

On Debian/Raspberry Pi OS, grant serial access if necessary:

```bash
sudo usermod -a -G dialout "$USER"
```

Log out and back in after changing group membership. Do not run the uploader as root to work around a serial permission problem.

## 5. Edit and check the private configuration

Open the configuration using a local editor:

```bash
nano phryktor-observer-private.json
```

Keep the `observer_id`, `secret`, and `endpoint` exactly as supplied by the operator. Replace only the `device` placeholder with the radio path you identified. Example shape, with placeholders:

```json
{
  "observer_id": "contributor-REPLACE_FROM_JAMES",
  "secret": "REPLACE_WITH_PRIVATE_SECRET_FROM_JAMES",
  "endpoint": "https://phryktor.tail4ac0a6.ts.net/api/observers/upload",
  "device": "/dev/serial/by-id/REPLACE_WITH_YOUR_RADIO"
}
```

Optional `self_public_key` can contain your observer's public key to exclude it from the contact list uploaded by your computer. the operator’s registry suppression still applies independently. These are identifiers, not private radio keys.

```bash
chmod 600 phryktor-observer-private.json
.venv/bin/python observer-uploader.py --config phryktor-observer-private.json --check
timedatectl status
```

The check validates the configuration locally and makes no connection or upload. Ensure the clock is synchronized; signed upload timestamps must be within 60 seconds of the operator’s receiver clock. HTTPS certificates are checked; the uploader refuses redirects and other upload hostnames.

## 6. Start the real receiver test

```bash
.venv/bin/python observer-uploader.py --config phryktor-observer-private.json
```

Leave the terminal running. It connects to the companion over USB, observes receive-log events, and uploads batches of genuine packet metadata about every 12 seconds when packets are heard. Quiet radio periods produce no fabricated packet uploads. Contact reads refresh about every five minutes.

The program requests local companion information and contact reads. It does not call mesh message-send, advert-send, reboot, or radio-setting commands. Raw RF payload bytes and decoded messages are not retained in its outgoing schema.

Expected log progression:

- `Receiver connected. Uploads contain metadata only.`
- `pending: N observations` while the operator requires review.
- `published: N observations` after the operator explicitly enables automatic publication.

Do not generate test packets from invented JSON for a public test. Hear ordinary existing traffic, or agree on an actual test transmission separately with the operator. This setup does not automatically transmit a test advert.

Press Ctrl+C to stop this foreground test. A pending batch is not publicly visible merely because the upload succeeded.

## 7. the operator reviews the first observations

1. In local admin **Observer uploads**, check the source's review queue and last upload/reception times.
2. Review sample packet types, signal values, timestamps, contacts and advertised positions. Open **Review / download complete metadata** to inspect the full batch before approval.
3. Confirm that the contributor actually operates the agreed receiver. Schema checks cannot establish that a person's report is honest.
4. Approve a genuine batch, or reject it. Approval publishes it with contributor provenance.
5. Check **Packet explorer**, **Live packets & map**, and the master **Situation map**. Routes animate only when the uploaded route has usable uniquely matched public contact positions. A packet without those positions remains a metadata observation, not a made-up line.
6. Fresh approved observations appear in the fast feed in about one second; archive/source inventory updates follow their existing collection cycle. If a batch waits too long for approval, its original timestamp stays old and it will not be animated as current traffic.
7. If the first batches are sensible and the operator trusts the operator, explicitly enable **Trust this source for automatic publication**. Validation, rate limits, credential checks and attribution remain in place. Otherwise continue reviewing batches.

## 8. Run automatically after login/reboot

First finish the foreground test and stop it. Create a user systemd service:

```bash
mkdir -p "$HOME/.config/systemd/user"
nano "$HOME/.config/systemd/user/phryktor-observer-uploader.service"
```

Paste this file, replacing **YOUR_LINUX_USER** with the actual account name. Paths must match the directory used above.

```ini
[Unit]
Description=Phryktor outbound observer metadata uploader
After=network-online.target

[Service]
Type=simple
WorkingDirectory=/home/YOUR_LINUX_USER/phryktor-observer
ExecStart=/home/YOUR_LINUX_USER/phryktor-observer/.venv/bin/python /home/YOUR_LINUX_USER/phryktor-observer/observer-uploader.py --config /home/YOUR_LINUX_USER/phryktor-observer/phryktor-observer-private.json
Restart=on-failure
RestartSec=15
NoNewPrivileges=true
UMask=0077
MemoryMax=192M

[Install]
WantedBy=default.target
```

Then enable it:

```bash
systemctl --user daemon-reload
systemctl --user enable --now phryktor-observer-uploader.service
systemctl --user status phryktor-observer-uploader.service
journalctl --user -u phryktor-observer-uploader.service -n 40 --no-pager
sudo loginctl enable-linger "$USER"
```

Linger lets user services run when your SSH session closes and start without an interactive login. This uploader reconnects when the USB radio disappears and retries when internet service returns. It does not change your Wi-Fi/router/firewall or require inbound ports on your computer.

Stop or restart it:

```bash
systemctl --user stop phryktor-observer-uploader.service
systemctl --user restart phryktor-observer-uploader.service
```

Do not run the foreground test and service at the same time against the same radio.

## 9. What the safety checks actually do

- **Scoped authentication:** a random 256-bit signing secret belongs to one registered observer. HMAC-SHA256 covers the observer ID, timestamp, random nonce, and exact request body. Modified or unsigned requests fail. Credentials never enter public exports.
- **Replay checks:** request timestamps outside 60 seconds fail; a previously accepted nonce fails. Observation IDs are deduplicated; conflicting contents for an existing ID fail.
- **Bounded intake:** only uncompressed JSON, at most 128 KiB, 1–250 observations and 0–200 contacts per upload. Six accepted batches per source per minute, a global ingress limiter and four worker slots bound request processing. The private pending queue holds at most 100 batches and expires after 24 hours on intake activity.
- **Strict metadata schema:** unknown fields, raw payloads, private keys, message text, scripts/markup in contact names, invalid numbers, future times, malformed hexadecimal paths, duplicate JSON keys and nonfinite numeric values fail. Remote observations must be no older than ten minutes at intake. Contact positions are rounded to two decimal degrees; configured self identities and Phryktor identities are excluded from public nodes.
- **No execution or fetching:** submissions are parsed as data. They do not run commands, select files, fetch supplied URLs, control radios, change map code, or access the local admin console. Database queries use parameters and customer text is rendered as text.
- **Review before trust:** all new credentials begin in private quarantine. Automatic publication needs an explicit per-source admin switch. Revocation and source disabling are available locally.
- **Bounded storage:** automatic uploads retain up to 2,000 observations per source within the registry's 20,000-record combined ceiling. Old automatic records are trimmed first; manual records retain their existing retention/limits. The uploader holds at most 5,000 queued observations, drops items older than nine minutes, and retries only within the freshness window. Public archive synchronization removes trimmed/disabled contributor records from active packet exports. Downloaded copies remain outside the operator’s control.

These controls prevent defined classes of injection and unauthorized writes, and reduce resource-exhaustion exposure. **They cannot guarantee that someone is not hacking the host or prove that a trusted source did not fabricate plausible metadata.** Valid RF-looking values are still contributor claims. Revoke a suspicious source and investigate. This is schema/authentication validation and moderation, not antivirus or cryptographic verification of RF reception.

the operator keeps the intake on its own Unix socket, with no public admin route. It has memory, CPU, worker and file-descriptor limits. The public nginx container remains read-only and without a network interface; it forwards only the specific upload endpoint through the socket. Existing local radios continue independently.

## 10. Troubleshooting

| Symptom | Check |
|---|---|
| No serial path | USB data cable, matching companion firmware, powered radio, correct USB port. |
| Permission denied | `dialout` membership, log out/in, and no other process owning the port. |
| No observations | Correct mesh RF settings, antenna, nearby traffic, compatible receive-log support. Quiet periods are valid. |
| `401` | Clock synchronization, matching current credential; the operator may have rotated it. |
| `403` | the operator disabled the source or upload credential. |
| `400` | Metadata/schema failure; inspect the collector format without sharing your secret. |
| `409` | Request replay rejected; the supplied uploader generates a fresh nonce for each retry. |
| `413` | Batch exceeds 128 KiB; reduce contacts or batch size in an approved adapter. |
| `429` | Rate limit or review queue full; wait and ask the operator to clear the queue if needed. |
| `503` / connection unavailable | the operator’s intake/site or your internet is unavailable. Retried recent metadata is bounded. |
| Upload succeeds but nothing on map | Review not approved, no usable route positions, ambiguous route IDs, or records too old for live animation. Check Packet explorer and source timestamps. |
| Uploads stop after terminal closes | Finish the systemd service and linger steps. |
| Suspected bad source | Revoke upload access, disable source export, review intake audit and complete metadata. Do not assume a schema-valid report is true. |

## Metadata-file alternative

For an existing authorized collector that already produces the accepted schema, the same uploader can send one real metadata file without opening a radio:

```bash
.venv/bin/python observer-uploader.py --config phryktor-observer-private.json --file actual-received-metadata.json
```

the operator can download an example from local admin, but its placeholder values must be replaced with real reception records. Unknown fields and stale remote records fail. Older genuine records can use the local manual importer, which accepts its separately documented 30-day window.

## References and verification status

- [Official MeshCore project and firmware](https://github.com/meshcore-dev/MeshCore)
- [Official Python companion library](https://github.com/meshcore-dev/meshcore_py)
- [Official companion protocol](https://github.com/meshcore-dev/MeshCore/blob/main/docs/companion_protocol.md)

The supplied collector follows the same companion receive-event pattern used by the operator’s current dedicated observer. Server authentication, quarantine, rejection, revocation and publication are tested with isolated metadata fixtures. A new remote board and its USB/OS combination must still be verified in the foreground test at that location. Do not report it as a live remote observer before it sends genuine data.
