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
26 changes: 25 additions & 1 deletion .github/workflows/build-and-push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Build and Push to Dockerhub
on:
workflow_dispatch:
workflow_run:
workflows: ["Test and Lint"]
workflows: ["Test and Lint", "Code Scanning"]
types:
- completed
branches:
Expand All @@ -19,6 +19,30 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}

steps:
- name: Verify all prerequisite workflows passed
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: actions/github-script@v7
with:
script: |
const sha = context.payload.workflow_run.head_sha;
const requiredWorkflows = ['Test and Lint', 'Code Scanning'];

const runs = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
head_sha: sha,
status: 'success',
});

for (const name of requiredWorkflows) {
const passed = runs.data.workflow_runs.some(r => r.name === name);
if (!passed) {
core.info(`Workflow "${name}" has not succeeded yet for SHA ${sha}. Skipping build.`);
process.exit(1);
}
}
core.info(`All prerequisite workflows passed for SHA ${sha}.`);

- name: Checkout code
uses: actions/checkout@v4

Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/code-scanning.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Code Scanning

on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'Dockerfile'
- 'app/**'
- 'tests/**'
- 'pyproject.toml'
- 'requirements.txt'
pull_request:
branches:
- main

permissions:
security-events: write
contents: read

jobs:
security-scan:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'

- name: Install scanning tools
run: |
pip install --upgrade pip
pip install bandit[sarif] pip-audit

- name: Run Bandit (SAST)
run: bandit -r app/ -f sarif -o bandit-results.sarif --severity-level medium

- name: Upload Bandit SARIF
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: bandit-results.sarif
category: bandit

- name: Run pip-audit (dependency vulnerabilities)
run: pip-audit -r requirements.txt
9 changes: 7 additions & 2 deletions .github/workflows/deploy-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,24 @@ jobs:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}

steps:
- name: Checkout code
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: ${{ secrets.AWS_REGION }}

- name: Deploy via SSM
- name: Sync docker-compose and deploy via SSM
run: |
COMPOSE_B64=$(base64 -w0 docker-compose.yaml)

COMMAND_ID=$(aws ssm send-command \
--instance-ids "${{ secrets.EC2_INSTANCE_ID }}" \
--document-name "AWS-RunShellScript" \
--parameters 'commands=["cd /opt/app && docker compose pull && docker compose up -d --remove-orphans && docker image prune -f"]' \
--parameters "commands=[\"echo '${COMPOSE_B64}' | base64 -d > /opt/app/docker-compose.yaml && chown ubuntu:ubuntu /opt/app/docker-compose.yaml && grep -q DOCKER_IMAGE /opt/app/.env 2>/dev/null || echo 'DOCKER_IMAGE=${{ secrets.DOCKERHUB_USERNAME }}/smr-website:latest' > /opt/app/.env && cd /opt/app && docker compose pull && docker compose up -d --remove-orphans && docker image prune -f\"]" \
--query "Command.CommandId" \
--output text)

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12-slim
FROM python:3.13.12-slim

LABEL maintainer="Sean-Michael seanm.riesterer@gmail.com"

Expand Down
43 changes: 43 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: seanmichael-dev

services:
traefik:
image: traefik:v3.3
container_name: traefik
restart: unless-stopped
command:
- "--api.dashboard=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entryPoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=seanm.riesterer@gmail.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
depends_on:
- app

app:
image: ${DOCKER_IMAGE}
container_name: app
restart: unless-stopped
expose:
- "8000"
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`seanmichael.dev`) || Host(`www.seanmichael.dev`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=letsencrypt"
- "traefik.http.services.app.loadbalancer.server.port=8000"

volumes:
letsencrypt:
56 changes: 4 additions & 52 deletions infrastructure/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,56 +30,8 @@ usermod -aG docker ubuntu
mkdir -p /opt/app
chown ubuntu:ubuntu /opt/app

# Create docker-compose.yml
cat > /opt/app/docker-compose.yml <<'EOF'
services:
app:
image: ${docker_image}
container_name: app
restart: unless-stopped
expose:
- "8000"

nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app
# Write .env with Terraform-interpolated image value
cat > /opt/app/.env <<EOF
DOCKER_IMAGE=${docker_image}
EOF

# Create nginx config
cat > /opt/app/nginx.conf <<'EOF'
events {
worker_connections 1024;
}

http {
upstream app {
server app:8000;
}

server {
listen 80;
server_name _;

location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
EOF

# Start the application
cd /opt/app
docker compose pull
docker compose up -d
chown ubuntu:ubuntu /opt/app/.env
Loading