diff --git a/.github/workflows/debug-registry-auth.yml b/.github/workflows/debug-registry-auth.yml deleted file mode 100644 index aa74233..0000000 --- a/.github/workflows/debug-registry-auth.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Debug registry auth - -# TEMPORARY — delete after the registry publish auth is sorted. Separates the release's two auth -# domains so each can be tested alone, printing only shapes and HTTP status codes, never values: -# - atproto: are ATPROTO_HANDLE / ATPROTO_APP_PASSWORD valid credentials for the publisher's PDS? -# - edge: which edge environments gate /registry/upload, which is where the release currently 401s -# (the upload authenticates with a hub-identity verifiable presentation, not the ATProto secrets). - -on: - workflow_dispatch: - -jobs: - atproto: - runs-on: ubuntu-latest - env: - ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} - ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} - steps: - - name: Secret shape (lengths and whitespace only) - run: | - node -e ' - const shape = (name) => { - const value = process.env[name] ?? ""; - console.log(name, JSON.stringify({ - set: value.length > 0, - length: value.length, - trimmed: value === value.trim(), - startsWithAt: value.startsWith("@"), - })); - }; - shape("ATPROTO_HANDLE"); - shape("ATPROTO_APP_PASSWORD"); - ' - - - name: Resolve the handle (public API) - id: resolve - run: | - handle=$(node -e 'process.stdout.write(encodeURIComponent((process.env.ATPROTO_HANDLE ?? "").trim()))') - status=$(curl -s -o /tmp/resolve.json -w '%{http_code}' "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${handle}") - echo "resolveHandle: HTTP ${status} (200 = handle exists)" - did=$(node -e 'try { process.stdout.write(JSON.parse(require("fs").readFileSync("/tmp/resolve.json","utf8")).did ?? "") } catch {}') - echo "did: ${did:-}" - echo "did=${did}" >> "$GITHUB_OUTPUT" - - - name: Resolve the PDS (plc.directory) - id: pds - run: | - did='${{ steps.resolve.outputs.did }}' - pds="" - if [ -n "${did}" ]; then - curl -s "https://plc.directory/${did}" -o /tmp/plc.json || true - pds=$(node -e ' - try { - const doc = JSON.parse(require("fs").readFileSync("/tmp/plc.json","utf8")); - const service = (doc.service ?? []).find((entry) => entry.type === "AtprotoPersonalDataServer"); - process.stdout.write(service?.serviceEndpoint ?? ""); - } catch {} - ') - fi - echo "pds: ${pds:-}" - echo "pds=${pds:-https://bsky.social}" >> "$GITHUB_OUTPUT" - - - name: createSession (status only — 200 means the secrets are valid) - run: | - body=$(node -e 'process.stdout.write(JSON.stringify({ - identifier: (process.env.ATPROTO_HANDLE ?? "").trim(), - password: (process.env.ATPROTO_APP_PASSWORD ?? "").trim(), - }))') - status=$(curl -s -o /tmp/session.json -w '%{http_code}' -X POST \ - -H 'content-type: application/json' --data "${body}" \ - '${{ steps.pds.outputs.pds }}/xrpc/com.atproto.server.createSession') - echo "createSession: HTTP ${status}" - node -e ' - try { - const response = JSON.parse(require("fs").readFileSync("/tmp/session.json","utf8")); - console.log("error field:", response.error ?? "(none)"); - } catch {} - ' - - edge: - runs-on: ubuntu-latest - env: - DX_HUB_API_KEY: ${{ secrets.DX_HUB_API_KEY }} - steps: - - name: Probe /registry/plugins (read) and /registry/upload (gated) per environment - run: | - for base in https://edge.dxos.workers.dev https://main.dxos.network https://dxos.network; do - read_status=$(curl -s -o /dev/null -w '%{http_code}' "${base}/registry/plugins" || echo 'ERR') - upload_status=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'content-type: application/json' --data '{}' "${base}/registry/upload" || echo 'ERR') - keyed_status='' - if [ -n "${DX_HUB_API_KEY}" ]; then - keyed_status=$(curl -s -o /dev/null -w '%{http_code}' -X POST -H 'content-type: application/json' -H "X-Admin-Key: ${DX_HUB_API_KEY}" --data '{}' "${base}/registry/upload" || echo 'ERR') - fi - echo "${base} GET /registry/plugins: ${read_status} POST /registry/upload unauthenticated: ${upload_status} with X-Admin-Key: ${keyed_status}" - done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a569fc4..1621d4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,10 +107,11 @@ jobs: env: ALL: ${{ inputs.registry_only }} # Pinned to a pkg.pr.new preview: npm's @dxos/cli@0.10.0 cannot read a dx.config.ts from a - # plugin directory (fixed in dxos/dxos#12514). Override with the DX_CLI_PACKAGE repo - # variable, and drop the default once a working CLI is on npm. Installing the single - # linux-x64 package rather than the launcher avoids fetching all five platforms. - DX_CLI_PACKAGE: ${{ inputs.cli_package || vars.DX_CLI_PACKAGE || 'https://pkg.pr.new/dxos/dxos/@dxos/cli-linux-x64@ba08e65' }} + # plugin directory (fixed in dxos/dxos#12514) and lacks DX_HUB_API_KEY upload auth + # (dxos/dxos#12528). Override with the DX_CLI_PACKAGE repo variable, and drop the default + # once a working CLI is on npm. Installing the single linux-x64 package rather than the + # launcher avoids fetching all five platforms. + DX_CLI_PACKAGE: ${{ inputs.cli_package || vars.DX_CLI_PACKAGE || 'https://pkg.pr.new/dxos/dxos/@dxos/cli-linux-x64@1edc570' }} PUBLISHED: ${{ steps.changesets.outputs.publishedPackages }} ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} diff --git a/.github/workflows/setup-publisher.yml b/.github/workflows/setup-publisher.yml new file mode 100644 index 0000000..448ddfa --- /dev/null +++ b/.github/workflows/setup-publisher.yml @@ -0,0 +1,82 @@ +name: Setup publisher (one-time) + +# NOT part of the release flow — releases never need this. This is a one-time bootstrap for the +# ATProto account the registry publishes from: run it once when the publisher account changes +# (i.e. after updating the ATPROTO_HANDLE / ATPROTO_APP_PASSWORD repository secrets), then leave +# it alone. Re-running is harmless (records are upserts), just pointless. +# +# It writes two records on the publisher's PDS: +# - `publisher.profile` (rkey `self`) — the human/org metadata shown alongside published plugins. +# - `publisher.verification` (rkey = publisher DID, optional) — the attestation the registry +# indexer requires before it lists a publisher's plugins. The indexer only honors verifications +# authored by the configured curator (`REGISTRY_CURATOR_DID` in dxos/edge); this self-verification +# only affects discovery in environments where the publisher account IS the curator (currently +# production). In every other environment the curator must run `dx registry verify` with their +# own credentials instead. + +on: + workflow_dispatch: + inputs: + display_name: + description: 'Publisher display name (the human/org name shown in UIs).' + required: true + bio: + description: 'Short publisher bio.' + required: false + homepage_url: + description: 'Publisher homepage URL.' + required: false + contact: + description: 'Contact (email, handle, etc.).' + required: false + self_verify: + description: 'Also write a self-authored publisher.verification (only meaningful where this account is the curator).' + type: boolean + default: true + cli_package: + description: 'npm spec for the dx CLI, overriding the pinned default — e.g. a pkg.pr.new URL for a branch build.' + required: false + +jobs: + setup-publisher: + runs-on: ubuntu-latest + env: + # Same pin as release.yml — see the comment there for why and when to drop it. + DX_CLI_PACKAGE: ${{ inputs.cli_package || vars.DX_CLI_PACKAGE || 'https://pkg.pr.new/dxos/dxos/@dxos/cli-linux-x64@1edc570' }} + ATPROTO_HANDLE: ${{ secrets.ATPROTO_HANDLE }} + ATPROTO_APP_PASSWORD: ${{ secrets.ATPROTO_APP_PASSWORD }} + steps: + # No checkout: the CLI is self-contained and every input arrives via dispatch, so the job + # needs nothing from the repo. The CLI requires node 24 (engines), which the runner image + # is not guaranteed to default to. + - uses: actions/setup-node@v4 + with: + node-version: 24 + + - name: Install the dx CLI + run: | + # npm's global bin is not necessarily on PATH; npm reports the prefix it installs into. + export PATH="$(npm prefix -g)/bin:${PATH}" + npm install -g "${DX_CLI_PACKAGE}" + dx --version + echo "$(npm prefix -g)/bin" >> "$GITHUB_PATH" + + - name: Publish the publisher profile + env: + DISPLAY_NAME: ${{ inputs.display_name }} + BIO: ${{ inputs.bio }} + HOMEPAGE_URL: ${{ inputs.homepage_url }} + CONTACT: ${{ inputs.contact }} + run: | + args=(--display-name "${DISPLAY_NAME}") + [ -n "${BIO}" ] && args+=(--bio "${BIO}") + [ -n "${HOMEPAGE_URL}" ] && args+=(--homepage-url "${HOMEPAGE_URL}") + [ -n "${CONTACT}" ] && args+=(--contact "${CONTACT}") + dx registry publish-publisher "${args[@]}" + + - name: Self-verify the publisher + if: ${{ inputs.self_verify }} + env: + DISPLAY_NAME: ${{ inputs.display_name }} + run: | + dx registry verify --subject "${ATPROTO_HANDLE}" --display-name "${DISPLAY_NAME}"