Skip to content

feat(server): deploy the API to Serverpod Cloud (re-land #115 on main) - #117

Merged
TOMOKI977 merged 14 commits into
mainfrom
chore/110-serverpod-4-upgrade
Oct 8, 2026
Merged

TOMOKI977 merged 14 commits into
mainfrom
chore/110-serverpod-4-upgrade

Conversation

@TOMOKI977

Copy link
Copy Markdown
Contributor

Refs #31

Summary

This PR re-lands #115 on main. #115 was merged into chore/110-serverpod-4-upgrade after #114 had already merged, because its base was never retargeted. Its code (Serverpod Cloud deploy, CORS gate, health version) therefore never reached main.

The branch is the same one: origin/main is merged in, and the conflicts are resolved by keeping the branch side, whose tree equals fc8b9e6. The squash of #114 left identical content on both sides, so nothing new is introduced. The diff against main is exactly the 17 files of #115 (+1011/-4).

The content is unchanged from #115, which @moises-cisneros already tested: analysis is clean, the unit tests pass, and the live API returns the health version and the 200/403 CORS responses.

What lands:

  • Health version: /health/check reports 1.0.0+<sha>. PULS3_GIT_SHA is set by the scloud pre-deploy script (tool/set_deploy_sha.dart).
  • CORS gate: PULS3_ALLOWED_ORIGINS sets the allowed origins, and loopback origins are allowed only in development.
  • Serverpod Cloud config: scloud.yaml is linked to puls3-hub-on-stellar, and .scloudignore excludes .env, passwords.yaml and the tests.
  • Docs: the "Deploy to Serverpod Cloud" section in puls3_server/README.md.
  • Pages: scripts/cloudflare-pages-build.sh passes PULS3_API_URL as a dart-define.
  • .env.example: PULS3_ALLOWED_ORIGINS and PULS3_GIT_SHA.

Acceptance criteria

Unchanged from #115:

  • The public app works end to end on testnet (GIF). Pending the Pages deploy and the demo flow.
  • health returns the deployed version.
  • A teammate redeploys by following the README.
  • No secret in the repo or the upload.
  • Both URLs are in the README.

Verification evidence

$ git diff --cached fc8b9e6        # after resolving the merge: empty
$ git diff --stat origin/main..HEAD
 17 files changed, 1011 insertions(+), 4 deletions(-)
$ cd puls3_server && dart analyze --fatal-infos
No issues found!
$ dart test test/unit
00:12 +325: All tests passed!
$ bash scripts/tests/env-inventory.test.sh
passed: 68, failed: 0
$ bash scripts/tests/cloudflare-pages-build.test.sh
passed 7, failed 0

Notes for reviewers

  • After this merges, the Serverpod Cloud database will be wiped and the server redeployed from main. The live DB carries the pre-rebase upgrade-4-0 migration name. It holds no real data, and the team agreed to the wipe.
  • The non-blocking notes from build(serverpod)!: upgrade to Serverpod 4.0.4 for Cloud deployment #114 and feat(server): deploy the API to Serverpod Cloud with health version and CORS gate #115 are tracked for a follow-up and are not part of this re-land:
    • the shell-form ENTRYPOINT
    • alpine:latest
    • a migration regression test
    • untracked files not marked -dirty
    • PULS3_GIT_SHA set before the upload
    • the OPTIONS header
  • delete_branch_on_merge is now enabled on the repo. When a parent PR merges, GitHub deletes its branch and retargets stacked PRs to main, which prevents this from happening again.

TOMOKI977 and others added 14 commits October 7, 2026 00:49
Copy serverpod_auth_idp_rate_limited_request_attempt rows through a
transaction-scoped temp table while the generator recreates the table to
rename nonce to key. Document the upgrade, migration and rollback steps.

Refs #110
…f the image

Serverpod 4 dependencies use Dart build hooks, so the image now uses
dart build cli and ships the emitted bundle. The new .dockerignore keeps
.env files and config/passwords.yaml out of the build context, which
previously baked local secrets into the runtime image.

Refs #110
Add a docker job, triggered by server, domain, lockfile and
.dockerignore changes, that builds the image with planted secret files
and asserts the bundle exists and passwords.yaml is excluded.

Refs #110
…itory

The Pages build command cloned Flutter 3.41.4 from the dashboard, so every
branch on Serverpod 4 (Dart ^3.12.2) failed version solving. The build now
runs scripts/cloudflare-pages-build.sh, which installs the branch's pinned
Flutter. A test keeps its version in sync with CI.

Refs #110
…-upgrade

Brings in the hire and hire_payment tables from #93. The generated protocol
files are regenerated with serverpod_cli 4.0.4 instead of resolved by hand.
The forced upgrade-4-0 migration is removed here because its snapshot predates
migration 20261006210458593; it is recreated in the next commit.
… the hire tables

The forced upgrade-4-0 migration was created before #93 added migration
20261006210458593 with the hire and hire_payment tables, so its snapshot
did not know about them. Recreate it with serverpod_cli 4.0.4 as
20261007214534790-upgrade-4-0 so it now sorts after 20261006210458593 and its
definition includes hire, hire_payment, chain_submission and the Serverpod 4
module tables. The migration does not create, drop or alter the hire tables.

The hand edit is re-applied: rows of
serverpod_auth_idp_rate_limited_request_attempt are copied to a temporary
table before Serverpod drops and recreates it, with nonce mapped to key, and
restored afterwards, so rate-limit history still survives the upgrade.

Refs #110
#93 reads it in HireService without listing it, which fails
scripts/tests/env-inventory.test.sh on main.

Refs #110
scripts/tests/*.test.sh were never run in CI, so main broke the env
inventory (PULS3_HIRE_JOB_DURATION_SECONDS) without a red check. The new
scripts job runs them all and gates the merge.

Refs #110
…nd CORS gate (#115)

* feat(server): report the deployed commit in the health version

health.check returns the package version, plus +<sha> when PULS3_GIT_SHA
is set, so a deployed server names the commit it runs. A unit test keeps
the version constant equal to pubspec.yaml, and an invalid value stops the
server at startup.

* feat(server): restrict browser callers to allowed origins

Serverpod 3.4.13 sends a static wildcard Access-Control-Allow-Origin and
answers preflights in its core middleware, before added middleware runs.
An origin gate on the API server now rejects requests whose Origin is not
loopback or listed in PULS3_ALLOWED_ORIGINS with 403 before any endpoint
runs, and echoes allowed origins with Vary: Origin. Requests without an
Origin header (curl, probes) pass through.

* chore(server): add Serverpod Cloud project config

scloud.yaml uses the format serverpod_cloud_cli 1.0.0 writes, with a
placeholder project id that scloud project link replaces, and no
pre-deploy scripts: the Flutter web app ships to Cloudflare Pages and the
generated code is committed. The root .scloudignore keeps passwords, run-mode
configs, tests, Docker files and web/app out of the upload; paths are
workspace-relative because scloud reads it from the workspace root.

* docs: add Serverpod Cloud deploy and redeploy steps

Document the first deploy (scloud install, login, project create and link,
variables, platform-managed passwords), the mapping of each secret in
docs/infra/secrets.md (#30) to Serverpod Cloud, the redeploy flow with the
health check, the PULS3_GIT_SHA version and the PULS3_ALLOWED_ORIGINS allow-list.
The tracker stays off in this deployment.

* fix(server): allow loopback origins only in development

Loopback origins (localhost, 127.0.0.1, [::1]) are now allowed only in the
development run mode; production, staging and test allow just the origins
listed in PULS3_ALLOWED_ORIGINS. A request with several Origin headers is
rejected. server.dart builds the gate through originGateFromEnvironment,
which is tested end to end over HTTP, and new cases cover default-port and
duplicate normalization, Vary merging, an existing
Access-Control-Allow-Origin and lookalike loopback hosts.

* feat(server): set PULS3_GIT_SHA in a Serverpod Cloud pre-deploy script

scloud 1.0.0 runs scripts.pre_deploy on the deploying machine, in the server
directory, through cmd /c on Windows and bash -c elsewhere, before it zips
the project, and stops the deploy on a non-zero exit. The new
tool/set_deploy_sha.dart sets PULS3_GIT_SHA to git rev-parse --short HEAD
(suffixed -dirty when tracked files changed) with scloud variable set.
scloud.yaml now runs it and explains the project id placeholder.

* docs(server): make the Serverpod Cloud first deploy one linear path

First deploy is now seven ordered steps without jumps: a pinned CLI install
(1.0.1 on Dart 3.12.2+, or 1.0.0 with serverpod_cloud_shared pinned on older
Dart), login, project create with an explicit plan, link, variables, deploy
with the PULS3_GIT_SHA pre-deploy script, and verification including CORS
checks (200 for the Pages origin, 403 for another). Adds a failure section
(build log, logs, deployment status, fix forward or redeploy a good commit)
that marks the Cloud behaviour the CLI cannot confirm, and documents that
loopback origins are allowed in development only.

* chore(server): recommend --from-file for Serverpod Cloud passwords

* docs(server): check that the deployed API rejects localhost origins

Loopback origins are allowed only in the development run mode, and Serverpod Cloud's run mode is not verified yet. A localhost-origin call that returns 200 after deploy reveals a non-production run mode.

* chore(server): link Serverpod Cloud project puls3-hub-on-stellar

Refs #31

* docs(server): record the Serverpod Cloud API URL and send a body in health checks

The Cloud load balancer answers 411 to a POST without Content-Length, so
the verify commands now send an empty JSON body.

Refs #31

* feat(flutter): point the Pages build at the deployed API through PULS3_API_URL

When the Pages project sets PULS3_API_URL, the build passes it as a
dart-define, so the web app calls the Serverpod Cloud server.

Refs #31

* docs(env): list PULS3_ALLOWED_ORIGINS and PULS3_GIT_SHA in .env.example

Refs #31
…-upgrade

# Conflicts:
#	.env.example
#	scripts/cloudflare-pages-build.sh
#	scripts/tests/cloudflare-pages-build.test.sh
@TOMOKI977 TOMOKI977 added area: infra CI/CD, deploy, testnet, secrets type: chore Maintenance and tooling P0 Blocks a deadline deliverable labels Oct 8, 2026
@TOMOKI977 TOMOKI977 self-assigned this Oct 8, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying puls3 with  Cloudflare Pages  Cloudflare Pages

Latest commit: 80ea9d0
Status: ✅  Deploy successful!
Preview URL: https://299339e0.puls3-4lw.pages.dev
Branch Preview URL: https://chore-110-serverpod-4-upgrad.puls3-4lw.pages.dev

View logs

@TOMOKI977

Copy link
Copy Markdown
Contributor Author

@moises-cisneros @fercodes @Pericena @XxHugheadxX this needs a quick review and approval, with priority: it is P0 and blocks the Serverpod Cloud redeploy (#31).

It is the same content as #115. Moises already tested it there, but it was merged into the parent branch instead of main, so the Cloud deploy code never landed. The diff against main is exactly the 17 files of #115, and all checks are green.

Moises, your non-blocking notes from #114 and #115 will go into a follow-up, so they don't hold this up.

Once it merges I'll wipe the Cloud DB and redeploy from main.

@XxHugheadxX XxHugheadxX left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and verified locally. Approving.

Re-land is exact

  • git diff fc8b9e6 pr117 is empty, so the tree equals #115.
  • The merge base is the current tip of main, and the diff against main is the 17 files of #115 (+1011/-4).
  • scripts/tests/cloudflare-pages-build.test.sh: 7 passed, 0 failed. scripts/tests/env-inventory.test.sh: 68 passed, 0 failed.

CORS gate checked against Serverpod 4.0.4 source

  • In 4.0.4, Server.injectIn still registers the core _headers middleware around user middleware, and _headers answers OPTIONS before originGate runs. The documented preflight behavior still holds.
  • The native SERVERPOD_ALLOWED_ORIGINS in 4.0.4 only guards the WebSocket handshake and credentialed CORS when authCookie is set. We don't use authCookie, so the custom gate is still the only thing rejecting cross-origin POSTs.

Non-blocking, for the follow-up list

  1. puls3_server/README.md ("Health version and allowed origins") and the originGate docstring still say "Serverpod 3.4.13". The behavior is the same in 4.0.4; only the version number in the text is stale.
  2. The Deploy table in the root README.md still shows https://<project-id>.api.serverpod.space/ ("set after the first deploy"). The real URL, https://puls3-hub-on-stellar.api.serverpod.space/, is already in the server README. Needed for the "Both URLs are in the README" criterion of #31.
  3. Minor: the gate's 403 response gets Access-Control-Allow-Origin: *, because core _headers fills absent headers with ??=. Harmless, since the endpoint never runs.

I couldn't run dart analyze / dart test locally, so I'm relying on the CI checks being green before merge. After merge: wipe the DB, redeploy from main, and run the three curl checks from step 7 (200 / 403 / 403).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: infra CI/CD, deploy, testnet, secrets P0 Blocks a deadline deliverable type: chore Maintenance and tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants