Commit Graph

9 Commits

Author SHA1 Message Date
bflatastic d74eeb4ab3 M9: open hosting: fair limits for shared addresses
Third review:
- a broadcast counts once per recipient block, welcomes don't count
  against block caps, and presence, cursor chat and pings have their own
  per-address budget, so an office behind one address works together
- when everyone's reading budget runs out, the biggest readers are cut
  off until it fits (their bytes freed at once), no cliff
- block counters are forgotten once only the table holds them
- install.sh: root's downloads, clone, swap file and built binary in a
  root-only directory; the build user builds its own copy; the binary is
  copied out with the build user's rights
- recommended open-mode settings in install.sh's config and the docs
2026-10-12 11:07:27 +07:00
bflatastic 395a2d9dae M9: close the remaining open-hosting DoS paths
Second security review:
- a broadcast counts once against the queued-bytes budget; each block of
  addresses gets at most an eighth; when the budget runs out the biggest
  consumer is shed, not the receiver
- forced compactions at most once a minute per file
- plugin downloads check the per-address share before the global budget
  and before reading
- install.sh never replaces a Caddyfile with other sites (one import line,
  once; install-test.sh in packaging lint); the built binary runs only as
  the server's account
- bytes being read count against per-block and global budgets, the
  biggest readers dropped first
- block multipliers x2 (/56) and x4 (/48), connection caps at most a
  sixteenth/eighth of all; global publish and download budgets eight times
  a /48's share
- problems capped at 200; writes refused below max(1 GiB, two files) free;
  the private-server file cap documented
2026-10-12 11:07:26 +07:00
bflatastic 47d56034a5 M9: harden open hosting against resource exhaustion
Fixes from the security review of the public server:
- team libraries only in key-published files on open servers; unpacking
  bounded by the file cap (zip bombs refused)
- byte-bounded send queues per connection and in all, a bound on bytes
  waiting for the hub, per-address and global download budgets
- batches checked against the file, per-address and total caps as files
  grow (refused with a Reject the app rolls back); the core's recent-batch
  memory bounded in bytes; nothing confirmed when a log can't be written
- usage reports kept in memory; per-kind message caps checked while read
- layered IPv6 limits (/64, /56, /48), a global publish rate, connections
  reserved for sessions, short idle timeout for one-shot requests
- revoked links and removed files cut library connections
- data directory 0700, server key 0600 from creation
- install.sh: SSH ports from sshd, ufw left off when other services
  listen, unprivileged builds of the latest tag, settings checked before
  use, own Caddy snippet; systemd memory and network caps; backups check
  free space and say they hold the key
- linger reads raw bytes after an oversized frame (tungstenite asserted)
2026-10-12 11:07:25 +07:00
bflatastic d0f649699c M9: install.sh, backup timer and docs for a public figcraft-server
packaging/server/install.sh sets up Debian/Ubuntu behind Caddy: binary from
its release archive, the source tree it sits in, the latest release or a
clone (temporary swap under 2 GB of memory), user, dirs and config only when
missing, systemd unit, Caddy site, nightly backup timer, ufw for 22/80/443;
safe to re-run for upgrades. docs/server.md gains Running a public server
(limits, expiry, moderation, operator responsibility, no end-to-end
encryption, threat model) and an install.sh walk-through; protocol, MCP,
CLI and parity docs updated.
2026-10-12 11:07:23 +07:00
bflatastic 01bc5a9dac M11: plugin installs saved at once; directory and library requests off the UI thread
- **Installed plugins survive a crash.** Importing, installing, updating or removing a plugin, and a
  plugin changing its settings, now sets `Runtime::unsaved`; at the end of each frame the app hands
  the preferences to a new `Services::save_prefs`, and the desktop app writes `state.json` at once
  (temporary file + rename, the same atomic write the quit path uses, now shared). Nothing waits for
  quit any more.
- **The UI never waits on the network.** New `Session::start_background(id, params)`: the command
  runs as usual until it makes a server request; the request goes out on the link's own thread
  (`LiveLink::background`, implemented by the desktop `NetLink` with one short-lived thread per
  request) and the command stops; when the answer arrives (`Event::Net`, taken by `live_poll`, which
  the network's waker already triggers) the command runs again from the start on the file it
  started on, answered from the answers so far, until it needs no new request. All server requests
  go through one funnel (`Session::net`), so `plugin.directory.*`, `library.publishToServer`,
  `library.add` (links), `library.usage` and library fetches work this way without being split.
  A re-run of `library.publishToServer` caches the library it actually sent (a fresh copy differs
  in its time and, on a first publish, its id). One job per command; `cancel_background` drops a
  late answer.
- **UI:** `FigApp::run_background(ctx, id, params, done)` with a boxed completion. Manage plugins >
  Browse shows "Fetching from the server..." with Cancel instead of Browse; the install confirmation
  shows "Installing..." with Cancel and stays open until the download is in (an error leaves it open
  to retry); row buttons wait during an install; closing the window cancels. Assets > Libraries:
  Publish to server shows "Publishing..." + Cancel, Show usage and pasting a team library link show
  "Fetching from the server..." + Cancel. No polling repaints: the window draws again when the
  answer's waker fires; pending state is a scan of a short Vec, no per-frame allocation.
- **Programmatic callers are unchanged:** MCP, CLI, control channel and scripts still run the same
  commands synchronously (and the browser link, which has no background requests, too).
- New string "Installing..." (en/es); docs: plugins.md (immediate saves, Browse/Install never wait),
  server.md (team library requests never wait).
2026-10-12 06:15:00 +07:00
bflatastic def6f84b22 M11: plugin directory on figcraft-server
A self-hosted plugin directory, with no third-party service: `figcraft-server` keeps plugin bundles,
the app browses, installs and updates from it, and `figcraft-cli` publishes to it.

- **Bundles** (`figcraft_plugin::bundle`): a plugin's manifest, module and up to 32 plain-named
  assets in one uncompressed, length-prefixed binary form (nothing can expand beyond the bytes
  sent). Its SHA-256 names it. `Bundle::decode` never allocates beyond its input and refuses
  trailing bytes; `Bundle::check` parses the manifest (refusing a plugin API this build doesn't
  run), requires a version of 1-4 numbers, compiles the module without running it (only the
  sandbox's imports allowed) and checks asset names (no paths, no dot files, no Windows device
  names, unique ignoring case). Manifests gain an optional `author`.
- **Protocol**: a new connection kind next to managing and team-library connections:
  `OpenPlugins {key}` then `Plugins {list | fetch | publish | unpublish}`. Browsing needs no
  credential; publishing and unpublishing need the server key (a wrong key is refused). Older
  servers refuse the connection and the app says the server has no plugin directory. The protocol
  version is unchanged; old clients are unaffected.
- **Server** (`apps/figcraft-server/src/directory.rs`): `<data>/plugins/index.json` plus
  `<data>/plugins/<sha256>.bin`, so no publisher-chosen name reaches the file system. Every publish
  is checked in full and must be a newer version; caps `max_plugins` (versions, 0 turns it off),
  `max_plugin_mb` (<= 32), `max_plugins_mb`. Fetches re-hash the file before sending (a damaged
  bundle is never sent) and share a download budget (~32 MiB/s) so a flood is refused, not queued.
  Damaged index entries are skipped and reported, never deleted; an unreadable index lists nothing
  and refuses writes until the operator fixes it. Operator moderation:
  `figcraft-server plugins [list]` and `plugins remove ID [--version V]` (offline or through the
  running server). Logs name ids and versions, never keys.
- **App**: Plugins > Manage plugins gets Installed / Browse tabs. Browse lists a server's plugins
  (version, author as typed, size, access) with a search; nothing is fetched until Browse is
  pressed. Install / Update open a confirmation listing the permissions asked for, the source and
  the SHA-256; only its button downloads. The download must match the listed SHA-256 (and the one
  shown), then is checked like an imported plugin before anything is written. Installed plugins
  live per user under `<settings>/FigCraft/Plugins/<hash of id>/` and run in the same sandbox;
  Remove deletes the folder.
- **Commands**: `plugin.directory.list {server?, query?}`, `plugin.directory.install {id, version?,
  confirm, server?, sha256?}` (refused without `confirm: true`; never opens a dialog),
  `plugin.directory.publish {path | plugin, server}`, `plugin.directory.unpublish {id, version?,
  server}`. `ui.set {pluginsTab, pluginConfirm}` on the control channel.
- **CLI**: `figcraft-cli plugin publish FOLDER --server ws://host:port/` with the key from
  `FIGCRAFT_SERVER_KEY` (or `?key=`), never printed.
- Docs: plugins.md (directories, publishing, commands, manifest), server.md (operating the
  directory: access, checks, caps, moderation, backups, security), collab-protocol.md, mcp.md,
  cli.md, control-protocol.md, target-app-parity area 18 and Ecosystem, ROADMAP, roadmap.md M11,
  gaps.md gap 6. Plugins & widgets 55%/32% -> 60%/35%, 10-30 h -> 8-26 h.
2026-10-12 05:28:53 +07:00
BFlat 76df2d4136
M9: hosting figcraft-server: wss in the desktop app, server releases, deployment guide (#125)
The desktop app joins wss:// sessions (rustls with webpki roots, certificates always checked, clear errors; FIGCRAFT_CA_FILE for private authorities), with one TLS session shared by the reader and writer threads. figcraft-server stops gracefully on SIGTERM/SIGINT/SIGHUP/Ctrl+C, ships in Linux x86_64/aarch64 release archives, and docs/server.md gains a Deploy on a Linux server guide with a hardened systemd unit, Caddyfile, nginx config and Dockerfile in packaging/server.
2026-10-11 15:52:27 +07:00
BFlat 09600d6da2
M9: team libraries on the server; swap library (#117)
A file's library can be published to figcraft-server and enabled in other files by a share link (view access is enough); placing, styles, variables and library updates with overrides kept work as for local libraries, with a background check while the Assets panel is open. library.swap re-points instances, styles and variables to another library by name (one undo step, unmatched items listed). The server keeps per-file usage reports (library.usage).
2026-10-11 13:05:46 +07:00
BFlat 07fa4d92ed
M9: standalone collaboration server with share links and permissions (#112)
figcraft-server hosts many files over the same hub the app uses for sessions, persists each as a checksummed snapshot plus an append-only log (synced before replies, compacted, damaged files skipped and reported) so restarts lose nothing confirmed, and gives per-file share links with edit or view access (hashed 128-bit tokens, revocable). The app can publish a file to a server and join server-hosted files; view-only tabs refuse edits. Protocol version 2.
2026-10-11 10:19:03 +07:00