The SwiftUI app bundles the Go swarm engine through gomobile, so an iPhone is a real peer using the existing PAKE handshake, chunk verification, and relay fallback. XcodeGen builds the project and devicectl installs it on the paired phone.
The ignore rule matched "share-*" but not the binary's actual name,
"share", so every build was committed and pushed. Ignore the binary
explicitly and untrack it; releases now carry the built artifacts.
Daemon now also runs a plain HTTP server bound to 127.0.0.1 only (never
the network), serving an embedded vanilla HTML/CSS/JS page plus a small
JSON API (status/join/stop/leave/restart) that calls the same internal
methods the Unix-socket protocol already uses. No frontend framework or
build step — the page is embedded straight into the binary via
go:embed, keeping distribution a single file.
"share web" ensures the daemon is running, asks it (a new "web" control
action) for the dashboard's address, and opens it in the default browser
(open/xdg-open), falling back to printing the URL if that fails.
RoomStatus/FileStatus gained JSON tags for clean camelCase output, and
RoomStatus now carries RelayAddress so the dashboard can show it.
- "share stop <code>" pauses just that room (kept in rooms.json and in
"share status", so its code can still be looked up and resumed) without
touching any other room or the daemon.
- "share leave <code>" forgets a room entirely — no bare "stop" collapses
into "leave" anymore, since those need to be distinct: pausing must
survive to be resumable, forgetting must not.
- "share restart <code>" now works whether the room is still active or
already paused (falls back to the persisted list), rejoining with its
original settings.
- "share status" shows the same layout for running and stopped rooms —
an explicit running/stopped label, relay address, and file details in
both cases. A stopped room's file details come from stat()ing its
seed files (always locally complete); a stopped receiving room has no
file list since that's only knowable once it reconnects.
count and a startup hang
- src/daemon/persist.go: rooms.json records every active room so a
restarted daemon (crash, reboot, or "share stop" then relaunch) resumes
them automatically. A --one-time room clears itself once it finishes
on its own; anything else stays persisted since it only ever stops via
an explicit Stop.
- "share restart": stops the running daemon, waits for it to actually
release its socket, then relaunches it the same detached way
EnsureRunning does.
- Fixed a real startup hang: resuming persisted rooms used to block
before the daemon's Accept loop ever started, so a slow/unreachable
relay in one persisted room could leave the daemon looking "alive" to
IsRunning() while unable to answer any request — including its own
successor's attempt to detect and clean up a stuck instance. Resuming
now runs in the background instead. Added deadlines on both ends so a
stuck daemon fails fast with an actionable message rather than hanging.
- Fixed peer counts being inflated by duplicate connections: two peers
can end up with more than one link to each other (both sides happened
to dial at once). Links now carry the remote peer's ID (known
immediately when we dialed them, learned from their first manifest
otherwise), and Status dedupes by that ID.
Multi-stage build: compiles with CGO disabled in golang:1.25-alpine,
runs the static binary in a plain alpine:3.20 image. Defaults to
"share relay" on port 9009; override with extra args at `docker run`.
status: swarm.Client.Status()/RoomStatus/FileStatus report per-room peer
count and per-file chunk progress; daemon gets a "status" control action;
CLI gets "share status" to display it.
Relay fallback: peers advertising a LAN-only address can't be dialed
directly by peers on a different network. connectToPeer now races a
direct dial against a relay-piped connection (both sides derive the same
pairing key from their two IDs, independent of who arrives first) and
uses whichever succeeds. The relay grows a second, on-demand path
alongside room membership: a "pipe" request pairs two connections on a
key and forwards raw bytes between them from then on, same as the
original relay's behavior before the rendezvous-only redesign, just now
triggered per-pair only when direct connection fails.
Daemon (src/daemon): a Unix-socket control server holds every room this
machine is participating in. "share send"/"share <code>" resolve paths to
absolute, ask the daemon to join (auto-starting it if needed), and exit
immediately instead of blocking. "share stop" leaves every room and shuts
the daemon down; a stale socket from a crashed daemon self-heals on next
start.
swarm.Client is now a Start/Wait/Stop handle instead of a single blocking
Run, so the daemon can hold many rooms concurrently.
Also added per-chunk sha256 verification: the original seeder publishes a
hash for every chunk alongside the whole-file hash, and each chunk is
checked against it the moment it arrives — before it's written to disk or
reshared to other peers — rather than only catching corruption after the
whole file is assembled.
- src/pake: trimmed fork of schollz/pake/v3, P256-only (stdlib
crypto/elliptic), dropping the tscholl2/siec and filippo.io/edwards25519
transitive deps along with it.
- src/logger: minimal in-repo replacement for schollz/logger with the
same leveled-logging call surface.
- src/cli: replaced schollz/cli (urfave/cli fork) with a small
flag-package-based parser for the send/relay/receive commands actually
in use; subcommand keyword can appear before or after its flags.
- Dropped schollz/progressbar entirely (its show-progress code paths were
already unreachable dead code since no CLI flag wired them up).
go.mod/go.sum no longer reference any schollz/* module.
Peers join a room and the relay just broadcasts membership (id + dialable
address); peers then connect to each other directly, PAKE-handshake per
link using the room codephrase, and exchange 1MB chunks. Resume reuses
croc's zero-block scan to detect already-received chunks, whole-file hash
verifies completion, and every peer keeps reseeding what it has to others
until stopped (--one-time restores exit-on-complete for a quick fetch).
Relay pairs connections by room name and pipes bytes between them,
with no password/PAKE handshake at the relay layer — codephrase-based
encryption is left to the client side of the transfer.