| 1 |
# 12. Network, mesh, sync, and backup |
| 2 |
|
| 3 |
Alloy assumes the machine is disposable and your state is not. An install |
| 4 |
should be rejoinable: build the medium, install, sign in to two things, and |
| 5 |
your files and your other machines are there. |
| 6 |
|
| 7 |
Both enrollments are opt-in. Nothing connects to anything until you say so. |
| 8 |
|
| 9 |
## Wifi and wired networking |
| 10 |
|
| 11 |
alloy net |
| 12 |
|
| 13 |
lists interfaces and connections. Connecting, disconnecting, and toggling the |
| 14 |
wifi radio all work without any privilege, because NetworkManager grants those |
| 15 |
to an active session. |
| 16 |
|
| 17 |
Joining a network you have never joined before is a different permission, and |
| 18 |
the console does not yet ask for it. For a new network: |
| 19 |
|
| 20 |
run0 nmcli device wifi connect "Network Name" --ask |
| 21 |
|
| 22 |
Once it is saved, `alloy net` connects to it like any other. |
| 23 |
|
| 24 |
## The firewall |
| 25 |
|
| 26 |
Alloy runs firewalld, on every install and both profiles. Inbound connections |
| 27 |
are refused unless something allows them; outbound traffic is untouched. |
| 28 |
|
| 29 |
What is allowed by default on a desktop install is `ssh`, mDNS, and DHCPv6 |
| 30 |
replies. That is the same list as the services Alloy actually runs, which is |
| 31 |
the point: the firewall is the enforcement of the defaults rather than a |
| 32 |
separate policy to keep in step with them. |
| 33 |
|
| 34 |
Printing needs nothing here. CUPS listens on a unix socket and on localhost, |
| 35 |
and loopback is not filtered. |
| 36 |
|
| 37 |
To open something you installed yourself: |
| 38 |
|
| 39 |
run0 firewall-cmd --permanent --add-port=8080/tcp |
| 40 |
run0 firewall-cmd --reload |
| 41 |
|
| 42 |
`--add-service=<name>` takes a name from `firewall-cmd --get-services` and is |
| 43 |
better when one exists, because it carries every port the service needs. |
| 44 |
|
| 45 |
To see what is currently allowed: |
| 46 |
|
| 47 |
firewall-cmd --list-all |
| 48 |
|
| 49 |
**A server image is stricter by one entry.** It allows `ssh` and DHCPv6 and not |
| 50 |
mDNS, because that profile is also what runs on a machine with a public |
| 51 |
address, and answering mDNS to the internet buys nothing. If your server is on |
| 52 |
a LAN and you want to reach it at `<name>.local`: |
| 53 |
|
| 54 |
run0 firewall-cmd --permanent --add-service=mdns |
| 55 |
run0 firewall-cmd --reload |
| 56 |
|
| 57 |
Without it the machine is still findable: it sends its hostname over DHCP, so |
| 58 |
it appears in the router's lease table under the name it was minted with. |
| 59 |
|
| 60 |
**The tailnet is exempt.** The `tailscale0` interface is in firewalld's trusted |
| 61 |
zone, so anything reaching this machine over Tailscale is accepted on any port |
| 62 |
regardless of the rules above. That is deliberate, and it is worth knowing |
| 63 |
rather than discovering: a service you start for yourself is reachable by every |
| 64 |
machine on your tailnet, without opening a port. Access there is controlled by |
| 65 |
your Tailscale ACL rather than by this firewall. |
| 66 |
|
| 67 |
## Mesh: Tailscale |
| 68 |
|
| 69 |
alloy mesh |
| 70 |
|
| 71 |
A machine that has not joined anything shows an offer rather than an empty |
| 72 |
list. Press `e` to set the control server before signing in: leave it empty for |
| 73 |
tailscale.com, or enter a URL for a self-hosted Headscale. |
| 74 |
|
| 75 |
Signing in escalates to root and then waits on a browser login, so the console |
| 76 |
gets out of the way while that happens and comes back into the peer list |
| 77 |
afterwards. |
| 78 |
|
| 79 |
Once enrolled, the view lists your peers, whether each is online, when it was |
| 80 |
last seen, and lets you pick or clear an exit node. This machine is listed |
| 81 |
first. If you are on a self-hosted control plane, the title says so. |
| 82 |
|
| 83 |
Anything that acts on the whole tailnet rather than on this machine — deleting |
| 84 |
a node, editing the ACL, minting an auth key — lives in the admin console and |
| 85 |
nowhere else. The `tailscale` client has no verb for any of it. `w` opens that |
| 86 |
console in a browser. On a self-hosted control plane the address is a guess at |
| 87 |
the control host's root, and the status line says so, because Headscale serves |
| 88 |
the control API there and no UI. |
| 89 |
|
| 90 |
The plain command is `run0 tailscale up`, and `alloy tail` is kept as an alias |
| 91 |
for the verb. |
| 92 |
|
| 93 |
Alloy does not ship a Headscale server. That is fleet-admin work, not |
| 94 |
client-OS work. |
| 95 |
|
| 96 |
## File sync: Syncthing |
| 97 |
|
| 98 |
alloy sync |
| 99 |
|
| 100 |
Three tabs: **folders**, **devices**, and **pending**. |
| 101 |
|
| 102 |
- Folders shows each synchronized folder's path, share mode, and whether it is |
| 103 |
paused. `a` adds one, `d` removes one, and both confirms tell you what is |
| 104 |
*not* deleted, because "remove folder" must never read as "delete my |
| 105 |
documents". `s` opens the folder's share list, where each device toggles on |
| 106 |
or off as you press it; this machine is not offered there, because taking it |
| 107 |
off the list is what `d` does. |
| 108 |
- Devices shows connection state, this machine first. |
| 109 |
- Pending lists devices that have added you and are knocking. Accepting one |
| 110 |
finishes the pairing. |
| 111 |
|
| 112 |
Enrollment is a user service and needs no root: |
| 113 |
|
| 114 |
systemctl --user enable --now syncthing.service |
| 115 |
|
| 116 |
Syncing over the LAN also needs the firewall opened, because Syncthing is off |
| 117 |
by default and Alloy does not open ports for services nobody is running: |
| 118 |
|
| 119 |
run0 firewall-cmd --permanent --add-service=syncthing |
| 120 |
run0 firewall-cmd --reload |
| 121 |
|
| 122 |
Two machines on the same tailnet need none of that. The tailnet is exempt from |
| 123 |
the firewall, so they find each other with no rule at all. |
| 124 |
|
| 125 |
One asymmetry to know about: you can accept a pending device but not decline |
| 126 |
it. The command-line client Syncthing ships has no verb for dropping a pending |
| 127 |
entry, so an unaccepted device stays listed. The view says so when you press |
| 128 |
`d` there. |
| 129 |
|
| 130 |
Syncthing's own web UI is still at `127.0.0.1:8384` for anything the console |
| 131 |
does not cover — ignore patterns, versioning, per-folder advanced settings. |
| 132 |
`w` opens it rather than making you remember the address. |
| 133 |
|
| 134 |
## Backup: restic |
| 135 |
|
| 136 |
restic is in the image and Alloy prescribes no policy. Where you back up to is |
| 137 |
answerable only by you, so there is no default repository, schedule, or |
| 138 |
retention. |
| 139 |
|
| 140 |
This matters more than it looks. Sync is mesh plus latest-wins; it gets your |
| 141 |
files onto the new machine. It does not get back the file you deleted three |
| 142 |
weeks ago and only just noticed. That case needs versioned backup, which means |
| 143 |
configuring restic: |
| 144 |
|
| 145 |
restic init --repo <destination> |
| 146 |
restic backup ~/Documents ~/Pictures |
| 147 |
restic snapshots |
| 148 |
|
| 149 |
Any of restic's backends work: local disk, SFTP, S3, B2, rclone. A systemd |
| 150 |
user timer is the usual way to make it recurring. |
| 151 |
|
| 152 |
There is no `alloy backup` view. It is a candidate, not a plan. |
| 153 |
|
| 154 |
## What is deliberately not synced |
| 155 |
|
| 156 |
- **Secrets.** Too personal, and the security models vary too much for a |
| 157 |
defensible default. gopass (chapter 10) is the shipped tool, and you decide |
| 158 |
what it holds and where it goes. |
| 159 |
- **Dotfiles.** Syncthing can carry `~/.config` and Alloy does not set that up |
| 160 |
for you. |
| 161 |
- **The first-boot marker.** Deliberately never replicated: a synced copy would |
| 162 |
suppress the enrollment screen on the next machine you set up, which is |
| 163 |
exactly the machine that needs it. |
| 164 |
|