Releases: freema/drobek
Releases · freema/drobek
Release list
v0.5.3
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, orPUBLIC_ORIGINfor 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) whenPUBLIC_ORIGINis unset, instead ofhttp://localhost:3041.
v0.5.2
Image: ghcr.io/freema/drobek:v0.5.2
Changed
- All workspaces shows each workspace's apps (NSO-373): a super-admin's list on
/workspacesreads "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.mdsays that an npm package with thedrobek-modulekeyword 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
Image: ghcr.io/freema/drobek:v0.5.1
Added
/api/versionnames the build and the process start (NSO-340). Next to the unchangedsha,versionandmodulesit answersname("drobek"),commitTime(the committer time ofshain UTC ISO, baked into the image as theCOMMIT_TIMEbuild arg by CI,task buildandtask e2e:image; the same sources rebuild to the same value;nullwhen unknown) andstartedAt(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_listedrecordspreviousAllowDuplicatenext topreviousDescription. 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
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_duplicatetoset_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 toolduplicate_app({ from, workspace?, name? })(scopewrite, 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_frominget_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. Auditedapp.duplicate/app.duplicated;DUPLICATES_PER_USER_HOUR(default 10) caps copies per person. The public gallery API addsduplicable,duplicateUrlandduplicatesto each item. Migration0028_gallery_duplicate. - Likes and opens in the public gallery (NSO-340). Each
GET /api/public/galleryitem carrieslikes(signed-in accounts that like the app, one per account),opens(visits through the gallery in the last 30 days),openUrlandlikeUrl;?sort=popularorders by 5 × likes + opens (page mode)./gallery/open/<slug>counts a visit per app and day — no prefetch, noHEAD, at mostGALLERY_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 inGALLERY_FRAME_ANCESTORS.get_appshowslikesandopensread-only. Migration0029_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
frommust be this server's (NSO-340).DUPLICATES_PER_USER_HOURis 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_apptakes a slug or an address of this server (app host, verified custom domain,/duplicate/<slug>); another server's address isinvalid_paramsinstead 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
summarycolumn. Stored audit rows are unchanged. - Moderation asks before it acts (NSO-371). In
/admin/abuseevery 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/connectionsexplains 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
releasejob (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.mdopens with the app author's steps — available modules, per-app configuration in the dashboard or withconfigure_module, confirming risky changes, secrets, the SDK — and names the operator'sDROBEK_MODULESsection "Enabling modules on your server (operators)". The Compatibility section notes that a sign-in provider must returnissuersince 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 todocs/MODULES.md.
v0.4.0
Image: ghcr.io/freema/drobek:v0.4.0
Added
- Proxy upstreams over MCP (NSO-372).
list_upstreams,register_upstreamandremove_upstream(workspace admins; scopesread/write/write) do what the workspace → Upstreams page does, audited as the agent. An upstream withauth_type: "none"registers at once;bearer/headeranswerregistered: falsewithsecret_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_upstreamneedsuser_confirmed: trueand 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) withinvalid_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/:formanswers{ ok, id, notified }anddrobek.forms.submit/<Form onSuccess>passnotifiedon. It isfalsewhen 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):
podzimnifindsPodzimní 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
Image: ghcr.io/freema/drobek:v0.3.4
Changed
- Sign-in and account pages point to the next step (NSO-366).
/loginsays that a new email gets an account and what drobek does, with a link to the docs (<DOCS_URL>/overview, or the README whenDOCS_URLis unset)./meadds Start building: this server's MCP address, the Claude Codeclaude mcp addline 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>onmainand tags; a new job tags that imageedge(main) orvX.Y.Z,latestandprevious(tags) once all three pass — no rebuild. The npm job runs after it. - The external module check pins
drobek-module-counterat the commit that installs the published@freema/drobek-modules/@freema/drobek-sdk0.3.3.
Fixed
qs6.16 (#13) — an override replaces Express 4'sqs6.15.3 (advisories GHSA-x5fp-wj9c-mxmx and GHSA-4mjr-xmp4-gh2g); Express stays on 4.
Removed
- Dead code (#13): the test-only
canReadActivityandactivityCsvLineshelpers (the Activity route and the CSV export already use the live checks and serializers). The auth, module and app-host cookie readers sharereadCookieValuefrom@drobek/core; parsing is unchanged.
v0.3.3
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/domainsoperations (validation,DOMAINS_MAX_PER_APPfrom the workspace's limits, DNS verification, audit rows as the agent):list_domains({ app_id })(read, viewer+): per domainhost,status(pending/verified),primary, the two DNSrecords(CNAME<host>→<slug>.<APPS_DOMAIN>, TXT_drobek.<host>=drobek-verify=<token>),verified_at,last_check_at,last_error,certificate; pluscname_targetandmax_per_app.add_domain({ app_id, host })(write, editor+): returns the records to create.verify_domain({ app_id, host })(write, editor+): on failuredomain_not_verifiedwithcname/txt=ok/missing/wrongand the expectedrecords(the message says DNS can take up to 48 hours), ordns_unavailablewhen a lookup failed (nothing changes).set_primary_domain({ app_id, host | null, user_confirmed })(publish, editor+) andremove_domain({ app_id, host, user_confirmed })(write, editor+): setting or clearing the primary domain and removing a verified domain needuser_confirmed: true— the user's explicit yes (elseuser_confirmation_required); removing a pending domain does not.- A taken-down app refuses adding, verifying and a primary domain; removing stays possible.
get_appaddsdomains(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/startand 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.txtlinks the agent guide, the modules, self-hosting and security docs as.mdpages there,/llms-full.txtthe agent guide's.md, and/build-with-your-agentand the landing page<DOCS_URL>/agent; unset, they link the Markdown files on GitHub./llms.txtnow lists those docs, and/llms-full.txtand/build-with-your-agentlink 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-sdkandcreate-drobek-module(unscoped;npm create drobek-module@latestis unchanged) — there is no@drobeknpm organisation. Module code keeps importing@drobek/modules/@drobek/sdk, installed through an npm alias ("@drobek/modules": "npm:@freema/drobek-modules@^X.Y.Z", whichcreate-drobek-modulewrites), and keeps the peer"@drobek/modules": ">=X.Y.Z"the server's installer checks;@freema/drobek-modulesdepends on@drobek/sdkthe same way. The workspace package names are unchanged.
v0.3.2
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
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(thePUBLISH_APPROVALmode decides),allowed(may publish in both modes — what v0.3.0 called approved; approved workspaces stay allowed) orblocked(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, otherwiseopenallows andapprovalrefuses as before.PUBLISH_APPROVALstill defaults toopen, so a server lets everyone publish and the operator turns a workspace off when needed. set_publish_approvalis renamedset_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: trueafter the super-admin's explicit yes; returns{ workspace, publishing, mode, can_publish_now, changed }.list_apps(alsoall_workspaces) andget_appadd the workspace'spublishingnext tocan_publish/publish_contact./admin/publishinglists every workspace with its state, filters for waiting requests, default, allowed and blocked, and?workspace=<slug>for one.openmode puts Block publishing / Unblock first;approvalmode 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 fieldcontact; 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(withfrom/to). Migration 0027 addsworkspaces.publish_blocked_at/publish_blocked_by. - Publish notifications (
PUBLISH_NOTIFY=off|first|every, defaultoff):firste-mails the operator (OPERATOR_EMAIL, else every super-admin) about the first publish of each app,everyabout 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
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. Withapprovala 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,approvalwithoutSUPERADMIN_EMAILor anOPERATOR_EMAILthat is not one address stops the server at start.- The gate sits in
publish()itself, so the MCPpublish, the dashboard Publish button and the production rollback all answerpublish_not_approved("Publishing on this server needs approval from … An approval request was sent to …", MCP fieldcontact; 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(alsoall_workspaces) andget_appreturncan_publish(+publish_contact); the new super-admin-only toolset_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 codepublish_not_approved. - Audit:
workspace.publish_approval_request,workspace.publish_approve,workspace.publish_revoke. Migration 0026 adds the approval columns toworkspacesand approves every workspace that already has a published app.
- The gate sits in
Fixed
- E-mail sender name: a bare
EMAIL_FROMaddress is sent asdrobek <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, auditedauth.identity_relinked. The provider's connection — its identity fields and the operator's non-secretAUTH_<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 returnissuer(the verified OIDCiss/ SAML Issuer) — an identity without it is aprovider_error. A provider's configSchema may not declarerelinkByEmail.endUsers.currentgetscontributions; a session record may carryconnection. - Upgrade: auth migration 0002 creates
mod_auth_identities, moves every linkedmod_auth_users.subjectthere 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".
- Provider authors:
- 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_enabledon 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, itsauth.signedInobserver was told, and its sessions stayed valid. Contributions now follow the workspace switch: a route'sctx.contributions, theonAppCreate/onPublishservices,endUsers.currentand 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.onAppDeletestill gets every contribution (cleanup);composestill 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 underAPPS_DOMAINcan set withDomain=<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=/, noDomain), which only the app host itself can set;completereads 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.