files: per-user public at ~name/public; home = member directory

Re-model the web file host as a file server (not a website host):

- Drop the misnamed /site area. A member's public files are now their
  /me/public subfolder (unix ~/public), served anonymously at
  ~<name>/public. The rest of /me stays private; only ~name/public is
  ever exposed. Bare /~name redirects to /~name/public/.
- The root / is now a directory of ALL members, each linked to their BBS
  site (https://<bbs-host>/~name via WebConfig.SiteBase) AND their public
  files here (~name/public). No longer hides empty members.
- Sites/homepages stay on the BBS — files.<host> only links to them.
- Usage gauge is just /me again (which includes /me/public).

setup.sh + docs updated; tests cover ~name/public browse/download, the
bare-~name redirect, empty-member empty-listing, /public-only exposure,
and traversal confinement.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-26 01:21:00 +00:00
parent 87fb2b4a1f
commit a2d4817a8e
9 changed files with 203 additions and 166 deletions

View file

@ -31,28 +31,32 @@ gated on membership, *not* on the paid Founding Lifetime plan:
- Operators can revoke an individual account's SFTP access (abuse response)
without touching its BBS login — see the management TUI below.
## Three areas
## Two areas
When you connect you see a virtual root with three directories:
When you connect you see a virtual root with two directories:
| Path | What it is | Access |
|---|---|---|
| `/me` | Your **private** per-user workspace | read/write, quota-limited |
| `/site` | Your **own public area**, served on the web at `~<name>` | read/write (yours), world-read anonymously |
| `/public` | The single **shared public file area** (old-school BBS file area) | world-read; members-only write by default |
`/me` is private — there is **no** path from one member's `/me` to another's. The
two *public* surfaces are `/site` (per-member, the only thing exposed at
`~<name>`) and the single shared `/public`. All three areas are confined: a path
that tries to escape its root (`../`, an absolute path, or a planted symlink) is
rejected, and the unauthenticated web surface (below) has no route into anyone's
`/me`.
Your `/me` home has one special subfolder: **`/me/public`**. Anything you put
there is published on the web at **`~<name>/public`** (the unix `~/public`
convention) — that, and only that, is the per-member public surface. The rest of
`/me` stays private; there is **no** path from one member's `/me` to another's.
Both areas are confined: a path that tries to escape its root (`../`, an absolute
path, or a planted symlink) is rejected, and the unauthenticated web surface
(below) only ever exposes `~name/public`, never the rest of a member's `/me`.
> files.\<host\> is a **file server, not a website host**. Member homepages
> ("sites") live on the BBS at `https://<host>/~<name>`; the file host's `~user`
> directory links out to them but does not serve them.
## Quotas
Each member's **owned storage** has a byte quota (default **1 GiB**, set by
`AGENTBBS_FILES_QUOTA_MB`). The gauge sums your private `/me` **and** your public
`/site`; writes to either that would exceed the quota fail. Operators can set a
Each private workspace has a byte quota (default **1 GiB**, set by
`AGENTBBS_FILES_QUOTA_MB`). The gauge measures all of `/me` — including your
`/me/public` folder — and writes that would exceed it fail. Operators can set a
per-user override in the management TUI. The shared `/public` area is
operator-managed and **not** metered per user.
@ -90,7 +94,7 @@ content-blind.
|---|---|---|
| `AGENTBBS_FILES` | `1` | enable the SFTP subsystem + Files plugin (`0` disables) |
| `AGENTBBS_FILES_QUOTA_MB` | `1024` | default per-user workspace quota (MB) |
| `AGENTBBS_DATA` | `./data` | storage lives under `<data>/files/{users,sites,public}` |
| `AGENTBBS_DATA` | `./data` | storage lives under `<data>/files/{users,public}` (a member's public files are `users/<name>/public`) |
## Provisioning members from a public key (for external services)
@ -124,10 +128,11 @@ has no route into anyone's `/me`.
| URL | Serves | Auth |
|---|---|---|
| `/` | A **directory of members' `~user` sites**, plus a sign-in link | none |
| `/~<name>[/path]` | That member's public `/site` — browse + download | none |
| `/` | A **directory of all members** — each linked to their BBS **site** and their **public files** | none |
| `/~<name>/public[/path]` | That member's public files (`/me/public`) — browse + download | none |
| `/~<name>` | Redirects to `/~<name>/public/` | none |
| `/public[/path]` | The shared public area — browse + download | none |
| `/?path=…`, `/upload`, … | The authenticated manager: your `/me`, `/site`, `/public` | webmail login |
| `/?path=…`, `/upload`, … | The authenticated manager: your `/me` + the shared `/public` | webmail login |
Clean URLs map **1:1 to the SFTP paths**, so share links just work:
@ -135,8 +140,8 @@ Clean URLs map **1:1 to the SFTP paths**, so share links just work:
scp dist.crx files@files.profullstack.com:/public/extensions/acme/
-> https://files.profullstack.com/public/extensions/acme/dist.crx
scp index.html files@files.profullstack.com:/site/
-> https://files.profullstack.com/~chovy/index.html
scp index.html files@files.profullstack.com:/me/public/
-> https://files.profullstack.com/~chovy/public/index.html
```
Directories render a read-only browse listing; files stream with a content type