Skip to content

Releases: freema/drobek

v0.5.3

Choose a tag to compare

@github-actions github-actions released this 28 Sep 10:34

Image: ghcr.io/freema/drobek:v0.5.3

Changed

  • Platform e-mails have real buttons and name the server (NSO-375): the pending-change mail, the abuse-report, takedown/restore, publish notification, publish approval/block, lost-domain and invite mails link the dashboard page as a button (the address stays in the text part and as a fallback line). A button can only point at the server's own origin (PUBLIC_APP_URL, or PUBLIC_ORIGIN for invites); an app's mail (ctx.email.send) still renders as escaped text with no links and cannot add buttons.
  • Self-hosted wording (NSO-375): the mails say "the dashboard at " and end with "Sent by the drobek server at because …" instead of "Sent by an app hosted on drobek"; the pending-change mail goes out under the server's sender, not the app's fromName / Reply-To; the sign-in code mail names the sign-in page's host.

Fixed

  • Invite links fall back to PUBLIC_APP_URL (NSO-375) when PUBLIC_ORIGIN is unset, instead of http://localhost:3041.

v0.5.2

Choose a tag to compare

@github-actions github-actions released this 28 Sep 09:55

Image: ghcr.io/freema/drobek:v0.5.2

Changed

  • All workspaces shows each workspace's apps (NSO-373): a super-admin's list on /workspaces reads "3 apps · 1 published" per workspace ("No apps" when empty; deleted apps are not counted), from one grouped query.

Docs

  • Community modules (NSO-374): docs/MODULES.md says that an npm package with the drobek-module keyword is listed on www.drobek.app/modules under "Community modules" (not reviewed), and that the submission form gets it reviewed into the directory.

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 28 Sep 08:51

Image: ghcr.io/freema/drobek:v0.5.1

Added

  • /api/version names the build and the process start (NSO-340). Next to the unchanged sha, version and modules it answers name ("drobek"), commitTime (the committer time of sha in UTC ISO, baked into the image as the COMMIT_TIME build arg by CI, task build and task e2e:image; the same sources rebuild to the same value; null when unknown) and startedAt (when the running process started, i.e. the last deploy or restart).

Changed

  • Activity tells a duplicates switch apart (NSO-340): turning "Allow duplicates" on or off reads "Allowed duplicates of the app from the public gallery" / "Stopped allowing …" instead of "Listed the app in the public gallery"; app.gallery_listed records previousAllowDuplicate next to previousDescription. Earlier entries keep their old summary.
  • The duplicate page names the source workspace with its slug (NSO-340): "From the workspace Personal (/smoke)" instead of "By Personal", since every personal workspace is called Personal.

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 19:06

Image: ghcr.io/freema/drobek:v0.5.0

Added

  • Duplicate an app from the gallery (NSO-340). An owner turns on "Allow duplicates" next to "Show in the gallery" (off by default; agents pass allow_duplicate to set_gallery_listing, inside the listing's confirmation). A signed-in person then copies the app on the dashboard at /duplicate/<slug> (sign-in first, then back) or with the new MCP tool duplicate_app({ from, workspace?, name? }) (scope write, editor+ in the target workspace): a new, unpublished app with the published files as version 1 that remembers its source ("Duplicated from" in the app header, duplicated_from in get_app). The source's module settings are proposed to the copy through its confirmation flow, without e-mail addresses and proxy upstreams; secrets, data, end users, uploads, app assets, domains and the listing are never copied. Audited app.duplicate / app.duplicated; DUPLICATES_PER_USER_HOUR (default 10) caps copies per person. The public gallery API adds duplicable, duplicateUrl and duplicates to each item. Migration 0028_gallery_duplicate.
  • Likes and opens in the public gallery (NSO-340). Each GET /api/public/gallery item carries likes (signed-in accounts that like the app, one per account), opens (visits through the gallery in the last 30 days), openUrl and likeUrl; ?sort=popular orders by 5 × likes + opens (page mode). /gallery/open/<slug> counts a visit per app and day — no prefetch, no HEAD, at most GALLERY_OPENS_PER_IP_HOUR (60) per IP — and redirects to the app; nothing about the visitor is stored. /gallery/like/<slug> asks the visitor to sign in, then likes or unlikes (GALLERY_LIKES_PER_USER_HOUR, 30) and returns to ?back= when its origin is in GALLERY_FRAME_ANCESTORS. get_app shows likes and opens read-only. Migration 0029_gallery_likes.
  • After a dashboard duplicate, the copy's Overview says what happened to the module settings (NSO-340): applied, waiting for confirmation (with a link to the module page) or not copied, with the reason and the next step.

Changed

  • Duplicates: the hourly cap holds under parallel requests, and from must be this server's (NSO-340). DUPLICATES_PER_USER_HOUR is checked and recorded in the copy's create transaction under a per-person lock, across workspaces and dashboard/MCP; a copy counts once its app exists. duplicate_app takes a slug or an address of this server (app host, verified custom domain, /duplicate/<slug>); another server's address is invalid_params instead of copying a local app with the same slug.
  • The workspace app search ignores accents and case like the public gallery (one shared helper, @drobek/apps/search): "podzimni" finds "Podzimní obloha"; % and _ match literally (NSO-371).
  • Filtered dashboard lists offer "Clear filters" with the count after the reset (apps, Forms, Activity, data records, end users); the search and filter fields reset with it and follow back/forward, so the next search does not bring a cleared filter back (NSO-371).
  • The Forms tab tells its empty states apart: no forms yet (with a copyable prompt for the coding agent naming the workspace and app; viewers are told to ask an editor), no submissions yet, no match for the filters, and a loading error (NSO-371).
  • Activity reads as sentences (NSO-371). Each row of a workspace's Activity has a readable summary ("Published version 3 (replacing version 2)"), links to the app, version, module, upstream, domain or member it is about while they exist (a deleted one is plain text with a note) and the stored record under "Technical details" with credential-like values redacted. A From/To (UTC days) range joins the filters; the CSV export applies the same filters and gains a summary column. Stored audit rows are unchanged.
  • Moderation asks before it acts (NSO-371). In /admin/abuse every reported, taken-down and gallery app links to its dashboard overview and, when published, its public address; the workspace link opens its apps. Take down (also from /admin/publishing) and Block publishing open a confirm panel naming the app or workspace, the reason and the effect on its addresses and people; only the panel's button acts, a submit without it is refused, and a repeated submit changes nothing more (a same-reason takedown is now a no-op: no second audit row or e-mail).
  • Admin pages tidied (NSO-371).
    • Publishing: the states are tabs, and a workspace slug search sits under them. Each workspace groups its live apps with the takedown form.
    • Moderation queue: the takedown reason, Take down and Mark resolved are labelled and on one row.
    • Workspaces: a super-admin gets cards linking to Publishing and the Moderation queue, and can filter the list of all workspaces. The create-team form fits on one row.
    • App Files: the tree and the viewer stack on a phone instead of overflowing the page.
    • Empty lists on these pages say what would appear there and what to do next.
  • Module settings read as settings (NSO-371).
    • The config form labels each field with the schema's title and description (the built-in auth, email, forms and files modules now declare them). The config keys moved to a collapsed "Config keys for agents" table.
    • Each setting is tagged "Default" or "Saved for this app". A field touched by a change awaiting confirmation shows the new value next to the one in force.
    • * marks only text, number and select fields that need a value. A list says whether it can be left empty and how many items it takes.
    • A module's source, contract, slots, contributions and error codes are collapsed "Technical details" on the module page and the workspace Modules page.
    • The workspace Modules page has a search (?q=) and a jump list, whose field follows the URL ("Show all", back/forward), and collapses limits and technical facts. Limits read in human units (10 MB, 1 min) with the exact value underneath.
  • Workspace orientation (NSO-371). The workspace header shows the slug next to the name, the owner of a personal workspace you are not a member of, and where your access comes from: your membership role, or "Superadmin access — not a member". A "Switch workspace" menu reaches your account and your own workspaces; the all-workspaces list names each personal workspace's owner and your access. Authorization is unchanged.
  • Connecting an agent from /me (NSO-371). The MCP URL, the Claude Code command and every client snippet have a Copy button that says whether the copy worked (and selects the text when the browser refuses). A picker shows the agent guide's steps for Claude Code, Claude (web and desktop), Cursor and Codex; the page names the workspace the agent uses by default, lists your workspaces and, while that workspace is empty, offers a first prompt to paste. /me/connections explains that it lists approved OAuth clients and points to API keys, the other way in.
  • App pages on a phone (NSO-371). The app, workspace and publishing tabs are one row that scrolls sideways, with the current tab scrolled into view; long app addresses in the header end in "…"; the version history shows one card per version with its actions below 640 px; long names, file paths and notes wrap instead of widening the page (a long code line scrolls inside the file viewer).
  • Gallery and Settings point to each other (NSO-371). Settings shows the app's gallery state with a link to the Gallery section on Overview, and the Gallery section links to Settings for visibility and embedding. The Embedding copy says what the list does not control: the dashboard's app-list preview and, while the app is shown in the gallery, the gallery website's preview of the production address.

Fixed

  • Every release tag gets its GitHub Release: CI's new release job (tags, after the image is promoted) creates it from the tag's CHANGELOG section with the image name, Latest or pre-release. The releases v0.2.1–v0.4.0 were missing and have been added by hand.
  • The e2e stacks allow 300 sign-in codes per IP per 15 minutes (dev and image flow; production defaults unchanged): the gallery duplicate and like specs pushed the suite over the old test limit of 100.

Docs

  • Using modules in an app (NSO-371). docs/MODULES.md opens with the app author's steps — available modules, per-app configuration in the dashboard or with configure_module, confirming risky changes, secrets, the SDK — and names the operator's DROBEK_MODULES section "Enabling modules on your server (operators)". The Compatibility section notes that a sign-in provider must return issuer since v0.3.0.
  • Module directory and submission form (NSO-340). The published modules are also listed at www.drobek.app/modules; authors submit theirs through the GitHub issue form module-submission (package, repository, contract, license and the module rules) instead of a pull request to docs/MODULES.md.

v0.4.0

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.4.0

Added

  • Proxy upstreams over MCP (NSO-372). list_upstreams, register_upstream and remove_upstream (workspace admins; scopes read / write / write) do what the workspace → Upstreams page does, audited as the agent. An upstream with auth_type: "none" registers at once; bearer / header answer registered: false with secret_url, the Upstreams page with every field filled in through query parameters, where the user pastes the key — a secret is never an MCP argument. remove_upstream needs user_confirmed: true and names the apps that call it.
  • The public gallery API names each app's configured modules (#14): modules, the sorted names with a saved configuration — never config values, pending proposals or owners. It says a module is configured, not that it is used.

Changed

  • configure_module('proxy') refuses an unregistered upstream (NSO-372) with invalid_params, details.reason: upstream_not_registered; unassigning such a name still works. The pending confirmation says "with its secret" only for an upstream that has one.
  • A form submission says whether its e-mail went out (NSO-370). POST /__drobek/v1/forms/:form answers { ok, id, notified } and drobek.forms.submit / <Form onSuccess> pass notified on. It is false when nobody is to be notified, a mail limit or the pause refused the message, or the transport failed; the submission is stored either way.
  • Clearer upstream card and delete-app panel (NSO-371). On an app's proxy module page the rate-limit input has its own line and Save and Unassign share one row; an upstream that is not registered yet points to the Upstreams page. Delete app names the app and its identifier, lists what deleting does and labels the confirmation field.

Fixed

  • Gallery search ignores accents (#14): podzimni finds Podzimní obloha, in both pagination modes, with literal %, _ and \ still matched as text; built-in PostgreSQL normalization, no migration.

Docs

  • The README starts with what drobek is and how to try it (#15, NSO-340): drobek.app versus running the same core yourself, what the agent plugins and the backend modules do, three public example apps and a first-app walkthrough; the self-host quickstart stays identical to docs/SELF-HOSTING.md.

v0.3.4

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.3.4

Changed

  • Sign-in and account pages point to the next step (NSO-366). /login says that a new email gets an account and what drobek does, with a link to the docs (<DOCS_URL>/overview, or the README when DOCS_URL is unset). /me adds Start building: this server's MCP address, the Claude Code claude mcp add line and a link to the agent guide.
  • CI runs the lint, typecheck and unit job, the external module check and the image e2e in parallel instead of one after another. The e2e job pushes the tested image as ghcr.io/freema/drobek:<sha> on main and tags; a new job tags that image edge (main) or vX.Y.Z, latest and previous (tags) once all three pass — no rebuild. The npm job runs after it.
  • The external module check pins drobek-module-counter at the commit that installs the published @freema/drobek-modules / @freema/drobek-sdk 0.3.3.

Fixed

Removed

  • Dead code (#13): the test-only canReadActivity and activityCsvLines helpers (the Activity route and the CSV export already use the live checks and serializers). The auth, module and app-host cookie readers share readCookieValue from @drobek/core; parsing is unchanged.

v0.3.3

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.3.3

Added

  • Custom domains over MCP (NSO-366) — everything the dashboard's Domains tab does, through the same @drobek/domains operations (validation, DOMAINS_MAX_PER_APP from the workspace's limits, DNS verification, audit rows as the agent):
    • list_domains({ app_id }) (read, viewer+): per domain host, status (pending / verified), primary, the two DNS records (CNAME <host> → <slug>.<APPS_DOMAIN>, TXT _drobek.<host> = drobek-verify=<token>), verified_at, last_check_at, last_error, certificate; plus cname_target and max_per_app.
    • add_domain({ app_id, host }) (write, editor+): returns the records to create.
    • verify_domain({ app_id, host }) (write, editor+): on failure domain_not_verified with cname / txt = ok / missing / wrong and the expected records (the message says DNS can take up to 48 hours), or dns_unavailable when a lookup failed (nothing changes).
    • set_primary_domain({ app_id, host | null, user_confirmed }) (publish, editor+) and remove_domain({ app_id, host, user_confirmed }) (write, editor+): setting or clearing the primary domain and removing a verified domain need user_confirmed: true — the user's explicit yes (else user_confirmation_required); removing a pending domain does not.
    • A taken-down app refuses adding, verifying and a primary domain; removing stays possible. get_app adds domains (host, status, primary).
    • New error codes invalid_hostname, hostname_not_allowed, domain_already_added, domain_taken, domain_not_verified, dns_unavailable. The manifest, the briefing (a custom-domain flow), skills/drobek, skills/start and docs/AGENT.md describe them.
  • DOCS_URL — the base of a website with the drobek docs (e.g. https://www.drobek.app/docs, each page at <DOCS_URL>/<slug> with a Markdown twin <DOCS_URL>/<slug>.md). When set, /llms.txt links the agent guide, the modules, self-hosting and security docs as .md pages there, /llms-full.txt the agent guide's .md, and /build-with-your-agent and the landing page <DOCS_URL>/agent; unset, they link the Markdown files on GitHub. /llms.txt now lists those docs, and /llms-full.txt and /build-with-your-agent link the agent guide. A value that is not an http(s) URL (or has a query or fragment) stops the server at start.

Changed

  • npm packages are published under the maintainer's npm scope (NSO-366): @freema/drobek-modules, @freema/drobek-sdk and create-drobek-module (unscoped; npm create drobek-module@latest is unchanged) — there is no @drobek npm organisation. Module code keeps importing @drobek/modules / @drobek/sdk, installed through an npm alias ("@drobek/modules": "npm:@freema/drobek-modules@^X.Y.Z", which create-drobek-module writes), and keeps the peer "@drobek/modules": ">=X.Y.Z" the server's installer checks; @freema/drobek-modules depends on @drobek/sdk the same way. The workspace package names are unchanged.

v0.3.2

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.3.2

Changed

  • Dashboard copy (#12) — the Users tab tells an empty list, an email search with no match and a failed load apart (a failed load shows no false user count); signing everyone out points to the app's enabled sign-in methods, not only a new code; the app header, history and logs say build (identifiers unchanged); settings explain password protection, embedding and deletion in plain terms, and the delete asks for the app's identifier by name.

v0.3.1

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.3.1

Changed

  • Who may publish is per workspace, in both modes (NSO-366). A super-admin sets each workspace to default (the PUBLISH_APPROVAL mode decides), allowed (may publish in both modes — what v0.3.0 called approved; approved workspaces stay allowed) or blocked (may not publish in either mode); setting one clears the other. For one publish: a super-admin publisher is always allowed, a blocked workspace is refused, an allowed one or one with a super-admin member may publish, otherwise open allows and approval refuses as before. PUBLISH_APPROVAL still defaults to open, so a server lets everyone publish and the operator turns a workspace off when needed.
  • set_publish_approval is renamed set_workspace_publishing (it shipped only in the v0.3.0 tag): set_workspace_publishing({ workspace, publishing: "default" | "allowed" | "blocked", user_confirmed }), still super-admin only, publish scope, user_confirmed: true after the super-admin's explicit yes; returns { workspace, publishing, mode, can_publish_now, changed }. list_apps (also all_workspaces) and get_app add the workspace's publishing next to can_publish / publish_contact.
  • /admin/publishing lists every workspace with its state, filters for waiting requests, default, allowed and blocked, and ?workspace=<slug> for one. open mode puts Block publishing / Unblock first; approval mode Approve / Revoke approval / Block publishing. Each workspace's live apps are listed with the moderation queue's takedown form.
  • Abuse reports are e-mailed to every super-admin and to OPERATOR_EMAIL (each address once, case-insensitive).

Added

  • Blocking a workspace's publishing: a publish from it answers the new error publish_blocked ("Publishing from this workspace was turned off by the operator of this server (). Previews, versions and everything else keep working; live apps keep serving unless taken down.", MCP field contact; dashboard 403) — no approval request is sent. The dashboard shows "Publishing from this workspace was turned off by the operator ()." and disables the Publish buttons. Blocking does not unpublish anything (the takedown does). Blocking and unblocking e-mail the workspace's editors and admins what happened and whom to contact. Audit: workspace.publish_block, workspace.publish_unblock (with from / to). Migration 0027 adds workspaces.publish_blocked_at / publish_blocked_by.
  • Publish notifications (PUBLISH_NOTIFY=off|first|every, default off): first e-mails the operator (OPERATOR_EMAIL, else every super-admin) about the first publish of each app, every about every publish, at most one e-mail per app per hour. The e-mail names the app, its live URL and custom domains, the workspace, the publisher and whether it came from the dashboard or MCP, the version and whether it was the first publish, a republish or a rollback, with links to the app, its dashboard page and /admin/publishing?workspace=<slug> (takedown, block). A super-admin's own publishes are not e-mailed; the mail never delays or fails the publish. An invalid value stops the server at start.

v0.3.0

Choose a tag to compare

@freema freema released this 27 Sep 17:53

Image: ghcr.io/freema/drobek:v0.3.0

Added

  • Publish approval (PUBLISH_APPROVAL=open|approval, OPERATOR_EMAIL). Sign-up stays open: anyone can create workspaces and build and preview apps. With approval a workspace may publish only after a super-admin approved it (or when a super-admin is its member); open, the default, changes nothing. An invalid value, approval without SUPERADMIN_EMAIL or an OPERATOR_EMAIL that is not one address stops the server at start.
    • The gate sits in publish() itself, so the MCP publish, the dashboard Publish button and the production rollback all answer publish_not_approved ("Publishing on this server needs approval from … An approval request was sent to …", MCP field contact; dashboard 403). Previews, versions, restore, data, secrets and domains are never gated; apps already live keep serving after a revoke.
    • The first blocked publish, or the owner's Request approval button, e-mails the operator (OPERATOR_EMAIL, else every super-admin): the workspace, the requester's e-mail, the app and a link to /admin/publishing — at most once per workspace per 24 hours until someone decides.
    • /admin/publishing (super-admins): waiting requests, unapproved and approved workspaces, Approve / Revoke. The app pages and the workspace's apps list show "Publishing on this server needs approval from " with the Request approval button, and the Publish buttons say why they are disabled.
    • MCP: list_apps (also all_workspaces) and get_app return can_publish (+ publish_contact); the new super-admin-only tool set_publish_approval({ workspace, approved, user_confirmed }) (publish scope, registered only for a super-admin's grant) approves or revokes with the super-admin's explicit yes. New error code publish_not_approved.
    • Audit: workspace.publish_approval_request, workspace.publish_approve, workspace.publish_revoke. Migration 0026 adds the approval columns to workspaces and approves every workspace that already has a published app.

Fixed

  • E-mail sender name: a bare EMAIL_FROM address is sent as drobek <address>, so inboxes show drobek instead of the address's local part (no-reply). Name <address> still sets any other name.

Security

  • Sign-in provider identities are scoped to their issuer (NSO-360) — a provider account was found by (provider, subject) alone, so after the owner pointed a provider at another issuer (or the operator changed its env fallback) a different person with the same subject there signed in as the existing user, with its data. A person is now (provider, issuer, subject) in the new table mod_auth_identities: the same subject from another issuer is a new user, and an address held by an account linked to another identity is refused (account_linked, a 409 page) instead of being re-linked. providers.<id>.relinkByEmail (owner-confirmed, off by default) moves such accounts to the new issuer by their verified address for an issuer migration, audited auth.identity_relinked. The provider's connection — its identity fields and the operator's non-secret AUTH_<ID>_* variables — is bound into the sign-in state, the handoff and the session: a change refuses sign-ins in flight ("Start again", sign_in_denied { reason: settings_changed }) and signs that provider's sessions out.
    • Provider authors: callback() must return issuer (the verified OIDC iss / SAML Issuer) — an identity without it is a provider_error. A provider's configSchema may not declare relinkByEmail. endUsers.current gets contributions; a session record may carry connection.
    • Upgrade: auth migration 0002 creates mod_auth_identities, moves every linked mod_auth_users.subject there with an unknown issuer and drops the column. Such an identity is claimed once by the same provider + subject asserting the user's own address; any other address is refused. Provider sessions from before this release end (sign in again); e-mail sessions stay. Provider sign-ins in flight during the upgrade answer "Start again".
  • A module switched off for a workspace contributes nothing there (NSO-360) — an opt-in module that was off for a workspace answered module_not_enabled on its own routes, but the modules that were on still received its slot contributions: a slot host's route called them, its sign-in provider was listed and could begin, call back and complete a sign-in, its auth.signedIn observer was told, and its sessions stayed valid. Contributions now follow the workspace switch: a route's ctx.contributions, the onAppCreate / onPublish services, endUsers.current and the end-user callback (which resolves the app's workspace from its signed state before it picks the provider; EndUserCallbackApp.contributions) see only the modules on for the app's workspace; a sign-in in flight when the module is switched off cannot finish, and its sessions end on the next request. onAppDelete still gets every contribution (cleanup); compose still sees all of them (one config schema per server).
  • The sign-in flow cookie cannot be planted by another app (NSO-360) — the provider flow's cookie was __Secure-drobek_eu_flow, which a page on any other app host under APPS_DOMAIN can set with Domain=<APPS_DOMAIN>; knowing its own flow token, that app could bind a victim's browser to the attacker's sign-in (login CSRF). It is now __Host-drobek_eu_flow (Secure, Path=/, no Domain), which only the app host itself can set; complete reads no other name, so a sign-in begun before the upgrade answers "Start again" (its state or handoff record is refused anyway) and the old cookie expires within 10 minutes.