Packaging¶
.ipk for OpenWrt ≤24.10 (opkg), .apk for 25.x+ (apk). Both are built from the same payload
by scripts/ipk/build-ipk.sh and scripts/apk/build-apk.sh.
./scripts/ipk/build-ipk.sh <version> <openwrt_arch> <binary_dir> [luci_dir] [output_dir]
./scripts/ipk/build-ipk.sh 1.33.1 aarch64_generic ./binaries
luci_dir defaults to luci-app-nym-vpn/ in the repo, output_dir to the current directory. The
script checks every required file is present, assembles the tree, computes installed size from
du -sk, fills in the control template, and emits nym-vpn_{version}_{arch}.ipk.
What's in it¶
nym-vpn_1.33.1_aarch64_generic.ipk (tar.gz)
├── debian-binary # "2.0"
├── control.tar.gz
│ ├── control # generated from control.template
│ ├── conffiles # /etc/config/nym-vpn
│ ├── postinst
│ └── prerm
└── data.tar.gz
├── usr/sbin/nym-vpnd
├── usr/bin/nym-vpnc
├── usr/libexec/rpcd/nym-vpn
├── www/luci-static/resources/view/nym-vpn/config.js # the page (composer)
├── www/luci-static/resources/nym-vpn/ # module tree, copied recursively
│ ├── api.js, store.js, rpc.js, ui.js, theme.js, countries.js, assets.js
│ ├── components/ # card, toggle, select, modal, toast, gateway-picker
│ ├── flows/ # connect, daemon
│ └── cards/ # connection, tunnel-settings, mixnet-tuning, dns, ...
├── etc/init.d/nym-vpnd
├── etc/config/nym-vpn
├── etc/uci-defaults/luci-app-nym-vpn
├── etc/nym/data/ # empty, for daemon state
├── usr/share/luci/menu.d/luci-app-nym-vpn.json
├── usr/share/rpcd/acl.d/luci-app-nym-vpn.json
├── usr/share/nym-vpn/fw3-include.sh # run by fw3
├── usr/share/nym-vpn/fw4-include.sh # run by fw4
├── usr/share/nym-vpn/fw-boot-guard.sh # sourced by the includes, init, uci-defaults, prerm; not executable
├── usr/share/nym-vpn/fw-rules.sh # generated by nym-firewall build.rs; sourced by the includes
├── lib/upgrade/keep.d/nym-vpn # sysupgrade keeps /etc/config/nym-vpn and /etc/nym/
├── etc/opkg/keys/<fingerprint>
└── etc/apk/keys/dial0ut.pub
The .apk carries the same payload (scripts/ci/check-pkg-payload.sh compares the two) with
three format-specific exceptions: the apk feed key etc/apk/keys/dial0ut-apk.pem instead of the
two keys above, and lib/apk/packages/nym-vpn.conffiles plus
nym-vpn.conffiles_static, apk's form of the conffiles list (see conffiles).
The opkg key is installed under its fingerprint, not its filename — build-ipk.sh derives it
by base64-decoding the second line of scripts/feed/dial0ut.pub and taking bytes from the header.
usign looks keys up that way.
etc/uci-defaults/luci-app-nym-vpn runs once on first boot after install. It is what registers
the firewall include, so the kill-switch survives fw4 reload, and what declares the nym
firewall zone (device 'nym+', masquerade, MSS clamp) and the lan -> nym forwarding that
fw3/fw4 render as the tunnel plane.
Dependencies come from one list in scripts/pkg-depends.sh, which both builders format (Depends:
for opkg, depends: for apk):
| Package | Why |
|---|---|
libc |
musl libc; libc (>= 1.2) on the 32-bit architectures (arm, i386, mips, mipsel), whose binaries import musl 1.2's time64 symbols. OpenWrt's libc package carries the musl version (21.02: 1.1.24), so opkg refuses the install there instead of leaving a daemon that cannot load |
libgcc |
libgcc_s.so.1, which the binaries link against for unwinding (OpenWrt's libgcc1 provides it) |
kmod-tun |
TUN device for userspace WireGuard |
kmod-ipt-conntrack-extra |
conntrack marking for inbound exemptions on fw3 |
luci-base |
LuCI web framework |
rpcd |
RPC backend for LuCI |
For apk these go in a single -I "depends:..." flag. Repeated -I depends: flags overwrite
each other rather than accumulating — every .apk up to 1.30.5 shipped depending on rpcd alone,
so kmod-tun was never pulled in and the daemon died at TUN device creation.
postinst¶
- TUN device —
mknod /dev/net/tun c 10 200if it does not already exist - Feed registration — reads the architecture from
/etc/openwrt_release, falling back to/etc/os-releasesince not every image ships both. Then:- apk: removes any stale nym-vpn lines from
/etc/apk/repositoriesand/etc/apk/repositories.d/*.list, then writes/etc/apk/repositories.d/nym-vpn.list - opkg: appends a
src/gz nym-vpnline to/etc/opkg/customfeeds.conf
- apk: removes any stale nym-vpn lines from
- Service — enables and restarts
nym-vpnd(restart, because on an upgrade the old daemon is still running; the new init script's restart path keeps the kill-switch armed). Then the one-shot migration of the retired shell watchdog (packages ≤ 1.34.x): if UCInym-vpn.settings.always_onis1, a detached job waits up to 30 s for the daemon socket and runsnym-vpnc tunnel set --always-on on(detached for the same reason as the rpcd refresh below: postinst must not block on the daemon); either way delete thealways_on/watchdog_interval/watchdog_max_retriesoptions, remove the old service's/etc/rc.dsymlinks, kill a leftover watchdog process by its pidfile and remove the pidfile,/var/run/nym-watchdog.stateand/tmp/nym-watchdog.state. The block is a no-op on a fresh install and is due to be dropped two releases after it shipped. - LuCI plumbing — clears
/tmp/luci-indexcache*and/tmp/luci-modulecache/, then refreshes rpcd from a detached job that waits for the opkg/apk process to exit (bounded at two minutes) before it fires. rpcd scans/usr/libexec/rpcdonly at start-up and computes a session's ACL grants at login, so a fresh install restarts it. An upgrade compares the new package's exported rpcd method list (/usr/libexec/rpcd/nym-vpn list, which isnym-vpnc rpcd listbehind a fixed wrapper) and ACL file against the checksums prerm stashed in/var/run/nym-vpn.rpcd-state: nothing changed → no refresh; only the method list changed →rpcd reload(SIGHUP — rpcd re-executes itself and keeps its sessions); the ACL file changed, or no stash →rpcd restart, which drops the sessions and forces the re-login that picks up the new grants. Deferred because LuCI's Software page runs opkg/apk through rpcd (file exec): a restart inside the transaction kills the request the page is waiting on (#13). Detached means a subshell with every fd on/dev/null— opkg only waits for the script's pid, but apk also reads its stdout/stderr pipes to EOF, so an inherited pipe would make apk sit out the delay. - Prints both upgrade commands,
opkgandapk, without picking between them
prerm¶
On an upgrade (PKG_UPGRADE=1) prerm only stashes md5sums of the exported rpcd method list
(from the binary still installed) and the ACL file in /var/run/nym-vpn.rpcd-state for postinst's
rpcd decision above. The daemon keeps running through the file swap, and the firewall, DNS and
data are left for the incoming version to reuse; tearing the firewall down here would open WAN
egress for the whole transaction.
On a real removal (opkg remove, apk del, and the remove half of
opkg install --force-reinstall):
- Stop and disable
nym-vpnd - Firewall cleanup — the part that matters. The
inet nym_boottable wherevernftexists; fw4:nft delete table inet nym(nothing of ours is insideinet fw4). fw3: run the include under the state lock with the stop marker present, which removes the jumps and theNYM_*chains; an inline teardown of the same chains covers an install whose include is gone. Skip this and an uninstall leaves the kill-switch up with nothing to take it down. - Stray routing rules — flush any
ip ruleentries matching the daemon's fwmarks, v4 and v6. The daemon normally clears these itself on stop; a crashed one does not. Matching on fwmark rather than priority avoids touching unrelated rules. - UCI and split tunnel — delete the
firewall.nym_vpninclude and thenym_zone/nym_lan_fwdsections (the tunnel zone and itslan -> nymforwarding), commit, remove the split-tunnel drop-in/etc/nftables.d/30-nym-split.nft, reload the firewall - DNS — revert
dhcp.@dnsmasq[0].resolvfileto stock, delete the split-tunnel domain sets (dhcp.nym_split_ipset), remove the managed resolv file and the adblock drop-in (/tmp/dnsmasq.d/nym-adblock.conf), and restart dnsmasq only if one of those was live. On upgrade none of this happens: the new daemon reconverges without a restart and reuses the adblock list without a download, and restarting dnsmasq here would be a LAN-wide DNS outage for no reason. - Logs and feed — remove
/var/log/nym-vpnd, strip nym-vpn lines fromcustomfeeds.conf, remove/etc/apk/repositories.d/nym-vpn.list
/etc/nym is kept, and prerm says so: it holds the account (mnemonic, device keys, the ticket
databases) and the daemon settings, and a removal also runs on a force-reinstall. A reinstall picks
the same account up again. rm -rf /etc/nym after the removal erases it. A downgrade is an upgrade
to both package managers (PKG_UPGRADE=1), so it never took the removal path.
apk hook mapping¶
apk does not run post-install/pre-deinstall on an upgrade; it runs pre-upgrade/post-upgrade
instead, always from the incoming package. build-apk.sh registers postinst as both
post-install and post-upgrade, prerm as pre-deinstall, and a copy of prerm with
PKG_UPGRADE=1 exported as pre-upgrade — apk never sets that variable itself. One visible
consequence: on the first upgrade to a package that stashes the rpcd checksums, apk runs the new
prerm (stash present, checksums compared) while opkg runs the old one (no stash, rpcd restart).
conffiles¶
/etc/config/nym-vpn is marked as a config file, so an upgrade keeps your version and writes new
defaults to .new alongside it. The .ipk lists it in control/conffiles; the .apk ships the
same list as /lib/apk/packages/nym-vpn.conffiles and a nym-vpn.conffiles_static of
<path> <sha256> lines, the files OpenWrt's buildroot generates and sysupgrade reads on apk
systems.
sysupgrade¶
/lib/upgrade/keep.d/nym-vpn lists /etc/config/nym-vpn and /etc/nym/ (recursively, so the
SQLite -wal/-shm files come along). sysupgrade backs them up and restores them into the new
image, so after reinstalling the package the router has the same account and settings, and the
boot guard finds the kill-switch flag in /etc/nym/nym-vpnd.json instead of reading it as off.
The package itself is not part of a stock image: reinstall it after the upgrade (or build it into
the image).
The shipped default:
Nothing reads enabled or network today; they stay for compatibility. Daemon settings — Always
On included — live in /etc/nym/nym-vpnd.json and are changed over gRPC, not UCI. The file also
holds the split-tunnel exclusion sections the rpcd bridge parses.