HBlink4 draws operators who have never run a network server before, and
several things in the install path assumed otherwise.
Sample configuration split in two. config_sample.json was a feature
showcase, and copying it as documented produced a broken server: its
example repeater pattern claimed IDs 310000-312999 with a passphrase of
its own, so an operator whose repeater fell in that range was rejected
with no way to diagnose it (patterns match first-wins and never fall
through to "default"). It also shipped an outbound link and an OpenBridge
trunk enabled against hosts that do not exist, and bound both address
families to one port, which cannot work. It is now a minimal working
config needing one edit -- the passphrase. The full reference moves to
config_advanced_sample.json with its link examples disabled and the ID
overlap called out.
Ship IPv4-only. Dual-stack is fully supported and now documented as four
concrete layouts, each verified to bind; it is simply not the default,
because most repeaters are IPv4 and the same-port bind conflict is not
something a new operator should have to learn first. The built-in default
when the key is absent stays false, so existing installs are unaffected.
"IPv6 is globally disabled" drops from WARNING to INFO -- it is now the
recommended setting and should not read as a fault.
install_services.sh: refuse paths containing a space, $ or % (systemd
mis-parses them in ExecStart=, and a space previously produced a unit
that passed verification and then failed at start); escape & in the path
substitution; render and verify units in a temp dir so a failed
substitution cannot leave a broken unit in /etc/systemd/system. Check the
things that otherwise surface only in the journal: complete checkout,
Python 3.9+, server and dashboard dependencies, both config files, and
agreement between the two halves of the dashboard event link.
readme: seven numbered steps with no diversions, adding the prerequisites
(python3-venv, which Debian and Ubuntu users hit constantly) and the
firewall ports, neither of which were mentioned.
Also: the dashboard logged a hardcoded localhost:8080 regardless of the
configured port; run_all.sh now fails clearly on missing config instead of
a traceback; pyproject's comment claimed systemd should invoke the console
scripts, contradicting how the units actually work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Leaving <unit>.bak-<stamp> next to the live units puts clutter in a
system directory that systemd itself scans, and it accumulates where
nobody looks. Back up into the project root instead, chowned back to the
invoking user rather than left owned by root.
Renamed to <unit>.<timestamp>.bak so the existing *.bak ignore rule
actually matches -- the previous .bak-<timestamp> form does not end in
.bak and would have shown up as untracked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shipped unit files carry the author's user and paths (User=cort,
/home/cort/hblink4), so copying them verbatim produces two services that
fail to start. The docs said so only in a Customization section below the
copy instructions, and told people to edit the repo copies -- which then
conflict on the next git pull.
The installer prompts for the service user and group (defaulting to the
invoking sudo user and their primary group), substitutes them and the
detected installation path into both units, installs to
/etc/systemd/system/, reloads systemd, and optionally enables and starts
the services. It backs up existing units, refuses unknown users/groups,
checks the venv exists, warns when the service user cannot write the
installation directory, and verifies the substitution landed before
leaving anything in place. DESTDIR previews the output without touching
the live system.
The manual instructions now say to copy first and edit the installed
copy, and note that daemon-reload is required after any unit change. Both
unit files carry a header saying the same.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>