Skip to content

Commit 5e01850

Browse files
committed
feat: add multi-project cloud hosting with artifact publishing
1 parent cf4ef5d commit 5e01850

16 files changed

Lines changed: 280 additions & 16 deletions

File tree

apps/cloud/cloudflare/worker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface Env {
4242
OS_BASE_URL?: string;
4343
OS_TRUSTED_ORIGINS?: string;
4444
OS_COOKIE_DOMAIN?: string;
45+
OS_ROOT_DOMAIN?: string;
4546
GOOGLE_CLIENT_ID?: string;
4647
GOOGLE_CLIENT_SECRET?: string;
4748
GITHUB_CLIENT_ID?: string;
@@ -126,6 +127,7 @@ const FORWARDED_ENV_KEYS: readonly (keyof Env)[] = [
126127
'OS_BASE_URL',
127128
'OS_TRUSTED_ORIGINS',
128129
'OS_COOKIE_DOMAIN',
130+
'OS_ROOT_DOMAIN',
129131
'GOOGLE_CLIENT_ID',
130132
'GOOGLE_CLIENT_SECRET',
131133
'GITHUB_CLIENT_ID',
@@ -296,7 +298,7 @@ export class CloudContainer extends Container<Env> {
296298
// match the origin the browser hits, otherwise sign-up / OAuth
297299
// callbacks fail with "Invalid origin". Override per environment
298300
// via `wrangler secret put AUTH_BASE_URL` or [vars] in wrangler.toml.
299-
AUTH_BASE_URL: 'https://objectstack-cloud.objectstack.workers.dev',
301+
AUTH_BASE_URL: 'https://cloud.objectos.app',
300302
// Comma-separated extra origins to add to better-auth's trusted
301303
// list (custom domains, preview hosts, …). AUTH_BASE_URL is
302304
// already trusted automatically.

apps/cloud/scripts/setup-cloudflare-secrets.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@ APP_BASENAME="$(basename "$APP_DIR")"
3636
case "$APP_BASENAME" in
3737
objectos)
3838
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
39-
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN ) ;;
39+
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN ) ;;
4040
cloud)
4141
KEYS=( OS_DATABASE_URL OS_CONTROL_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
4242
OS_CONTROL_PG_POOL_MIN OS_CONTROL_PG_POOL_MAX
43-
TURSO_API_TOKEN TURSO_ORG_NAME OS_COOKIE_DOMAIN ) ;;
43+
TURSO_API_TOKEN TURSO_ORG_NAME
44+
OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN
45+
OS_STORAGE_ADAPTER OS_STORAGE_LOCAL_DIR OS_STORAGE_KEY_PREFIX
46+
OS_S3_BUCKET OS_S3_REGION OS_S3_ENDPOINT
47+
OS_S3_ACCESS_KEY_ID OS_S3_SECRET_ACCESS_KEY OS_S3_FORCE_PATH_STYLE ) ;;
4448
*)
4549
echo "✗ unknown app dir: $APP_BASENAME" >&2; exit 1 ;;
4650
esac

apps/cloud/wrangler.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ compatibility_flags = ["nodejs_compat"]
4545
# rebuild + re-push to ship a new version, then run `wrangler deploy`.
4646
[[containers]]
4747
class_name = "CloudContainer"
48-
image = "registry.cloudflare.com/2846eb40a60f4738e292b90dcd8cce10/objectstack-cloud:61dafc8e"
48+
image = "registry.cloudflare.com/2846eb40a60f4738e292b90dcd8cce10/objectstack-cloud:seed-fix-1"
4949
max_instances = 3
5050
instance_type = "standard-1"
5151

apps/console/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
"type": "module",
66
"description": "ObjectStack Console - opinionated, fork-ready runtime console built on @object-ui/app-shell with the full plugin set wired up.",
77
"license": "Apache-2.0",
8+
"files": [
9+
"dist"
10+
],
811
"scripts": {
912
"dev": "vite",
1013
"build": "tsc && vite build",

apps/objectos/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ COPY package.json pnpm-workspace.yaml turbo.json tsconfig.json tsup.config.ts ./
7474
COPY packages ./packages
7575
COPY examples ./examples
7676
COPY apps/objectos ./apps/objectos
77+
COPY apps/console ./apps/console
78+
COPY apps/account ./apps/account
7779

7880
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
7981
pnpm install --frozen-lockfile --prefer-offline --prod=false

apps/objectos/cloudflare/worker.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface Env {
3636
OS_BASE_URL?: string;
3737
OS_TRUSTED_ORIGINS?: string;
3838
OS_COOKIE_DOMAIN?: string;
39+
OS_ROOT_DOMAIN?: string;
3940
GOOGLE_CLIENT_ID?: string;
4041
GOOGLE_CLIENT_SECRET?: string;
4142
GITHUB_CLIENT_ID?: string;
@@ -94,7 +95,7 @@ const FORWARDED_ENV_KEYS: readonly (keyof Env)[] = [
9495
// auth
9596
'AUTH_SECRET', 'OS_AUTH_SECRET',
9697
'AUTH_BASE_URL', 'OS_BASE_URL',
97-
'OS_TRUSTED_ORIGINS', 'OS_COOKIE_DOMAIN',
98+
'OS_TRUSTED_ORIGINS', 'OS_COOKIE_DOMAIN', 'OS_ROOT_DOMAIN',
9899
'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET',
99100
'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET',
100101
// cloud client
@@ -132,13 +133,59 @@ export class ObjectOSContainer extends Container<Env> {
132133
enableInternet = true;
133134
requiredPorts = [3000];
134135

136+
/**
137+
* Cold start budget for the Node app to bind port 3000. The default
138+
* in `@cloudflare/containers` is 20s which is not enough for a fresh
139+
* boot that has to open a remote DB connection + run schema sync for
140+
* every registered `sys_*` object. See apps/cloud/cloudflare/worker.ts
141+
* for the full rationale — this is the same override.
142+
*/
143+
private readonly PORT_READY_TIMEOUT_MS = 120_000;
144+
145+
override async startAndWaitForPorts(
146+
portsOrArgs?: any,
147+
cancellationOptions?: any,
148+
startOptions?: any,
149+
): Promise<void> {
150+
const TIMEOUT = this.PORT_READY_TIMEOUT_MS;
151+
if (
152+
portsOrArgs !== null &&
153+
typeof portsOrArgs === 'object' &&
154+
!Array.isArray(portsOrArgs) &&
155+
('ports' in portsOrArgs || 'cancellationOptions' in portsOrArgs || 'startOptions' in portsOrArgs)
156+
) {
157+
const inner = { ...(portsOrArgs.cancellationOptions ?? {}) };
158+
delete inner.abort;
159+
const merged = {
160+
...portsOrArgs,
161+
cancellationOptions: {
162+
portReadyTimeoutMS: TIMEOUT,
163+
instanceGetTimeoutMS: TIMEOUT,
164+
...inner,
165+
},
166+
};
167+
return super.startAndWaitForPorts(merged);
168+
}
169+
const inner = { ...(cancellationOptions ?? {}) };
170+
delete inner.abort;
171+
const merged = {
172+
portReadyTimeoutMS: TIMEOUT,
173+
instanceGetTimeoutMS: TIMEOUT,
174+
...inner,
175+
};
176+
return super.startAndWaitForPorts(portsOrArgs, merged, startOptions);
177+
}
178+
135179
envVars: Record<string, string> = {
136180
NODE_ENV: 'production',
137181
PORT: '3000',
138182
HOST: '0.0.0.0',
139183
OS_KERNEL_CACHE_SIZE: '50',
140184
OS_KERNEL_TTL_MS: '1800000',
141185
OS_ENV_CACHE_TTL_MS: '300000',
186+
// Schema sync against a cold remote DB easily exceeds Workers'
187+
// ~30s inbound budget; run migrations out of band before deploy.
188+
OS_SKIP_SCHEMA_SYNC: '1',
142189
};
143190

144191
constructor(state: DurableObjectState, env: Env) {

apps/objectos/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"@example/app-todo": "workspace:*",
2828
"@hono/node-server": "^2.0.2",
2929
"@libsql/client": "^0.17.3",
30+
"@objectstack/account": "workspace:*",
31+
"@objectstack/console": "workspace:*",
3032
"@objectstack/driver-memory": "workspace:*",
3133
"@objectstack/driver-mongodb": "workspace:*",
3234
"@objectstack/driver-sql": "workspace:*",

apps/objectos/scripts/setup-cloudflare-secrets.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ APP_BASENAME="$(basename "$APP_DIR")"
3636
case "$APP_BASENAME" in
3737
objectos)
3838
KEYS=( OS_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
39-
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN ) ;;
39+
OS_CLOUD_URL OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN ) ;;
4040
cloud)
4141
KEYS=( OS_DATABASE_URL OS_CONTROL_DATABASE_URL OS_DATABASE_AUTH_TOKEN AUTH_SECRET
4242
OS_CONTROL_PG_POOL_MIN OS_CONTROL_PG_POOL_MAX
43-
TURSO_API_TOKEN TURSO_ORG_NAME OS_COOKIE_DOMAIN ) ;;
43+
TURSO_API_TOKEN TURSO_ORG_NAME
44+
OS_CLOUD_API_KEY OS_COOKIE_DOMAIN OS_ROOT_DOMAIN ) ;;
4445
*)
4546
echo "✗ unknown app dir: $APP_BASENAME" >&2; exit 1 ;;
4647
esac

apps/objectos/wrangler.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ compatibility_flags = ["nodejs_compat"]
4444
# rebuild + re-push to ship a new version, then run `wrangler deploy`.
4545
[[containers]]
4646
class_name = "ObjectOSContainer"
47-
image = "registry.cloudflare.com/<account-id>/objectos:latest"
47+
image = "registry.cloudflare.com/2846eb40a60f4738e292b90dcd8cce10/objectos:59ed1a93-r2"
4848
max_instances = 5
4949
instance_type = "standard-1"
5050

packages/cli/src/commands/serve.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,12 +636,30 @@ export default class Serve extends Command {
636636
if (isDev && !trustedOrigins.includes('http://localhost:*')) {
637637
trustedOrigins.push('http://localhost:*');
638638
}
639+
// Per-project subdomains: when OS_ROOT_DOMAIN is set (multi-
640+
// project hosting under `*.<root>`), every project hostname
641+
// must be trusted by better-auth or sign-up/sign-in is
642+
// rejected with "Invalid origin". Mirrors the OS_COOKIE_DOMAIN
643+
// wildcard semantics — they are always set together.
644+
const rootDomain = (process.env.OS_ROOT_DOMAIN ?? process.env.ROOT_DOMAIN)?.trim();
645+
if (rootDomain) {
646+
const wildcard = `https://*.${rootDomain}`;
647+
if (!trustedOrigins.includes(wildcard)) trustedOrigins.push(wildcard);
648+
}
639649

640650
await kernel.use(new AuthPlugin({
641651
secret,
642652
baseUrl,
643653
socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,
644654
trustedOrigins: trustedOrigins.length ? trustedOrigins : undefined,
655+
advanced: process.env.OS_COOKIE_DOMAIN
656+
? ({
657+
crossSubDomainCookies: {
658+
enabled: true,
659+
domain: process.env.OS_COOKIE_DOMAIN,
660+
},
661+
} as any)
662+
: undefined,
645663
}));
646664
trackPlugin('Auth');
647665

0 commit comments

Comments
 (0)