diff --git a/content/storage/_index.md b/content/storage/_index.md index d27b905..12fcdeb 100644 --- a/content/storage/_index.md +++ b/content/storage/_index.md @@ -3,6 +3,6 @@ title: 'Storage' linkTitle: 'Storage' weight: 4 icon: 'disk' -description: 'Persistent disks for workloads that need to keep data between restarts.' -lead: 'Containers are ephemeral. When a workload needs to persist data across restarts and rollouts, attach a managed disk.' +description: 'Persistent disks for workloads that keep data between restarts, plus temporary file sharing.' +lead: 'Containers are ephemeral. Attach a managed disk when a workload needs to persist data across restarts and rollouts, or use Dropbox to hand a file out as a short-lived download URL.' --- diff --git a/content/storage/dropbox.md b/content/storage/dropbox.md new file mode 100644 index 0000000..44d46f3 --- /dev/null +++ b/content/storage/dropbox.md @@ -0,0 +1,130 @@ +--- +title: 'Dropbox' +linkTitle: 'Dropbox' +weight: 2 +description: 'Temporary file storage — upload a file, get a short-lived public download URL.' +lead: 'Dropbox is temporary file storage for a project. Upload a file and you get back a public, signed download URL that expires on its own — a day by default, up to seven. Good for handing someone a build artifact, sharing a generated report, or staging an archive to publish — without standing up a bucket or minting long-lived credentials.' +--- + +## When to reach for it + +A disk is for data a deployment keeps. Dropbox is the opposite: a file you want +to *get out* of the platform briefly and then forget about. + +- **Share an artifact** — a build output, a database dump, a log bundle — with + someone who just needs a link, not an account. +- **Hand a generated file to a human** — an export or report produced by a job, + surfaced as a URL they can click. +- **Stage a static-site archive** — upload a `.tar.gz`, then point + [static-site publishing](/deployments/static-sites/) at the returned URL. + +Every file carries a **TTL of 1–7 days** (default 1). Once it expires the link +stops working and the bytes are reclaimed — there's no manual delete and no way +to extend a TTL. If you need a file to live longer, re-upload it. + +The download URL is signed: a tampered or made-up token is rejected before it +ever touches storage, so a link only works if it came from a real upload. But +the URL itself is the only credential — anyone who has it can download the file +until it expires. Treat a Dropbox link like a secret, and don't use Dropbox for +anything you need to keep private indefinitely. + +## The Dropbox page + +Open **Dropbox** in a project to upload files and see what's currently stored. + +{{< shot src="/img/dropbox-list.png" url="console.deploys.app/dropbox?project=acme" alt="Dropbox: a drop zone above a list of four uploaded files, each with a download URL, size, and expiry" caption="Drop a file in, get a shareable URL. Each row shows the file's size, when it went up, and when it expires." >}} + +Drag a file onto the drop zone (or click to browse) and hit **Upload**. The new +file appears in the list below with its download URL — copy it with the clipboard +button, or open it in a new tab. Each row shows the size, the upload time, and +when the link expires. + +The **Usage** button (top right) opens charts for the project's Dropbox +**egress** (bytes downloaded) and **storage** (bytes held) over the last 7, 30, +or 90 days. + +## Upload from the API + +Uploads are a raw `POST` to the Dropbox service — not a JSON-RPC action — so they +live on their own host, `https://dropbox.deploys.app/`. The request carries your +normal Bearer token; the caller needs the `dropbox.upload` permission on the +project. + +```bash +curl -fsS -X POST \ + "https://dropbox.deploys.app/?project=acme&ttl=3&filename=build.tar.gz" \ + -H "Authorization: Bearer $DEPLOYS_TOKEN" \ + --data-binary @build.tar.gz +``` + +| Query param | | Description | +|---|---|---| +| `project` | required | Project ID the upload is authorized and billed against | +| `ttl` | optional | Lifetime in days, 1–7 (default 1) | +| `filename` | optional | Name recorded in `Content-Disposition` for the download | + +The same values can be passed as `param-project` / `param-ttl` / `param-filename` +headers instead; query params win when both are present. A successful response is +JSON: + +```json +{ + "ok": true, + "result": { + "downloadUrl": "https://dropbox.deploys.app/files/", + "expiresAt": "2026-06-19T08:00:00Z" + } +} +``` + +On an auth or validation failure the service still answers `200` but with +`{"ok": false, "error": {"message": "…"}}` — check the `ok` field, not just the +HTTP status. + +### From Go + +The [typed client](/api/conventions/#typed-clients) wraps the upload in a helper +that returns the URL, expiry, and byte count: + +```go +res, err := c.DropboxUpload(ctx, &client.DropboxUploadOptions{ + Project: "acme", + Content: content, // the file bytes + Filename: "build.tar.gz", // optional + TTLDays: 3, // 1–7, default 1 +}) +// res.DownloadURL, res.ExpiresAt, res.Size +``` + +This is exactly what static-site publishing uses internally: upload an archive, +then pass `res.DownloadURL` to `PublishSite`. + +## List and meter from the CLI + +The [`deploys` CLI](/automation/cli/) exposes the read side — listing stored +files and pulling usage metrics. (Uploading is done through the console or the +API above.) Both require the `dropbox.list` permission. + +```bash +# everything currently stored in the project +deploys dropbox list --project acme + +# narrow by upload time, cap the count +deploys dropbox list --project acme \ + --after 2026-06-01 --before 2026-06-15 --limit 20 + +# egress + storage over the last 30 days (7d / 30d / 90d) +deploys dropbox metrics --project acme --time-range 30d +``` + +`list` returns each file's download URL, filename, size, TTL, and its created / +expires timestamps — the same view the console shows. + +## Permissions + +| Permission | Grants | +|---|---| +| `dropbox.upload` | Upload files to the project | +| `dropbox.list` | List stored files and read usage metrics | + +Grant these through a [role](/access/roles/) like any other permission. diff --git a/scripts/screenshots/capture.mjs b/scripts/screenshots/capture.mjs index 442dee3..b5136d2 100644 --- a/scripts/screenshots/capture.mjs +++ b/scripts/screenshots/capture.mjs @@ -40,6 +40,7 @@ const screens = [ ['domain-list', `/domain?${P}`], ['route-list', `/route?${P}`], ['disk-list', `/disk?${P}`], + ['dropbox-list', `/dropbox?${P}`], ['registry-list', `/registry?${P}`], ['role-list', `/role?${P}`], ['service-account-list', `/service-account?${P}`], diff --git a/scripts/screenshots/mock-enrichment.patch b/scripts/screenshots/mock-enrichment.patch index 76b3548..18e1887 100644 --- a/scripts/screenshots/mock-enrichment.patch +++ b/scripts/screenshots/mock-enrichment.patch @@ -1,8 +1,8 @@ diff --git a/src/lib/server/mock.js b/src/lib/server/mock.js -index 7849ada..56a4585 100644 +index 1402806..c360791 100644 --- a/src/lib/server/mock.js +++ b/src/lib/server/mock.js -@@ -262,58 +262,72 @@ const deployments = [ +@@ -268,58 +268,72 @@ const deployments = [ } ] @@ -120,7 +120,7 @@ index 7849ada..56a4585 100644 ] const wafZone = { -@@ -667,6 +681,20 @@ const roles = [ +@@ -673,6 +687,20 @@ const roles = [ createdAt: CREATED_AT, createdBy: USER_EMAIL }, @@ -141,7 +141,7 @@ index 7849ada..56a4585 100644 { role: 'admin', name: 'Administrator', -@@ -690,7 +718,15 @@ const serviceAccounts = [ +@@ -696,7 +724,15 @@ const serviceAccounts = [ sid: 'sa_mock_ci', email: 'ci@acme.serviceaccount.deploys.app', name: 'CI Deployer', @@ -158,7 +158,7 @@ index 7849ada..56a4585 100644 createdAt: CREATED_AT, createdBy: USER_EMAIL } -@@ -787,7 +823,10 @@ const auditLogItems = (() => { +@@ -793,7 +829,10 @@ const auditLogItems = (() => { const repositories = [ { name: 'acme/web', size: 184320000, createdAt: CREATED_AT }, @@ -170,3 +170,41 @@ index 7849ada..56a4585 100644 ] const repositoryTags = [ +@@ -978,12 +1017,36 @@ function invoiceListItem (inv) { + + const dropboxItems = [ + { +- downloadUrl: 'https://dropbox.deploys.app/d/mock-build.tar.gz', ++ downloadUrl: 'https://dropbox.deploys.app/files/k3Qp9mZx7Rt2Lv8Wd4Nc1Bg-a1b2c3d4e5f6a7b8c9d0', + filename: 'build.tar.gz', + size: 5242880, + ttl: 86400, + createdAt: CREATED_AT, + expiresAt: '2026-05-02T08:00:00Z' ++ }, ++ { ++ downloadUrl: 'https://dropbox.deploys.app/files/Hn5Tb2Wq8Yx1Kd6Vc3Mf7Lp-9f8e7d6c5b4a3210ffee', ++ filename: 'db-dump-2026-05-01.sql.gz', ++ size: 50331648, ++ ttl: 604800, ++ createdAt: CREATED_AT, ++ expiresAt: '2026-05-08T08:00:00Z' ++ }, ++ { ++ downloadUrl: 'https://dropbox.deploys.app/files/Rj4Cf8Nz1Px6Bm2Gd9Wt5Qk-1234abcd5678ef90aa11', ++ filename: 'report-april.pdf', ++ size: 1258291, ++ ttl: 259200, ++ createdAt: CREATED_AT, ++ expiresAt: '2026-05-04T08:00:00Z' ++ }, ++ { ++ downloadUrl: 'https://dropbox.deploys.app/files/Wd7Lq2Vx9Bn4Kp1Cf6Mz3Rt-0fe1dc2b3a49587667cc', ++ filename: 'site.tar.gz', ++ size: 839680, ++ ttl: 172800, ++ createdAt: CREATED_AT, ++ expiresAt: '2026-05-03T08:00:00Z' + } + ] + diff --git a/static/img/dropbox-list-dark.png b/static/img/dropbox-list-dark.png new file mode 100644 index 0000000..9e03a7b Binary files /dev/null and b/static/img/dropbox-list-dark.png differ diff --git a/static/img/dropbox-list.png b/static/img/dropbox-list.png new file mode 100644 index 0000000..44c4ded Binary files /dev/null and b/static/img/dropbox-list.png differ