From 4c200bc75e1cdd0c347069ddb355412d5f01ecad Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Tue, 21 Oct 2025 17:25:44 -0700 Subject: [PATCH 1/4] feat: add Fly.io multi-environment deployment configuration Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) --- .github/workflows/fly-dev-deploy.yml | 16 +++++++++++++ .github/workflows/fly-prod-deploy.yml | 16 +++++++++++++ fly.development.toml | 33 +++++++++++++++++++++++++++ fly.production.toml | 33 +++++++++++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 .github/workflows/fly-dev-deploy.yml create mode 100644 .github/workflows/fly-prod-deploy.yml create mode 100644 fly.development.toml create mode 100644 fly.production.toml diff --git a/.github/workflows/fly-dev-deploy.yml b/.github/workflows/fly-dev-deploy.yml new file mode 100644 index 00000000..751b9b0a --- /dev/null +++ b/.github/workflows/fly-dev-deploy.yml @@ -0,0 +1,16 @@ +name: Fly Dev Deploy +on: + push: + branches: + - development +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + concurrency: deploy-group + steps: + - uses: actions/checkout@v4 + - uses: superfly/flyctl-actions/setup-flyctl@master + - run: flyctl deploy --remote-only -c fly.development.toml + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/fly-prod-deploy.yml b/.github/workflows/fly-prod-deploy.yml new file mode 100644 index 00000000..4258ab35 --- /dev/null +++ b/.github/workflows/fly-prod-deploy.yml @@ -0,0 +1,16 @@ +name: Fly Prod Deploy +on: + push: + branches: + - main +jobs: + deploy: + name: Deploy app + runs-on: ubuntu-latest + concurrency: deploy-group + steps: + - uses: actions/checkout@v4 + - uses: superfly/flyctl-actions/setup-flyctl@master + - run: flyctl deploy --remote-only -c fly.production.toml + env: + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/fly.development.toml b/fly.development.toml new file mode 100644 index 00000000..e1e8f972 --- /dev/null +++ b/fly.development.toml @@ -0,0 +1,33 @@ +app = 'semble-development' +primary_region = 'yyz' + +[build] + +[processes] + web = "npm start" + feed-worker = "npm run worker:feeds" + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 0 + processes = ['web'] + +[[vm]] + memory = '512mb' + cpu_kind = 'shared' + cpus = 1 + +[[vm]] + processes = ['feed-worker'] + memory = '256mb' + cpu_kind = 'shared' + cpus = 1 + +[env] +NODE_ENV="dev" +BASE_URL="https://api.dev.semble.so" +HOST="0.0.0.0" +APP_URL="https://dev.semble.so" diff --git a/fly.production.toml b/fly.production.toml new file mode 100644 index 00000000..65eaa133 --- /dev/null +++ b/fly.production.toml @@ -0,0 +1,33 @@ +app = 'semble-production' +primary_region = 'yyz' + +[build] + +[processes] + web = "npm start" + feed-worker = "npm run worker:feeds" + +[http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 1 + processes = ['web'] + +[[vm]] + memory = '1gb' + cpu_kind = 'shared' + cpus = 1 + +[[vm]] + processes = ['feed-worker'] + memory = '512mb' + cpu_kind = 'shared' + cpus = 1 + +[env] +NODE_ENV="prod" +BASE_URL="https://api.semble.so" +HOST="0.0.0.0" +APP_URL="https://semble.so" -- 2.54.0 From 43c187a44575f636a3b578bff7b05e28da2c147f Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Tue, 21 Oct 2025 17:29:13 -0700 Subject: [PATCH 2/4] delete old fly and workflow files --- .github/workflows/fly.yml | 16 --------------- fly.toml | 41 --------------------------------------- 2 files changed, 57 deletions(-) delete mode 100644 .github/workflows/fly.yml delete mode 100644 fly.toml diff --git a/.github/workflows/fly.yml b/.github/workflows/fly.yml deleted file mode 100644 index 3629f405..00000000 --- a/.github/workflows/fly.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Fly Dev Deploy -on: - push: - branches: - - development -jobs: - deploy: - name: Deploy app - runs-on: ubuntu-latest - concurrency: deploy-group # optional: ensure only one action runs at a time - steps: - - uses: actions/checkout@v4 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/fly.toml b/fly.toml deleted file mode 100644 index 59b4f62e..00000000 --- a/fly.toml +++ /dev/null @@ -1,41 +0,0 @@ -# fly.toml app configuration file generated for annos on 2025-05-13T18:45:49-07:00 -# -# See https://fly.io/docs/reference/configuration/ for information about how to use this file. -# - -app = 'annos' -primary_region = 'yyz' - -[build] - -# Define different process types -[processes] - web = "npm start" - feed-worker = "npm run worker:feeds" - -[http_service] - internal_port = 3000 - force_https = true - auto_stop_machines = 'stop' - auto_start_machines = true - min_machines_running = 1 - processes = ['web'] # Only web processes handle HTTP traffic - -# Default VM configuration for all processes -[[vm]] - memory = '1gb' - cpu_kind = 'shared' - cpus = 1 - -# Override VM settings for worker processes -[[vm]] - processes = ['feed-worker'] - memory = '512mb' # Workers typically need less memory - cpu_kind = 'shared' - cpus = 1 - -[env] -NODE_ENV="dev" -BASE_URL="https://api.dev.semble.so" -HOST="0.0.0.0" -APP_URL="https://dev.semble.so" -- 2.54.0 From 71b8ec60e2e38570b1f43c0098b236df3188d7f6 Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Tue, 21 Oct 2025 17:33:23 -0700 Subject: [PATCH 3/4] refactor: introduce Environment enum for type-safe environment handling Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) --- .../config/EnvironmentConfigService.ts | 25 +++++++++++-------- src/shared/infrastructure/http/app.ts | 8 +++--- .../http/services/CookieService.ts | 10 ++++---- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/shared/infrastructure/config/EnvironmentConfigService.ts b/src/shared/infrastructure/config/EnvironmentConfigService.ts index 808343f6..5ec525c1 100644 --- a/src/shared/infrastructure/config/EnvironmentConfigService.ts +++ b/src/shared/infrastructure/config/EnvironmentConfigService.ts @@ -1,5 +1,11 @@ +export enum Environment { + LOCAL = 'local', + DEV = 'dev', + PROD = 'prod', +} + export interface EnvironmentConfig { - environment: 'local' | 'dev' | 'prod'; + environment: Environment; database: { url: string; }; @@ -42,10 +48,7 @@ export class EnvironmentConfigService { private config: EnvironmentConfig; constructor() { - const environment = (process.env.NODE_ENV || 'local') as - | 'local' - | 'dev' - | 'prod'; + const environment = (process.env.NODE_ENV || Environment.LOCAL) as Environment; this.config = { environment, @@ -70,15 +73,15 @@ export class EnvironmentConfigService { baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000', collections: { card: - environment === 'prod' + environment === Environment.PROD ? 'network.cosmik.card' : `network.cosmik.${environment}.card`, collection: - environment === 'prod' + environment === Environment.PROD ? 'network.cosmik.collection' : `network.cosmik.${environment}.collection`, collectionLink: - environment === 'prod' + environment === Environment.PROD ? 'network.cosmik.collectionLink' : `network.cosmik.${environment}.collectionLink`, }, @@ -109,10 +112,10 @@ export class EnvironmentConfigService { private applyEnvironmentSpecificConfig(): void { switch (this.config.environment) { - case 'dev': + case Environment.DEV: // Override defaults with dev-specific values break; - case 'prod': + case Environment.PROD: // Override defaults with production-specific values if ( this.config.auth.jwtSecret === 'default-secret-change-in-production' @@ -120,7 +123,7 @@ export class EnvironmentConfigService { throw new Error('JWT secret must be set in production environment'); } break; - case 'local': + case Environment.LOCAL: default: // Local development defaults are already set break; diff --git a/src/shared/infrastructure/http/app.ts b/src/shared/infrastructure/http/app.ts index 9046581c..a11f06cf 100644 --- a/src/shared/infrastructure/http/app.ts +++ b/src/shared/infrastructure/http/app.ts @@ -6,7 +6,7 @@ import { createUserRoutes } from '../../../modules/user/infrastructure/http/rout import { createAtprotoRoutes } from '../../../modules/atproto/infrastructure/atprotoRoutes'; import { createCardsModuleRoutes } from '../../../modules/cards/infrastructure/http/routes'; import { createFeedRoutes } from '../../../modules/feeds/infrastructure/http/routes/feedRoutes'; -import { EnvironmentConfigService } from '../config/EnvironmentConfigService'; +import { EnvironmentConfigService, Environment } from '../config/EnvironmentConfigService'; import { RepositoryFactory } from './factories/RepositoryFactory'; import { ServiceFactory } from './factories/ServiceFactory'; import { UseCaseFactory } from './factories/UseCaseFactory'; @@ -23,11 +23,11 @@ export const createExpressApp = ( const appUrl = configService.getAppConfig().appUrl; switch (environment) { - case 'prod': + case Environment.PROD: return ['https://semble.so', 'https://api.semble.so']; - case 'dev': + case Environment.DEV: return ['https://dev.semble.so', 'https://api.dev.semble.so']; - case 'local': + case Environment.LOCAL: default: // Allow both localhost:4000 and configured appUrl for flexibility return [ diff --git a/src/shared/infrastructure/http/services/CookieService.ts b/src/shared/infrastructure/http/services/CookieService.ts index 6e29b918..916581da 100644 --- a/src/shared/infrastructure/http/services/CookieService.ts +++ b/src/shared/infrastructure/http/services/CookieService.ts @@ -1,5 +1,5 @@ import { Response, Request } from 'express'; -import { EnvironmentConfigService } from '../../config/EnvironmentConfigService'; +import { EnvironmentConfigService, Environment } from '../../config/EnvironmentConfigService'; export interface CookieOptions { httpOnly: boolean; @@ -26,11 +26,11 @@ export class CookieService { const environment = this.configService.get().environment; switch (environment) { - case 'prod': + case Environment.PROD: return '.semble.so'; - case 'dev': + case Environment.DEV: return '.dev.semble.so'; - case 'local': + case Environment.LOCAL: default: return undefined; // Don't set domain for localhost } @@ -41,7 +41,7 @@ export class CookieService { */ private getBaseCookieOptions(): Omit { const environment = this.configService.get().environment; - const isProduction = environment === 'prod'; + const isProduction = environment === Environment.PROD; return { httpOnly: true, -- 2.54.0 From d66e30455f03d8c6754d6721ea99def21ed21bf0 Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Tue, 21 Oct 2025 17:34:57 -0700 Subject: [PATCH 4/4] formatting --- src/shared/infrastructure/config/EnvironmentConfigService.ts | 3 ++- src/shared/infrastructure/http/app.ts | 5 ++++- src/shared/infrastructure/http/services/CookieService.ts | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/shared/infrastructure/config/EnvironmentConfigService.ts b/src/shared/infrastructure/config/EnvironmentConfigService.ts index 5ec525c1..f49d1894 100644 --- a/src/shared/infrastructure/config/EnvironmentConfigService.ts +++ b/src/shared/infrastructure/config/EnvironmentConfigService.ts @@ -48,7 +48,8 @@ export class EnvironmentConfigService { private config: EnvironmentConfig; constructor() { - const environment = (process.env.NODE_ENV || Environment.LOCAL) as Environment; + const environment = (process.env.NODE_ENV || + Environment.LOCAL) as Environment; this.config = { environment, diff --git a/src/shared/infrastructure/http/app.ts b/src/shared/infrastructure/http/app.ts index a11f06cf..1de72a3d 100644 --- a/src/shared/infrastructure/http/app.ts +++ b/src/shared/infrastructure/http/app.ts @@ -6,7 +6,10 @@ import { createUserRoutes } from '../../../modules/user/infrastructure/http/rout import { createAtprotoRoutes } from '../../../modules/atproto/infrastructure/atprotoRoutes'; import { createCardsModuleRoutes } from '../../../modules/cards/infrastructure/http/routes'; import { createFeedRoutes } from '../../../modules/feeds/infrastructure/http/routes/feedRoutes'; -import { EnvironmentConfigService, Environment } from '../config/EnvironmentConfigService'; +import { + EnvironmentConfigService, + Environment, +} from '../config/EnvironmentConfigService'; import { RepositoryFactory } from './factories/RepositoryFactory'; import { ServiceFactory } from './factories/ServiceFactory'; import { UseCaseFactory } from './factories/UseCaseFactory'; diff --git a/src/shared/infrastructure/http/services/CookieService.ts b/src/shared/infrastructure/http/services/CookieService.ts index 916581da..2e361650 100644 --- a/src/shared/infrastructure/http/services/CookieService.ts +++ b/src/shared/infrastructure/http/services/CookieService.ts @@ -1,5 +1,8 @@ import { Response, Request } from 'express'; -import { EnvironmentConfigService, Environment } from '../../config/EnvironmentConfigService'; +import { + EnvironmentConfigService, + Environment, +} from '../../config/EnvironmentConfigService'; export interface CookieOptions { httpOnly: boolean; -- 2.54.0