diff --git a/.cursor/rules/deployment-workflow.mdc b/.cursor/rules/deployment-workflow.mdc
new file mode 100644
index 0000000..5b696c3
--- /dev/null
+++ b/.cursor/rules/deployment-workflow.mdc
@@ -0,0 +1,62 @@
+---
+description:
+globs:
+alwaysApply: false
+---
+# Deployment Workflow
+
+## Testing Requirements
+- All changes must be tested locally before deployment
+- Tests must pass for both mobile and desktop viewports
+- Lighthouse tests must maintain perfect scores (100/100) for accessibility and SEO
+- Playwright tests must pass for all viewport sizes
+
+## Git Operations
+- Avoid use direct git commands (`git add`, `git commit`, `git push`) if we do git flows do then as oneliners.
+- All git operations must be performed through `./build-test-deploy.sh`
+- The script handles:
+ - Building the Docker container
+ - Running all tests
+ - Committing changes
+ - Pushing to repository
+
+## Build-Test-Deploy Script
+The `./build-test-deploy.sh` script is the single source of truth for deployment:
+1. Builds the Docker container
+2. Runs the test suite
+3. Only proceeds with git operations if tests pass
+4. Handles all git operations in the correct order
+
+## Testing Process
+1. Make changes to the codebase
+2. Run `./build-test-deploy.sh` to:
+ - Build the container
+ - Run tests
+ - Deploy if tests pass
+3. If tests fail:
+ - Fix the issues
+ - Run the script again
+ - Do not proceed with deployment until all tests pass
+
+## Viewport Testing
+The test suite runs against multiple viewport sizes:
+- Mobile: 375x667
+- Desktop: 1024x768
+
+All tests must pass for all viewport sizes before deployment is allowed.
+
+## Lighthouse Requirements
+Maintain perfect scores:
+- Accessibility: 100/100
+- SEO: 100/100
+- Performance: 97/100 minimum
+- Best Practices: 93/100 minimum
+
+## Playwright Tests
+Must pass all accessibility tests:
+- WCAG 2.1 Level AAA compliance
+- ARIA attributes
+- Heading structure
+- External link validation
+- Color contrast
+- Image alt text
diff --git a/build-test-deploy.sh b/build-test-deploy.sh
new file mode 100755
index 0000000..2059749
--- /dev/null
+++ b/build-test-deploy.sh
@@ -0,0 +1,84 @@
+#!/bin/bash
+set -e
+
+IMAGE_NAME="resume:latest"
+CONTAINER_NAME="resume_test_container"
+DOCKER_DIR="docker"
+RESUME_DIR="$DOCKER_DIR/resume"
+
+# Recalculate SHA256 hash for styles.css and update integrity in index.html
+STYLES_HASH=$(shasum -a 256 $RESUME_DIR/styles.css | awk '{print $1}' | xxd -r -p | base64)
+sed -i -E "s|href=\"styles.css\" integrity=\"sha256-[^\"]*\"|href=\"styles.css\" integrity=\"sha256-$STYLES_HASH\"|" $RESUME_DIR/index.html
+
+# Verify the hash update
+if ! grep -q "integrity=\"sha256-$STYLES_HASH\"" $RESUME_DIR/index.html; then
+ echo "Error: Integrity hash update failed."
+ exit 1
+fi
+
+# Test the CSS loading
+if ! curl -s http://localhost:8080/styles.css > /dev/null; then
+ echo "Error: CSS file not accessible."
+ exit 1
+fi
+
+# Verify the hash in the container
+CONTAINER_HASH=$(docker exec $CONTAINER_NAME cat /srv/styles.css | shasum -a 256 | awk '{print $1}' | xxd -r -p | base64)
+if [ "$CONTAINER_HASH" != "$STYLES_HASH" ]; then
+ echo "Error: Integrity hash mismatch in container."
+ exit 1
+fi
+
+# Build Docker image
+cd "$DOCKER_DIR"
+echo "Building Docker image..."
+docker build -t $IMAGE_NAME ./resume/
+
+# Stop and remove any previous container
+if [ $(docker ps -aq -f name=$CONTAINER_NAME) ]; then
+ echo "Removing previous test container..."
+ docker rm -f $CONTAINER_NAME || true
+fi
+
+# Ensure port 8080 is free
+echo "Ensuring port 8080 is free..."
+lsof -i :8080 | grep LISTEN | awk '{print $2}' | xargs kill -9 || true
+
+# Run Docker container in the background
+echo "Starting Docker container..."
+docker run -d --name $CONTAINER_NAME -p 8080:8080 $IMAGE_NAME
+
+# Wait for the server to be ready
+MAX_TRIES=20
+TRIES=0
+until curl -s http://localhost:8080/ > /dev/null; do
+ TRIES=$((TRIES+1))
+ if [ $TRIES -ge $MAX_TRIES ]; then
+ echo "Server did not start in time."
+ docker rm -f $CONTAINER_NAME
+ exit 1
+ fi
+ echo "Waiting for server... ($TRIES/$MAX_TRIES)"
+ sleep 2
+done
+
+echo "Server is up. Running tests..."
+cd ..
+
+npm install
+npm run setup
+if npm test; then
+ echo "Tests passed. Committing and pushing changes."
+ git add .
+ git commit -m "Automated build, test, and deploy"
+ git push
+else
+ echo "Tests failed. Not deploying."
+ docker rm -f $CONTAINER_NAME
+ exit 1
+fi
+
+echo "Cleaning up Docker container..."
+docker rm -f $CONTAINER_NAME
+
+echo "Done."
\ No newline at end of file
diff --git a/docker/resume/Caddyfile b/docker/resume/Caddyfile
index 957e32c..9f19f31 100644
--- a/docker/resume/Caddyfile
+++ b/docker/resume/Caddyfile
@@ -1,4 +1,4 @@
-:8080 {
+colinknapp.com {
root * .
file_server
encode gzip
@@ -10,7 +10,57 @@
-X-Powered-By
# HSTS
- # Strict-Transport-Security "max-age=31536000; includeSubDomains"
+ Strict-Transport-Security "max-age=31536000; includeSubDomains"
+
+ # Basic security headers
+ X-Frame-Options "DENY"
+ X-Content-Type-Options "nosniff"
+ Referrer-Policy "strict-origin-when-cross-origin"
+
+ # Permissions policy
+ Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
+
+ # Cross-origin isolation headers
+ Cross-Origin-Embedder-Policy "require-corp"
+ Cross-Origin-Resource-Policy "same-origin"
+ Cross-Origin-Opener-Policy "same-origin"
+
+ # Cache control for static assets
+ Cache-Control "public, max-age=31536000, immutable"
+
+ # CSP with hashes for scripts and styles
+ Content-Security-Policy "default-src 'none'; script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544=' 'sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8='; style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none';"
+ }
+
+ # Handle 404s
+ handle_errors {
+ respond "{err.status_code} {err.status_text}"
+ }
+
+ # Logging
+ log {
+ output stdout
+ format json
+ }
+
+ # Enable static file serving with caching
+ file_server {
+ precompressed
+ browse
+ }
+}
+
+# Local development server
+:8080 {
+ root * .
+ file_server
+ encode gzip
+
+ # Performance optimizations
+ header {
+ # Remove default Caddy headers
+ -Server
+ -X-Powered-By
# Basic security headers
X-Frame-Options "DENY"
diff --git a/docker/resume/index.html b/docker/resume/index.html
index f2a6b8e..c1b630d 100644
--- a/docker/resume/index.html
+++ b/docker/resume/index.html
@@ -5,7 +5,7 @@
Colin Knapp Portfolio
-
+
@@ -18,6 +18,13 @@
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓
+
@@ -195,5 +202,24 @@
Accessibility: This website is designed and developed to meet WCAG 2.1 Level AAA standards, ensuring the highest level of accessibility for all users. Features include high contrast ratios, keyboard navigation, screen reader compatibility, and responsive design. The site supports both light and dark modes with automatic system preference detection.