Skip to content

OpenWrt Deployment

iliyan85 edited this page Aug 15, 2026 · 3 revisions

OpenWrt Deployment

OpenWrt R2 is the current packaged deployment path for running AISMixer and nmea_sproxy as native OpenWrt services. This page covers the published operator interface: repository trust, installation, package layout, configuration, service lifecycle, endpoint identities, and UDPSEC trust provisioning.

Supported scope

Item Current published support
OpenWrt release OpenWrt 25.12
Published target x86_64 only
Repository format Signed APK v3 repository
Published package version 0.2.0-r2 for all three packages
Runtime implementation Python
Service manager procd through /etc/init.d scripts
Distribution Signed public repository at aismixer.net

x86_64 is the first published and validated OpenWrt target, and all three published APK manifests identify their architecture as x86_64. The current Python-and-shell payload is not inherently x86-specific, but no ARM, AArch64, or MIPS repository is published or supported yet. Do not use the x86_64 feed on another architecture.

R2 does not provide a native C runtime, UCI integration, or OpenWrt multi-instance services. The published packages were validated through signed repository, clean-install, reboot, plain-UDP, serial, and UDPSEC runtime acceptance; this page documents the resulting operator interface rather than the release-validation history.

Package model

The repository contains three packages:

Package Purpose Direct shared dependency
aismixer-common Shared Python runtime modules, key tooling, and common utilities Python 3
aismixer Mixer/router service, UDPSEC server support, and aismixerctl aismixer-common
nmea_sproxy One station-side UDP or serial input to one UDPSEC or explicit plain-UDP output aismixer-common

aismixer and nmea_sproxy may be installed independently. APK resolves aismixer-common and the other required Python dependencies automatically; operators normally do not install aismixer-common directly.

The package recipe installs these important paths.

AISMixer

/usr/bin/aismixer
/usr/bin/aismixerctl
/usr/lib/aismixer/...
/etc/init.d/aismixer
/etc/aismixer/config.yaml
/etc/aismixer/udp_alias_map.yaml
/etc/aismixer/authorized_keys.yaml
/etc/aismixer/keys/

nmea_sproxy

/usr/bin/nmea_sproxy
/usr/lib/aismixer/nmea_sproxy/...
/etc/init.d/nmea_sproxy
/etc/nmea_sproxy/config.yaml
/etc/nmea_sproxy/keys/

Shared modules are installed below /usr/lib/aismixer/core/; the shared key tool is /usr/lib/aismixer/tools/aismixer_keys.py.

Signed repository and trust bootstrap

The public repository endpoints are:

Run the following as root. It adds the AISMixer feed without replacing any operator-defined custom repositories:

wget -O /etc/apk/keys/aismixer-openwrt.pem \
  https://aismixer.net/openwrt/keys/aismixer-openwrt.pem

chmod 0644 /etc/apk/keys/aismixer-openwrt.pem

REPO_FILE=/etc/apk/repositories.d/customfeeds.list
REPO_URL='https://aismixer.net/openwrt/25.12/x86_64/packages.adb'

grep -qxF "$REPO_URL" "$REPO_FILE" 2>/dev/null || {
  printf '\n# AISMixer OpenWrt 25.12 x86_64 repository\n%s\n' \
    "$REPO_URL" >> "$REPO_FILE"
}

apk update

To compare the downloaded key with the published value, run:

sha256sum /etc/apk/keys/aismixer-openwrt.pem

/etc/apk/keys/ is the package-manager trust store used here. Once this public key is trusted, APK signatures authenticate the repository and package artifacts. Downloading the key and checking its fingerprint from the same HTTPS origin is a useful identity and integrity check, but it is not an independent second trust channel or stronger authentication than the HTTPS bootstrap itself.

Install the mixer/router package with:

apk add aismixer

Install the station-side proxy package with:

apk add nmea_sproxy

Do not use --allow-untrusted, and do not perform a blanket apk upgrade as part of AISMixer installation.

Configuration and preserved operator state

The OpenWrt configuration and identity layout is:

Service Path Package treatment
AISMixer /etc/aismixer/config.yaml Declared conffile
AISMixer /etc/aismixer/udp_alias_map.yaml Declared conffile
AISMixer /etc/aismixer/authorized_keys.yaml Declared conffile
AISMixer /etc/aismixer/keys/ Empty directory installed; identity generated locally; no private key packaged
nmea_sproxy /etc/nmea_sproxy/config.yaml Declared conffile
nmea_sproxy /etc/nmea_sproxy/keys/ Empty directory installed; identity and trust material generated or provisioned locally

The declared conffiles are operator-managed configuration and are intended to survive package upgrades. Endpoint private keys and trusted peer material are created or provisioned locally and must not be replaced merely because a package is upgraded.

Use the packaged files as starting points rather than copying large examples from this Wiki:

Service lifecycle with procd

OpenWrt does not use the systemd installation path documented for conventional Linux. R2 installs these procd services:

/etc/init.d/aismixer
/etc/init.d/nmea_sproxy

OpenWrt's package post-install handling enables and attempts to start each installed service. Consequently, apk add aismixer normally prepares the mixer identity and launches AISMixer. With the packaged UDPSEC proxy configuration, apk add nmea_sproxy prepares the station identity but safely leaves the runtime stopped when its required peer key has not been provisioned; the package installation itself still succeeds.

Enable and manage AISMixer with:

/etc/init.d/aismixer enable
/etc/init.d/aismixer start
/etc/init.d/aismixer status
/etc/init.d/aismixer restart

Enable and manage the station proxy with:

/etc/init.d/nmea_sproxy enable
/etc/init.d/nmea_sproxy start
/etc/init.d/nmea_sproxy status
/etc/init.d/nmea_sproxy restart

Both init scripts send runtime standard output and error to OpenWrt logging and register a procd respawn policy after successful startup preparation.

AISMixer startup

Before registering the runtime with procd, the AISMixer init script:

  1. creates /etc/aismixer/keys/ and /run/aismixer;
  2. creates the local server identity when both identity files are absent, or repairs the derived public key from an existing private key; and
  3. refuses an incomplete public-only identity instead of replacing it with a new private identity.

The packaged default configuration enables the local control socket at /run/aismixer/control.sock, and the operator command is /usr/bin/aismixerctl. See Runtime Routing Control for control semantics and authorization boundaries.

nmea_sproxy startup

OpenWrt R2 provides one singleton nmea_sproxy service using /etc/nmea_sproxy/config.yaml. It does not provide the conventional-Linux nmea_sproxy@name systemd template model.

Before registering the runtime with procd, its init script:

  1. verifies that the configuration file exists;
  2. creates or repairs the local station identity without replacing an existing private key;
  3. loads and validates the runtime configuration; and
  4. for UDPSEC output, verifies that the configured peer public key exists, is readable, and is a valid public key.

Only a runtime that passes this preparation is registered with procd. Genuine runtime failures after launch may therefore use procd respawn; a missing UDPSEC trust anchor is handled before launch.

Identity generation and key ownership

Two unrelated trust domains are involved:

  1. APK repository signing identity authenticates repository and package artifacts to APK after the repository public key is trusted. Its private key is not shipped to routers.
  2. AISMixer/UDPSEC endpoint identities authenticate the mixer and station to each other. Their private keys are generated locally on the OpenWrt host.

The exact endpoint identity files are:

Endpoint Private identity Derived public identity
AISMixer /etc/aismixer/keys/aismixer_private.pem /etc/aismixer/keys/aismixer_public.pem
nmea_sproxy station /etc/nmea_sproxy/keys/station_private.pem /etc/nmea_sproxy/keys/station_public.pem

Private key files remain local. When a valid private key exists, the init scripts preserve it and use the key tool to repair a missing, invalid, or mismatched derived public key. If only the public identity exists, startup refuses to generate a replacement private identity. This prevents an incomplete state from silently changing endpoint identity.

The current init scripts do not select an alternate procd user, so they run in OpenWrt's default root service context. They set each endpoint key directory to mode 0700; the key tool sets private keys to 0600 and derived public keys to 0644.

Provision UDPSEC peer trust

Local identity generation does not establish peer trust. For one nmea_sproxy to AISMixer UDPSEC relation, provision both directions through a trusted operator-controlled channel.

1. Trust AISMixer on the station

Copy only the mixer public identity from:

/etc/aismixer/keys/aismixer_public.pem

to the path configured as remote_public_key on the station. The packaged default is:

/etc/nmea_sproxy/keys/aismixer_public.pem

Never copy the AISMixer private identity to the station.

2. Authorize the station on AISMixer

The first nmea_sproxy start prepares the station identity before checking for the UDPSEC peer key. If that peer key is not provisioned yet, the new station identity remains in place while the runtime safely stays stopped. After the station private identity exists, the OpenWrt-installed key tool can repair its public key if needed and print the compressed base64 value required by AISMixer:

/usr/bin/python3 /usr/lib/aismixer/tools/aismixer_keys.py station \
  --keys-dir /etc/nmea_sproxy/keys \
  --station-id boat_001 \
  --repair-public

Replace boat_001 with the station_id in /etc/nmea_sproxy/config.yaml. Add the printed value to /etc/aismixer/authorized_keys.yaml:

authorized_clients:
  - name: boat_001
    pubkey: <compressed-public-key-base64>

name must exactly match the proxy's configured station_id. Restart AISMixer after changing its authorization file, then start or restart nmea_sproxy:

/etc/init.d/aismixer restart
/etc/init.d/nmea_sproxy restart

Trust material is never fetched, enrolled, or exchanged automatically. See the canonical nmea_sproxy key and trust guide for the full operator workflow.

Expected safe UDPSEC preflight

When UDPSEC output is configured and remote_public_key is missing or cannot be read, the init script logs:

UDPSEC peer public key not provisioned; service not started: <path>

When the file is invalid or cannot be loaded as a public key, it logs:

UDPSEC peer public key invalid or unreadable; service not started: <path>

The runtime is intentionally not launched in either case. This prevents procd from repeatedly respawning a process that cannot establish trust. Package installation may therefore be successful while the proxy runtime is correctly stopped and waiting for peer trust. Provision the AISMixer public key, then start or restart the service.

An explicit plain-UDP output does not require the UDPSEC peer public key. The OpenWrt init script still prepares the local station identity before its general runtime-readiness check, but the plain-UDP runtime does not load or use UDPSEC peer trust.

Input, output, and routing terminology

nmea_sproxy accepts two first-class input types:

  • UDP input, either through an explicit input.type: udp mapping or the backward-compatible top-level form where input: is omitted; and
  • serial or USB virtual serial input through input.type: serial.

UDP input is not deprecated and is not a legacy input type. Only its omitted input: top-level configuration form is described as backward-compatible.

The output types are:

  • UDPSEC, through explicit output.type: udpsec or the legacy, backward-compatible top-level remote_host / remote_port form; and
  • explicit plain UDP through output.type: udp, for a trusted LAN, VPN, or equivalent external security boundary.

Each R2 proxy process still represents one local input to one network output. See Secure UDP and nmea_sproxy for configuration and transport details.

AISMixer's original no-routing behavior remains legacy broadcast mode. It is supported and backward-compatible, not deprecated. Routing mode instead uses named targets and target-scoped deduplication. See Routing Engine; OpenWrt packaging does not change those semantics.

USB virtual serial receivers

Serial input uses a normal device path. /dev/ttyACM0 is one example, not a universal path:

input:
  type: serial
  port: /dev/ttyACM0
  baudrate: 38400
  bytesize: 8
  parity: N
  stopbits: 1
  read_timeout: 1.0
  reconnect_delay: 5
  max_line_bytes: 4096

A USB virtual serial receiver may need an OpenWrt kernel driver before a /dev/tty* device appears. For a USB CDC ACM device, install:

apk add kmod-usb-acm

After the driver loads and binds, a CDC ACM device typically appears as /dev/ttyACM0, /dev/ttyACM1, and so on. Check recent kernel messages and device nodes with:

dmesg | tail -n 30
ls -l /dev/ttyACM* 2>/dev/null

A successful binding can look like:

cdc_acm 1-1:1.0: ttyACM0: USB ACM device

kmod-usb-acm is hardware-specific, not a universal nmea_sproxy runtime requirement or package dependency. UDP input does not need it, a native UART does not need it, and USB-to-serial devices using other chipsets may require their own OpenWrt kernel driver instead.

OpenWrt images may omit stty. For optional serial diagnostics, install:

apk add coreutils-stty

Then inspect the endpoint, for example:

stty -F /dev/ttyACM0 -a

coreutils-stty is diagnostic tooling only. nmea_sproxy does not require it because pySerial configures the serial endpoint itself. A common AIS NMEA 0183 high-speed serial configuration is 38400 baud, 8 data bits, no parity, and one stop bit, but the operator must use the receiver manufacturer's specified settings.

Because python3-pyserial is already an nmea_sproxy package dependency, an operator can test the exact device path with a bounded read before starting the service:

python3 - <<'PY'
import time
import serial

with serial.Serial(
    "/dev/ttyACM0",
    baudrate=38400,
    bytesize=8,
    parity="N",
    stopbits=1,
    timeout=1,
) as s:
    print("opened:", s.name, flush=True)

    total = 0
    deadline = time.monotonic() + 15

    while time.monotonic() < deadline:
        data = s.read(512)
        if data:
            total += len(data)
            print(repr(data), flush=True)

    print("total bytes:", total)
PY

Seeing !AIVDM or !AIVDO data verifies the receiver to kernel driver to /dev/tty* to pySerial path independently of nmea_sproxy.

One real-hardware acceptance unit identified as USB VID:PID 27c5:0602, manufacturer SRT Marine Technology Ltd., and product AIS Virtual COM Port. It exposed CDC ACM interfaces and OpenWrt bound it through cdc_acm as /dev/ttyACM0.

During this EM-Trak/SRT AIS Virtual COM Port test, the device enumerated successfully on one USB 3 host port but produced no serial data. Reading the COM port directly under Windows reproduced the symptom. Moving the receiver to a USB 2 port restored AIS data immediately. This is one observed hardware/host-controller compatibility case, not a general requirement for EM-Trak, SRT, CDC ACM, USB 3, or OpenWrt systems.

For this specific acceptance unit, the completed R2 real-hardware test directly observed the full path: physical EM-Trak AIS receiver -> USB CDC ACM -> OpenWrt /dev/ttyACM0 -> pySerial / nmea_sproxy -> authenticated UDPSEC -> AISMixer -> processing -> UDP egress.

Compact operational verification

The following commands answer the usual post-install questions without reproducing the R2 release-acceptance sequence:

apk list -I | grep -E '^(aismixer|nmea_sproxy)-'
/etc/init.d/aismixer status
/etc/init.d/nmea_sproxy status
logread -e aismixer
logread -e nmea_sproxy
ls -l /run/aismixer/control.sock
aismixerctl status

Interpret them as follows:

  • apk list -I shows which AISMixer packages are installed;
  • the init scripts report current procd service state;
  • logread shows startup, identity, preflight, and runtime messages;
  • the socket listing confirms whether the configured local control socket exists; and
  • aismixerctl status confirms that the enabled control socket responds.

If the station log contains one of the safe-preflight messages above, complete peer-trust provisioning and start the service again. A disabled control socket is a valid operator configuration; in that case, the socket and aismixerctl status checks are not expected to succeed.

Targeted package updates

Leave the repository URL in customfeeds.list, refresh package metadata, and target only the installed AISMixer package or packages:

apk update
apk add --upgrade aismixer
apk add --upgrade nmea_sproxy

Run only the package commands relevant to the router. APK resolves updates to aismixer-common as a dependency. For a UDPSEC relation, keep both endpoints on compatible runtime versions and restart the affected services after the targeted package update.

Declared conffiles are intended to retain operator configuration. Endpoint identity files are locally generated rather than package payload and must not be replaced during an upgrade. Review package-manager messages and the effective configuration before restarting. Do not use a global apk upgrade as an AISMixer update procedure.

R2 boundary and R3 handoff

R2 provides:

  • native OpenWrt packages in a signed repository;
  • procd lifecycle integration;
  • the local configuration and key layout documented above;
  • independent installation of AISMixer and nmea_sproxy;
  • one singleton OpenWrt nmea_sproxy service; and
  • safe UDPSEC startup preflight before the runtime is launched.

R3 is future work. It may introduce UCI integration, named multiple nmea_sproxy procd instances, and router-oriented configuration integration. No R3 behavior, syntax, or service model is implemented or promised here. The conventional-Linux nmea_sproxy@name systemd model will not simply be copied to OpenWrt; any multi-instance design will use an OpenWrt-native model defined by that future work.

Further documentation

Clone this wiki locally