lightweight vpn topology client/server
Find a file
2026-08-31 19:44:48 -04:00
cmd refactor: pulled out admin clients from internal/ into public pkg/ packages 2026-08-31 19:44:48 -04:00
deploy added: client systemd unit 2026-08-22 17:02:12 -04:00
internal refactor: pulled out admin clients from internal/ into public pkg/ packages 2026-08-31 19:44:48 -04:00
pkg refactor: pulled out admin clients from internal/ into public pkg/ packages 2026-08-31 19:44:48 -04:00
.gitignore initial commit: simple daemon 2026-06-19 20:25:08 -04:00
AGENTS.md added: network topology command improvements 2026-08-27 23:35:25 -04:00
go.mod added: locked in 'command-go@v0.8.0' update 2026-07-07 21:01:53 -04:00
go.sum added: locked in 'command-go@v0.8.0' update 2026-07-07 21:01:53 -04:00
Makefile refactor: pulled out 'runtime' from 'service' layers 2026-08-21 23:12:40 -04:00
README.md added: client systemd unit 2026-08-22 17:02:12 -04:00

cord

cord coordinates WireGuard networks. A server daemon owns the network topology and reconciles WireGuard devices, while client daemons on peers install networks from invites and keep them in sync.

Requirements

  • Go 1.26 or newer to build from source
  • A host with WireGuard interface support (the wireguard kernel module, or the userspace backend)

Building

make build   # builds bin/cord
make install # installs cord to /usr/local/bin
make test    # runs the test suite

Server

Start the daemon on Linux or macOS:

sudo cord server daemon

The daemon creates its protected runtime directory when needed and listens on the Unix socket /var/run/cord/server.sock. By default, the socket trusts local users, so subsequent management commands do not need sudo:

cord server status
cord server network add <name> <main-cidr> <external-ip>
cord server network list

The daemon stores its state in a SQLite database at data/server.db, relative to the current working directory. --backend selects the WireGuard implementation (auto, kernel, or userspace) and --debug enables verbose logging.

Client

sudo cord client daemon
cord client network install invite.json

The client daemon listens on /var/run/cord/client.sock and stores its state in a SQLite database at data/client.db, relative to the current working directory.

Running under systemd

Example units for the server and client daemons live in deploy/. They start the daemons at boot, restart them if they exit with a failure, and pin the working directory to /var/lib/cord (systemd creates it), so the SQLite databases live under /var/lib/cord/data/ and survive reboots. The units run as root because the daemons must create WireGuard interfaces. The CLI uses the same default socket path.

Create the administrative group, add the users who should manage cord, and install the desired hardened example units (omit either service if that host only has one role):

sudo groupadd --system cord
sudo usermod --append --groups cord <admin-user>
sudo cp deploy/cord-server.service /etc/systemd/system/
sudo cp deploy/cord-client.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now cord-server cord-client

Check status and follow the logs:

systemctl status cord-server
journalctl -u cord-server -f
cord server status

systemctl status cord-client
journalctl -u cord-client -f
cord client status

Start a new login session after changing group membership.

Socket permissions

Daemon sockets default to 0666, intentionally trusting local user accounts. Use --socket-mode to select a stricter policy:

sudo cord server daemon --socket-mode 0600 # root only
sudo -g cord cord server daemon --socket-mode 0660 # root and cord group

The example systemd units use 0660 and run the daemons with primary group cord. Launchd deployments can apply the same policy by assigning each daemon an administrative group.