Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 86 additions & 23 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,51 +1,114 @@
name: Deploy
name: CI and deploy

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: tigermap-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5

- run: bun install --frozen-lockfile
- run: bun run lint
- run: bun run build

deploy-frontend:
name: Frontend → Cloudflare Pages
if: github.event_name == 'push'
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: 1.3.5

- run: bun install
working-directory: apps/web
- run: bun install --frozen-lockfile

- name: Build
working-directory: apps/web
env:
VITE_CAMPUS_MAP_TOKEN: ${{ secrets.VITE_CAMPUS_MAP_TOKEN }}
VITE_CAMPUS_MAP_STYLE: ${{ secrets.VITE_CAMPUS_MAP_STYLE }}
VITE_TIGERAPPS_MAPBOX_TOKEN: ${{ secrets.VITE_TIGERAPPS_MAPBOX_TOKEN }}
run: npx vite build
VITE_CAMPUS_MAP_TOKEN: ${{ vars.VITE_CAMPUS_MAP_TOKEN }}
VITE_CAMPUS_MAP_STYLE: ${{ vars.VITE_CAMPUS_MAP_STYLE }}
VITE_TIGERAPPS_MAPBOX_TOKEN: ${{ vars.VITE_TIGERAPPS_MAPBOX_TOKEN }}
run: bun run build --filter=@tigermap/web

- name: Deploy to Cloudflare Pages
working-directory: apps/web
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: bunx wrangler pages deploy dist --project-name=tigermap --commit-dirty=true
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
run: >-
bunx wrangler@4.78.0 pages deploy apps/web/dist
--project-name=tigermap
--branch=main
--commit-hash=${{ github.sha }}

deploy-backend:
name: Backend → EC2
if: github.event_name == 'push'
needs: validate
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
export PATH=$HOME/.bun/bin:$PATH
cd ~/TigerMap
git pull origin main
bun install
sudo systemctl restart tigermap-api
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ${{ vars.AWS_REGION }}

- name: Deploy with Systems Manager
env:
INSTANCE_ID: ${{ vars.EC2_INSTANCE_ID }}
run: |
command_id=$(aws ssm send-command \
--instance-ids "$INSTANCE_ID" \
--document-name AWS-RunShellScript \
--comment "Deploy TigerMap ${GITHUB_SHA}" \
--parameters 'commands=[
"set -eu",
"runuser -u ubuntu -- git -C /home/ubuntu/TigerMap fetch origin main",
"runuser -u ubuntu -- git -C /home/ubuntu/TigerMap checkout main",
"runuser -u ubuntu -- git -C /home/ubuntu/TigerMap merge --ff-only origin/main",
"runuser -u ubuntu -- /home/ubuntu/.bun/bin/bun install --cwd /home/ubuntu/TigerMap --frozen-lockfile",
"sudo systemctl restart tigermap-api",
"i=0; until curl -fsS http://127.0.0.1:3001/api/categories >/dev/null; do i=$((i+1)); if [ $i -ge 30 ]; then sudo journalctl -u tigermap-api -n 100 --no-pager; exit 1; fi; sleep 2; done"
]' \
--query 'Command.CommandId' \
--output text)

status=Pending
for _ in $(seq 1 80); do
status=$(aws ssm get-command-invocation \
--command-id "$command_id" \
--instance-id "$INSTANCE_ID" \
--query Status \
--output text 2>/dev/null || true)
case "$status" in
Success|Cancelled|Failed|TimedOut) break ;;
esac
sleep 3
done

aws ssm get-command-invocation \
--command-id "$command_id" \
--instance-id "$INSTANCE_ID" \
--query '{Status:Status,Output:StandardOutputContent,Error:StandardErrorContent}'
test "$status" = Success
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
node_modules
dist
.turbo
.wrangler
.env
.env.local
*.log
Expand Down
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"fastify": "^5.0.0"
},
"devDependencies": {
"@types/bun": "^1.3.5",
"typescript": "^5.7.0"
}
}
9 changes: 9 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions infra/aws/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# TigerMap production infrastructure

TigerMap uses Cloudflare Pages for the web app and one EC2 instance for the
Fastify API and its local SQLite data.

## AWS resources

- API instance: `i-0e1651e5f2954427d` (`tigermap-api`)
- API Elastic IP: `3.91.107.148`
- API security group: `sg-052b6b922dce1a08e` (`tigermap-api-sg`)
- Instance role/profile: `TigerMapInstanceRole`
- GitHub OIDC deploy role: `TigerMapGitHubDeployRole`
- Encrypted runtime parameters: `/tigermap/production/*`

The GitHub role is scoped to the `main` branch of the exact TigerAppsOrg
repository ID. It can only send an SSM command to the TigerMap instance and
read that command's result. No long-lived AWS key is stored in GitHub.

The EC2 environment file remains the runtime source for the current service.
Its values are also backed up as encrypted SSM parameters for recovery. Never
commit decrypted parameter values.

The API security group exposes HTTP on port 80 for the Pages Function proxy.
Administrative access uses SSM, so the instance has no public SSH ingress.

## GitHub configuration

Repository variables:

- `AWS_REGION=us-east-1`
- `AWS_DEPLOY_ROLE_ARN=arn:aws:iam::104733724423:role/TigerMapGitHubDeployRole`
- `EC2_INSTANCE_ID=i-0e1651e5f2954427d`
- `CLOUDFLARE_ACCOUNT_ID=e2744783298a24185adfbc07174ba2ae`
- `VITE_CAMPUS_MAP_TOKEN` (public token embedded in the web bundle)
- `VITE_CAMPUS_MAP_STYLE`
- `VITE_TIGERAPPS_MAPBOX_TOKEN` (public token embedded in the web bundle)

Repository secrets:

- `CLOUDFLARE_API_TOKEN`

Backend deployment uses SSM. The production checkout is updated with a
fast-forward-only merge, dependencies are installed from the lockfile, and
the API is restarted only after those steps succeed.
12 changes: 12 additions & 0 deletions infra/aws/ec2-assume-role-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
18 changes: 18 additions & 0 deletions infra/aws/github-assume-role-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::104733724423:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:TigerAppsOrg@1450294/TigerMap@1373863252:ref:refs/heads/main"
}
}
}
]
}
23 changes: 23 additions & 0 deletions infra/aws/github-deploy-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RunTigerMapDeployment",
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ec2:us-east-1:104733724423:instance/i-0e1651e5f2954427d",
"arn:aws:ssm:us-east-1::document/AWS-RunShellScript"
]
},
{
"Sid": "ReadTigerMapDeploymentResult",
"Effect": "Allow",
"Action": [
"ssm:GetCommandInvocation",
"ssm:ListCommandInvocations"
],
"Resource": "*"
}
]
}
15 changes: 15 additions & 0 deletions infra/aws/instance-parameters-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadTigerMapProductionParameters",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
],
"Resource": "arn:aws:ssm:us-east-1:104733724423:parameter/tigermap/production/*"
}
]
}
Loading