diff --git a/.dockerignore b/.dockerignore index 1f5f855728..271e5a1953 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,42 @@ +# Environment files +.env* +**/.env* + +# Docker files +.dockerignore +**/.dockerignore +Dockerfile* +**/Dockerfile* +docker-compose* +**/docker-compose* + +# Build outputs +build/ +**/build/ +dist/ +**/dist/ target/ -**/target/ \ No newline at end of file +**/target/ + +# Dependencies +node_modules/ +**/node_modules/ +vendor/ +**/vendor/ + +# Version control +.git/ +.gitignore +.gitattributes + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# TS/Rust types, this is generated during the build process +quadratic-client/src/app/quadratic-core/ +quadratic-client/src/app/quadratic-core-types/ +quadratic-client/src/app/quadratic-rust-client/ diff --git a/.env.docker b/.env.docker new file mode 100644 index 0000000000..fc22943302 --- /dev/null +++ b/.env.docker @@ -0,0 +1,109 @@ +# global +ENVIRONMENT=docker +RUST_LOG=info + +# Your license key for Quadratic. Get one here https://selfhost.quadratichq.com/ +LICENSE_KEY="#LICENSE_KEY#" + +# postgres database +DATABASE_IN_DOCKER_COMPOSE=true +DATABASE_DSN=postgresql://postgres:postgres@host.docker.internal:5432/postgres + +# pubsub +PUBSUB_IN_DOCKER_COMPOSE=true +PUBSUB_HOST=host.docker.internal +PUBSUB_PORT=6379 +PUBSUB_PASSWORD="" +PUBSUB_ACTIVE_CHANNELS=active_channels +PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL=processed_transactions + +# auth: ory or auth0 +AUTH_TYPE=ory +JWKS_URI=http://host.docker.internal:3000/.well-known/jwks.json +M2M_AUTH_TOKEN=M2M_AUTH_TOKEN +ENCRYPTION_KEY=eb4758047f74bdb2603cce75c4370327ca2c3662c4786867659126da8e64dfcc + +# auth=ory +ORY_IN_DOCKER_COMPOSE=true +ORY_DSN=postgresql://postgres:postgres@host.docker.internal:5432/kratos?sslmode=disable +ORY_LOG_LEVEL=trace +ORY_ADMIN_HOST=http://host.docker.internal:4434 +KRATOS_URL_INTERNAL=http://host.docker.internal:4433/ +KRATOS_URL_EXTERNAL=http://localhost:4433/ +KRATOS_NODE_PORT=4455 +KRATOS_COOKIE_SECRET=changeme +KRATOS_CSRF_COOKIE_NAME=__HOST-localhost-x-csrf-token +KRATOS_CSRF_COOKIE_SECRET=changeme + +# caddy +CADDY_IN_DOCKER_COMPOSE=false + +# client +QUADRATIC_CLIENT_IN_DOCKER_COMPOSE=true + +# api +QUADRATIC_API_IN_DOCKER_COMPOSE=true +QUADRATIC_API_URL_EXTERNAL=http://localhost:8000 +QUADRATIC_API_URL_INTERNAL=http://host.docker.internal:8000 + +# multiplayer +QUADRATIC_MULTIPLAYER_IN_DOCKER_COMPOSE=true +QUADRATIC_MULTIPLAYER_RUST_LOG=info +QUADRATIC_MULTIPLAYER_HOST=0.0.0.0 +QUADRATIC_MULTIPLAYER_PORT=3001 +QUADRATIC_MULTIPLAYER_HEARTBEAT_CHECK_S=3 +QUADRATIC_MULTIPLAYER_HEARTBEAT_TIMEOUT_S=600 +QUADRATIC_MULTIPLAYER_URL_EXTERNAL=ws://localhost:3001/ws +QUADRATIC_MULTIPLAYER_URL_INTERNAL=ws://host.docker.internal:3001 + +# files +QUADRATIC_FILES_IN_DOCKER_COMPOSE=true +QUADRATIC_FILES_RUST_LOG=info +QUADRATIC_FILES_HOST=0.0.0.0 +QUADRATIC_FILES_PORT=3002 +QUADRATIC_FILES_FILE_CHECK_S=5 +QUADRATIC_FILES_FILES_PER_CHECK=1000 +QUADRATIC_FILES_TRUNCATE_FILE_CHECK_S=60 +QUADRATIC_FILES_TRUNCATE_TRANSACTION_AGE_DAYS=5 +QUADRATIC_FILES_URL_EXTERNAL=http://localhost:3002 +QUADRATIC_FILES_URL_INTERNAL=http://host.docker.internal:3002 + +# connection +QUADRATIC_CONNECTION_IN_DOCKER_COMPOSE=true +QUADRATIC_CONNECTION_RUST_LOG=info +QUADRATIC_CONNECTION_HOST=0.0.0.0 +QUADRATIC_CONNECTION_PORT=3003 +QUADRATIC_CONNECTION_URL_EXTERNAL=http://localhost:3003 +QUADRATIC_CONNECTION_URL_INTERNAL=http://host.docker.internal:3003 +QUADRATIC_CONNECTION_MAX_RESPONSE_BYTES=15728640 # 15MB +QUADRATIC_CONNECTION_STATIC_IPS=0.0.0.0,127.0.0.1 + +# stripe +STRIPE_SECRET_KEY=STRIPE_SECRET_KEY +STRIPE_WEBHOOK_SECRET=STRIPE_WEBHOOK_SECRET + +# storage - s3 or file-system +STORAGE_TYPE=file-system + +# storage=file-system +STORAGE_DIR=/file-storage + +# storage=s3 +AWS_S3_BUCKET_NAME=quadratic-api-docker +AWS_S3_REGION=us-west-2 +AWS_S3_ACCESS_KEY_ID=AWS_S3_ACCESS_KEY_ID +AWS_S3_SECRET_ACCESS_KEY=AWS_S3_SECRET_ACCESS_KEY + +# ai +OPENAI_API_KEY=OPENAI_API_KEY +ANTHROPIC_API_KEY=ANTHROPIC_API_KEY +EXA_API_KEY=EXA_API_KEY + +# use image from ECR or build locally +ECR_OR_BUILD=build + +# build client in dev mode without wasm-opt +CLIENT_DEV=false + +ECR_URL=public.ecr.aws/z7e3d4w1 +IMAGE_TAG=latest \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index c1fe3a6a24..0000000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -VITE_AUTH0_ISSUER=https://quadratic-community.us.auth0.com/ -VITE_AUTH0_DOMAIN=quadratic-community.us.auth0.com -VITE_AUTH0_CLIENT_ID=DCPCvqyU5Q0bJD8Q3QmJEoV48x1zLH7W -VITE_AUTH0_AUDIENCE=community-quadratic - -VITE_QUADRATIC_API_URL=http://localhost:8000 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57b930abb8..c1c8b26bb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,21 +10,30 @@ on: - main pull_request: +concurrency: + group: ci-${{ github.event.pull_request.number || github.ref_name }} + cancel-in-progress: true + jobs: - test_rust: - runs-on: ubuntu-latest-8-cores - timeout-minutes: 15 + test_core: + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + + - uses: useblacksmith/setup-node@v5 with: node-version: 18 + cache: "npm" - name: Set up Rust - uses: moonrepo/setup-rust@v1 + uses: actions-rs/toolchain@v1 with: + toolchain: stable components: clippy, llvm-tools-preview - cache: false + override: true + + - uses: useblacksmith/rust-cache@v3 - name: Install grcov run: if ! which grcov; then cargo install grcov; fi @@ -43,18 +52,20 @@ jobs: LLVM_PROFILE_FILE: grcov-%p-%m.profraw RUSTFLAGS: -Cinstrument-coverage RUSTC_BOOTSTRAP: 1 + CARGO_BUILD_JOBS: 4 run: | cd quadratic-core - cargo test + cargo test -- --test-threads=2 - name: Generate coverage for quadratic-core env: RUSTC_BOOTSTRAP: 1 run: | - grcov $(find . -name "grcov-*.profraw" -print) \ + cd quadratic-core + grcov $(find . ../quadratic-rust-client ../quadratic-rust-shared -name "grcov-*.profraw" -print) \ --branch \ --ignore-not-existing \ - --binary-path ./target/debug/ \ + --binary-path ../target/debug/ \ -s . \ -t lcov \ --ignore "/*" \ @@ -63,67 +74,163 @@ jobs: --ignore "./docker/*" \ -o lcov.info - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 + - name: Upload coverage reports to Codecov quadratic-core + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + test_multiplayer: + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: useblacksmith/setup-node@v5 + with: + node-version: 18 + cache: "npm" + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy, llvm-tools-preview + override: true + + - uses: useblacksmith/rust-cache@v3 + + - name: Install grcov + run: if ! which grcov; then cargo install grcov; fi + + - name: Install llvm-tools-preview + run: if ! which llvm-tools-preview; then rustup component add llvm-tools-preview; fi + + - name: Install pkg-config + if: github.runner.isHosted == true + run: | + sudo apt-get update + sudo apt-get install -y pkg-config + - name: Test quadratic-multiplayer env: LLVM_PROFILE_FILE: grcov-%p-%m.profraw RUSTFLAGS: -Cinstrument-coverage RUSTC_BOOTSTRAP: 1 + CARGO_BUILD_JOBS: 4 run: | cd quadratic-multiplayer npm run docker:test - - name: Generate coverage quadratic-multiplayer + - name: Generate coverage for quadratic-multiplayer env: RUSTC_BOOTSTRAP: 1 run: | - grcov $(find . -name "grcov-*.profraw" -print) \ + cd quadratic-multiplayer + grcov $(find . ../quadratic-rust-shared -name "grcov-*.profraw" -print) \ --branch \ --ignore-not-existing \ - --binary-path ./target/debug/ \ + --binary-path ../target/debug/ \ -s . \ -t lcov \ --ignore "/*" \ --ignore "./src/wasm_bindings/*" \ --ignore "./src/bin/*" \ --ignore "./docker/*" \ - --ignore "./../docker/*" \ -o lcov.info - name: Upload coverage reports to Codecov quadratic-multiplayer - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + test_connection: + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: useblacksmith/setup-node@v5 + with: + node-version: 18 + cache: "npm" + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + components: clippy, llvm-tools-preview + override: true + + - uses: useblacksmith/rust-cache@v3 + + - name: Install grcov + run: if ! which grcov; then cargo install grcov; fi + + - name: Install llvm-tools-preview + run: if ! which llvm-tools-preview; then rustup component add llvm-tools-preview; fi + + - name: Install pkg-config + if: github.runner.isHosted == true + run: | + sudo apt-get update + sudo apt-get install -y pkg-config + - name: Test quadratic-connection env: LLVM_PROFILE_FILE: grcov-%p-%m.profraw RUSTFLAGS: -Cinstrument-coverage RUSTC_BOOTSTRAP: 1 + CARGO_BUILD_JOBS: 4 + run: | + cd quadratic-connection + npm run docker:test + + - name: Generate coverage for quadratic-connection + env: + RUSTC_BOOTSTRAP: 1 run: | cd quadratic-connection - npm run test:docker:ci + grcov $(find . ../quadratic-rust-shared -name "grcov-*.profraw" -print) \ + --branch \ + --ignore-not-existing \ + --binary-path ../target/debug/ \ + -s . \ + -t lcov \ + --ignore "/*" \ + --ignore "./src/wasm_bindings/*" \ + --ignore "./src/bin/*" \ + --ignore "./docker/*" \ + -o lcov.info + + - name: Upload coverage reports to Codecov quadratic-connection + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test_unit: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-2vcpu-ubuntu-2204 timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + + - uses: useblacksmith/setup-node@v5 with: node-version: 18 - - uses: actions/setup-python@v5 + cache: "npm" + + - uses: useblacksmith/setup-python@v6 with: python-version: "3.11.3" + cache: "pip" - name: Set up Rust - uses: moonrepo/setup-rust@v1 + uses: actions-rs/toolchain@v1 with: - channel: "nightly" + toolchain: stable + target: wasm32-unknown-unknown + override: true + + - uses: useblacksmith/rust-cache@v3 - uses: jetli/wasm-pack-action@v0.4.0 @@ -145,14 +252,17 @@ jobs: npm run test:ts test_python: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-2vcpu-ubuntu-2204 timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + + - uses: useblacksmith/setup-node@v5 with: node-version: 18 - - uses: actions/setup-python@v5 + cache: "npm" + + - uses: useblacksmith/setup-python@v6 with: python-version: "3.11.3" cache: "pip" @@ -163,13 +273,15 @@ jobs: npm run test test_api: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-2vcpu-ubuntu-2204 timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + + - uses: useblacksmith/setup-node@v5 with: node-version: 18 + cache: "npm" - name: Run npm test:ci in quadratic-api run: | @@ -178,15 +290,19 @@ jobs: npm run docker:test:ci lint_rust: - runs-on: ubuntu-latest-8-cores + runs-on: blacksmith-2vcpu-ubuntu-2204 timeout-minutes: 10 steps: - uses: actions/checkout@v4 + - name: Set up Rust - uses: moonrepo/setup-rust@v1 + uses: actions-rs/toolchain@v1 with: + toolchain: stable components: clippy - cache: false + override: true + + - uses: useblacksmith/rust-cache@v3 - name: Run cargo clippy in quadratic-core run: | @@ -194,17 +310,26 @@ jobs: cargo clippy -- -D warnings lint: - runs-on: ubuntu-latest-8-cores - timeout-minutes: 15 + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + + - uses: useblacksmith/setup-node@v5 with: node-version: 18 + cache: "npm" + - name: Set up Rust - uses: moonrepo/setup-rust@v1 + uses: actions-rs/toolchain@v1 with: - cache: false + toolchain: stable + components: clippy + target: wasm32-unknown-unknown + override: true + + - uses: useblacksmith/rust-cache@v3 + - uses: jetli/wasm-pack-action@v0.4.0 with: version: "latest" @@ -225,46 +350,48 @@ jobs: npm run lint:ts check-version-increment: - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 # If we are merging into main, but not pushed on main if: github.base_ref == 'main' && github.ref != 'refs/heads/main' steps: - - name: Checkout current branch - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Get current VERSION - id: current_version - run: echo "CURRENT_VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT - - - name: Checkout main branch - uses: actions/checkout@v3 - with: - ref: main - fetch-depth: 1 - - - name: Get main VERSION - id: main_version - run: echo "MAIN_VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT - - - name: Compare versions to main, verify this version is higher - run: | - current_version="${{ steps.current_version.outputs.CURRENT_VERSION }}" - main_version="${{ steps.main_version.outputs.MAIN_VERSION }}" - if [ "$(printf '%s\n' "$main_version" "$current_version" | sort -V | tail -n1)" != "$current_version" ]; then - echo "Error: VERSION in the current branch ($current_version) is not greater than VERSION in main ($main_version)" - exit 1 - else - echo "VERSION check passed: Current branch ($current_version) > main ($main_version)" - fi + - name: Checkout current branch + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Get current VERSION + id: current_version + run: echo "CURRENT_VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: main + fetch-depth: 1 + + - name: Get main VERSION + id: main_version + run: echo "MAIN_VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + + - name: Compare versions to main, verify this version is higher + run: | + current_version="${{ steps.current_version.outputs.CURRENT_VERSION }}" + main_version="${{ steps.main_version.outputs.MAIN_VERSION }}" + if [ "$(printf '%s\n' "$main_version" "$current_version" | sort -V | tail -n1)" != "$current_version" ]; then + echo "Error: VERSION in the current branch ($current_version) is not greater than VERSION in main ($main_version)" + exit 1 + else + echo "VERSION check passed: Current branch ($current_version) > main ($main_version)" + fi check-versions-match: - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 10 steps: - - name: Checkout current branch - uses: actions/checkout@v3 + - name: Checkout current branch + uses: actions/checkout@v3 - - name: Verify that all versions match - run: ./bump.sh verify + - name: Verify that all versions match + run: ./bump.sh verify diff --git a/.github/workflows/production-publish-images.yml b/.github/workflows/production-publish-images.yml index 5e4315b3e9..79c20b0abd 100644 --- a/.github/workflows/production-publish-images.yml +++ b/.github/workflows/production-publish-images.yml @@ -3,7 +3,6 @@ name: Build and Publish Images to ECR on: push: branches: - - self-hosting-setup #remove - main concurrency: @@ -16,45 +15,45 @@ jobs: matrix: service: [multiplayer, files, connection, client, api] steps: - - uses: actions/checkout@v4 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 - - - name: Login to Amazon ECR Public - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - with: - registry-type: public - - - name: Define repository name - id: repo-name - run: | - echo "REPO_NAME=quadratic-${{ matrix.service }}" >> $GITHUB_OUTPUT - - - name: Create Public ECR Repository if not exists - id: create-ecr - env: - REPO_NAME: ${{ steps.repo-name.outputs.REPO_NAME }} - run: | - aws ecr-public create-repository --repository-name $REPO_NAME || true - REPO_INFO=$(aws ecr-public describe-repositories --repository-names $REPO_NAME) - ECR_URL=$(echo $REPO_INFO | jq -r '.repositories[0].repositoryUri') - echo "ECR_URL=$ECR_URL" >> $GITHUB_OUTPUT - - - name: Read VERSION file - id: version - run: echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT - - - name: Build, Tag, and Push Image to Amazon ECR Public - env: - ECR_URL: ${{ steps.create-ecr.outputs.ECR_URL }} - IMAGE_TAG: ${{ steps.version.outputs.VERSION }} - run: | - docker build -t $ECR_URL:$IMAGE_TAG -t $ECR_URL:latest -f quadratic-${{ matrix.service }}/Dockerfile . - docker push $ECR_URL:$IMAGE_TAG - docker push $ECR_URL:latest \ No newline at end of file + - uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + - name: Login to Amazon ECR Public + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + with: + registry-type: public + + - name: Define repository name + id: repo-name + run: | + echo "REPO_NAME=quadratic-${{ matrix.service }}" >> $GITHUB_OUTPUT + + - name: Create Public ECR Repository if not exists + id: create-ecr + env: + REPO_NAME: ${{ steps.repo-name.outputs.REPO_NAME }} + run: | + aws ecr-public create-repository --repository-name $REPO_NAME || true + REPO_INFO=$(aws ecr-public describe-repositories --repository-names $REPO_NAME) + ECR_URL=$(echo $REPO_INFO | jq -r '.repositories[0].repositoryUri') + echo "ECR_URL=$ECR_URL" >> $GITHUB_OUTPUT + + - name: Read VERSION file + id: version + run: echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + + - name: Build, Tag, and Push Image to Amazon ECR Public + env: + ECR_URL: ${{ steps.create-ecr.outputs.ECR_URL }} + IMAGE_TAG: ${{ steps.version.outputs.VERSION }} + run: | + docker build -t $ECR_URL:$IMAGE_TAG -t $ECR_URL:latest -f quadratic-${{ matrix.service }}/Dockerfile . + docker push $ECR_URL:$IMAGE_TAG + docker push $ECR_URL:latest diff --git a/.github/workflows/qa-wolf-notify.yml b/.github/workflows/qa-wolf-notify.yml index 0790fb3b87..4db6ff0c3c 100644 --- a/.github/workflows/qa-wolf-notify.yml +++ b/.github/workflows/qa-wolf-notify.yml @@ -7,7 +7,7 @@ on: jobs: qa-wolf-notify: - runs-on: ubuntu-latest + runs-on: blacksmith-2vcpu-ubuntu-2204 concurrency: group: qa-wolf-notify cancel-in-progress: true diff --git a/.github/workflows/staging-build-deploy-images.yml b/.github/workflows/staging-build-deploy-images.yml new file mode 100644 index 0000000000..380b57afec --- /dev/null +++ b/.github/workflows/staging-build-deploy-images.yml @@ -0,0 +1,526 @@ +name: Build & Deploy Images + +on: + pull_request: + types: [opened, synchronize, reopened, labeled, unlabeled] + +concurrency: + group: pr-${{ github.event.pull_request.number }}-build-images + cancel-in-progress: ${{ (github.event.action != 'labeled' && github.event.action != 'unlabeled') || ((github.event.action == 'labeled' || github.event.action == 'unlabeled') && startsWith(github.event.label.name, 'build:')) }} + +jobs: + create_deployment: + name: Create Deployment - Preview + runs-on: blacksmith-2vcpu-ubuntu-2204 + if: | + github.event.action == 'opened' || + github.event.action == 'synchronize' || + github.event.action == 'reopened' || + (github.event.action == 'labeled' && startsWith(github.event.label.name, 'build:')) || + (github.event.action == 'unlabeled' && startsWith(github.event.label.name, 'build:')) + permissions: + contents: read + pull-requests: write + issues: write + deployments: write + outputs: + deployment_id: ${{ steps.deployment.outputs.id }} + steps: + - name: Find Build & Deploy Images Comment + uses: peter-evans/find-comment@v3 + id: preview-build-deploy-images-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Build & Deploy Images" + + - name: Create initial status comment + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-build-deploy-images-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Build & Deploy Images + ⏳ Building images... + ⏳ Deploy images + + 🔍 Track progress in the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + edit-mode: replace + + - name: Deactivate previous deployments + run: | + gh api \ + --method GET \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments \ + -f ref="${{ github.event.pull_request.head.ref }}" \ + -f environment=preview \ + --jq '.[] | .id' | while read -r id; do + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/$id/statuses \ + -f state="inactive" \ + -f environment="preview" \ + -f description="Superseded by newer deployment" + done + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create deployment + id: deployment + run: | + DEPLOYMENT_ID=$(gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments \ + -f ref="${{ github.event.pull_request.head.ref }}" \ + -f description="Building and deploying PR #${{ github.event.pull_request.number }} (${GITHUB_SHA::7})" \ + -F auto_merge=false \ + -f required_contexts\[\] \ + -f environment=preview \ + -F transient_environment=true \ + -F production_environment=false \ + --jq '.id') + echo "id=$DEPLOYMENT_ID" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update deployment status (building images) + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/${{ steps.deployment.outputs.id }}/statuses \ + -f state="in_progress" \ + -f description="Building Docker images..." \ + -f environment=preview \ + -f log_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build_images: + name: Build Images - Preview + needs: create_deployment + permissions: + contents: read + pull-requests: write + issues: write + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + matrix: + include: + - service: client + runner: blacksmith-8vcpu-ubuntu-2204 + - service: api + runner: blacksmith-2vcpu-ubuntu-2204 + - service: connection + runner: blacksmith-4vcpu-ubuntu-2204 + - service: files + runner: blacksmith-4vcpu-ubuntu-2204 + - service: multiplayer + runner: blacksmith-4vcpu-ubuntu-2204 + fail-fast: true + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check for build:wasm-opt:disabled label + id: build-wasm-opt-disabled + run: | + if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build:wasm-opt:disabled') }} == true ]]; then + echo "CLIENT_DEV=true" >> $GITHUB_OUTPUT + else + echo "CLIENT_DEV=false" >> $GITHUB_OUTPUT + fi + + - name: Check for build:cache:disabled label + id: build-cache + run: | + if [[ ${{ contains(github.event.pull_request.labels.*.name, 'build:cache:disabled') }} == true ]]; then + echo "CACHE_DISABLED=true" >> $GITHUB_OUTPUT + else + echo "CACHE_DISABLED=false" >> $GITHUB_OUTPUT + fi + + - name: Generate Build Metadata + id: build-metadata + run: | + echo "BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + echo "GIT_SHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "BRANCH_NAME=$(echo "${{ github.head_ref }}" | tr '/' '-')" >> $GITHUB_OUTPUT + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEVELOPMENT }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEVELOPMENT }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Login to Amazon ECR Private + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Define repository name + id: repo-name + run: | + echo "REPO_NAME=quadratic-${{ matrix.service }}" >> $GITHUB_OUTPUT + + - name: Create Private ECR Repository + id: create-ecr + env: + REPO_NAME: ${{ steps.repo-name.outputs.REPO_NAME }} + run: | + # Try to describe the repository first + if ! aws ecr describe-repositories --repository-names $REPO_NAME 2>/dev/null; then + # Repository doesn't exist, create it + aws ecr create-repository --repository-name $REPO_NAME || true + fi + + # Get the repository URI either way + REPO_INFO=$(aws ecr describe-repositories --repository-names $REPO_NAME) + ECR_URL=$(echo $REPO_INFO | jq -r '.repositories[0].repositoryUri') + echo "ECR_URL=$ECR_URL" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: | + image=moby/buildkit:latest + network=host + + - name: Build and push (with cache) + if: steps.build-cache.outputs.CACHE_DISABLED == 'false' + uses: useblacksmith/build-push-action@v1 + with: + context: . + file: quadratic-${{ matrix.service }}/Dockerfile + push: true + tags: ${{ steps.create-ecr.outputs.ECR_URL }}:pr-${{ github.event.pull_request.number }} + build-args: | + BUILD_TIME=${{ steps.build-metadata.outputs.BUILD_TIME }} + GIT_SHA_SHORT=${{ steps.build-metadata.outputs.GIT_SHA_SHORT }} + BRANCH_NAME=${{ steps.build-metadata.outputs.BRANCH_NAME }} + PR_NUMBER=${{ github.event.pull_request.number }} + CLIENT_DEV=${{ steps.build-wasm-opt-disabled.outputs.CLIENT_DEV }} + labels: | + org.opencontainers.image.created=${{ steps.build-metadata.outputs.BUILD_TIME }} + org.opencontainers.image.revision=${{ steps.build-metadata.outputs.GIT_SHA_SHORT }} + + - name: Build and push (without cache) + if: steps.build-cache.outputs.CACHE_DISABLED == 'true' + uses: docker/build-push-action@v6 + with: + context: . + file: quadratic-${{ matrix.service }}/Dockerfile + push: true + tags: ${{ steps.create-ecr.outputs.ECR_URL }}:pr-${{ github.event.pull_request.number }} + build-args: | + BUILD_TIME=${{ steps.build-metadata.outputs.BUILD_TIME }} + GIT_SHA_SHORT=${{ steps.build-metadata.outputs.GIT_SHA_SHORT }} + BRANCH_NAME=${{ steps.build-metadata.outputs.BRANCH_NAME }} + PR_NUMBER=${{ github.event.pull_request.number }} + CLIENT_DEV=${{ steps.build-wasm-opt-disabled.outputs.CLIENT_DEV }} + labels: | + org.opencontainers.image.created=${{ steps.build-metadata.outputs.BUILD_TIME }} + org.opencontainers.image.revision=${{ steps.build-metadata.outputs.GIT_SHA_SHORT }} + + - name: Find Build & Deploy Images Comment + uses: peter-evans/find-comment@v3 + if: failure() + id: preview-build-deploy-images-comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Build & Deploy Images" + + - name: Update comment on build images failure + if: failure() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-build-deploy-images-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Build & Deploy Images + ❌ Build images + ❌ Deploy images + + 🔍 Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + edit-mode: replace + + - name: Update deployment status (failure) + if: failure() + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/${{ needs.create_deployment.outputs.deployment_id }}/statuses \ + -f state="failure" \ + -f description="Failed to build ${{ matrix.service }} image" \ + -f environment=preview \ + -f log_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + deploy_images: + name: Deploy Images - Preview + needs: [create_deployment, build_images] + permissions: + contents: read + pull-requests: write + issues: write + deployments: write + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 30 + env: + STACK_NAME: pr-${{ github.event.pull_request.number }} + MAX_ATTEMPTS: 50 + steps: + - name: Find Build & Deploy Images Comment + uses: peter-evans/find-comment@v3 + id: preview-build-deploy-images-comment-start + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Build & Deploy Images" + + - name: Update comment on deploy images start + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-build-deploy-images-comment-start.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Build & Deploy Images + ✅ Build images + ⏳ Deploying images... + + 🔍 Track progress in the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + edit-mode: replace + + - name: Update deployment status (deploying) + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/${{ needs.create_deployment.outputs.deployment_id }}/statuses \ + -f state="in_progress" \ + -f description="Deploying images..." \ + -f environment=preview \ + -f log_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEVELOPMENT }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEVELOPMENT }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Wait for stack deployment + id: check-stack + run: | + ATTEMPTS=0 + echo "Waiting for stack deployment..." + while [ $ATTEMPTS -lt ${{ env.MAX_ATTEMPTS }} ]; do + if ! STATUS=$(aws cloudformation describe-stacks \ + --stack-name ${{ env.STACK_NAME }} \ + --query 'Stacks[0].StackStatus' \ + --output text 2>&1); then + echo "Error getting stack status: $STATUS" + echo "Stack might not exist yet. Waiting..." + sleep 30 + ATTEMPTS=$((ATTEMPTS + 1)) + continue + fi + + echo "Current stack status: $STATUS" + + # Fail if stack is in a failed or rollback state + if [[ $STATUS == *FAILED* ]] || [[ $STATUS == *ROLLBACK* ]]; then + echo "::error::Stack is in a failed or rollback state: $STATUS" + exit 1 + fi + + # Continue if stack is ready + if [[ $STATUS == "CREATE_COMPLETE" ]] || [[ $STATUS == "UPDATE_COMPLETE" ]]; then + echo "::notice::Stack is ready with status: $STATUS" + break + fi + + # Wait and check again if stack is still being created/updated + if [[ $STATUS == *IN_PROGRESS* ]]; then + echo "Stack operation in progress. Waiting 30 seconds..." + sleep 30 + ATTEMPTS=$((ATTEMPTS + 1)) + continue + fi + done + + if [ $ATTEMPTS -eq ${{ env.MAX_ATTEMPTS }} ]; then + echo "::error::Timeout waiting for stack to be ready" + exit 1 + fi + + - name: Get EC2 Instance ID + id: get-instance + run: | + INSTANCE_ID=$(aws cloudformation describe-stack-resources \ + --stack-name ${{ env.STACK_NAME }} \ + --logical-resource-id EC2Instance \ + --query 'StackResources[0].PhysicalResourceId' \ + --output text) + if [ -z "$INSTANCE_ID" ]; then + echo "::error::Failed to get EC2 instance ID" + exit 1 + fi + echo "instance_id=$INSTANCE_ID" >> $GITHUB_OUTPUT + + - name: Wait for instance to be ready + run: | + aws ec2 wait instance-status-ok \ + --instance-ids ${{ steps.get-instance.outputs.instance_id }} + + - name: Run deployment script on EC2 + id: deploy + run: | + COMMAND_ID=$(aws ssm send-command \ + --instance-ids ${{ steps.get-instance.outputs.instance_id }} \ + --document-name "AWS-RunShellScript" \ + --parameters commands=["cd /quadratic-selfhost && ./login.sh && ./pull_start.sh"] \ + --comment "Deploying new images after build" \ + --query 'Command.CommandId' \ + --output text) + + # Wait for command completion + ATTEMPTS=0 + echo "Waiting for deployment command to complete..." + while [ $ATTEMPTS -lt ${{ env.MAX_ATTEMPTS }} ]; do + STATUS=$(aws ssm get-command-invocation \ + --command-id "$COMMAND_ID" \ + --instance-id ${{ steps.get-instance.outputs.instance_id }} \ + --query "Status" \ + --output text 2>/dev/null || echo "Pending") + + echo "Command status: $STATUS" + + if [ "$STATUS" = "Success" ]; then + echo "Deployment completed successfully" + exit 0 + elif [ "$STATUS" = "Failed" ] || [ "$STATUS" = "Cancelled" ] || [ "$STATUS" = "TimedOut" ]; then + echo "Deployment failed with status: $STATUS" + + # Get command output for debugging + aws ssm get-command-invocation \ + --command-id "$COMMAND_ID" \ + --instance-id ${{ steps.get-instance.outputs.instance_id }} \ + --query "StandardOutputContent" \ + --output text + + exit 1 + fi + + echo "Waiting for deployment to complete... (Attempt $((ATTEMPTS + 1))/${{ env.MAX_ATTEMPTS }})" + sleep 30 + ATTEMPTS=$((ATTEMPTS + 1)) + done + + echo "Deployment timed out after ${{ env.MAX_ATTEMPTS }} attempts" + exit 1 + + - name: Delete untagged (old) images + run: | + for service in client api connection files multiplayer; do + REPO_NAME="quadratic-${service}" + + # Get list of untagged image digests + UNTAGGED_IMAGES=$(aws ecr list-images \ + --repository-name "$REPO_NAME" \ + --filter "tagStatus=UNTAGGED" \ + --query 'imageIds[*].imageDigest' \ + --output json) + + # Delete untagged images if any exist + if [ "$UNTAGGED_IMAGES" != "[]" ]; then + echo "Deleting untagged images from $REPO_NAME" + aws ecr batch-delete-image \ + --repository-name "$REPO_NAME" \ + --image-ids "$(echo $UNTAGGED_IMAGES | jq -r 'map({imageDigest: .}) | @json')" + else + echo "No untagged images found in $REPO_NAME" + fi + done + + - name: Generate Deploy Metadata + id: deploy-metadata + run: | + echo "DEPLOY_TIME=$(date -u +'%b %d, %Y at %I:%M %p UTC')" >> $GITHUB_OUTPUT + + - name: Find Build & Deploy Images Comment + uses: peter-evans/find-comment@v3 + if: success() || failure() + id: preview-build-deploy-images-comment-update + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Build & Deploy Images" + + - name: Update comment on deploy images success + if: success() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-build-deploy-images-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Build & Deploy Images + ✅ Build images + ✅ Deploy images + + 🕒 Last deployed: ${{ steps.deploy-metadata.outputs.DEPLOY_TIME }} + + 🔗 URL: https://pr-${{ github.event.pull_request.number }}.quadratic-preview.com + edit-mode: replace + + - name: Update comment on deploy images failure + if: failure() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-build-deploy-images-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Build & Deploy Images + ✅ Build images + ❌ Deploy images + + 🔍 Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + edit-mode: replace + + - name: Update deployment status (success) + if: success() + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/${{ needs.create_deployment.outputs.deployment_id }}/statuses \ + -f state="success" \ + -f environment_url="https://pr-${{ github.event.pull_request.number }}.quadratic-preview.com" \ + -f description="Deployment successful!" \ + -f environment=preview \ + -f log_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update deployment status (failure) + if: failure() + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/deployments/${{ needs.create_deployment.outputs.deployment_id }}/statuses \ + -f state="failure" \ + -f description="Deployment failed" \ + -f environment=preview \ + -f log_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/staging-create-stack.yml b/.github/workflows/staging-create-stack.yml new file mode 100644 index 0000000000..a417166973 --- /dev/null +++ b/.github/workflows/staging-create-stack.yml @@ -0,0 +1,133 @@ +name: Create Stack + +on: + pull_request: + types: [opened, reopened] + +concurrency: + group: pr-${{ github.event.pull_request.number }}-stack + +jobs: + create_stack: + name: Create Stack - Preview + permissions: + contents: read + pull-requests: write + issues: write + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 30 + env: + STACK_NAME: pr-${{ github.event.pull_request.number }} + steps: + - name: Find Create Stack Comment + uses: peter-evans/find-comment@v3 + id: preview-create-stack-comment-start + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Stack" + + - name: Comment on create stack start + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-create-stack-comment-start.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ⏳ Stack creation in progress... + + 🔍 Track progress in the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + edit-mode: replace + + - uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEVELOPMENT }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEVELOPMENT }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Delete Stack if Exists + id: check-stack + run: | + if aws cloudformation describe-stacks --stack-name ${{ env.STACK_NAME }} 2>/dev/null; then + STACK_STATUS=$(aws cloudformation describe-stacks --stack-name ${{ env.STACK_NAME }} --query 'Stacks[0].StackStatus' --output text) + echo "Current stack status: $STACK_STATUS" + + echo "Stack exists. Attempting deletion..." + if aws cloudformation delete-stack --stack-name ${{ env.STACK_NAME }}; then + echo "Waiting for stack deletion to complete..." + if aws cloudformation wait stack-delete-complete --stack-name ${{ env.STACK_NAME }}; then + echo "Stack deleted successfully" + echo "deleted=true" >> $GITHUB_OUTPUT + else + echo "::error::Stack deletion wait timed out" + exit 1 + fi + else + echo "::error::Failed to initiate stack deletion" + exit 1 + fi + else + echo "Stack does not exist" + echo "deleted=false" >> $GITHUB_OUTPUT + fi + + - name: Create Stack + id: create-stack + uses: aws-actions/aws-cloudformation-github-deploy@v1 + with: + name: ${{ env.STACK_NAME }} + template: infra/aws-cloudformation/quadratic-preview.yml + parameter-overrides: >- + ImageTag=pr-${{ github.event.pull_request.number }} + capabilities: CAPABILITY_IAM + no-fail-on-empty-changeset: "1" + disable-rollback: false + termination-protection: false + + - name: Verify Stack Creation + if: success() + run: | + STACK_STATUS=$(aws cloudformation describe-stacks --stack-name ${{ env.STACK_NAME }} --query 'Stacks[0].StackStatus' --output text) + echo "Final stack status: $STACK_STATUS" + if [ "$STACK_STATUS" != "CREATE_COMPLETE" ] && [ "$STACK_STATUS" != "UPDATE_COMPLETE" ]; then + echo "Stack creation did not complete successfully. Status: $STACK_STATUS" + exit 1 + fi + + - name: Find Create Stack Comment + uses: peter-evans/find-comment@v3 + if: success() || failure() + id: preview-create-stack-comment-update + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Stack" + + - name: Update comment on create stack success + if: success() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-create-stack-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ✅ Preview stack created successfully. + + 🔗 URL: ${{ steps.create-stack.outputs.WebsiteURL }} + edit-mode: replace + + - name: Update comment on create stack failure + if: failure() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-create-stack-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ❌ Preview stack creation encountered an error. + + 🔍 Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + edit-mode: replace diff --git a/.github/workflows/staging-delete-stack.yml b/.github/workflows/staging-delete-stack.yml new file mode 100644 index 0000000000..4871e84d82 --- /dev/null +++ b/.github/workflows/staging-delete-stack.yml @@ -0,0 +1,97 @@ +name: Delete Stack + +on: + pull_request: + types: [closed] + +concurrency: + group: pr-${{ github.event.pull_request.number }}-stack + +jobs: + delete_stack: + name: Delete Stack - Preview + permissions: + contents: read + pull-requests: write + issues: write + runs-on: blacksmith-2vcpu-ubuntu-2204 + timeout-minutes: 30 + env: + STACK_NAME: pr-${{ github.event.pull_request.number }} + steps: + - name: Find Delete Stack Comment + uses: peter-evans/find-comment@v3 + id: preview-delete-stack-comment-start + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Stack" + + - name: Comment on delete stack start + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-delete-stack-comment-start.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ⏳ Stack deletion in progress... + + 🔍 Track progress in the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + edit-mode: replace + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_DEVELOPMENT }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_DEVELOPMENT }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Delete CloudFormation Stack + run: | + if aws cloudformation describe-stacks --stack-name ${{ env.STACK_NAME }} 2>/dev/null; then + echo "Deleting stack ${{ env.STACK_NAME }}..." + aws cloudformation delete-stack --stack-name ${{ env.STACK_NAME }} + + echo "Waiting for stack deletion to complete..." + if aws cloudformation wait stack-delete-complete --stack-name ${{ env.STACK_NAME }}; then + echo "Stack deleted successfully" + else + echo "::error::Stack deletion wait timed out" + exit 1 + fi + else + echo "Stack ${{ env.STACK_NAME }} does not exist" + fi + + - name: Find Delete Stack Comment + uses: peter-evans/find-comment@v3 + if: success() || failure() + id: preview-delete-stack-comment-update + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Preview - Stack" + + - name: Update comment on delete stack success + if: success() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-delete-stack-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ✅ Preview stack deleted successfully. + edit-mode: replace + + - name: Update comment on delete stack failure + if: failure() + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.preview-delete-stack-comment-update.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Preview - Stack + ❌ Preview stack deletion encountered an error. + + 🔍 Please check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details. + edit-mode: replace diff --git a/.gitignore b/.gitignore index 513b8e7b81..da9a09843f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,8 @@ node_modules **/node_modules /.pnp .pnp.js - +.cache +.cargo # testing /coverage diff --git a/.vscode/settings.json b/.vscode/settings.json index 668f82a699..7dc8e157e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,17 +2,28 @@ "editor.formatOnSave": true, "cSpell.words": [ "actix", + "autoclean", + "awscli", + "awscliv", "ayush", "bigdecimal", "bincode", "bindgen", + "buildkit", + "Buildx", + "codegen", + "containerd", "CRPXNLSKVLJFHH", "dashmap", "dbgjs", "dcell", "ddimaria", + "dearmor", + "demangle", "dgraph", "dotenv", + "dpkg", + "elif", "endregion", "finitize", "Fuzzysort", @@ -27,17 +38,27 @@ "htmlescape", "indexmap", "indicies", + "initdb", + "isready", "itertools", "jwks", + "keyrings", + "localstack", + "mailslurper", "MDSL", "micropip", "minmax", + "moby", "msdf", + "mysqladmin", "nonblank", "Northbridge", "openai", + "opencontainers", "opensans", + "oryd", "peekable", + "PGUSER", "pixi", "pixiapp", "Plotly", @@ -45,15 +66,25 @@ "pulumi", "pyimport", "rects", + "Referer", "RELCELL", "relcells", "reqwest", "rfind", + "rustup", "scrollend", + "selfhost", + "selfhosted", + "selfservice", + "serde", "shadcn", "Signin", "smallpop", + "smtps", "Southborough", + "sqlcmd", + "sqlservr", + "sslmode", "Strftime", "szhsin", "thiserror", @@ -61,9 +92,14 @@ "trackpad", "undoable", "unspill", + "useblacksmith", + "usermod", "vals", + "vcpu", + "VITE", "websockets", - "Westborough" + "Westborough", + "zstd" ], "editor.codeActionsOnSave": { "source.organizeImports": "explicit" @@ -101,12 +137,15 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[dockercompose]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "ms-azuretools.vscode-docker" }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" } } diff --git a/Cargo.lock b/Cargo.lock index a0405b62d5..dc0aa4cb89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4478,7 +4478,7 @@ dependencies = [ [[package]] name = "quadratic-connection" -version = "0.6.0" +version = "0.6.1" dependencies = [ "arrow 51.0.0", "arrow-schema 51.0.0", @@ -4501,7 +4501,6 @@ dependencies = [ "log", "openssl", "parquet 51.0.0", - "quadratic-core", "quadratic-rust-shared", "reqwest 0.11.27", "serde", @@ -4520,7 +4519,7 @@ dependencies = [ [[package]] name = "quadratic-core" -version = "0.6.0" +version = "0.6.1" dependencies = [ "anyhow", "arrow-array 51.0.0", @@ -4574,7 +4573,7 @@ dependencies = [ [[package]] name = "quadratic-files" -version = "0.6.0" +version = "0.6.1" dependencies = [ "axum", "axum-extra", @@ -4608,7 +4607,7 @@ dependencies = [ [[package]] name = "quadratic-multiplayer" -version = "0.6.0" +version = "0.6.1" dependencies = [ "axum", "axum-extra", @@ -4642,7 +4641,7 @@ dependencies = [ [[package]] name = "quadratic-rust-client" -version = "0.6.0" +version = "0.6.1" dependencies = [ "chrono", "console_error_panic_hook", @@ -4657,7 +4656,7 @@ dependencies = [ [[package]] name = "quadratic-rust-shared" -version = "0.6.0" +version = "0.6.1" dependencies = [ "aes", "arrow 53.2.0", diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000000..6cd7e48e9b --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,28 @@ +FROM node:18 + +SHELL ["/bin/bash", "-c"] + +# Install build-essential, curl, python and python3-pip +RUN echo 'Installing build-essential, curl, python and python3-pip...' && apt-get update && \ + apt-get install -y \ + build-essential \ + curl \ + python-is-python3 \ + python3-pip + +# Install rustup +RUN echo 'Installing rustup...' && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install wasm-pack +RUN echo 'Installing wasm-pack...' && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + +# Install wasm32-unknown-unknown target +RUN echo 'Installing wasm32-unknown-unknown target...' && rustup target add wasm32-unknown-unknown + +# Install cargo-watch +RUN echo 'Installing cargo-watch...' && cargo install cargo-watch + +WORKDIR /quadratic + +CMD ["bash", "-c", "source ~/.bashrc && npm install --no-audit --no-fund && npm run compile --workspace=quadratic-shared && npm run dev:docker"] \ No newline at end of file diff --git a/client.Dockerfile b/client.Dockerfile deleted file mode 100644 index ea1366e10e..0000000000 --- a/client.Dockerfile +++ /dev/null @@ -1,88 +0,0 @@ -# Use an official node image as a parent image -FROM node:18 AS build - -# Install rustup -RUN echo 'Installing rustup...' && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -ENV PATH="/root/.cargo/bin:${PATH}" - -# Install wasm-pack -RUN echo 'Installing wasm-pack...' && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh - -# Install wasm32-unknown-unknown target -RUN rustup target add wasm32-unknown-unknown - -# Install python, binaryen & clean up -RUN apt-get update && apt-get install -y python-is-python3 python3-pip binaryen && apt-get clean && rm -rf /var/lib/apt/lists/* - -# Install npm dependencies -WORKDIR /app -COPY package.json . -COPY package-lock.json . -COPY ./quadratic-kernels/python-wasm/package*.json ./quadratic-kernels/python-wasm/ -COPY ./quadratic-core/package*.json ./quadratic-core/ -COPY ./quadratic-rust-client/package*.json ./quadratic-rust-client/ -COPY ./quadratic-shared/package*.json ./quadratic-shared/ -COPY ./quadratic-client/package*.json ./quadratic-client/ -RUN npm install - -# Install typescript -RUN npm install -D typescript - -# Copy the rest of the application -WORKDIR /app -COPY updateAlertVersion.json . -COPY ./quadratic-kernels/python-wasm/. ./quadratic-kernels/python-wasm/ -COPY ./quadratic-core/. ./quadratic-core/ -COPY ./quadratic-rust-client/. ./quadratic-rust-client/ -COPY ./quadratic-shared/. ./quadratic-shared/ -COPY ./quadratic-client/. ./quadratic-client/ - -# Run the packaging script for quadratic_py -WORKDIR /app -RUN ./quadratic-kernels/python-wasm/package.sh --no-poetry - -# Build wasm -WORKDIR /app/quadratic-core -RUN echo 'Building wasm...' && wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs - -# Export TS/Rust types -WORKDIR /app/quadratic-core -RUN echo 'Exporting TS/Rust types...' && cargo run --bin export_types - -# Build the quadratic-rust-client -WORKDIR /app -ARG GIT_COMMIT -ENV GIT_COMMIT=$GIT_COMMIT -RUN echo 'Building quadratic-rust-client...' && npm run build --workspace=quadratic-rust-client - -# Build the quadratic-shared -WORKDIR /app -RUN echo 'Building quadratic-shared...' && npm run compile --workspace=quadratic-shared - -# Build the front-end -WORKDIR /app -RUN echo 'Building front-end...' -ENV VITE_DEBUG=VITE_DEBUG_VAL -ENV VITE_QUADRATIC_API_URL=VITE_QUADRATIC_API_URL_VAL -ENV VITE_QUADRATIC_MULTIPLAYER_URL=VITE_QUADRATIC_MULTIPLAYER_URL_VAL -ENV VITE_QUADRATIC_CONNECTION_URL=VITE_QUADRATIC_CONNECTION_URL_VAL -ENV VITE_AUTH_TYPE=VITE_AUTH_TYPE_VAL -ENV VITE_ORY_HOST=VITE_ORY_HOST_VAL -RUN npm run build --workspace=quadratic-client - -# The default command to run the application -# CMD ["npm", "run", "start:production"] - -FROM nginx:stable-alpine -COPY --from=build /app/build /usr/share/nginx/html - -EXPOSE 80 443 3000 - -CMD ["nginx", "-g", "daemon off;"] - - - - - - - diff --git a/dev/cli.js b/dev/cli.js index b37db69a5a..7d722ff75a 100644 --- a/dev/cli.js +++ b/dev/cli.js @@ -29,6 +29,7 @@ export class CLI { .option("-O, --rustClient", "Hide RustClient") .option("-E, --hideRustClient", "Hide RustClient") .option("-L, --servicesLocal", "Set Redis & Postgres as running locally") + .option("-D, --dockerDev", "Run dev in docker") .option("-d, --dark", "Use dark theme") .showHelpAfterError(); program.parse(); diff --git a/dev/cli.ts b/dev/cli.ts index b6bdaa3d64..ccf1ffaffa 100644 --- a/dev/cli.ts +++ b/dev/cli.ts @@ -23,6 +23,7 @@ export class CLI { hidePython: boolean; hideRustClient: boolean; servicesLocal: boolean; + dockerDev: boolean; dark: boolean; }; @@ -62,6 +63,7 @@ export class CLI { .option("-O, --rustClient", "Hide RustClient") .option("-E, --hideRustClient", "Hide RustClient") .option("-L, --servicesLocal", "Set Redis & Postgres as running locally") + .option("-D, --dockerDev", "Run dev in docker") .option("-d, --dark", "Use dark theme") .showHelpAfterError(); diff --git a/dev/compile.sh b/dev/compile.sh index 44fc06dc96..37f948c500 100755 --- a/dev/compile.sh +++ b/dev/compile.sh @@ -1,4 +1,4 @@ #!/bin/bash -tsc --module nodenext --moduleResolution nodenext dev/index.ts +npx tsc --module nodenext --moduleResolution nodenext --skipLibCheck true dev/index.ts node dev/index.js \ No newline at end of file diff --git a/dev/control.js b/dev/control.js index 17395f08d9..f11609ce9c 100644 --- a/dev/control.js +++ b/dev/control.js @@ -446,7 +446,11 @@ export class Control { this.signals.rustClient = new AbortController(); this.rustClient = spawn("npm", [ "run", - this.cli.options.rustClient ? (this.cli.options.perf ? "dev:perf" : "dev") : "build", + this.cli.options.rustClient + ? this.cli.options.perf + ? "dev:perf" + : "dev" + : "build", "--workspace=quadratic-rust-client", ], { signal: this.signals.rustClient.signal }); this.ui.printOutput("rustClient", (data) => this.handleResponse("rustClient", data, { @@ -521,10 +525,14 @@ export class Control { return new Promise((resolve) => { if (this.quitting) resolve(false); + const dockerDev = this.cli.options.dockerDev; + if (dockerDev) { + resolve(true); + } const servicesLocal = this.cli.options.servicesLocal; const redis = servicesLocal ? spawn("redis-cli", ["ping"]) - : spawn("docker", ["exec", "quadratic-redis-1", "redis-cli", "ping"]); + : spawn("docker", ["exec", "redis", "redis-cli", "ping"]); redis.on("error", (e) => { if (e.code === "ENOENT") { resolve("not found"); @@ -539,6 +547,10 @@ export class Control { return new Promise((resolve) => { if (this.quitting) resolve(false); + const dockerDev = this.cli.options.dockerDev; + if (dockerDev) { + resolve(true); + } const servicesLocal = this.cli.options.servicesLocal; const postgres = servicesLocal ? spawn("pg_isready") diff --git a/dev/control.ts b/dev/control.ts index 3f520ada5a..484e63c5b0 100644 --- a/dev/control.ts +++ b/dev/control.ts @@ -531,7 +531,11 @@ export class Control { "npm", [ "run", - this.cli.options.rustClient ? (this.cli.options.perf ? "dev:perf" :"dev") : "build", + this.cli.options.rustClient + ? this.cli.options.perf + ? "dev:perf" + : "dev" + : "build", "--workspace=quadratic-rust-client", ], { signal: this.signals.rustClient.signal } @@ -608,10 +612,14 @@ export class Control { isRedisRunning(): Promise { return new Promise((resolve) => { if (this.quitting) resolve(false); + const dockerDev = this.cli.options.dockerDev; + if (dockerDev) { + resolve(true); + } const servicesLocal = this.cli.options.servicesLocal; const redis = servicesLocal ? spawn("redis-cli", ["ping"]) - : spawn("docker", ["exec", "quadratic-redis-1", "redis-cli", "ping"]); + : spawn("docker", ["exec", "redis", "redis-cli", "ping"]); redis.on("error", (e: any) => { if (e.code === "ENOENT") { resolve("not found"); @@ -626,6 +634,10 @@ export class Control { isPostgresRunning(): Promise { return new Promise((resolve) => { if (this.quitting) resolve(false); + const dockerDev = this.cli.options.dockerDev; + if (dockerDev) { + resolve(true); + } const servicesLocal = this.cli.options.servicesLocal; const postgres = servicesLocal ? spawn("pg_isready") diff --git a/dev/input.js b/dev/input.js index d8836222e1..ad1ecf0989 100644 --- a/dev/input.js +++ b/dev/input.js @@ -139,6 +139,9 @@ export class Input { this.cli.options.servicesLocal = !this.cli.options.servicesLocal; this.control.checkServices(); break; + case "D": + this.cli.options.dockerDev = !this.cli.options.dockerDev; + break; } }; } diff --git a/dev/input.ts b/dev/input.ts index 3cc411fd6a..584ce6f22e 100644 --- a/dev/input.ts +++ b/dev/input.ts @@ -148,6 +148,10 @@ export class Input { this.cli.options.servicesLocal = !this.cli.options.servicesLocal; this.control.checkServices(); break; + case "D": + this.cli.options.dockerDev = !this.cli.options.dockerDev; + this.control.checkServices(); + break; } }; } diff --git a/docker-compose.base.yml b/docker-compose.base.yml deleted file mode 100644 index 278c217a08..0000000000 --- a/docker-compose.base.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: "3.8" - -services: - redis: - image: redis/redis-stack:latest - restart: always - ports: - - "6379:6379" - - "8001:8001" - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: "5s" - networks: - - host - - postgres: - image: postgres:15 - restart: always - container_name: postgres - ports: - - "5432:5432" - environment: - POSTGRES_USER: postgres - PGUSER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] - interval: 10s - timeout: 5s - retries: 5 - - localstack: - container_name: "${LOCALSTACK_DOCKER_NAME:-localstack}" - image: localstack/localstack:latest - ports: - - "127.0.0.1:4566:4566" # LocalStack Gateway - - "127.0.0.1:4510-4559:4510-4559" - environment: - # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ - - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:-} - - DEBUG=${DEBUG:-0} - - SERVICES=s3:4566 - - HOSTNAME=localstack - - HOSTNAME_EXTERNAL=localstack - - DEFAULT_REGION=us-east-2 - - DISABLE_CUSTOM_CORS_S3=true - - DISABLE_CORS_CHECKS=true - - EXTRA_CORS_ALLOWED_ORIGINS=* - networks: - - host - -networks: - host: diff --git a/docker-compose.build.yml b/docker-compose.build.yml new file mode 100644 index 0000000000..8bec62417c --- /dev/null +++ b/docker-compose.build.yml @@ -0,0 +1,37 @@ +# used to build the images locally + +services: + # quadratic services - client, api, multiplayer, files, connection + + quadratic-client: + build: + context: . + dockerfile: quadratic-client/Dockerfile + args: + CLIENT_DEV: ${CLIENT_DEV} + + quadratic-api: + build: + context: . + dockerfile: quadratic-api/Dockerfile + + quadratic-multiplayer: + build: + context: . + dockerfile: quadratic-multiplayer/Dockerfile + args: + - binary=quadratic-multiplayer + + quadratic-files: + build: + context: . + dockerfile: quadratic-files/Dockerfile + args: + - binary=quadratic-files + + quadratic-connection: + build: + context: . + dockerfile: quadratic-connection/Dockerfile + args: + - binary=quadratic-connection diff --git a/docker-compose.ecr.yml b/docker-compose.ecr.yml new file mode 100644 index 0000000000..0050c8f72c --- /dev/null +++ b/docker-compose.ecr.yml @@ -0,0 +1,19 @@ +# used to pull production images from ECR + +services: + # quadratic services - client, api, multiplayer, files, connection + + quadratic-client: + image: ${ECR_URL}/quadratic-client:${IMAGE_TAG} + + quadratic-api: + image: ${ECR_URL}/quadratic-api:${IMAGE_TAG} + + quadratic-multiplayer: + image: ${ECR_URL}/quadratic-multiplayer:${IMAGE_TAG} + + quadratic-files: + image: ${ECR_URL}/quadratic-files:${IMAGE_TAG} + + quadratic-connection: + image: ${ECR_URL}/quadratic-connection:${IMAGE_TAG} diff --git a/docker-compose.yml b/docker-compose.yml index db29d07208..92ba64c050 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,225 +1,401 @@ -version: "3.8" - services: + # base services - redis, postgres + redis: - extends: - file: docker-compose.base.yml - service: redis + image: redis/redis-stack:latest + container_name: redis + restart: always + ports: + - "6379:6379" + - "8001:8001" + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: "5s" volumes: - - ./docker/redis/data:/data + - ./docker/redis/data:/data:rw profiles: - base + - dev + - all postgres: - extends: - file: docker-compose.base.yml - service: postgres + image: postgres:15 + container_name: postgres + restart: always + ports: + - "5432:5432" environment: + POSTGRES_USER: postgres + PGUSER: postgres + POSTGRES_PASSWORD: postgres ADDITIONAL_DATABASES: kratos + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres -d postgres" ] + interval: 10s + timeout: 5s + retries: 5 volumes: - - ./docker/postgres/data:/var/lib/postgresql/data + - ./docker/postgres/data:/var/lib/postgresql/data:rw - ./docker/postgres/scripts:/docker-entrypoint-initdb.d profiles: - base + - dev + - all + + # files service - local aws alternative, use this or file-storage localstack: - extends: - file: docker-compose.base.yml - service: localstack + container_name: "${LOCALSTACK_DOCKER_NAME:-localstack}" + image: localstack/localstack:latest + ports: + - "127.0.0.1:4566:4566" # LocalStack Gateway + - "127.0.0.1:4510-4559:4510-4559" + environment: + # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ + - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:-} + - DEBUG=${DEBUG:-0} + - SERVICES=s3:4566 + - HOSTNAME=localstack + - HOSTNAME_EXTERNAL=localstack + - DEFAULT_REGION=us-east-2 + - DISABLE_CUSTOM_CORS_S3=true + - DISABLE_CORS_CHECKS=true + - EXTRA_CORS_ALLOWED_ORIGINS=* volumes: - "./docker/localstack/scripts/init-aws.sh:/etc/localstack/init/ready.d/init-aws.sh" # ready hook - - "./docker/localstack/data:/var/lib/localstack" - - "/var/run/docker.sock:/var/run/docker.sock" + - "./docker/localstack/data:/var/lib/localstack:rw" + - "/var/run/docker.sock:/var/run/docker.sock:rw" profiles: - base + networks: + - host - # nginx: - # restart: always - # image: nginx:1-alpine - # ports: - # - 80:80 - # - 443:443 - # - 3000:80 - # volumes: - # - ./docker/static/html:/usr/share/nginx/html - # - ./docker/static/conf/:/etc/nginx/conf.d/:ro - # - ./docker/static/certs:/etc/nginx/ssl - # # - ./docker/static/default.conf:/etc/nginx/conf.d/default.conf - # depends_on: - # - quadratic-api - # # networks: - # # - host - - # https-portal: - # image: steveltn/https-portal:1 - # ports: - # - "80:80" - # - "443:443" - # restart: always - # env_file: - # - quadratic-client/.env.local - # - quadratic-client/.env.docker - # # override env vars here - # environment: - # DOMAINS: > - # quadratic.lvh.me, - # quadratic-api.lvh.me -> http://quadratic-api:8000, - # quadratic-multiplayer.lvh.me -> http://quadratic-multiplayer:3001/ws, - # quadratic-localstack.lvh.me -> http://localstack:4566 - # STAGE: "local" # Use 'production' to use a LetsEncrypt signed SSL cert - # FORCE_RENEW: "false" - # WEBSOCKET: "true" - - # VITE_QUADRATIC_API_URL: http://0.0.0.0:8000 - # VITE_QUADRATIC_MULTIPLAYER_URL: ws://0.0.0.0:3001 - # VITE_QUADRATIC_CONNECTION_URL: http://0.0.0.0:3003 - # depends_on: - # # - quadratic-client - # - quadratic-api - # volumes: - # - ./docker/https-portal/data:/var/lib/https-portal - # - ./docker/https-portal/vhosts:/var/www/vhosts - # - ./docker/https-portal/quadratic.lvh.me.conf.erb:/var/lib/nginx-conf/quadratic.lvh.me.conf.erb:ro - # - ./docker/https-portal/quadratic.lvh.me.ssl.conf.erb:/var/lib/nginx-conf/quadratic.lvh.me.ssl.conf.erb:ro + # quadratic services - client, api, multiplayer, files, connection quadratic-client: - build: - context: . - dockerfile: client.Dockerfile + extends: + file: docker-compose.${ECR_OR_BUILD}.yml + service: quadratic-client + container_name: client environment: VITE_DEBUG: 1 - VITE_QUADRATIC_API_URL: http://localhost:8000 - VITE_QUADRATIC_MULTIPLAYER_URL: ws://localhost:3001/ws - VITE_QUADRATIC_CONNECTION_URL: http://localhost:3003 - VITE_AUTH_TYPE: ory - VITE_ORY_HOST: http://localhost:4433 - restart: "always" + VITE_QUADRATIC_API_URL: ${QUADRATIC_API_URL_EXTERNAL} + VITE_QUADRATIC_MULTIPLAYER_URL: ${QUADRATIC_MULTIPLAYER_URL_EXTERNAL} + VITE_QUADRATIC_CONNECTION_URL: ${QUADRATIC_CONNECTION_URL_EXTERNAL} + VITE_AUTH_TYPE: ${AUTH_TYPE} + VITE_STORAGE_TYPE: ${STORAGE_TYPE} + VITE_ORY_HOST: ${KRATOS_URL_EXTERNAL} + VITE_SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN} ports: - # - "3000:3000" - - 80:80 - - 443:443 - 3000:80 - # command: "npm run start:production --workspace=quadratic-client" + command: > + sh -c "/client/scripts/replace_env_vars.sh && + nginx -g \"daemon off;\"" + healthcheck: + test: [ "CMD-SHELL", "curl -f http://host.docker.internal:3000/ || exit 1" ] + interval: 10s + timeout: 5s + restart: "always" + volumes: + - ./docker/client:/client + - ./docker/client/config/default.conf:/etc/nginx/conf.d/default.conf depends_on: - postgres: - condition: service_healthy + quadratic-api: + condition: service_started profiles: - - client + - quadratic-client - frontend - volumes: - # - ./docker/static/html:/usr/share/nginx/html - - ./docker/static/conf/:/etc/nginx/conf.d/:ro - - ./docker/static/certs:/etc/nginx/ssl - # - ./docker/static/default.conf:/etc/nginx/conf.d/default.conf - # networks: - # - host + - all + networks: + - host + extra_hosts: + - "host.docker.internal:host-gateway" quadratic-api: - build: - context: . - dockerfile: quadratic-api/Dockerfile - env_file: - - quadratic-api/.env - - quadratic-api/.env.docker - # override env vars here + extends: + file: docker-compose.${ECR_OR_BUILD}.yml + service: quadratic-api + container_name: api environment: - AWS_S3_ENDPOINT: https://localhost/localstack + CORS: "*" + DATABASE_URL: ${DATABASE_DSN} + ENVIRONMENT: ${ENVIRONMENT} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} + OPENAI_API_KEY: ${OPENAI_API_KEY} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + EXA_API_KEY: ${EXA_API_KEY} + AWS_S3_REGION: ${AWS_S3_REGION} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + AWS_S3_ACCESS_KEY_ID: ${AWS_S3_ACCESS_KEY_ID} + AWS_S3_SECRET_ACCESS_KEY: ${AWS_S3_SECRET_ACCESS_KEY} + M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + AUTH_TYPE: ${AUTH_TYPE} + ORY_JWKS_URI: ${JWKS_URI} + ORY_ADMIN_HOST: ${ORY_ADMIN_HOST} + STORAGE_TYPE: ${STORAGE_TYPE} + QUADRATIC_FILE_URI: ${QUADRATIC_FILES_URL_INTERNAL} + QUADRATIC_FILE_URI_PUBLIC: ${QUADRATIC_FILES_URL_EXTERNAL} + LICENSE_KEY: ${LICENSE_KEY} restart: "always" ports: - "8000:8000" - command: "npm run start:prod --workspace=quadratic-api" + command: bash -c "npx prisma migrate deploy --schema quadratic-api/prisma/schema.prisma && npm run start:prod --workspace=quadratic-api" depends_on: postgres: condition: service_healthy profiles: - api - - frontend + - backend + - all + networks: + - host + extra_hosts: + - "host.docker.internal:host-gateway" quadratic-multiplayer: - build: - context: . - dockerfile: quadratic-multiplayer/Dockerfile - args: - - binary=quadratic-multiplayer - env_file: - - quadratic-multiplayer/.env.docker - # override env vars here + extends: + file: docker-compose.${ECR_OR_BUILD}.yml + service: quadratic-multiplayer + container_name: multiplayer environment: - QUADRATIC_API_URI: http://host.docker.internal:8000 - RUST_LOG: info + RUST_LOG: ${QUADRATIC_MULTIPLAYER_RUST_LOG} + MULTIPLAYER__HOST: ${QUADRATIC_MULTIPLAYER_HOST} + MULTIPLAYER__PORT: ${QUADRATIC_MULTIPLAYER_PORT} + MULTIPLAYER__HEARTBEAT_CHECK_S: ${QUADRATIC_MULTIPLAYER_HEARTBEAT_CHECK_S} + MULTIPLAYER__HEARTBEAT_TIMEOUT_S: ${QUADRATIC_MULTIPLAYER_HEARTBEAT_TIMEOUT_S} + MULTIPLAYER__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + MULTIPLAYER__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + MULTIPLAYER__ENVIRONMENT: ${ENVIRONMENT} + MULTIPLAYER__PUBSUB_HOST: ${PUBSUB_HOST} + MULTIPLAYER__PUBSUB_PORT: ${PUBSUB_PORT} + MULTIPLAYER__PUBSUB_PASSWORD: ${PUBSUB_PASSWORD} + MULTIPLAYER__PUBSUB_ACTIVE_CHANNELS: ${PUBSUB_ACTIVE_CHANNELS} + MULTIPLAYER__AUTH0_JWKS_URI: ${JWKS_URI} + MULTIPLAYER__AUTHENTICATE_JWT: true restart: "always" ports: - "3001:3001" depends_on: - postgres: - condition: service_healthy redis: condition: service_healthy + quadratic-client: + condition: service_healthy quadratic-api: condition: service_started profiles: - - backend - quadratic-multiplayer + - backend + - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" quadratic-files: - build: - context: . - dockerfile: quadratic-files/Dockerfile - args: - - binary=quadratic-files - env_file: - - quadratic-files/.env.docker - # override env vars here + extends: + file: docker-compose.${ECR_OR_BUILD}.yml + service: quadratic-files + container_name: files environment: - QUADRATIC_API_URI: http://host.docker.internal:8000 - RUST_LOG: info + RUST_LOG: ${QUADRATIC_FILES_RUST_LOG} + FILES__HOST: ${QUADRATIC_FILES_HOST} + FILES__PORT: ${QUADRATIC_FILES_PORT} + FILES__FILE_CHECK_S: ${QUADRATIC_FILES_FILE_CHECK_S} + FILES__FILES_PER_CHECK: ${QUADRATIC_FILES_FILES_PER_CHECK} + FILES__TRUNCATE_FILE_CHECK_S: ${QUADRATIC_FILES_TRUNCATE_FILE_CHECK_S} + FILES__TRUNCATE_TRANSACTION_AGE_DAYS: ${QUADRATIC_FILES_TRUNCATE_TRANSACTION_AGE_DAYS} + FILES__ENVIRONMENT: ${ENVIRONMENT} + FILES__AUTH0_JWKS_URI: ${JWKS_URI} + FILES__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + FILES__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + FILES__PUBSUB_HOST: ${PUBSUB_HOST} + FILES__PUBSUB_PORT: ${PUBSUB_PORT} + FILES__PUBSUB_PASSWORD: ${PUBSUB_PASSWORD} + FILES__PUBSUB_ACTIVE_CHANNELS: ${PUBSUB_ACTIVE_CHANNELS} + FILES__PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL: ${PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL} + FILES__STORAGE_TYPE: ${STORAGE_TYPE} + FILES__AWS_S3_REGION: ${AWS_S3_REGION} + FILES__AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + FILES__AWS_S3_ACCESS_KEY_ID: ${AWS_S3_ACCESS_KEY_ID} + FILES__AWS_S3_SECRET_ACCESS_KEY: ${AWS_S3_SECRET_ACCESS_KEY} + FILES__STORAGE_DIR: ${STORAGE_DIR} + FILES__STORAGE_ENCRYPTION_KEYS: ${ENCRYPTION_KEY} restart: "always" ports: - "3002:3002" + volumes: + - ./docker/file-storage:/file-storage:rw depends_on: - postgres: - condition: service_healthy redis: condition: service_healthy + quadratic-client: + condition: service_healthy quadratic-api: condition: service_started profiles: - - backend - quadratic-files + - backend + - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" quadratic-connection: - build: - context: . - dockerfile: quadratic-connection/Dockerfile - args: - - binary=quadratic-connection - env_file: - - quadratic-connection/.env.docker - # override env vars here + extends: + file: docker-compose.${ECR_OR_BUILD}.yml + service: quadratic-connection + container_name: connection environment: - QUADRATIC_API_URI: http://host.docker.internal:8000 - RUST_LOG: info + RUST_LOG: ${QUADRATIC_CONNECTION_RUST_LOG} + CONNECTION__HOST: ${QUADRATIC_CONNECTION_HOST} + CONNECTION__PORT: ${QUADRATIC_CONNECTION_PORT} + CONNECTION__ENVIRONMENT: ${ENVIRONMENT} + CONNECTION__AUTH0_JWKS_URI: ${JWKS_URI} + CONNECTION__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + CONNECTION__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + CONNECTION__MAX_RESPONSE_BYTES: ${QUADRATIC_CONNECTION_MAX_RESPONSE_BYTES} + CONNECTION__STATIC_IPS: ${QUADRATIC_CONNECTION_STATIC_IPS} restart: "always" ports: - "3003:3003" - # depends_on: - # postgres: - # condition: service_healthy - # quadratic-api: - # condition: service_started - + depends_on: + quadratic-client: + condition: service_healthy + quadratic-api: + condition: service_started profiles: - - backend - quadratic-connection + - backend + - all + networks: + - host + extra_hosts: + - "host.docker.internal:host-gateway" - # Auth Providers + quadratic-dev: + build: + context: . + dockerfile: Dockerfile.dev + container_name: quadratic-dev + environment: + # common + RUST_LOG: ${RUST_LOG} + # client + VITE_DEBUG: 1 + VITE_QUADRATIC_API_URL: ${QUADRATIC_API_URL_EXTERNAL} + VITE_QUADRATIC_MULTIPLAYER_URL: ${QUADRATIC_MULTIPLAYER_URL_EXTERNAL} + VITE_QUADRATIC_CONNECTION_URL: ${QUADRATIC_CONNECTION_URL_EXTERNAL} + VITE_AUTH_TYPE: ${AUTH_TYPE} + VITE_STORAGE_TYPE: ${STORAGE_TYPE} + VITE_ORY_HOST: ${KRATOS_URL_EXTERNAL} + VITE_SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN} + # api + CORS: "*" + DATABASE_URL: ${DATABASE_DSN} + STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} + STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET} + OPENAI_API_KEY: ${OPENAI_API_KEY} + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY} + EXA_API_KEY: ${EXA_API_KEY} + AWS_S3_REGION: ${AWS_S3_REGION} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + AWS_S3_ACCESS_KEY_ID: ${AWS_S3_ACCESS_KEY_ID} + AWS_S3_SECRET_ACCESS_KEY: ${AWS_S3_SECRET_ACCESS_KEY} + M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} + AUTH_TYPE: ${AUTH_TYPE} + ORY_JWKS_URI: ${JWKS_URI} + ORY_ADMIN_HOST: ${ORY_ADMIN_HOST} + STORAGE_TYPE: ${STORAGE_TYPE} + QUADRATIC_FILE_URI: ${QUADRATIC_FILES_URL_INTERNAL} + QUADRATIC_FILE_URI_PUBLIC: ${QUADRATIC_FILES_URL_EXTERNAL} + LICENSE_KEY: ${LICENSE_KEY} + # multiplayer + MULTIPLAYER__HOST: ${QUADRATIC_MULTIPLAYER_HOST} + MULTIPLAYER__PORT: ${QUADRATIC_MULTIPLAYER_PORT} + MULTIPLAYER__HEARTBEAT_CHECK_S: ${QUADRATIC_MULTIPLAYER_HEARTBEAT_CHECK_S} + MULTIPLAYER__HEARTBEAT_TIMEOUT_S: ${QUADRATIC_MULTIPLAYER_HEARTBEAT_TIMEOUT_S} + MULTIPLAYER__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + MULTIPLAYER__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + MULTIPLAYER__ENVIRONMENT: ${ENVIRONMENT} + MULTIPLAYER__PUBSUB_HOST: ${PUBSUB_HOST} + MULTIPLAYER__PUBSUB_PORT: ${PUBSUB_PORT} + MULTIPLAYER__PUBSUB_PASSWORD: ${PUBSUB_PASSWORD} + MULTIPLAYER__PUBSUB_ACTIVE_CHANNELS: ${PUBSUB_ACTIVE_CHANNELS} + MULTIPLAYER__AUTH0_JWKS_URI: ${JWKS_URI} + MULTIPLAYER__AUTHENTICATE_JWT: true + # files + FILES__HOST: ${QUADRATIC_FILES_HOST} + FILES__PORT: ${QUADRATIC_FILES_PORT} + FILES__FILE_CHECK_S: ${QUADRATIC_FILES_FILE_CHECK_S} + FILES__FILES_PER_CHECK: ${QUADRATIC_FILES_FILES_PER_CHECK} + FILES__TRUNCATE_FILE_CHECK_S: ${QUADRATIC_FILES_TRUNCATE_FILE_CHECK_S} + FILES__TRUNCATE_TRANSACTION_AGE_DAYS: ${QUADRATIC_FILES_TRUNCATE_TRANSACTION_AGE_DAYS} + FILES__ENVIRONMENT: ${ENVIRONMENT} + FILES__AUTH0_JWKS_URI: ${JWKS_URI} + FILES__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + FILES__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + FILES__PUBSUB_HOST: ${PUBSUB_HOST} + FILES__PUBSUB_PORT: ${PUBSUB_PORT} + FILES__PUBSUB_PASSWORD: ${PUBSUB_PASSWORD} + FILES__PUBSUB_ACTIVE_CHANNELS: ${PUBSUB_ACTIVE_CHANNELS} + FILES__PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL: ${PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL} + FILES__STORAGE_TYPE: ${STORAGE_TYPE} + FILES__AWS_S3_REGION: ${AWS_S3_REGION} + FILES__AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + FILES__AWS_S3_ACCESS_KEY_ID: ${AWS_S3_ACCESS_KEY_ID} + FILES__AWS_S3_SECRET_ACCESS_KEY: ${AWS_S3_SECRET_ACCESS_KEY} + FILES__STORAGE_DIR: ${STORAGE_DIR} + FILES__STORAGE_ENCRYPTION_KEYS: ${ENCRYPTION_KEY} + # connection + CONNECTION__HOST: ${QUADRATIC_CONNECTION_HOST} + CONNECTION__PORT: ${QUADRATIC_CONNECTION_PORT} + CONNECTION__ENVIRONMENT: ${ENVIRONMENT} + CONNECTION__AUTH0_JWKS_URI: ${JWKS_URI} + CONNECTION__QUADRATIC_API_URI: ${QUADRATIC_API_URL_INTERNAL} + CONNECTION__M2M_AUTH_TOKEN: ${M2M_AUTH_TOKEN} + CONNECTION__MAX_RESPONSE_BYTES: ${QUADRATIC_CONNECTION_MAX_RESPONSE_BYTES} + CONNECTION__STATIC_IPS: ${QUADRATIC_CONNECTION_STATIC_IPS} + ports: + - "3000:3000" + - "8000:8000" + - "3001:3001" + - "3002:3002" + - "3003:3003" + volumes: + - ./:/quadratic:rw + - ./docker/file-storage:/file-storage:rw + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + ory-auth: + condition: service_started + ory-auth-migrate: + condition: service_started + ory-auth-node: + condition: service_started + ory-auth-mail: + condition: service_started + profiles: + - quadratic-dev + - dev + networks: + - host + extra_hosts: + - "host.docker.internal:host-gateway" + tty: true + stdin_open: true + + # auth service - ory ory-auth: image: oryd/kratos:v1.2.0 + container_name: ory-auth ports: - "4433:4433" # public - "4434:4434" # admin @@ -227,55 +403,68 @@ services: volumes: - ./docker/ory-auth/config:/etc/config/kratos environment: - DSN: postgresql://postgres:postgres@host.docker.internal:5432/kratos?sslmode=disable - LOG_LEVEL: trace + DSN: ${ORY_DSN} + LOG_LEVEL: ${ORY_LOG_LEVEL} restart: unless-stopped depends_on: - postgres - ory-auth-migrate profiles: - ory + - dev - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" ory-auth-migrate: image: oryd/kratos:v1.2.0 + container_name: ory-auth-migrate command: migrate -c /etc/config/kratos/kratos.yml sql -e --yes volumes: - ./docker/ory-auth/config:/etc/config/kratos environment: - DSN: postgresql://postgres:postgres@host.docker.internal:5432/kratos?sslmode=disable + DSN: ${ORY_DSN} restart: on-failure depends_on: - postgres profiles: - ory + - dev - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" ory-auth-node: image: oryd/kratos-selfservice-ui-node:v1.2.0 + container_name: ory-auth-node ports: - "4455:4455" environment: - PORT: 4455 - SECURITY_MODE: - KRATOS_PUBLIC_URL: http://host.docker.internal:4433/ - KRATOS_BROWSER_URL: http://localhost:4433/ - COOKIE_SECRET: changeme - CSRF_COOKIE_NAME: ory_csrf_ui - CSRF_COOKIE_SECRET: changeme + PORT: ${KRATOS_NODE_PORT} + KRATOS_PUBLIC_URL: ${KRATOS_URL_INTERNAL} + KRATOS_BROWSER_URL: ${KRATOS_URL_EXTERNAL} + COOKIE_SECRET: ${KRATOS_COOKIE_SECRET} + CSRF_COOKIE_NAME: ${KRATOS_CSRF_COOKIE_NAME} + CSRF_COOKIE_SECRET: ${KRATOS_CSRF_COOKIE_SECRET} restart: on-failure + depends_on: + - ory-auth profiles: - ory + - dev - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" ory-auth-mail: image: oryd/mailslurper:latest-smtps + container_name: ory-auth-mail ports: - "1025:1025" - "4436:4436" @@ -283,11 +472,14 @@ services: - "8080:8080" profiles: - ory + - dev - all networks: - host + extra_hosts: + - "host.docker.internal:host-gateway" - # Databases to be used for testing by the connection service + # databases to be used for testing by the connection service - postgres, mysql, mssql postgres-connection: image: postgres:15 @@ -301,12 +493,12 @@ services: PGUSER: user POSTGRES_PASSWORD: password healthcheck: - test: ["CMD-SHELL", "pg_isready -U user -d postgres-connection"] + test: [ "CMD-SHELL", "pg_isready -U user -d postgres-connection" ] interval: 10s timeout: 5s retries: 5 volumes: - - ./docker/postgres-connection/data:/var/lib/postgresql/data + - postgres-connection-data:/var/lib/postgresql/data - ./docker/postgres-connection/scripts:/docker-entrypoint-initdb.d profiles: - quadratic-connection @@ -325,12 +517,12 @@ services: MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] interval: 10s timeout: 5s retries: 5 volumes: - - ./docker/mysql-connection/data:/var/lib/mysql + - mysql-connection-data:/var/lib/mysql - ./docker/mysql-connection/scripts:/docker-entrypoint-initdb.d/ profiles: - quadratic-connection @@ -348,33 +540,32 @@ services: ACCEPT_EULA: Y MSSQL_SA_PASSWORD: yourStrong(!)Password MSSQL_PID: Express + volumes: + - mssql-connection-data:/var/opt/mssql + - ./docker/mssql-connection/scripts:/docker-entrypoint-initdb.d/ healthcheck: - test: - [ - "CMD-SHELL", - "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P '${MSSQL_SA_PASSWORD}' -Q 'SELECT 1' || exit 1", - ] + test: [ "CMD", "/opt/mssql-tools18/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "yourStrong(!)Password", "-Q", "SELECT 1", "-C", "-N", "-t", "30" ] interval: 10s timeout: 5s + start_period: 30s retries: 5 - volumes: - - ./docker/mssql-connection/data:/var/opt/mssql - - ./docker/mssql-connection/scripts:/docker-entrypoint-initdb.d/ command: > - bash -c " - /opt/mssql/bin/sqlservr & - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -i /docker-entrypoint-initdb.d/create_db.sql -C && - /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -i /docker-entrypoint-initdb.d/seed_db.sql -C && - tail -f /dev/null - " + bash -c " /opt/mssql/bin/sqlservr & sleep 10 && /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yourStrong(!)Password' -i /docker-entrypoint-initdb.d/create_db.sql -C -N -t 30 && /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'yourStrong(!)Password' -i /docker-entrypoint-initdb.d/seed_db.sql -C -N -t 30 && tail -f /dev/null" profiles: - quadratic-connection - quadratic-connection-db - quadratic-connection-db-mssql volumes: - docker: - name: docker + postgres-connection-data: + name: postgres-connection-data + driver: local + mysql-connection-data: + name: mysql-connection + driver: local + mssql-connection-data: + name: mssql-connection-data + driver: local networks: host: diff --git a/docker/client/config/default.conf b/docker/client/config/default.conf new file mode 100644 index 0000000000..a58529d83b --- /dev/null +++ b/docker/client/config/default.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html =404; + } + + location ~* \.(?:css|js|json|gif|png|jpg|jpeg|svg|ico)$ { + expires 1y; + access_log off; + add_header Cache-Control "public, no-transform"; + + add_header Cross-Origin-Opener-Policy "same-origin"; + add_header Cross-Origin-Embedder-Policy "require-corp"; + } + + # Add CORS headers to all requests + add_header Cross-Origin-Opener-Policy "same-origin"; + add_header Cross-Origin-Embedder-Policy "require-corp"; +} diff --git a/docker/client/scripts/replace_env_vars.sh b/docker/client/scripts/replace_env_vars.sh new file mode 100755 index 0000000000..60160a8ccf --- /dev/null +++ b/docker/client/scripts/replace_env_vars.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +escape_for_sed() { + input="$1" + printf '%s\n' "$input" | sed -e 's/[\/&]/\\&/g' +} + +replace_env_vars() { + vite_vars="" + + for env_var in $(env); do + case "$env_var" in + VITE_*) + vite_vars="$vite_vars $env_var" + ;; + esac + done + + find "/usr/share/nginx/html/assets" -type f -name "*.js" | xargs grep -l "VITE_" | while read file; do + + for env_var in $vite_vars; do + var="$(echo "$env_var" | cut -d'=' -f1)" + val="$(echo "$env_var" | cut -d'=' -f2-)" + appended_var="${var}_VAL" + escaped_val=$(escape_for_sed "$val") + + # echo "Replacing $appended_var with $escaped_val in $file" + sed -i "s/${appended_var}/${escaped_val}/g" "$file" + done + done +} + +echo "Replacing .env values in $ENV_PATH" +replace_env_vars diff --git a/docker/file-storage/.gitignore b/docker/file-storage/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docker/ory-auth/config/kratos.yml b/docker/ory-auth/config/kratos.yml index c815b5713f..60381666f0 100644 --- a/docker/ory-auth/config/kratos.yml +++ b/docker/ory-auth/config/kratos.yml @@ -70,7 +70,7 @@ selfservice: ui_url: http://localhost:4455/verification use: link after: - default_browser_return_url: http://localhost:3000 + default_browser_return_url: http://localhost:3000/login-result logout: after: @@ -99,6 +99,10 @@ session: jwks_url: http://host.docker.internal:3000/.well-known/jwks.json # claims_mapper_url: base64://... # A JsonNet template for modifying the claims ttl: 24h # 24 hours (defaults to 10 minutes) +cookies: + domain: "localhost" + path: / + same_site: Lax log: level: debug @@ -130,4 +134,4 @@ courier: connection_uri: smtps://test:test@host.docker.internal:1025/?skip_ssl_verify=true feature_flags: - use_continue_with_transitions: true \ No newline at end of file + use_continue_with_transitions: true diff --git a/docker/postgres/.gitignore b/docker/postgres/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docker/redis/.gitignore b/docker/redis/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docker/static/certs/example.com+1-key.pem b/docker/static/certs/example.com+1-key.pem deleted file mode 100644 index d0abec41d1..0000000000 --- a/docker/static/certs/example.com+1-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDtw3Bzjnl5uSTf -F4Wb8iRanOx9PhfAKY6M1CsssVeHm3tLiy41LLM2pI8Aq1aXqnrCZAEJQj/hdXuJ -7WXcN1VOur8xQP+N23oPSjwzRIkfwwRAd/VS0VPsnp+xajq268bI5BlVt8M3XwLG -Etgh46m1ezuapyN+cbqqRFsDUtDSKNK4Hv6HJa1XyPi8ogC4deVst2QIfuasYcJv -ZytMRNmGr/otdPuenjpGsa/HszERWidueVpQ84s+XSQBXW+j0yBMZXx9a2FxRlQ1 -Gk/Vyzm1GQk8kU1ngVG+jgKFnoi6qIn0v2bFPEOaiULrcqDQ1gUXL4vHcHlXeiF3 -fZEbWnC3AgMBAAECggEAE6cI33RSVB5ajtoZ4Bb2rEq2PW/pdKe6sadD3lDWRE40 -tbzOV/TW51hYvZxr7uNXfEPZ1hMUxqT4TiFCPx6PvY8wCHkv5mDSyPrA2Rf8IZT0 -AAQqUesdfbxqxLZcHgyFBMvd0Dj9ONFwoECkfsCboXWLKrBP/b6WyEYYI8Evo381 -hIN0xTujmakVhJ33wOWTiSoJPX7gZlFh9HnU/A3sGcfuqrmRvwKZWwlLkW5GMeEJ -HC4bSh25H6UUq8/F0fywPjqCWloTOYxFqgvlXvW2BAi+sq3XNj9Ec0/CV9UAfbyT -HUcQRZeDaIDfxUqyzrT8VFaTkeaL0cDmanjRDiUR8QKBgQDuZFXDWY8YPgW9RZCq -n9T9IvyEs2ANtAtfcALlCohbXs62cEAH4mwBUyQshHBpuMePgDOvDoZG1p9JAfYi -3pMAZYV8f+3ndmhut+N2YGX/FB7KAb5wFWZVT89bsWK529VR+5IzYEwuWxSf5ZWM -BXRzFOlo5VxSungznUrLlm8ByQKBgQD/UzheuzP2J9xRdFncXUediYCJP9k9kxKO -xAuAP50zQ87oNx95cx42+9bskWMG2rZGNdvVr+yqRwdsFjVdarnmM/ctLQ8x4Y9X -srtXyadYUbxyA0Tzt5gkJlWoyTTAU71kZ0fgPEIfvQ5C/EuRNQ+rXxgob+V4+I+O -ItLn+PsefwKBgHoICo7xbXqvZSi6T4/IObNLEZCsceMR4zB7mj+84IhFQ/PICj7+ -/OLAuKfBM/oqiJ1AtzRJbxscCnXI90JqRT3Suj49Dn+J8XOb1mhmeE/W8NvfgKjH -i5boP/FkIHGbwtswuGpsRRMFtM0VLTR8JlwyvDjiEByZL6bcQcltvG1hAoGBAM95 -yZm5F51UgMSz/n2CUzqhzJA7EQXnKDJY/luF1fEdjdnHSU1AjXHyrZBpCAY+3dUp -2OzI21D3DQH4/f5eRpfY7GeKcQmAmCGUfIX2uISdTrt7CqHdM8VUXVEdxz7uDT20 -a4S8kqMF1rv6FlH1wzjnulLJsrfdi5HdnHKiMTVzAoGBAK2SJ47mXybh/9FYM0VD -7gLs+xKCvKN4XgXyO8W3nT0JXBoB5uxNUqZvfaFKJj1J7JcrNP7txdjenYX7kGgy -BKC6zKX+Ayw96FDrH/6Fv2l3JcSJS0HkiaJBOUivehWL3aYTyZvq1fYdKnX3o963 -TmV8WP6TIFJUl4BbikDoF2NN ------END PRIVATE KEY----- diff --git a/docker/static/certs/example.com+1.pem b/docker/static/certs/example.com+1.pem deleted file mode 100644 index 9021dcee1c..0000000000 --- a/docker/static/certs/example.com+1.pem +++ /dev/null @@ -1,26 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEdDCCAtygAwIBAgIRAKCFg40tG4cXo0xziiChOQ0wDQYJKoZIhvcNAQELBQAw -gZkxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE3MDUGA1UECwwuZGF2 -aWRkaW1hcmlhQE1hY0Jvb2stUHJvLmxvY2FsIChEYXZpZCBEaU1hcmlhKTE+MDwG -A1UEAww1bWtjZXJ0IGRhdmlkZGltYXJpYUBNYWNCb29rLVByby5sb2NhbCAoRGF2 -aWQgRGlNYXJpYSkwHhcNMjQwNTI0MTk0ODQ0WhcNMjYwODI0MTk0ODQ0WjBkMScw -JQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxOTA3BgNVBAsM -MGRhdmlkZGltYXJpYUBNYWNCb29rLVByby0zLmxvY2FsIChEYXZpZCBEaU1hcmlh -KTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO3DcHOOeXm5JN8XhZvy -JFqc7H0+F8ApjozUKyyxV4ebe0uLLjUsszakjwCrVpeqesJkAQlCP+F1e4ntZdw3 -VU66vzFA/43beg9KPDNEiR/DBEB39VLRU+yen7FqOrbrxsjkGVW3wzdfAsYS2CHj -qbV7O5qnI35xuqpEWwNS0NIo0rge/oclrVfI+LyiALh15Wy3ZAh+5qxhwm9nK0xE -2Yav+i10+56eOkaxr8ezMRFaJ255WlDziz5dJAFdb6PTIExlfH1rYXFGVDUaT9XL -ObUZCTyRTWeBUb6OAoWeiLqoifS/ZsU8Q5qJQutyoNDWBRcvi8dweVd6IXd9kRta -cLcCAwEAAaNrMGkwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB -MB8GA1UdIwQYMBaAFGDgmbnR2urMnwPJ/36wuw1Kit+IMCEGA1UdEQQaMBiCC2V4 -YW1wbGUuY29tgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGBANQQ8M4lmJIP -5R10rJl1i8v0KJQpMXQwTVH902YIKQUXgy79uxJ2L7W9dmkNXtw6R6CLXy+KAE03 -fswt28E1cbTO8ObcdW2gIaey0RCzTf034yhyDt9PdKKs+kG5MiIUu9v75JhyUlIK -mqPKH8F0ad+D7gxCK+bzNMCRmk1uWh7LnNrDmkHhv6gtCSmWzIeQS+6qlh39mePn -raiiXMLymX0tR0B34gjSi4ozp//P+8XbeznJXXsBiTMyTapfZhG1y67mqI9bcx28 -MzvREMlFauubeiSUNMuPciQInA8MUWN291hfYlSDOq1aSe99h5J+evEr2Tmp54yb -jbqoDSHDPvX0KvFBQOEmLu3tHogiYUKvsAk/92Vjat2I/pKP+oJR9cO7MUxPXYgv -bzuobMaGH8JEYyUBn7VoheZBnBEimo5fKLJCkR0JfifvGVOZKN0S37N7qQrgcJUj -td0vqF0RRbx9unraQoyXJlKHsOc+J8FlBRhX7jngi48o1GRq2oWNCg== ------END CERTIFICATE----- diff --git a/docker/static/certs/example.com+2-key.pem b/docker/static/certs/example.com+2-key.pem deleted file mode 100644 index 2fa836d6c7..0000000000 --- a/docker/static/certs/example.com+2-key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDbAlyE39O83JE+ -p0EBrySzVxpGBrVKtwUa4iVnF7OCvHiHEQ6p04wrvT73Aeob1zxRiwDYKa4rIgfk -Fkm8rjYWvcUsPzb+JVf3WGdF41eBxuMOvZ2GozcDLUe9qVZYLArrjots2W2p0l7r -5JPxdUEYjqRdcG4G3Hxq0Xeze8zyi9rQQxYXKxt18ZOdrsoW1Tblsgwvzoom0Wne -89klOq/nmpinocX2nEJaZOr/M7GJJAjGrc2F5b+EqOwiZ1QshVEMEETJKRcaXKrp -I+iKCKKlYnN72hkpfLxDPC3x2BxIUjn8Lyg+x6BCpEwKCFI5cNpQVJMjSrzPUcNd -3apkvZOpAgMBAAECggEAM3kjm4srrai9bldK/QVX/9qJ70lmVJAdYAsktU0pwKFh -RoVox9tTh3gsE0vc5Pw6TgP4h9WL/NE7v4zJedIMHl4tuE+HMkY+nYCmjRN4nqTK -+szv+BPmOxXqVZY2F/UKAAD5nfSgRJiP/Ks1ZjujuAzWbqudAHnTuBtRIdsH+T0c -AHC9YixwT7wUN7OrrQ8hItsQda7kQu+0FW97wc69jkLihhr3wGkwVfl5SuQxB1dt -PwFPdZpcumkF2GtCocDLn6uyyDhYbei/LoOj5ePMA4OFcxCPRdhA5zA1P5wHlSFm -Hy4iwL7VGc9XRhAuZzs1gmTF7e+Vn8e2WWfN9wbdMQKBgQDd+L151UQBiFC8Z0TP -2sJoSIi6oX91g/cO1mxKRw5K7hRxXTaQ0e9t3q0dPnRqR7gohymKKyi8IwUH1SHB -jur6p8qZsP+BWe/qcPhrMOyjM37MSs0R4RYq0gONyKF73hWMEQzlEaylyI2w3WKY -Q24go0V4vhnpsLp0Qd9CvFX8/QKBgQD8lVyHQEKjDuDyAgM1eSwdxqAUsnfvD5Ud -ot8PVjIfUdYM0KKFlBS1yH1UYvfjSw2yTYjotauKOXCykSnMs+qoBaX5ee/E8tju -SWuesiYFeQstKCRwzB+Fb2hieFZVq+uNYhz2MZVFn1wkt0j2L31R20qhjhnYqiDO -JtgbH/kHHQKBgQCfMbfiWtuNJdCRbpbhY4kt9WdqQk0BYQWdNJcxpkhP4PP0Yd+y -eX34FE2fvZ0MZCdlmZpnJ5DtbUg8V9T/1pob7p7VHyYABRqVzzO63Lm6SkJUDgmJ -Gx8k0r4Nv6hhB+P4MnpHBygFNhK4l+4QObwP2EkI0X4QJdlza5LNb/lTmQKBgEYy -pKKaQ2rZ6b6YvJeR86ba2walixuPsxundmLmy8tUjS7GlUSWoSLcc6iOUOKEq2vQ -jKpQQzqJOD8IhRt1LVRBLZ2mO/L6ozumgBh83oBK4cZND8Ohl2kYS2SCmUv6Gd8T -U4VAxoGxBoTVw5tYG3Yygg1gVuKWdcOVnB39xtIpAoGBANPNiiD9Pn2VZyac0QjL -FWhbFWCI85c/cOfP4Ws4Z6+tD0Xh53VM6ea4tp87PYdN0j+MFqW/evTo1/ybfzRP -du3JVrYFLnwp/NnPfbakhbCY1IMVmFs8Om9hcQRTEAbFDxf06PHrhSkbmqrAWIf/ -T1lYjEM7fOdoalKmEr5CtCR6 ------END PRIVATE KEY----- diff --git a/docker/static/certs/example.com+2.pem b/docker/static/certs/example.com+2.pem deleted file mode 100644 index 89ec8c8997..0000000000 --- a/docker/static/certs/example.com+2.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEkjCCAvqgAwIBAgIQGtOcyR+KllSAwkLKWUv91TANBgkqhkiG9w0BAQsFADCB -mTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTcwNQYDVQQLDC5kYXZp -ZGRpbWFyaWFATWFjQm9vay1Qcm8ubG9jYWwgKERhdmlkIERpTWFyaWEpMT4wPAYD -VQQDDDVta2NlcnQgZGF2aWRkaW1hcmlhQE1hY0Jvb2stUHJvLmxvY2FsIChEYXZp -ZCBEaU1hcmlhKTAeFw0yNDA1MjQxOTQ3MjJaFw0yNjA4MjQxOTQ3MjJaMGQxJzAl -BgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTE5MDcGA1UECwww -ZGF2aWRkaW1hcmlhQE1hY0Jvb2stUHJvLTMubG9jYWwgKERhdmlkIERpTWFyaWEp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2wJchN/TvNyRPqdBAa8k -s1caRga1SrcFGuIlZxezgrx4hxEOqdOMK70+9wHqG9c8UYsA2CmuKyIH5BZJvK42 -Fr3FLD82/iVX91hnReNXgcbjDr2dhqM3Ay1HvalWWCwK646LbNltqdJe6+ST8XVB -GI6kXXBuBtx8atF3s3vM8ova0EMWFysbdfGTna7KFtU25bIML86KJtFp3vPZJTqv -55qYp6HF9pxCWmTq/zOxiSQIxq3NheW/hKjsImdULIVRDBBEySkXGlyq6SPoigii -pWJze9oZKXy8Qzwt8dgcSFI5/C8oPsegQqRMCghSOXDaUFSTI0q8z1HDXd2qZL2T -qQIDAQABo4GJMIGGMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD -ATAfBgNVHSMEGDAWgBRg4Jm50drqzJ8Dyf9+sLsNSorfiDA+BgNVHREENzA1ggtl -eGFtcGxlLmNvbYIbZXhhbXBsZS1zdGFnaW5nLmFwcHNwb3QuY29tgglsb2NhbGhv -c3QwDQYJKoZIhvcNAQELBQADggGBALMDi2IFk0GeWk1x6Ulsp3BDOhEyIR2tBjDk -3m+B1emyhT2gXnfSLh/yGN4Sac8W+KtRVjvcBDHWvxPrqwChyuRgaev+jsgIGCVn -dMweTf2C75NSq8l5MlitzF6i74/i2vQT3CxJcblFlxL+j7fOq1ikTuuTGUz7f4PZ -XMl+g7dmLgDwLirU+PvEVRhztOGav7tl3hfnVPvDsEkL3G8xomNALfj6/6Qy3e2l -bNZKx6+jrLUidzPNrTKMOPnPugfDRamAi1vcl1B4N1WcwmoKWbY+HMbie3e7SqwL -tebyYrJACklFbal0UidcQJtweTkt1gMPCbkOhWk2IOHrcF7fiIi6fy8jWTpHB4Oq -Nv0HcOVrvEjAtT87v6X/fNqvcRwAMuP0oaWQz1ZTY0RQP5BgxEX0WzuRt0Nn1KAs -+auoqim5stKYZ4MSA+H52LVepA+f63e/is6rxvFJliVZFJrlkxNG3kETDy69/oik -KgiL6PePzuopMkRnZgiFzL52BzXfpw== ------END CERTIFICATE----- diff --git a/docker/static/conf/example.com+1.conf b/docker/static/conf/example.com+1.conf deleted file mode 100644 index 414c9c28b2..0000000000 --- a/docker/static/conf/example.com+1.conf +++ /dev/null @@ -1,40 +0,0 @@ -server { - listen 80; - listen [::]:80; - server_name localhost; - - location / { - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html =404; - } - - location = /localstack/ { - proxy_pass http://localhost:4566/; - } -} -server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - server_name localhost; - - ssl_certificate /etc/nginx/ssl/example.com+1.pem; - ssl_certificate_key /etc/nginx/ssl/example.com+1-key.pem; - - location / { - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Proto $scheme; - - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html =404; - } - - location = /localstack/ { - proxy_pass http://localhost:4566/; - } -} \ No newline at end of file diff --git a/docker/static/default.conf b/docker/static/default.conf deleted file mode 100644 index 0d3076d98e..0000000000 --- a/docker/static/default.conf +++ /dev/null @@ -1,13 +0,0 @@ -server { - listen 443 ssl; - - server_name localhost; - ssl_certificate /etc/nginx/certs/nginx-selfsigned.crt; - ssl_certificate_key /etc/nginx/certs/nginx-selfsigned.key; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $remote_addr; - - location / { - try_files $uri $uri/ /index.html =404; - } -} \ No newline at end of file diff --git a/infra/aws-cloudformation/quadratic-preview.yml b/infra/aws-cloudformation/quadratic-preview.yml new file mode 100644 index 0000000000..b5da7ecc91 --- /dev/null +++ b/infra/aws-cloudformation/quadratic-preview.yml @@ -0,0 +1,217 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: Quadratic Preview - Docker Deployment Template + +Parameters: + ImageTag: + Type: String + Description: Image tag to use for all services + +Resources: + # Security Group for the EC2 instance + SecurityGroup: + Type: AWS::EC2::SecurityGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + Tags: + - Key: Name + Value: !Ref ImageTag + GroupDescription: !Ref ImageTag + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + - IpProtocol: tcp + FromPort: 443 + ToPort: 443 + CidrIp: 0.0.0.0/0 + + # IAM Role for the EC2 instance + EC2Role: + Type: AWS::IAM::Role + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + Tags: + - Key: Name + Value: !Ref ImageTag + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: ec2.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly + - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess + + # IAM Instance Profile for the EC2 instance + EC2InstanceProfile: + Type: AWS::IAM::InstanceProfile + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + Roles: + - !Ref EC2Role + + # EC2 instance + EC2Instance: + Type: AWS::EC2::Instance + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + Tags: + - Key: Name + Value: !Sub "${ImageTag}" + InstanceType: m6a.large + ImageId: ami-0b8c6b923777519db # AMI for us-west-2 (Ubuntu 22.04 LTS amd64) + IamInstanceProfile: !Ref EC2InstanceProfile + SecurityGroups: + - !Ref SecurityGroup + BlockDeviceMappings: + - DeviceName: /dev/sda1 + Ebs: + VolumeSize: 30 + VolumeType: gp3 + UserData: + Fn::Base64: !Sub | + #!/bin/bash + + # Update and install dependencies + sudo apt-get update + sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common unzip jq awscli + + # Install AWS CLI v2 + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + sudo ./aws/install + rm -rf aws awscliv2.zip + + # Install Docker + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io + sudo curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + sudo chown ubuntu /var/run/docker.sock + sudo systemctl enable docker + sudo systemctl start docker + sudo usermod -aG docker ubuntu + + # Download Quadratic initialization script + curl -sSf https://raw.githubusercontent.com/quadratichq/quadratic-selfhost/main/init-aws-preview.sh -o init.sh + + # Fetch License Key + LICENSE_KEY="$(aws ssm get-parameter --name "/quadratic-development/QUADRATIC_LICENSE_KEY" --with-decryption --query "Parameter.Value" --output text)" + + # Generate Domain Name + DOMAIN_NAME="${ImageTag}.quadratic-preview.com" + + # Run Quadratic initialization script + chmod +x init.sh + ./init.sh $LICENSE_KEY $DOMAIN_NAME + + # Append environment variables to .env file + cat << EOF >> /quadratic-selfhost/.env + AWS_S3_REGION=$(aws ssm get-parameter --name "/quadratic-development/AWS_S3_REGION" --with-decryption --query "Parameter.Value" --output text) + AWS_S3_ACCESS_KEY_ID=$(aws ssm get-parameter --name "/quadratic-development/AWS_S3_ACCESS_KEY_ID" --with-decryption --query "Parameter.Value" --output text) + AWS_S3_SECRET_ACCESS_KEY=$(aws ssm get-parameter --name "/quadratic-development/AWS_S3_SECRET_ACCESS_KEY" --with-decryption --query "Parameter.Value" --output text) + + # ai api keys + OPENAI_API_KEY=$(aws ssm get-parameter --name "/quadratic-development/OPENAI_API_KEY" --with-decryption --query "Parameter.Value" --output text) + ANTHROPIC_API_KEY=$(aws ssm get-parameter --name "/quadratic-development/ANTHROPIC_API_KEY" --with-decryption --query "Parameter.Value" --output text) + EXA_API_KEY=$(aws ssm get-parameter --name "/quadratic-development/EXA_API_KEY" --with-decryption --query "Parameter.Value" --output text) + + # aws ecr + ECR_URL=${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com + IMAGE_TAG=${ImageTag} + EOF + + # Create login.sh script and run it + cat << 'EOF' > /quadratic-selfhost/login.sh + #!/bin/bash + aws ecr get-login-password --region ${AWS::Region} | docker login --username AWS --password-stdin ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com + EOF + + chmod +x /quadratic-selfhost/login.sh + chmod +x /quadratic-selfhost/pull_start.sh + + cd /quadratic-selfhost + ./login.sh + ./pull_start.sh + + # Global Accelerator + GlobalAccelerator: + Type: AWS::GlobalAccelerator::Accelerator + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + Name: !Sub "${ImageTag}" + Enabled: true + Tags: + - Key: Name + Value: !Ref ImageTag + + # Global Accelerator Listener + GlobalAcceleratorListener: + Type: AWS::GlobalAccelerator::Listener + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + AcceleratorArn: !Ref GlobalAccelerator + Protocol: TCP + PortRanges: + - FromPort: 80 + ToPort: 80 + - FromPort: 443 + ToPort: 443 + + # Global Accelerator Endpoint Group + GlobalAcceleratorEndpointGroup: + Type: AWS::GlobalAccelerator::EndpointGroup + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + ListenerArn: !Ref GlobalAcceleratorListener + EndpointGroupRegion: !Ref AWS::Region + EndpointConfigurations: + - EndpointId: !Ref EC2Instance + Weight: 100 + ClientIPPreservationEnabled: true + + # DNS record for the main application + DNSRecord: + Type: AWS::Route53::RecordSet + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + HostedZoneId: Z0126430TJ1UYIMO3SYX + Name: !Sub "${ImageTag}.quadratic-preview.com." + Type: A + AliasTarget: + DNSName: !GetAtt GlobalAccelerator.DnsName + HostedZoneId: Z2BJ6XQ5FK7U4H + EvaluateTargetHealth: true + + # Wildcard DNS record for all services on subdomains + WildcardDNSRecord: + Type: AWS::Route53::RecordSet + DeletionPolicy: Delete + UpdateReplacePolicy: Delete + Properties: + HostedZoneId: Z0126430TJ1UYIMO3SYX + Name: !Sub "*.${ImageTag}.quadratic-preview.com." + Type: A + AliasTarget: + DNSName: !GetAtt GlobalAccelerator.DnsName + HostedZoneId: Z2BJ6XQ5FK7U4H + EvaluateTargetHealth: true + +Outputs: + WebsiteURL: + Description: Website URL + Value: !Sub "https://${ImageTag}.quadratic-preview.com" diff --git a/infra/client/build-client-ci.sh b/infra/client/build-client-ci.sh index 19af1fb2b3..4cb8cbae08 100755 --- a/infra/client/build-client-ci.sh +++ b/infra/client/build-client-ci.sh @@ -37,5 +37,5 @@ echo 'Building quadratic-rust-client...' npm run build --workspace=quadratic-rust-client echo 'Building front-end...' -npm ci +npm ci --no-audit --no-fund npm run build --workspace=quadratic-client diff --git a/package-lock.json b/package-lock.json index ce031cc916..fa7ecb9b4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quadratic", - "version": "0.5.4", + "version": "0.6.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quadratic", - "version": "0.5.4", + "version": "0.6.1", "workspaces": [ "quadratic-api", "quadratic-shared", @@ -14,12 +14,12 @@ "quadratic-files", "quadratic-multiplayer", "quadratic-rust-client", + "quadratic-core", "quadratic-client", "quadratic-kernels/python-wasm" ], "dependencies": { "@ory/kratos-client": "^1.2.1", - "tsc": "^2.0.4", "vitest": "^1.5.0", "zod": "^3.23.8" }, @@ -27138,6 +27138,10 @@ "resolved": "quadratic-client", "link": true }, + "node_modules/quadratic-core": { + "resolved": "quadratic-core", + "link": true + }, "node_modules/quadratic-files": { "resolved": "quadratic-files", "link": true @@ -29795,13 +29799,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/tsc": { - "version": "2.0.4", - "license": "MIT", - "bin": { - "tsc": "bin/tsc" - } - }, "node_modules/tsconfck": { "version": "3.0.2", "dev": true, @@ -31835,7 +31832,7 @@ } }, "quadratic-api": { - "version": "0.5.4", + "version": "0.6.1", "hasInstallScript": true, "dependencies": { "@anthropic-ai/bedrock-sdk": "^0.11.2", @@ -31915,7 +31912,7 @@ } }, "quadratic-client": { - "version": "0.5.4", + "version": "0.6.1", "dependencies": { "@amplitude/analytics-browser": "^1.9.4", "@auth0/auth0-spa-js": "^2.1.0", @@ -32182,6 +32179,7 @@ "version": "4.0.0", "license": "ISC" }, + "quadratic-core": {}, "quadratic-files": { "devDependencies": {} }, @@ -32194,7 +32192,7 @@ }, "quadratic-rust-client": {}, "quadratic-shared": { - "version": "0.5.4", + "version": "0.6.1", "license": "ISC", "dependencies": { "typescript": "^5.6.2", diff --git a/package.json b/package.json index 0c4ac1df1b..1dce45ca45 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "quadratic-files", "quadratic-multiplayer", "quadratic-rust-client", + "quadratic-core", "quadratic-client", "quadratic-kernels/python-wasm" ], @@ -24,8 +25,9 @@ "start:services-local": "node dev -L", "dev": "node dev -l", "dev:services-local": "node dev -l -L", + "dev:docker": "node dev -l -D", "api:start": "npm start --workspace=quadratic-api", - "clean": "npm exec --workspaces -- npx rimraf node_modules && npx rimraf node_modules", + "clean": "npm exec --workspaces -- npx rimraf node_modules && npx rimraf target && npx rimraf node_modules && cargo clean", "lint:client": "cd quadratic-client && npm run lint:ts && npm run lint:eslint && lint:prettier && lint:clippy", "build:wasm:types": "cd quadratic-core && cargo run --bin export_types --features js", "watch:wasm:javascript": "cd quadratic-core && cargo watch -s 'wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs'", @@ -33,7 +35,8 @@ "watch:wasm:perf:javascript": "cd quadratic-core && cargo watch -s 'wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs'", "build:wasm:perf:javascript": "cd quadratic-core && wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs", "watch:rust-client": "cd quadratic-rust-client && cargo watch -s 'wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs'", - "build:rust-client": "cd quadratic-rust-client && wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs", + "build:rust-client": "cd quadratic-rust-client && wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs", + "build:rust-client:perf": "cd quadratic-rust-client && wasm-pack build--target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs", "watch:python": "cd quadratic-kernels/python-wasm && npm run dev", "build:python": "./quadratic-kernels/python-wasm/package.sh", "build:file:blank": "cd quadratic-core && cargo run --bin generate_blank_current_file", @@ -52,18 +55,21 @@ "lint:clippy": "cd quadratic-core && cargo clippy --all-targets --all-features -- -D warnings", "lint:clippy:fix": "cd quadratic-core && cargo clippy --all-targets --all-features --fix -- -D warnings", "heroku-postbuild": "npm run build --workspace=quadratic-api", - "kill": "kill-port 3000 && kill-port 3001 && kill-port 8001 && kill-port 8000", + "kill": "kill-port 3000 && kill-port 8000 && kill-port 3001 && kill-port 3002 && kill-port 3003 && kill-port 6379 && kill-port 8001 && kill-port 5432 && kill-port 4566 && kill-port 80 && kill-port 443", "prisma:migrate": "cd quadratic-api && npm run prisma:migrate", - "docker:up": "docker compose up -d --wait && npm run prisma:migrate --workspace=quadratic-api", - "docker:down": "docker compose down", - "docker:down:kill-volumes": "docker compose down -v", "copy:esbuild": "cp -f node_modules/esbuild-wasm/esbuild.wasm quadratic-client/public/", "gen:pyright:initialization": "npm run gen:pyright:worker --workspace=quadratic-kernels/python-wasm", - "gen:pyright:worker": "npm run gen:pyright:worker --workspace=quadratic-kernels/python-wasm" + "gen:pyright:worker": "npm run gen:pyright:worker --workspace=quadratic-kernels/python-wasm", + "docker:base": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose --profile base --env-file .env.docker up", + "docker:dev": "ECR_OR_BUILD=build CLIENT_DEV=true docker compose --profile dev --env-file .env.docker up -d && docker attach quadratic-dev", + "docker:build": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose --profile all --env-file .env.docker up", + "docker:build:dev": "ECR_OR_BUILD=build CLIENT_DEV=true docker compose --profile all --env-file .env.docker up", + "docker:ecr": "ECR_OR_BUILD=ecr CLIENT_DEV=false docker compose --profile all --env-file .env.docker up", + "docker:down": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose --profile all --env-file .env.docker down", + "docker:clean": "docker system prune -af && docker builder prune -af && docker volume prune -af" }, "dependencies": { "@ory/kratos-client": "^1.2.1", - "tsc": "^2.0.4", "vitest": "^1.5.0", "zod": "^3.23.8" }, diff --git a/quadratic-api/.env.docker b/quadratic-api/.env.docker deleted file mode 100644 index f4f8144b51..0000000000 --- a/quadratic-api/.env.docker +++ /dev/null @@ -1,20 +0,0 @@ -CORS="*" -DATABASE_URL="postgresql://postgres:postgres@host.docker.internal:5432/postgres" -ENVIRONMENT=docker -STRIPE_SECRET_KEY=STRIPE_SECRET_KEY -STRIPE_WEBHOOK_SECRET=STRIPE_WEBHOOK_SECRET -OPENAI_API_KEY= -M2M_AUTH_TOKEN=M2M_AUTH_TOKEN - -# Hex string to be used as the key for enctyption, use npm run key:generate -ENCRYPTION_KEY=eb4758047f74bdb2603cce75c4370327ca2c3662c4786867659126da8e64dfcc - -# Auth -AUTH_TYPE=auth0 -AWS_S3_ENDPOINT=http://localstack:4566 - -# Storage -STORAGE_TYPE=s3 - -# Admin -LICENSE_KEY=LICENSE_KEY diff --git a/quadratic-api/.env.example b/quadratic-api/.env.example index 466ea24bf8..9b772338c0 100644 --- a/quadratic-api/.env.example +++ b/quadratic-api/.env.example @@ -27,8 +27,8 @@ AUTH0_AUDIENCE=community-quadratic ORY_JWKS_URI='http://host.docker.internal:3000/.well-known/jwks.json' ORY_ADMIN_HOST=http://0.0.0.0:4434 -# Storage -STORAGE_TYPE=s3 # s3 or file-system +# Storage - s3 or file-system +STORAGE_TYPE=s3 QUADRATIC_FILE_URI=http://localhost:3002 QUADRATIC_FILE_URI_PUBLIC=http://localhost:3002 AWS_S3_REGION=us-east-2 diff --git a/quadratic-api/.env.test b/quadratic-api/.env.test index 27d0e2cfca..0c043da96d 100644 --- a/quadratic-api/.env.test +++ b/quadratic-api/.env.test @@ -18,8 +18,8 @@ AUTH0_DOMAIN="AUTH0_DOMAIN" ORY_JWKS_URI='http://host.docker.internal:3000/.well-known/jwks.json' ORY_ADMIN_HOST=http://0.0.0.0:4434 -# Storage -STORAGE_TYPE=s3 # s3 or file-system +# Storage - s3 or file-system +STORAGE_TYPE=s3 QUADRATIC_FILE_URI=http://localhost:3002 QUADRATIC_FILE_URI_PUBLIC=http://localhost:3002 AWS_S3_REGION=us-west-2 diff --git a/quadratic-api/Dockerfile b/quadratic-api/Dockerfile index 77a8ba4e16..8b57168303 100644 --- a/quadratic-api/Dockerfile +++ b/quadratic-api/Dockerfile @@ -1,14 +1,16 @@ FROM node:18-alpine AS builder -WORKDIR /app +WORKDIR /quadratic COPY package.json . COPY package-lock.json . +COPY ./quadratic-api/package*.json ./quadratic-api/ +COPY ./quadratic-shared/package*.json ./quadratic-shared/ +RUN npm install COPY quadratic-api ./quadratic-api COPY quadratic-shared ./quadratic-shared -RUN npm install FROM node:18-slim AS runtime -WORKDIR /app -COPY --from=builder /app . +WORKDIR /quadratic +COPY --from=builder /quadratic . RUN apt-get update && apt install -y openssl RUN npm run postinstall --workspace=quadratic-api RUN npm run build:prod --workspace=quadratic-api diff --git a/quadratic-client/.env.docker b/quadratic-client/.env.docker deleted file mode 100644 index ee0c206b50..0000000000 --- a/quadratic-client/.env.docker +++ /dev/null @@ -1,9 +0,0 @@ -VITE_DEBUG=1 // use =1 to enable debug flags -VITE_QUADRATIC_API_URL=http://localhost:8000 -VITE_QUADRATIC_MULTIPLAYER_URL=ws://localhost:3001/ws -VITE_QUADRATIC_CONNECTION_URL=http://localhost:3003 - -# Auth -VITE_AUTH_TYPE=ory -VITE_STORAGE_TYPE=file-system -VITE_ORY_HOST=http://localhost:4433 diff --git a/quadratic-client/.env.example b/quadratic-client/.env.example index 59862ad5c7..52bea703e9 100644 --- a/quadratic-client/.env.example +++ b/quadratic-client/.env.example @@ -2,6 +2,7 @@ VITE_GOOGLE_ANALYTICS_GTAG=G-0000000000 VITE_AMPLITUDE_ANALYTICS_API_KEY= VITE_MIXPANEL_ANALYTICS_KEY= VITE_SENTRY_DSN=https://xxxxxxxxxxxxxxxxxx@xxxxxxxxxxxx.ingest.sentry.io/xxxxxxxxxxxx +VITE_SENTRY_AUTH_TOKEN= VITE_DEBUG=0 // use =1 to enable debug flags VITE_QUADRATIC_API_URL=http://localhost:8000 VITE_QUADRATIC_MULTIPLAYER_URL=ws://localhost:3001/ws diff --git a/quadratic-client/Dockerfile b/quadratic-client/Dockerfile index e814a28455..82c78f9293 100644 --- a/quadratic-client/Dockerfile +++ b/quadratic-client/Dockerfile @@ -1,22 +1,143 @@ -# Use an official node image as a parent image -FROM node:18 AS build +########################################################################################### +# Builder 1 - Build core +########################################################################################### +FROM node:18 AS core-builder + +ARG CLIENT_DEV # Install rustup RUN echo 'Installing rustup...' && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +ENV CARGO_TARGET_DIR=/quadratic/target +ENV CARGO_HOME=/quadratic/.cargo +ENV CARGO_BUILD_JOBS=64 +ENV RUSTFLAGS='-C codegen-units=64' # Install wasm-pack RUN echo 'Installing wasm-pack...' && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh RUN echo 'wasm-pack version:' && wasm-pack --version # Install wasm32-unknown-unknown target -# RUN rustup target add wasm32-unknown-unknown +RUN echo 'Installing wasm32-unknown-unknown target...' && rustup target add wasm32-unknown-unknown -# Install python & clean up -RUN apt-get update && apt-get install -y python-is-python3 python3-pip && apt-get clean && rm -rf /var/lib/apt/lists/* +WORKDIR /quadratic -# Install npm dependencies -WORKDIR /app +# trigger rebuild if updateAlertVersion.json changes +COPY updateAlertVersion.json . + +# Copy required files for building core +COPY package.json . +COPY ./quadratic-core/. ./quadratic-core/ +COPY ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts + +# Build core +RUN if [ "$CLIENT_DEV" = "true" ]; then \ + echo 'Building core in dev mode...' && npm run build:dev --workspace=quadratic-core; \ + else \ + echo 'Building core...' && npm run build --workspace=quadratic-core; \ + fi + + +########################################################################################### +# Builder 2 - Build TS/Rust types +########################################################################################### +FROM node:18 AS ts-rust-types-builder + +# Install rustup +RUN echo 'Installing rustup...' && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" +ENV CARGO_TARGET_DIR=/quadratic/target +ENV CARGO_HOME=/quadratic/.cargo +ENV CARGO_BUILD_JOBS=64 +ENV RUSTFLAGS='-C codegen-units=64' + +# Install wasm-pack +RUN echo 'Installing wasm-pack...' && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh +RUN echo 'wasm-pack version:' && wasm-pack --version + +# Install wasm32-unknown-unknown target +RUN echo 'Installing wasm32-unknown-unknown target...' && rustup target add wasm32-unknown-unknown + +WORKDIR /quadratic + +# trigger rebuild if updateAlertVersion.json changes +COPY updateAlertVersion.json . + +# Copy required files for building ts/rust types +COPY package.json . +COPY ./quadratic-core/. ./quadratic-core/ +COPY ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts + +# Build TS/Rust types +RUN echo 'Building TS/Rust types...' && npm run export_types --workspace=quadratic-core + + +########################################################################################### +# Builder 3 - Build rust client +########################################################################################### +FROM node:18 AS rust-client-builder + +ARG CLIENT_DEV + +# Install rustup +RUN echo 'Installing rustup...' && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" +ENV CARGO_TARGET_DIR=/quadratic/target +ENV CARGO_HOME=/quadratic/.cargo +ENV CARGO_BUILD_JOBS=64 +ENV RUSTFLAGS='-C codegen-units=64' + +# Install wasm-pack +RUN echo 'Installing wasm-pack...' && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh +RUN echo 'wasm-pack version:' && wasm-pack --version + +# Install wasm32-unknown-unknown target +RUN echo 'Installing wasm32-unknown-unknown target...' && rustup target add wasm32-unknown-unknown + +WORKDIR /quadratic + +# trigger rebuild if updateAlertVersion.json changes +COPY updateAlertVersion.json . + +# Copy required files for building rust client +COPY package.json . +COPY ./quadratic-core/. ./quadratic-core/ +COPY ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts +COPY ./quadratic-rust-client/. ./quadratic-rust-client/ + +# Build the rust-client +ARG GIT_COMMIT +ENV GIT_COMMIT=$GIT_COMMIT +RUN if [ "$CLIENT_DEV" = "true" ]; then \ + echo 'Building rust-client in dev mode...' && npm run build:dev --workspace=quadratic-rust-client; \ + else \ + echo 'Building rust-client...' && npm run build --workspace=quadratic-rust-client; \ + fi + + +########################################################################################### +# Builder 4 - Combine all builder files and build the client +########################################################################################### +FROM node:18 AS vite-builder + +# Install python +RUN apt-get update && apt-get install -y --no-install-recommends python-is-python3 python3-pip + +WORKDIR /quadratic + +# Copy updateAlertVersion.json +COPY updateAlertVersion.json . + +# Copy files from core, ts/rust types, and rust client builders +COPY --from=core-builder /quadratic/quadratic-client/src/app/quadratic-core/. ./quadratic-client/src/app/quadratic-core +COPY --from=ts-rust-types-builder /quadratic/quadratic-client/src/app/quadratic-core-types/. ./quadratic-client/src/app/quadratic-core-types +COPY --from=rust-client-builder /quadratic/quadratic-client/src/app/quadratic-rust-client/. ./quadratic-client/src/app/quadratic-rust-client + +# Copy package.json and package-lock.json +COPY package.json . +COPY package-lock.json . + +# Copy all package.json files COPY package.json . COPY package-lock.json . COPY ./quadratic-kernels/python-wasm/package*.json ./quadratic-kernels/python-wasm/ @@ -24,59 +145,40 @@ COPY ./quadratic-core/package*.json ./quadratic-core/ COPY ./quadratic-rust-client/package*.json ./quadratic-rust-client/ COPY ./quadratic-shared/package*.json ./quadratic-shared/ COPY ./quadratic-client/package*.json ./quadratic-client/ -RUN npm install -# Install typescript -RUN npm install -D typescript +# Install npm dependencies +RUN npm install --no-audit --no-fund -# Copy the rest of the application -WORKDIR /app -COPY updateAlertVersion.json . +# Copy remaining files COPY ./quadratic-kernels/python-wasm/. ./quadratic-kernels/python-wasm/ -COPY ./quadratic-core/. ./quadratic-core/ -COPY ./quadratic-rust-client/. ./quadratic-rust-client/ COPY ./quadratic-shared/. ./quadratic-shared/ COPY ./quadratic-client/. ./quadratic-client/ # Run the packaging script for quadratic_py -WORKDIR /app RUN ./quadratic-kernels/python-wasm/package.sh --no-poetry -# Build wasm -WORKDIR /app/quadratic-core -RUN echo 'Building wasm...' && wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs - -# Export TS/Rust types -WORKDIR /app/quadratic-core -RUN echo 'Exporting TS/Rust types...' && cargo run --bin export_types - -# Build the quadratic-rust-client -WORKDIR /app -ARG GIT_COMMIT -ENV GIT_COMMIT=$GIT_COMMIT -RUN echo 'Building quadratic-rust-client...' && npm run build --workspace=quadratic-rust-client - # Build the quadratic-shared -WORKDIR /app RUN echo 'Building quadratic-shared...' && npm run compile --workspace=quadratic-shared # Build the front-end -WORKDIR /app -RUN echo 'Building front-end...' ENV VITE_DEBUG=VITE_DEBUG_VAL ENV VITE_QUADRATIC_API_URL=VITE_QUADRATIC_API_URL_VAL ENV VITE_QUADRATIC_MULTIPLAYER_URL=VITE_QUADRATIC_MULTIPLAYER_URL_VAL ENV VITE_QUADRATIC_CONNECTION_URL=VITE_QUADRATIC_CONNECTION_URL_VAL ENV VITE_AUTH_TYPE=VITE_AUTH_TYPE_VAL +ENV VITE_STORAGE_TYPE=VITE_STORAGE_TYPE_VAL ENV VITE_ORY_HOST=VITE_ORY_HOST_VAL -RUN npm run build --workspace=quadratic-client +ENV VITE_SENTRY_AUTH_TOKEN=VITE_SENTRY_AUTH_TOKEN_VAL +RUN echo 'Building front-end...' && npm run build --workspace=quadratic-client -# The default command to run the application -# CMD ["npm", "run", "start:production"] +########################################################################################### +# Runner - Serve the client with nginx +########################################################################################### FROM nginx:stable-alpine -COPY --from=build /app/build /usr/share/nginx/html + +COPY --from=vite-builder /quadratic/build /usr/share/nginx/html EXPOSE 80 443 3000 -CMD ["nginx", "-g", "daemon off;"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/quadratic-client/vite.config.js b/quadratic-client/vite.config.js index 0da34f0f54..42189311d0 100644 --- a/quadratic-client/vite.config.js +++ b/quadratic-client/vite.config.js @@ -46,6 +46,7 @@ export default defineConfig(() => { publicDir: './public', assetsInclude: ['**/*.py'], server: { + host: '0.0.0.0', port: 3000, }, resolve: { diff --git a/quadratic-connection/.env.docker b/quadratic-connection/.env.docker deleted file mode 100644 index 522914b032..0000000000 --- a/quadratic-connection/.env.docker +++ /dev/null @@ -1,9 +0,0 @@ -HOST=0.0.0.0 -PORT=3003 -ENVIRONMENT=docker - -AUTH0_JWKS_URI=http://host.docker.internal:3000/.well-known/jwks.json -QUADRATIC_API_URI=http://host.docker.internal:8000 -M2M_AUTH_TOKEN=M2M_AUTH_TOKEN -MAX_RESPONSE_BYTES=15728640 # 15MB -STATIC_IPS=0.0.0.0,127.0.0.1 diff --git a/quadratic-connection/Cargo.toml b/quadratic-connection/Cargo.toml index 18ec99c446..43c650bbef 100644 --- a/quadratic-connection/Cargo.toml +++ b/quadratic-connection/Cargo.toml @@ -15,10 +15,7 @@ chrono = { version = "0.4.31", features = ["serde"] } dotenv = "0.15.0" envy = "0.4.2" futures = "0.3.29" -futures-util = { version = "0.3.29", default-features = false, features = [ - "sink", - "std", -] } +futures-util = { version = "0.3.29", default-features = false, features = ["sink", "std"] } headers = "0.4.0" http = "1.1.0" http-body-util = "0.1.1" @@ -27,17 +24,9 @@ hyper-util = { version = "0.1.5", features = ["service"] } jsonwebtoken = "9.2.0" log = "0.4.21" openssl = { version = "0.10.66", features = ["vendored"] } -parquet = { version = "51.0.0", default-features = false, features = [ - "arrow", - "arrow-array", -] } +parquet = { version = "51.0.0", default-features = false, features = ["arrow", "arrow-array"] } quadratic-rust-shared = { path = "../quadratic-rust-shared" } -reqwest = { version = "0.11.22", features = [ - "cookies", - "json", - "serde_json", - "stream", -] } +reqwest = { version = "0.11.22", features = ["cookies", "json", "serde_json", "stream"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" strum = "0.26.3" @@ -46,14 +35,14 @@ thiserror = "1.0.50" tokio = { version = "1.34.0", features = ["full"] } tower = { version = "0.4.13", features = ["util"] } tower-http = { version = "0.5.0", features = [ - "auth", - "compression-gzip", - "cors", - "fs", - "sensitive-headers", - "trace", - "util", - "validate-request", + "auth", + "compression-gzip", + "cors", + "fs", + "sensitive-headers", + "trace", + "util", + "validate-request", ] } tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } @@ -61,7 +50,6 @@ uuid = { version = "1.6.1", features = ["serde", "v4"] } [dev-dependencies] fake = { version = "2.9.1", features = ["derive"] } -quadratic-core = { path = "../quadratic-core" } quadratic-rust-shared = { path = "../quadratic-rust-shared", features = ["test"] } tracing-test = "0.2.4" diff --git a/quadratic-connection/Dockerfile b/quadratic-connection/Dockerfile index 73556ef906..832306702f 100644 --- a/quadratic-connection/Dockerfile +++ b/quadratic-connection/Dockerfile @@ -1,12 +1,14 @@ -FROM rust:latest as builder +FROM rust:latest AS builder -RUN USER=root cargo new --bin quadratic-connection -COPY . /quadratic-connection +WORKDIR /quadratic +COPY ./quadratic-connection/. ./quadratic-connection/ +COPY ./quadratic-rust-shared/. ./quadratic-rust-shared/ RUN rustup component add rustfmt -WORKDIR /quadratic-connection -RUN cargo build --release --package quadratic-connection -FROM debian:stable-slim as runtime -COPY --from=builder /quadratic-connection/target/release/quadratic-connection . +WORKDIR /quadratic/quadratic-connection +RUN cargo build --release + +FROM debian:stable-slim AS runtime +COPY --from=builder /quadratic/quadratic-connection/target/release/quadratic-connection . RUN apt-get update && apt install -y ca-certificates CMD ["./quadratic-connection"] diff --git a/quadratic-connection/package.json b/quadratic-connection/package.json index 062ad84fdb..4c4b9fa5dc 100644 --- a/quadratic-connection/package.json +++ b/quadratic-connection/package.json @@ -9,13 +9,13 @@ "dev": "RUST_LOG=info cargo watch -x 'run'", "test": "cargo test", "test:watch": "RUST_LOG=info cargo watch -x 'test'", - "test:docker:ci": "npm run docker:up && npm run test && npm run docker:down", "lint": "cargo clippy --all-targets --all-features -- -D warnings", "coverage": "npm run coverage:gen && npm run coverage:html && npm run coverage:view", "coverage:gen": "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test", "coverage:html": "grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore '/*' -o coverage/html", "coverage:view": "open coverage/html/index.html", - "docker:up": "docker compose --profile quadratic-connection-db up -d && sleep 3", - "docker:down": "docker compose --profile quadratic-connection-db down" + "docker:up": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose -f ../docker-compose.yml --profile quadratic-connection-db --env-file ../.env.docker up -d --wait && sleep 10", + "docker:down": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose -f ../docker-compose.yml --profile quadratic-connection-db --env-file ../.env.docker down", + "docker:test": "ECR_OR_BUILD=build CLIENT_DEV=true docker compose kill && npm run docker:up && npm run test && npm run docker:down" } } diff --git a/quadratic-connection/src/config.rs b/quadratic-connection/src/config.rs index dcf8912f4c..2c96988be9 100644 --- a/quadratic-connection/src/config.rs +++ b/quadratic-connection/src/config.rs @@ -31,7 +31,11 @@ pub(crate) fn config() -> Result { dotenv::from_filename(filename).ok(); dotenv().ok(); - let config = envy::from_env::().map_err(|e| ConnectionError::Config(e.to_string()))?; + // Try prefixed first, fall back to non-prefixed if that fails + let config = envy::prefixed("CONNECTION__") + .from_env::() + .or_else(|_| envy::from_env::()) + .map_err(|e| ConnectionError::Config(e.to_string()))?; Ok(config) } diff --git a/quadratic-core/Cargo.toml b/quadratic-core/Cargo.toml index 226227c5a3..086bc101b0 100644 --- a/quadratic-core/Cargo.toml +++ b/quadratic-core/Cargo.toml @@ -103,6 +103,11 @@ harness = false [package.metadata.wasm-pack.profile.release] wasm-opt = ['-Os', '-g'] # TODO: -g seems to fix the name mangling problem +[package.metadata.wasm-pack.profile.release.wasm-bindgen] +debug-js-glue = false +demangle-name-section = true +dwarf-debug-info = false + [package.metadata.wasm-pack.profile.dev] wasm-opt = false @@ -112,14 +117,9 @@ demangle-name-section = true dwarf-debug-info = true [package.metadata.wasm-pack.profile.profiling] -wasm-opt = ['-O', '-g'] +wasm-opt = ['-O0', '-g'] [package.metadata.wasm-pack.profile.profiling.wasm-bindgen] debug-js-glue = false demangle-name-section = true dwarf-debug-info = true - -[package.metadata.wasm-pack.profile.release.wasm-bindgen] -debug-js-glue = false -demangle-name-section = true -dwarf-debug-info = false diff --git a/quadratic-core/package.json b/quadratic-core/package.json index fd52cd67ca..95b63a1c65 100644 --- a/quadratic-core/package.json +++ b/quadratic-core/package.json @@ -5,7 +5,9 @@ "scripts": { "start": "cargo watch -s 'wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs'", "performance": "cargo watch -s 'wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs'", - "build": "wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs", + "build": "wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs", + "build:dev": "wasm-pack build --profiling --target web --out-dir ../quadratic-client/src/app/quadratic-core --weak-refs", + "export_types": "cargo run --bin export_types --features js", "coverage": "npm run coverage:clean && npm run coverage:wasm:gen && npm run coverage:wasm:html && npm run coverage:wasm:view", "coverage:wasm:gen": "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test", "coverage:wasm:html": "grcov . --binary-path ../target/debug/deps/ -s src -t html --branch --ignore-not-existing --ignore 'src/wasm_bindings/*' --ignore 'src/bin/*' --ignore '../*' --ignore '/*' -o coverage/html", diff --git a/quadratic-files/.env.docker b/quadratic-files/.env.docker deleted file mode 100644 index 1691e5bfd9..0000000000 --- a/quadratic-files/.env.docker +++ /dev/null @@ -1,30 +0,0 @@ -HOST=0.0.0.0 -PORT=3002 -FILE_CHECK_S=5 -FILES_PER_CHECK=1000 -TRUNCATE_FILE_CHECK_S=60 -TRUNCATE_TRANSACTION_AGE_DAYS=5 # -ENVIRONMENT=docker - -AUTH0_JWKS_URI=http://host.docker.internal:3000/.well-known/jwks.json -QUADRATIC_API_URI=http://host.docker.internal:8000 -M2M_AUTH_TOKEN=M2M_AUTH_TOKEN - -PUBSUB_HOST=host.docker.internal -PUBSUB_PORT=6379 -PUBSUB_PASSWORD= -PUBSUB_ACTIVE_CHANNELS=active_channels -PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL=processed_transactions - -# Storage -STORAGE_TYPE=file-system # s3 or file-system - -# Storage: s3 -AWS_S3_REGION= -AWS_S3_BUCKET_NAME=quadratic-api-docker -AWS_S3_ACCESS_KEY_ID= -AWS_S3_SECRET_ACCESS_KEY= - -# Storage: file-system -STORAGE_DIR=/file-storage -STORAGE_ENCRYPTION_KEYS=eb4758047f74bdb2603cce75c4370327ca2c3662c4786867659126da8e64dfcc diff --git a/quadratic-files/.env.example b/quadratic-files/.env.example index 8d18ad5335..860c4cb29d 100644 --- a/quadratic-files/.env.example +++ b/quadratic-files/.env.example @@ -16,8 +16,8 @@ PUBSUB_PASSWORD= PUBSUB_ACTIVE_CHANNELS=active_channels PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL=processed_transactions -# Storage -STORAGE_TYPE=s3 # s3 or file-system +# Storage - s3 or file-system +STORAGE_TYPE=s3 # Storage: s3 AWS_S3_REGION=us-east-2 diff --git a/quadratic-files/.env.test b/quadratic-files/.env.test index fd3e72165d..b4e4bc85f2 100644 --- a/quadratic-files/.env.test +++ b/quadratic-files/.env.test @@ -16,8 +16,8 @@ PUBSUB_PASSWORD= PUBSUB_ACTIVE_CHANNELS=active_channels PUBSUB_PROCESSED_TRANSACTIONS_CHANNEL=processed_transactions -# Storage -STORAGE_TYPE=s3 # s3 or file-system +# Storage - s3 or file-system +STORAGE_TYPE=s3 # Storage: s3 AWS_S3_REGION= diff --git a/quadratic-files/Dockerfile b/quadratic-files/Dockerfile index 723ca8660e..de44b5c073 100644 --- a/quadratic-files/Dockerfile +++ b/quadratic-files/Dockerfile @@ -1,12 +1,16 @@ -FROM rust:latest as builder +FROM rust:latest AS builder -RUN USER=root cargo new --bin quadratic-files -COPY . /quadratic-files +WORKDIR /quadratic +COPY ./quadratic-files/. ./quadratic-files/ +COPY ./quadratic-core/. ./quadratic-core/ +COPY ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts +COPY ./quadratic-rust-shared/. ./quadratic-rust-shared/ RUN rustup component add rustfmt -WORKDIR /quadratic-files -RUN cargo build --release --package quadratic-files -FROM debian:stable-slim as runtime -COPY --from=builder /quadratic-files/target/release/quadratic-files . +WORKDIR /quadratic/quadratic-files +RUN cargo build --release + +FROM debian:stable-slim AS runtime +COPY --from=builder /quadratic/quadratic-files/target/release/quadratic-files . RUN apt-get update && apt install -y ca-certificates -CMD ["./quadratic-files"] +CMD ["./quadratic-files"] \ No newline at end of file diff --git a/quadratic-files/src/config.rs b/quadratic-files/src/config.rs index ea0061e32f..bd1a302dc8 100644 --- a/quadratic-files/src/config.rs +++ b/quadratic-files/src/config.rs @@ -56,12 +56,15 @@ pub(crate) struct Config { /// Load the global configuration from the environment into Config. pub(crate) fn config() -> Result { let filename = if cfg!(test) { ".env.test" } else { ".env" }; - // let filename = if cfg!(test) { ".env" } else { ".env" }; dotenv::from_filename(filename).ok(); dotenv().ok(); - let config = envy::from_env::().map_err(|e| FilesError::Config(e.to_string()))?; + // Try prefixed first, fall back to non-prefixed if that fails + let config = envy::prefixed("FILES__") + .from_env::() + .or_else(|_| envy::from_env::()) + .map_err(|e| FilesError::Config(e.to_string()))?; Ok(config) } diff --git a/quadratic-multiplayer/.env.docker b/quadratic-multiplayer/.env.docker deleted file mode 100644 index a6dfb7fa0f..0000000000 --- a/quadratic-multiplayer/.env.docker +++ /dev/null @@ -1,15 +0,0 @@ -HOST=0.0.0.0 -PORT=3001 -HEARTBEAT_CHECK_S=3 -HEARTBEAT_TIMEOUT_S=600 -QUADRATIC_API_URI=http://host.docker.internal:8000 -M2M_AUTH_TOKEN=M2M_AUTH_TOKEN -ENVIRONMENT=docker - -PUBSUB_HOST=host.docker.internal -PUBSUB_PORT=6379 -PUBSUB_PASSWORD= -PUBSUB_ACTIVE_CHANNELS=active_channels - -AUTH0_JWKS_URI=http://host.docker.internal:3000/.well-known/jwks.json -AUTHENTICATE_JWT=true \ No newline at end of file diff --git a/quadratic-multiplayer/Dockerfile b/quadratic-multiplayer/Dockerfile index d2921d58ae..5005cf49b1 100644 --- a/quadratic-multiplayer/Dockerfile +++ b/quadratic-multiplayer/Dockerfile @@ -1,12 +1,17 @@ -FROM rust:latest as builder +FROM rust:latest AS builder -RUN USER=root cargo new --bin quadratic-multiplayer -COPY . /quadratic-multiplayer +WORKDIR /quadratic +COPY updateAlertVersion.json . +COPY ./quadratic-multiplayer/. ./quadratic-multiplayer/ +COPY ./quadratic-core/. ./quadratic-core/ +COPY ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts ./quadratic-client/src/app/web-workers/quadraticCore/worker/rustCallbacks.ts +COPY ./quadratic-rust-shared/. ./quadratic-rust-shared/ RUN rustup component add rustfmt -WORKDIR /quadratic-multiplayer -RUN cargo build --release --package quadratic-multiplayer -FROM debian:stable-slim as runtime -COPY --from=builder /quadratic-multiplayer/target/release/quadratic-multiplayer . +WORKDIR /quadratic/quadratic-multiplayer +RUN cargo build --release + +FROM debian:stable-slim AS runtime +COPY --from=builder /quadratic/quadratic-multiplayer/target/release/quadratic-multiplayer . RUN apt-get update && apt install -y ca-certificates CMD ["./quadratic-multiplayer"] diff --git a/quadratic-multiplayer/package.json b/quadratic-multiplayer/package.json index f877bf4f8e..33a7790ca8 100644 --- a/quadratic-multiplayer/package.json +++ b/quadratic-multiplayer/package.json @@ -14,8 +14,8 @@ "coverage:gen": "CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='coverage/cargo-test-%p-%m.profraw' cargo test", "coverage:html": "grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore '/*' -o coverage/html", "coverage:view": "open coverage/html/index.html", - "docker:up": "docker compose -f ../docker-compose.base.yml up -d --wait", - "docker:down": "docker compose down -v", - "docker:test": "docker compose kill && npm run docker:up && npm run test && npm run docker:down" + "docker:up": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose -f ../docker-compose.yml --profile base --env-file ../.env.docker up -d --wait", + "docker:down": "ECR_OR_BUILD=build CLIENT_DEV=false docker compose -f ../docker-compose.yml --profile base --env-file ../.env.docker down -v", + "docker:test": "ECR_OR_BUILD=build CLIENT_DEV=true docker compose kill && npm run docker:up && npm run test && npm run docker:down" } } diff --git a/quadratic-multiplayer/src/config.rs b/quadratic-multiplayer/src/config.rs index c1188753a6..0ffad94018 100644 --- a/quadratic-multiplayer/src/config.rs +++ b/quadratic-multiplayer/src/config.rs @@ -32,12 +32,15 @@ pub(crate) struct Config { /// Load the global configuration from the environment into Config. pub(crate) fn config() -> Result { let filename = if cfg!(test) { ".env.test" } else { ".env" }; - // let filename = if cfg!(test) { ".env" } else { ".env" }; dotenv::from_filename(filename).ok(); dotenv().ok(); - let config = envy::from_env::().map_err(|e| MpError::Config(e.to_string()))?; + // Try prefixed first, fall back to non-prefixed if that fails + let config = envy::prefixed("MULTIPLAYER__") + .from_env::() + .or_else(|_| envy::from_env::()) + .map_err(|e| MpError::Config(e.to_string()))?; Ok(config) } diff --git a/quadratic-rust-client/Cargo.toml b/quadratic-rust-client/Cargo.toml index 1597fabf41..f750b63e9c 100644 --- a/quadratic-rust-client/Cargo.toml +++ b/quadratic-rust-client/Cargo.toml @@ -22,3 +22,27 @@ chrono = "0.4.38" [lib] crate-type = ["cdylib", "rlib"] + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ['-Os', '-g'] # TODO: -g seems to fix the name mangling problem + +[package.metadata.wasm-pack.profile.release.wasm-bindgen] +debug-js-glue = false +demangle-name-section = true +dwarf-debug-info = false + +[package.metadata.wasm-pack.profile.dev] +wasm-opt = false + +[package.metadata.wasm-pack.profile.dev.wasm-bindgen] +debug-js-glue = true +demangle-name-section = true +dwarf-debug-info = true + +[package.metadata.wasm-pack.profile.profiling] +wasm-opt = ['-O0', '-g'] + +[package.metadata.wasm-pack.profile.profiling.wasm-bindgen] +debug-js-glue = false +demangle-name-section = true +dwarf-debug-info = true diff --git a/quadratic-rust-client/package.json b/quadratic-rust-client/package.json index ae80d519d4..554a59c4e5 100644 --- a/quadratic-rust-client/package.json +++ b/quadratic-rust-client/package.json @@ -3,6 +3,7 @@ "private": true, "scripts": { "build": "wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs", + "build:dev": "wasm-pack build --profiling --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs", "dev": "cargo watch -s 'wasm-pack build --dev --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs'", "dev:perf": "cargo watch -s 'wasm-pack build --target web --out-dir ../quadratic-client/src/app/quadratic-rust-client --weak-refs'" } diff --git a/vercel.json b/vercel.json index 7aa121e10d..82f21b4ba5 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "./infra/client/build-client-ci.sh", - "installCommand": "npm install", + "installCommand": "npm install --no-audit --no-fund", "outputDirectory": "build", "headers": [ {