diff --git a/.dockerignore b/.dockerignore index 6be9e1f..3cea2a5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -79,3 +79,8 @@ frontend/**/*.sw? # flyctl launch added from node_modules/tailwindcss/stubs/.gitignore !node_modules/tailwindcss/stubs/**/* fly.toml + +# workspace +landing-page +docs +.github \ No newline at end of file diff --git a/.github/workflows/fly-deploy.yml b/.github/workflows/fly-deploy.yml new file mode 100644 index 0000000..b0c246e --- /dev/null +++ b/.github/workflows/fly-deploy.yml @@ -0,0 +1,18 @@ +# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ + +name: Fly Deploy +on: + push: + branches: + - main +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/Dockerfile b/Dockerfile index cd28374..1c3b822 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,15 @@ RUN bun run build FROM oven/bun as production WORKDIR /app -# Create directory for mount with correct permissions -RUN mkdir -p /.data/db /.data/cache && \ - chown -R bun:bun /.data +# Install LiteFS dependencies +RUN apt-get update -y && apt-get install -y ca-certificates fuse3 sqlite3 + +# Copy LiteFS binary +COPY --from=flyio/litefs:0.5 /usr/local/bin/litefs /usr/local/bin/litefs + +# Create directories for mounts with correct permissions +RUN mkdir -p /litefs /var/lib/litefs /public && \ + chown -R bun:bun /litefs /var/lib/litefs /public # Copy only necessary files from builder COPY --from=builder --chown=bun:bun /app/package.json /app/bun.lockb /app/turbo.json ./ @@ -41,12 +47,12 @@ COPY --from=builder --chown=bun:bun /app/frontend/dist ./frontend/dist COPY --from=builder --chown=bun:bun /app/backend/dist ./backend/dist # Set environment variables -ENV DATABASE_URL="file:/.data/db/sqlite.db" -ENV CACHE_DIR="/.data/cache" +ENV DATABASE_URL="file:/litefs/db" +ENV CACHE_DIR="/litefs/cache" ENV NODE_ENV="production" # Expose the port EXPOSE 3000 -# Start the application using the production start script -CMD ["bun", "run", "start"] +# Start LiteFS (runs app with distributed file system for SQLite) +ENTRYPOINT ["litefs", "mount"] diff --git a/bun.lockb b/bun.lockb index a01eded..f43144e 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/docs/developers/deployment.md b/docs/docs/developers/deployment.md index 3ae86ce..9603878 100644 --- a/docs/docs/developers/deployment.md +++ b/docs/docs/developers/deployment.md @@ -40,17 +40,22 @@ fly auth login 1. Initialize your Fly.io application: ```bash -cd backend/ bun run deploy:init ``` -2. Create persistent volumes for SQLite and cache: +2. Create the LiteFS volume (see [LiteFS Spreedrun](https://fly.io/docs/litefs/speedrun/) for more information): ```bash bun run deploy:volumes ``` -3. Deploy the application: +3. Attach Consul for LiteFS cluster management (this sets the FLY_CONSUL_URL secret for the app, which is required for LitefS leases): + +```bash +bun run deploy:consul +``` + +4. Deploy the application: ```bash bun run deploy @@ -60,11 +65,26 @@ bun run deploy The deployment includes: -- Persistent storage for SQLite database -- Cache directory support -- Auto-scaling configuration +- Distributed SQLite using LiteFS +- Automatic file replication across instances +- High availability with minimum 2 machines +- Primary/replica configuration using Consul - HTTPS enabled by default +#### Architecture + +- Primary instance (LAX region) handles write operations +- Replicas automatically sync data from primary +- Consul manages primary/replica coordination +- Files in /public directory are replicated across instances +- Automatic failover if primary becomes unavailable + +#### Key Files + +- `fly.toml`: Main Fly.io configuration +- `litefs.yml`: LiteFS configuration +- `Dockerfile`: Container and LiteFS setup + ### Environment Variables Make sure to configure your environment variables in Fly.io: @@ -103,18 +123,23 @@ fly dashboard Common issues and solutions: 1. **Database Connection Issues** - - Verify volume mount paths - - Check SQLite file permissions - - Ensure volumes are properly created - -2. **Memory/CPU Issues** - - Monitor resource usage with `fly status` - - Adjust VM size if needed - - Consider enabling auto-scaling - -3. **Network Issues** - - Check Fly.io region configuration - - Verify firewall settings - - Test network connectivity + - Check LiteFS mount status: `fly ssh console -C "ls -la /litefs"` + - Verify Consul connection: `fly consul status` + - Check primary/replica status: `fly logs` + +2. **File Replication Issues** + - Verify LiteFS FUSE mount: `fly ssh console -C "mount | grep litefs"` + - Check file permissions: `fly ssh console -C "ls -la /public"` + - Monitor LiteFS logs: `fly logs --level debug` + +3. **Memory/CPU Issues** + - Monitor resource usage: `fly status` + - Check machine distribution: `fly scale show` + - Adjust VM configuration in fly.toml if needed + +4. **Primary/Replica Issues** + - Verify Consul health: `fly consul status` + - Check region configuration: `fly regions list` + - Monitor primary elections: `fly logs --level info` For more help, consult the [Fly.io documentation](https://fly.io/docs/) or join their [community Discord](https://fly.io/discord). diff --git a/fly.toml b/fly.toml index 03f7cb3..6f87282 100644 --- a/fly.toml +++ b/fly.toml @@ -1,10 +1,10 @@ -# fly.toml app configuration file generated for public-goods-news-quiet-wildflower-8563 on 2024-12-18T14:47:13-06:00 +# fly.toml app configuration file generated for curatedotfun on 2025-01-15T13:48:42-07:00 # # See https://fly.io/docs/reference/configuration/ for information about how to use this file. # -app = 'public-goods-news-quiet-wildflower-8563' -primary_region = 'lax' +app = 'curatedotfun' +primary_region = 'den' [build] dockerfile = 'Dockerfile' @@ -13,10 +13,9 @@ primary_region = 'lax' PORT = '3000' [[mounts]] - source = 'data' - destination = '/.data' + source = "litefs" + destination = "/var/lib/litefs" initial_size = '1GB' - [http_service] internal_port = 3000 force_https = true diff --git a/frontend/package.json b/frontend/package.json index a2a94b3..7783af7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,10 +22,11 @@ "tailwindcss": "^3.4.16" }, "devDependencies": { - "@rsbuild/core": "1.1.9", + "@eslint/js": "^9.15.0", + "@rsbuild/core": "1.1.13", "@rsbuild/plugin-react": "1.1.0", "@tanstack/router-plugin": "^1.97.0", - "@eslint/js": "^9.15.0", + "@types/bun": "^1.1.16", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.4", diff --git a/frontend/rsbuild.config.ts b/frontend/rsbuild.config.ts index 0aed34f..8960ee0 100644 --- a/frontend/rsbuild.config.ts +++ b/frontend/rsbuild.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from '@rsbuild/core' -import { pluginReact } from '@rsbuild/plugin-react' -import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack' +import { defineConfig } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack'; export default defineConfig({ plugins: [pluginReact()], diff --git a/litefs.yml b/litefs.yml new file mode 100644 index 0000000..494ce4f --- /dev/null +++ b/litefs.yml @@ -0,0 +1,54 @@ +# The fuse section describes settings for the FUSE file system. This file system +# is used as a thin layer between the SQLite client in your application and the +# storage on disk. It intercepts disk writes to determine transaction boundaries +# so that those transactions can be saved and shipped to replicas. +fuse: + dir: "/litefs" + mount-timeout: "5s" + allow-other: true + directories: + - path: "/public" + read-only: false + +# The data section describes settings for the internal LiteFS storage. We'll +# mount a volume to the data directory so it can be persisted across restarts. +# However, this data should not be accessed directly by the user application. +data: + dir: "/var/lib/litefs" + +# This flag ensure that LiteFS continues to run if there is an issue on starup. +# It makes it easy to ssh in and debug any issues you might be having rather +# than continually restarting on initialization failure. +exit-on-error: false + +# This section defines settings for the option HTTP proxy. +# This proxy can handle primary forwarding & replica consistency +# for applications that use a single SQLite database. +proxy: + addr: ":8080" + target: "localhost:3000" + db: "db" + passthrough: + - "*.ico" + - "*.png" + +# This section defines a list of commands to run after LiteFS has connected +# and sync'd with the cluster. You can run multiple commands but LiteFS expects +# the last command to be long-running (e.g. an application server). When the +# last command exits, LiteFS is shut down. +exec: + - cmd: "bun run start" + +# The lease section specifies how the cluster will be managed. We're using the +# "consul" lease type so that our application can dynamically change the primary. +# +# These environment variables will be available in your Fly.io application. +lease: + type: "consul" + advertise-url: "http://${HOSTNAME}.vm.${FLY_APP_NAME}.internal:20202" + candidate: ${FLY_REGION == PRIMARY_REGION} + promote: true + + consul: + url: "${FLY_CONSUL_URL}" + key: "litefs/${FLY_APP_NAME}" diff --git a/package.json b/package.json index e5a5ee6..6c9f0a5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "public-goods-news", + "name": "curate-dot-fun", "private": true, "type": "module", "packageManager": "bun@1.0.27", @@ -9,15 +9,14 @@ "start": "NODE_ENV=production bun run backend/dist/index.js", "lint": "bunx turbo run lint", "deploy:init": "fly launch", - "deploy:volumes": "fly volumes create sqlite --size 1 --region lax && fly volumes create cache --size 1 --region lax", + "deploy:consul": "fly consul attach", "deploy": "fly deploy", "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" }, "workspaces": [ "frontend", - "backend", - "docs" + "backend" ], "devDependencies": { "turbo": "latest",