forked from colin/resume
2
0
Fork 0

Compare commits

..

57 Commits
main ... main

Author SHA1 Message Date
Your Name be50c5de9c fixing deploy step
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-06-05 13:11:42 -04:00
colin 34f659be3f Update stack.staging.yml
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-06-05 13:04:40 -04:00
colin 7aa1337538 Update stack.staging.yml
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-06-05 13:04:10 -04:00
colin aa3afc9b4c Update stack.production.yml
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-06-05 13:03:46 -04:00
Leopere 77517079a7 update to rules and tests
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-05-10 17:14:38 -04:00
Leopere 5ac1c24481 Fix CSP for PDF download button by moving to external script
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-05-10 17:04:31 -04:00
Leopere 911842dc06 Fix CSP for PDF download button by moving to external script
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-05-10 16:27:15 -04:00
Leopere 04e5a9fa34 Add download as PDF button and update test configurations
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-05-10 16:12:46 -04:00
Leopere 4f9596bbee fix: update Cal.com meeting URL to correct username
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-22 21:39:33 -04:00
Leopere ab493a89f8 fix: remove 60-minute meeting option due to URL issues
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-22 21:06:25 -04:00
Leopere ff0e765b31 fix: update 60-minute meeting URL to correct format
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-22 20:53:41 -04:00
Your Name 10e340c341 Add Cal.com calendar meeting links
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-21 22:14:57 -04:00
Leopere cd94db9c03 temporarily disable HSTS to resolve certificate provisioning issues
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-15 16:33:47 -04:00
colin 0b693d7d2b Update docker/resume/index.html
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-04-03 11:37:33 -04:00
Your Name 7a5666ffda Add Subresource Integrity (SRI) hashes to script and style tags
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:55:44 -04:00
Your Name d2e6cc7db8 Update CSP to use hash for styles.css and remove unsafe-inline
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:54:33 -04:00
Your Name 3853b6ba6f Remove unsupported protocols directive (HTTP/2 enabled by default in Caddy 2)
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:49:32 -04:00
Your Name b93b8564de Update Dockerfile.production with multi-stage build and security hardening
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:41:26 -04:00
Your Name e97ef265ff Update Docker configuration with production build and security hardening
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:40:52 -04:00
Your Name ad26460c92 Add production Dockerfile with multi-stage build and security hardening
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:36:21 -04:00
Your Name 2cdd7341c0 Remove Brotli compression (not available in default Caddy image)
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:35:43 -04:00
Your Name ab5f8e774e Update CSP to use script hashes instead of unsafe-inline
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:31:57 -04:00
Your Name a5583c3afe Remove test results from git tracking and update .gitignore
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:30:32 -04:00
Your Name 3a9068b883 Remove TLS directives (handled by reverse proxy)
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:24:43 -04:00
Your Name 3d0dd2c361 Optimize Caddyfile for better performance
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 09:24:24 -04:00
Your Name 3598c99b9f Update resume with Oh My Form Docker pulls and comprehensive experience
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:18:30 -04:00
Your Name 4434650aac Add Oh My Form Docker pulls achievement
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 09:00:48 -04:00
Your Name 0f81e0318e Add utils.js to Docker build and update CSP with hash
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 05:05:59 -04:00
Your Name 630ef90df1 Add utils.js with SHA-256 hash in CSP
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 05:01:34 -04:00
Your Name cc0142f000 Add local header testing infrastructure
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:58:51 -04:00
Your Name 0c3c133431 Add comprehensive README with testing instructions
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:43:41 -04:00
Your Name 8c35ab5296 Add Playwright configuration file
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:43:14 -04:00
Your Name a10eea979f Add Playwright and Lighthouse testing infrastructure
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:42:57 -04:00
Your Name 39caf88782 Enhance theme toggle accessibility and confirm auto mode default
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:39:44 -04:00
Your Name 015f8ce76f Improve link accessibility with underlines and better contrast
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:36:03 -04:00
Your Name 3e2a32c1cf Revert CSP configuration to stable version without unsafe-inline
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:32:25 -04:00
Your Name 90b9d2dd1b Fix theme toggle CSS and add data-theme attribute handling
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:29:22 -04:00
Your Name 7b80d9dfa0 Comment out wait-for-deploy-production step in Woodpecker CI config
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:27:35 -04:00
Your Name 71e142b82e Fix Docker configuration to include theme.js and styles.css files
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:23:32 -04:00
Your Name 1c328df0c7 Switch from SRI to nonce-based CSP approach for better script handling
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:20:58 -04:00
Your Name 885914812d Debug CSP: temporarily allow inline scripts and remove SRI requirement
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:16:50 -04:00
Your Name 905b480a2e Fix SRI hash for theme.js to match deployed content
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 04:14:39 -04:00
Your Name f739edc7eb Add form-action 'none' to CSP for additional security
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:11:35 -04:00
Your Name 0b46750148 Enhance CSP with default-src 'none' for maximum security
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:08:37 -04:00
Your Name ac3d30d597 Fix CSP issues by moving inline script to external file and adding SRI
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 04:04:47 -04:00
colin 0fbd77f073 Update docker/resume/Caddyfile
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 00:23:16 -04:00
colin a62baf40e1 Update docker/resume/index.html
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 00:22:58 -04:00
colin ece9887a5b Add docker/resume/styles.css
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 00:22:44 -04:00
colin bfae2029b8 Update docker/resume/Caddyfile
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-31 00:16:14 -04:00
colin 2e9c196d8a Update docker/resume/Caddyfile
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-31 00:14:49 -04:00
colin 12f3ca9a3b Update docker/resume/index.html
ci/woodpecker/push/woodpecker Pipeline was successful Details
2025-03-30 23:41:58 -04:00
colin 18ece0205f Update docker/resume/Caddyfile
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 22:46:05 -04:00
colin 759dfa290e Update docker/resume/resume.html
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 22:45:41 -04:00
colin 1e46b4cc50 Update .woodpecker.yml
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 21:30:20 -04:00
colin 79c5a6d935 Update docker/resume/nginx.conf
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 21:28:01 -04:00
colin de1f9d3364 Update docker/resume/Dockerfile
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 21:27:41 -04:00
colin f963abe2ed Update docker/resume/resume.html
ci/woodpecker/push/woodpecker Pipeline failed Details
2025-03-30 21:22:47 -04:00
36 changed files with 5444 additions and 100 deletions

View File

@ -0,0 +1,23 @@
---
description:
globs: *.js,*.css
alwaysApply: false
---
# Asset Hashing and CSP Update Rule
This rule ensures that all `.js` and `.css` assets are properly hashed and their integrity hashes are updated in `index.html` and added to the Content Security Policy (CSP) in the `Caddyfile` during every build and push process.
## Process to Follow
1. **Hash Calculation**: Before building the Docker image, calculate the SHA256 hash for each `.js` and `.css` file in the `docker/resume/` directory using a command like `shasum -a 256 filename | awk '{print $1}' | xxd -r -p | base64`.
2. **Update index.html**: Update the `integrity` attribute in the `<script>` and `<link>` tags in `index.html` with the new hashes for each respective file.
3. **Update CSP in Caddyfile**: Ensure the CSP in the `Caddyfile` includes the hashes for all scripts and styles under `script-src` and `style-src` directives respectively.
4. **Automate in Build Script**: Modify the `build-test-deploy.sh` script to automate the hashing and updating process before the Docker build step.
5. **Verification**: After updating, verify that the hashes in `index.html` and `Caddyfile` match the calculated hashes to prevent deployment with mismatched integrity.
## Relevant Files
- [docker/resume/index.html](mdc:docker/resume/index.html)
- [docker/resume/Caddyfile](mdc:docker/resume/Caddyfile)
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
This rule must be followed to maintain security integrity and prevent CSP violations during deployment.

View File

@ -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

View File

@ -0,0 +1,26 @@
---
description:
globs:
alwaysApply: false
---
# Test Suite Integrity Rule
This rule ensures that the test suite for the project contains meaningful tests that perform actual checks and validations, rather than being empty placeholder boilerplate. All tests MUST do SOMETHING significant to verify the functionality, security, or performance of the application.
## Guidelines to Follow
1. **Non-Empty Tests**: Every test file in the `tests/` directory must contain at least one test case that performs a specific validation or check. Empty test files or test cases with no assertions are not allowed.
2. **Meaningful Assertions**: Each test must include assertions or expectations that verify the behavior of the application. Tests should not simply run without checking outcomes (e.g., no empty `expect()` calls or trivial assertions like `expect(true).toBe(true)`).
3. **Coverage of Key Features**: The test suite must cover critical aspects of the application, including but not limited to:
- Accessibility (WCAG compliance)
- Security headers and CSP compliance
- Functional features like the PDF download button
- Performance metrics (via tools like Lighthouse)
4. **Regular Review**: Before running `build-test-deploy.sh`, review the test suite to ensure no placeholder or boilerplate tests remain. If a test is temporarily skipped or incomplete, it must be annotated with a clear `TODO` comment explaining the reason and a plan for completion.
5. **Automation in Build Script**: If possible, add a pre-test step in `build-test-deploy.sh` to scan test files for empty or placeholder content (e.g., using grep to detect empty test blocks or missing assertions) and fail the build if such issues are found.
## Relevant Files and Directories
- [tests/](mdc:tests)
- [build-test-deploy.sh](mdc:build-test-deploy.sh)
This rule must be adhered to in order to maintain a robust and reliable testing framework that ensures the quality and security of the application.

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Test results and reports
tests/reports/
playwright-report/
test-results/
# Node modules
node_modules/
# IDE files
.vscode/
.idea/
# OS files
.DS_Store
Thumbs.db

View File

@ -1,4 +1,3 @@
# build 0
labels:
location: manager
@ -122,22 +121,22 @@ steps:
event: [push, cron]
# Wait for Deploy Completion
wait-for-deploy-production:
name: wait-for-deploy-production
image: woodpeckerci/plugin-git
commands:
- echo "Waiting for deploy step to complete rollout."
- sleep 60
when:
branch: main
event: push
# wait-for-deploy-production:
# name: wait-for-deploy-production
# image: woodpeckerci/plugin-git
# commands:
# - echo "Waiting for deploy step to complete rollout."
# - sleep 60
# when:
# branch: main
# event: push
# Post-Deployment Smoke Tests
post-deploy-smoke-tests-git-nixc-us:
name: run-post-deploy-smoke-tests-git-nixc-us
image: codeberg.org/nixius/playwright:latest
environment:
BASE_URL: "https://git.nixc.us"
when:
branch: main
event: push
# post-deploy-smoke-tests-git-nixc-us:
# name: run-post-deploy-smoke-tests-git-nixc-us
# image: codeberg.org/nixius/playwright:latest
# environment:
# BASE_URL: "https://git.nixc.us"
# when:
# branch: main
# event: push

96
README.md Normal file
View File

@ -0,0 +1,96 @@
# Colin Knapp Portfolio Resume
A professional portfolio website with accessibility features and automated testing.
## Features
- Responsive design for all device sizes
- Light/dark mode toggle with system preference detection
- High contrast accessible design
- Keyboard navigable interface
- WCAG 2.1 Level AA+ compliant
## Development
### Prerequisites
- Node.js (v14 or higher)
- npm (v6 or higher)
### Setup
1. Clone the repository:
```bash
git clone git@git.nixc.us:colin/resume.git
cd resume
```
2. Install dependencies:
```bash
npm install
```
3. Set up Playwright browsers:
```bash
npm run setup
```
### Local Development
To serve the site locally for development:
```bash
npm run serve
```
This will start a local development server at http://localhost:8080.
### Testing
The project includes automated testing using Playwright for functional tests and Lighthouse for performance and accessibility audits.
#### Running all tests
```bash
npm test
```
#### Running only Playwright tests
```bash
npm run test:playwright
```
#### Running only Lighthouse tests
```bash
npm run test:lighthouse
```
### Docker Deployment
The site is deployed using Docker and Caddy. The deployment configuration is in the `docker` directory.
To build and run the Docker container locally:
```bash
cd docker
docker build -t resume:latest ./resume/
docker run -p 8080:8080 resume:latest
```
## Accessibility
This site is designed to meet WCAG 2.1 Level AA+ standards. Key accessibility features include:
- Proper heading hierarchy
- Keyboard navigable interface with visible focus states
- Color contrast ratios that exceed WCAG AA requirements
- Semantic HTML structure
- Accessible form controls and ARIA attributes
- Light/dark mode support with system preference detection
- Responsive design for all device sizes
## License
ISC © Colin Knapp

95
build-test-deploy.sh Executable file
View File

@ -0,0 +1,95 @@
#!/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
# Run tests and save output for AI parsing
if npm test > test_output.log 2>&1; 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."
cat test_output.log
docker rm -f $CONTAINER_NAME
exit 1
fi
# Optionally open report in browser if available, but don't require interaction
echo "Test output saved to test_output.log for AI parsing."
if command -v open >/dev/null 2>&1; then
echo "Opening HTML report in browser (if available)..."
open http://localhost:9323 || echo "Could not open browser automatically. Please visit http://localhost:9323 to view the report."
else
echo "Browser opening not supported. Report available at http://localhost:9323 if a server is running."
fi
echo "Cleaning up Docker container..."
docker rm -f $CONTAINER_NAME
echo "Done."

View File

@ -1,6 +1,6 @@
services:
lucky-ddg:
resume:
build:
context: ./docker/lucky-ddg/
dockerfile: Dockerfile
image: git.nixc.us/nixius/lucky-ddg:production
context: ./docker/resume/
dockerfile: Dockerfile.production
image: git.nixc.us/colin/resume:production

View File

@ -1,6 +1,6 @@
services:
lucky-ddg:
resume:
build:
context: ./docker/lucky-ddg/
context: ./docker/resume/
dockerfile: Dockerfile
image: git.nixc.us/nixius/lucky-ddg:staging
image: git.nixc.us/colin/resume:staging

View File

@ -1,21 +0,0 @@
# Use the official Python image from Docker Hub
FROM python:3.9-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install duckduckgo_search globally
RUN pip install --no-cache-dir duckduckgo_search
# Set working directory
WORKDIR /app
# Copy the application code
COPY . .
# Expose the Flask port
EXPOSE 5000
# Run the application
CMD ["python", "app.py"]

View File

@ -1,30 +0,0 @@
import subprocess
from flask import Flask, request, redirect
app = Flask(__name__)
@app.route('/search')
def search():
query = request.args.get('q')
if not query:
return "Query parameter 'q' is missing.", 400
try:
# Execute the ddgs CLI command to perform the search
result = subprocess.run(
['ddgs', 'text', '-k', query, '-m', '1'],
capture_output=True,
text=True,
check=True
)
# Parse the output to extract the first result URL
output_lines = result.stdout.splitlines()
for line in output_lines:
if line.startswith('http'):
return redirect(line)
return "No results found.", 404
except subprocess.CalledProcessError as e:
return f"An error occurred: {e}", 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)

View File

@ -1 +0,0 @@
flask==2.2.2

12
docker/package.json Normal file
View File

@ -0,0 +1,12 @@
{
"name": "docker",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}

101
docker/resume/Caddyfile Normal file
View File

@ -0,0 +1,101 @@
colinknapp.com {
root * .
file_server
encode gzip
# Performance optimizations
header {
# Remove default Caddy headers
-Server
-X-Powered-By
# HSTS
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=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; 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"
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=' 'sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs='; 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
}
}

19
docker/resume/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM caddy:2-alpine
# Copy Caddyfile and static content
COPY Caddyfile /etc/caddy/Caddyfile
COPY index.html /srv/
COPY theme.js /srv/
COPY utils.js /srv/
COPY styles.css /srv/
COPY pdf-download.js /srv/
COPY favicon.ico /srv/
# Expose port 8080
EXPOSE 8080
# Set working directory
WORKDIR /srv
# Run Caddy
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile"]

View File

@ -0,0 +1 @@
FROM git.nixc.us/colin/resume:staging

View File

@ -0,0 +1 @@

207
docker/resume/index.html Normal file
View File

@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
<title>Colin Knapp Portfolio</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous">
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script>
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script>
</head>
<body>
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
role="switch"
aria-checked="false"
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓</button>
<button
id="downloadPDF"
aria-label="Download as PDF"
title="Download resume as PDF"
tabindex="0"
>📄</button>
</div>
<div class="container-fluid" role="main">
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
</ul>
<hr>
<h2>Project Experience</h2>
<h3>DevSecOps at Addis Enterprises</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
</ul>
<h3>Healthcare Platform Infrastructure</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
</ul>
<h3>WordPress Security Automation</h3>
<p><strong>Timeframe:</strong> 2023<br>
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Created a Docker-based utility for automated malware detection and removal.</li>
<li>Implemented hardening measures to prevent reinfection.</li>
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
</ul>
<h3>YouTube Game Development & Cybersecurity</h3>
<p><strong>Timeframe:</strong> 2009-2022<br>
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Built immersive gaming experiences for large audiences.</li>
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
</ul>
<h3>Web Design & Java Plugin Development</h3>
<p><strong>Timeframe:</strong> 2009-2023<br>
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
</ul>
<h3>App Development for Influencers</h3>
<p><strong>Timeframe:</strong> 2013-2018<br>
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
</ul>
<h3>DevOps & Co-Founder at NitricConcepts</h3>
<p><strong>Timeframe:</strong> 2018-2021<br>
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
<li>Fostered a collaborative, innovative team culture.<br>
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
</ul>
<h3>Entrepreneurial Ventures</h3>
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
<p><strong>Timeframe:</strong> 2013-2017<br>
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
<strong>Impact:</strong> Created a profitable, independent venture.</p>
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
<p><strong>Timeframe:</strong> 2016<br>
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
<hr>
<h2>Additional Information</h2>
<h3>Personal Development</h3>
<p><strong>Timeframe:</strong> 2009-Present</p>
<ul>
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
</ul>
<h3>Relevant Links & Web Impact</h3>
<ul>
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
</ul>
<hr>
<div class="section" role="region" aria-labelledby="open-source-heading">
<h2 id="open-source-heading">Open Source & Infrastructure</h2>
<div class="entry">
<h3>PlotSquared & FastAsyncWorldEdit</h3>
<p class="date">2013-Present</p>
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
<ul>
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
</ul>
</div>
<div class="entry">
<h3>Athion.net Infrastructure</h3>
<p class="date">2013-Present</p>
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
<ul>
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
</ul>
</div>
<div class="experience-item">
<h3>Software Engineer</h3>
<p class="company">Oh My Form</p>
<p class="date">2020 - Present</p>
<p class="achievement">Led development of Oh My Form, achieving over 1.5 million Docker pulls as verified by <a href="https://hub.docker.com/u/ohmyform" target="_blank" rel="noopener noreferrer">Docker Hub</a> and <a href="https://archive.is/lZHAT" target="_blank" rel="noopener noreferrer">archived</a>.</p>
<ul>
<li>Developed and maintained a secure, high-performance form builder application</li>
<li>Implemented robust security measures and best practices</li>
<li>Optimized application performance and user experience</li>
</ul>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> 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.</p>
</div>
</body>
</html>

207
docker/resume/index.html-E Normal file
View File

@ -0,0 +1,207 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
<title>Colin Knapp Portfolio</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css" integrity="sha256-Ps1dklCHzk1leTAfqkeA64YDuDJxx5QZBjC2UQhSdz0=" crossorigin="anonymous">
<script src="theme.js" integrity="sha256-anTkUs/oFZJulKUMaMjZlwaALEmPOP8op0psAo5Bhh8=" crossorigin="anonymous"></script>
<script src="pdf-download.js" integrity="sha256-BASkmAmg7eoYCMd6odA6kQ8yGsFnoxaX48WbQvMkehs=" crossorigin="anonymous"></script>
</head>
<body>
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
role="switch"
aria-checked="false"
title="Toggle between light, dark, and auto theme modes"
tabindex="0"
>🌓</button>
<button
id="downloadPDF"
aria-label="Download as PDF"
title="Download resume as PDF"
tabindex="0"
>📄</button>
</div>
<div class="container-fluid" role="main">
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2025@colinknapp.com">recruitme2025@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a><br>
<strong>Schedule a Meeting:</strong> <a href="https://cal.com/colink/30min" target="_blank">30 Minute Meeting</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
</ul>
<hr>
<h2>Project Experience</h2>
<h3>DevSecOps at Addis Enterprises</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
</ul>
<h3>Healthcare Platform Infrastructure</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
</ul>
<h3>WordPress Security Automation</h3>
<p><strong>Timeframe:</strong> 2023<br>
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Created a Docker-based utility for automated malware detection and removal.</li>
<li>Implemented hardening measures to prevent reinfection.</li>
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
</ul>
<h3>YouTube Game Development & Cybersecurity</h3>
<p><strong>Timeframe:</strong> 2009-2022<br>
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Built immersive gaming experiences for large audiences.</li>
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
</ul>
<h3>Web Design & Java Plugin Development</h3>
<p><strong>Timeframe:</strong> 2009-2023<br>
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
</ul>
<h3>App Development for Influencers</h3>
<p><strong>Timeframe:</strong> 2013-2018<br>
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
</ul>
<h3>DevOps & Co-Founder at NitricConcepts</h3>
<p><strong>Timeframe:</strong> 2018-2021<br>
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
<li>Fostered a collaborative, innovative team culture.<br>
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
</ul>
<h3>Entrepreneurial Ventures</h3>
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
<p><strong>Timeframe:</strong> 2013-2017<br>
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
<strong>Impact:</strong> Created a profitable, independent venture.</p>
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
<p><strong>Timeframe:</strong> 2016<br>
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
<hr>
<h2>Additional Information</h2>
<h3>Personal Development</h3>
<p><strong>Timeframe:</strong> 2009-Present</p>
<ul>
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
</ul>
<h3>Relevant Links & Web Impact</h3>
<ul>
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
</ul>
<hr>
<div class="section" role="region" aria-labelledby="open-source-heading">
<h2 id="open-source-heading">Open Source & Infrastructure</h2>
<div class="entry">
<h3>PlotSquared & FastAsyncWorldEdit</h3>
<p class="date">2013-Present</p>
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
<ul>
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
</ul>
</div>
<div class="entry">
<h3>Athion.net Infrastructure</h3>
<p class="date">2013-Present</p>
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
<ul>
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
</ul>
</div>
<div class="experience-item">
<h3>Software Engineer</h3>
<p class="company">Oh My Form</p>
<p class="date">2020 - Present</p>
<p class="achievement">Led development of Oh My Form, achieving over 1.5 million Docker pulls as verified by <a href="https://hub.docker.com/u/ohmyform" target="_blank" rel="noopener noreferrer">Docker Hub</a> and <a href="https://archive.is/lZHAT" target="_blank" rel="noopener noreferrer">archived</a>.</p>
<ul>
<li>Developed and maintained a secure, high-performance form builder application</li>
<li>Implemented robust security measures and best practices</li>
<li>Optimized application performance and user experience</li>
</ul>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> 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.</p>
</div>
</body>
</html>

187
docker/resume/live.html Normal file
View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Colin Knapp - Cybersecurity Expert and Software Developer Portfolio">
<title>Colin Knapp Portfolio</title>
<link rel="stylesheet" href="styles.css">
<script src="theme.js" integrity="sha384-naUim5JriO81FOtYsICLhgAY4mC9xffM8+Zcsc2ztUoQLKKLrDgLFgBRau98yEN1" crossorigin="anonymous"></script>
</head>
<body>
<div class="theme-switch">
<button
id="themeToggle"
aria-label="Theme mode: Auto"
aria-pressed="false"
role="switch"
aria-controls="html"
title="Toggle between light, dark, and auto theme modes"
>🌓</button>
</div>
<div class="container-fluid">
<h1>Colin Knapp</h1>
<p><strong>Location:</strong> Kitchener-Waterloo, Ontario, Canada<br>
<strong>Contact:</strong> <a href="mailto:recruitme2023@colinknapp.com">recruitme2023@colinknapp.com</a> | <a href="https://colinknapp.com">colinknapp.com</a></p>
<hr>
<h2>Highlights & Measurables</h2>
<ul>
<li><strong>Cybersecurity Leadership:</strong> Currently spearheading <em><a href="http://ViperWire.ca">ViperWire.ca</a></em>, the public-facing arm of my AI-powered cybersecurity and development consultancy, delivering cutting-edge protection for digital assets (2023-Present).</li>
<li><strong>Open-Source Impact:</strong> Co-created <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, revolutionizing Minecraft development by enabling massive transformative edits—scaling from 50,000 server-crashing edits to billions without interruption—powering a $2 billion game brand with global contributor support (2014-Present).</li>
<li><strong>Team Leadership:</strong> Managed a distributed team of 45 contractors at NitricConcepts, fostering collaboration and deploying advanced DevSecOps practices (2018-2021).</li>
<li><strong>On-Premises Innovation:</strong> Architected self-managed, bare-metal infrastructure with orchestration for on-premises deployments, delivering performant, scalable systems compliant with WCAG 2.0 AA for clients like <a href="https://showerloop.cc">ShowerLoop</a>, meeting stringent government accessibility and compliance goals (2020-Present).</li>
<li><strong>Government Projects:</strong> Delivered scalable, secure learning management systems for the US government and consulted on <a href="https://bishopairport.org">Flint Bishop International Airport</a>'s website and domain infrastructure via Addis Enterprises, building a geographically redundant DNS cluster with an A+ standard resilient to extreme scenarios (2019-Present).</li>
<li><strong>Healthcare Infrastructure:</strong> Developed and deployed infrastructure for <a href="https://improvingmipractices.org">Improving MI Practices</a>, a critical healthcare education platform, ensuring high availability and security for sensitive medical training content (2023-Present).</li>
<li><strong>Security Automation:</strong> Created a Docker-based utility for automated WordPress malware removal and hardening, successfully deployed to protect <a href="https://mlpp.org">MLPP</a> from persistent cyber attacks, reducing infection frequency from daily to zero (2023).</li>
</ul>
<hr>
<h2>Project Experience</h2>
<h3>DevSecOps at Addis Enterprises</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Collaborated on US government projects and airport infrastructure, focusing on scalable, secure systems and domain resilience.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Partnered with senior professionals to deliver learning management systems meeting WCAG 2.0 AA compliance for government clients.</li>
<li>Consulted for Flint Bishop International Airport, architecting a geographically redundant DNS cluster achieving an A+ standard, capable of withstanding extreme disruptions.</li>
<li>Provided exceptional client service through effective communication and tailored solutions.<br>
<strong>Impact:</strong> Strengthened government digital infrastructure and ensured robust, resilient airport domain systems.</li>
</ul>
<h3>Healthcare Platform Infrastructure</h3>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Led infrastructure design and operations for <a href="https://improvingmipractices.org">Improving MI Practices</a> (<a href="https://archive.is/D5HIb">archive</a>) through Addis Enterprises, a critical healthcare education platform.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed and implemented secure, scalable infrastructure for healthcare education content delivery.</li>
<li>Administered CIS Level 1 and 2 security standards implementation for enhanced system hardening and security controls.</li>
<li>Implemented automated deployment pipelines and monitoring systems for high availability.<br>
<strong>Impact:</strong> Enabled reliable delivery of critical healthcare training content to medical professionals while maintaining robust security standards.</li>
</ul>
<h3>WordPress Security Automation</h3>
<p><strong>Timeframe:</strong> 2023<br>
<strong>Overview:</strong> Developed an automated solution for WordPress malware removal and hardening.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Created a Docker-based utility for automated malware detection and removal.</li>
<li>Implemented hardening measures to prevent reinfection.</li>
<li>Successfully deployed to protect MLPP from persistent cyber attacks.<br>
<strong>Impact:</strong> Reduced infection frequency from daily/weekly to zero, significantly improving site security and reliability.</li>
</ul>
<h3>YouTube Game Development & Cybersecurity</h3>
<p><strong>Timeframe:</strong> 2009-2022<br>
<strong>Overview:</strong> Designed custom video games for prominent online creators, integrating advanced cybersecurity measures.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Built immersive gaming experiences for large audiences.</li>
<li>Implemented DDoS defense, anti-phishing protocols, and data privacy measures.</li>
<li>Managed hardware/software lifecycles and created comprehensive documentation.<br>
<strong>Impact:</strong> Delivered secure, seamless gaming experiences to millions of users.</li>
</ul>
<h3>Web Design & Java Plugin Development</h3>
<p><strong>Timeframe:</strong> 2009-2023<br>
<strong>Overview:</strong> Developed web solutions and Java plugins focusing on CI/CD efficiency and client satisfaction.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Utilized Jenkins and GitLab CI/CD for streamlined workflows, leveraging a robust toolchain for rapid development.</li>
<li>Managed complex systems and ensured WCAG 2.0 AA accessibility standards.</li>
<li>Provided technical guidance and detailed client documentation, drawing on broad experience to resolve diverse issues.<br>
<strong>Impact:</strong> Enhanced project delivery speed and quality for diverse computing environments through prolific development practices.</li>
</ul>
<h3>App Development for Influencers</h3>
<p><strong>Timeframe:</strong> 2013-2018<br>
<strong>Overview:</strong> Created an ad revenue tracking app to optimize earnings and strategies for content creators.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Designed a user-friendly tool for real-time revenue monitoring using an optimized toolchain for efficiency.</li>
<li>Ensured secure data handling and system performance with extensive problem-solving expertise.<br>
<strong>Impact:</strong> Empowered creators to maximize earnings and refine content strategies.</li>
</ul>
<h3>DevOps & Co-Founder at NitricConcepts</h3>
<p><strong>Timeframe:</strong> 2018-2021<br>
<strong>Overview:</strong> Led a global team in building secure, scalable gaming solutions.<br>
<strong>Key Contributions:</strong></p>
<ul>
<li>Managed 45 contractors worldwide, implementing Docker, Fail2Ban, and Salt Stack as part of a comprehensive toolchain.</li>
<li>Co-developed <em>FastAsyncWorldEdit</em> and <em>PlotSquared</em>, enabling billions of seamless edits for Minecraft creators.</li>
<li>Fostered a collaborative, innovative team culture.<br>
<strong>Impact:</strong> Transformed NitricConcepts into a thriving multinational entity through prolific and efficient development.</li>
</ul>
<h3>Entrepreneurial Ventures</h3>
<h4><a href="http://Athion.net">Athion.net</a> Turnaround</h4>
<p><strong>Timeframe:</strong> 2013-2017<br>
<strong>Overview:</strong> Revitalized a struggling business into a self-sustaining operation in two weeks.<br>
<strong>Key Contributions:</strong> Optimized systems and streamlined operations with rapid, effective solutions.<br>
<strong>Impact:</strong> Created a profitable, independent venture.</p>
<h4><a href="http://MotherboardRepair.ca">MotherboardRepair.ca</a></h4>
<p><strong>Timeframe:</strong> 2019-Present<br>
<strong>Overview:</strong> Co-founded a company reducing e-waste through circuit board repairs.<br>
<strong>Key Contributions:</strong> Leveraged industry expertise and a versatile toolchain for sustainable tech solutions.<br>
<strong>Impact:</strong> Promoted environmental responsibility in electronics.</p>
<h4><a href="https://showerloop.cc">ShowerLoop Project</a></h4>
<p><strong>Timeframe:</strong> 2016<br>
<strong>Overview:</strong> Revamped the website for an eco-friendly recirculating shower system project, implementing WCAG 2.0 AA compliance and modern design principles.<br>
<strong>Key Contributions:</strong> Designed and implemented a responsive, accessible website with improved user experience and technical documentation.<br>
<strong>Impact:</strong> Enhanced the project's online presence and accessibility while maintaining the site's functionality through periodic maintenance.</p>
<hr>
<h2>Additional Information</h2>
<h3>Personal Development</h3>
<p><strong>Timeframe:</strong> 2009-Present</p>
<ul>
<li><strong>Self-Taught Mastery:</strong> Continuously honed cybersecurity and systems management skills, building a broad knowledge base to tackle unique challenges with a passion for innovation and problem-solving.</li>
<li><strong>Open-Source Contributions:</strong> Actively maintain smaller self-run open-source projects; previously led <em>OhMyForm</em> (retired in favor of FormBricks) and contributed to <em>PlotSquared</em>, <em>FastAsyncWorldEdit</em>, and <em>PlotHider</em>, reflecting a prolific commitment to advancing technology.</li>
<li><strong>Skill Maintenance:</strong> Regularly run Woodpecker CI and Gitea for on-premise source management, testing, and deployment, employing security scanning and unit testing to ensure core functionality and security baselines, alongside self-hosting exercises to sustain rapid, high-volume development capabilities across a vast array of innovative projects.</li>
</ul>
<h3>Relevant Links & Web Impact</h3>
<ul>
<li><strong>Repositories:</strong> <a href="https://github.com/IntellectualSites/PlotSquared">PlotSquared</a>, <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit">FastAsyncWorldEdit</a>, <a href="https://github.com/OhMyForm/OhMyForm">OhMyForm</a>, <a href="https://github.com/IntellectualSites/plothider">PlotHider</a></li>
<li><strong>Projects:</strong> <a href="https://viperwire.ca">ViperWire.ca</a>, <a href="https://nitricconcepts.com">NitricConcepts</a>, <a href="https://showerloop.cc">ShowerLoop</a></li>
</ul>
<hr>
<div class="section">
<h2>Open Source & Infrastructure</h2>
<div class="entry">
<h3>PlotSquared & FastAsyncWorldEdit</h3>
<p class="date">2013-Present</p>
<p class="overview">Contributor to major Minecraft server plugins, focusing on performance optimization and security enhancements.</p>
<ul>
<li>Contributed to <a href="https://github.com/IntellectualSites/PlotSquared" target="_blank">PlotSquared</a>, a land management plugin with 572+ stars and 809+ forks</li>
<li>Enhanced <a href="https://github.com/IntellectualSites/FastAsyncWorldEdit" target="_blank">FastAsyncWorldEdit</a>, improving world manipulation performance with 664+ stars</li>
<li>Implemented security improvements and performance optimizations for large-scale server operations</li>
</ul>
</div>
<div class="entry">
<h3>Athion.net Infrastructure</h3>
<p class="date">2013-Present</p>
<p class="overview">Established and maintained critical infrastructure for Minecraft development community.</p>
<ul>
<li>Set up and maintained <a href="https://ci.athion.net/" target="_blank">Jenkins CI/CD pipeline</a> since 2013, supporting continuous integration for game content development</li>
<li>Hosted infrastructure enabling collaboration between developers and Microsoft for game content creation</li>
<li>Implemented robust security measures and performance optimizations for high-traffic development environments</li>
</ul>
</div>
</div>
<hr>
<p class="accessibility-notice"><strong>Accessibility:</strong> 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.</p>
</div>
</body>
</html>

View File

@ -0,0 +1,22 @@
document.addEventListener('DOMContentLoaded', function() {
const downloadButton = document.getElementById('downloadPDF');
if (downloadButton) {
downloadButton.addEventListener('click', function() {
// Store current theme
const currentTheme = document.body.getAttribute('data-theme');
// Force light theme for PDF
document.body.setAttribute('data-theme', 'light');
// Wait for theme change to apply
setTimeout(function() {
window.print();
// Restore original theme
setTimeout(function() {
document.body.setAttribute('data-theme', currentTheme);
}, 100);
}, 100);
});
}
});

209
docker/resume/styles.css Normal file
View File

@ -0,0 +1,209 @@
:root {
--bg-color: #ffffff;
--text-color: #333333;
--accent-color: #0056b3;
--border-color: #e0e0e0;
--hover-color: #003d82;
--theme-bg: #f5f5f5;
--theme-border: #ddd;
--theme-hover: #e0e0e0;
--date-color: #555555;
}
/* Dark theme variables when system prefers dark mode (auto setting) */
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--accent-color: #5fa9ff;
--border-color: #404040;
--hover-color: #8ac2ff;
--theme-bg: #2d2d2d;
--theme-border: #404040;
--theme-hover: #3d3d3d;
--date-color: #a0a0a0;
}
}
/* Light theme variables when manually selected */
html[data-theme='light'] {
--bg-color: #ffffff;
--text-color: #333333;
--accent-color: #0056b3;
--border-color: #e0e0e0;
--hover-color: #003d82;
--theme-bg: #f5f5f5;
--theme-border: #ddd;
--theme-hover: #e0e0e0;
--date-color: #555555;
}
/* Dark theme variables when manually selected */
html[data-theme='dark'] {
--bg-color: #1a1a1a;
--text-color: #e0e0e0;
--accent-color: #5fa9ff;
--border-color: #404040;
--hover-color: #8ac2ff;
--theme-bg: #2d2d2d;
--theme-border: #404040;
--theme-hover: #3d3d3d;
--date-color: #a0a0a0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--bg-color);
margin: 0;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
h1, h2, h3 {
color: var(--text-color);
margin-top: 1.5em;
margin-bottom: 0.5em;
}
h1 {
font-size: 2.5em;
border-bottom: 2px solid var(--accent-color);
padding-bottom: 0.3em;
}
h2 {
font-size: 2em;
color: var(--accent-color);
}
h3 {
font-size: 1.5em;
}
a {
color: var(--accent-color);
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-thickness: 1px;
font-weight: 500;
transition: all 0.3s ease;
}
a:hover, a:focus {
color: var(--hover-color);
text-decoration-thickness: 2px;
outline: none;
}
a:focus {
outline: 2px solid var(--accent-color);
outline-offset: 2px;
}
.section {
margin-bottom: 2em;
padding: 1em;
border: 1px solid var(--border-color);
border-radius: 5px;
}
.entry {
margin-bottom: 1.5em;
}
.date {
color: var(--date-color);
font-style: italic;
margin: 0.5em 0;
}
.overview {
font-weight: 500;
margin: 0.5em 0;
}
ul {
margin: 0.5em 0;
padding-left: 1.5em;
}
li {
margin: 0.3em 0;
}
hr {
border: none;
border-top: 1px solid var(--border-color);
margin: 2em 0;
}
.theme-switch {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
display: flex;
gap: 10px;
}
.theme-switch button {
background: none;
border: none;
font-size: 1.5em;
cursor: pointer;
padding: 5px;
border-radius: 50%;
transition: background-color 0.3s;
}
.theme-switch button:hover {
background-color: rgba(128, 128, 128, 0.2);
}
/* Print styles */
@media print {
.theme-switch {
display: none;
}
body {
background: white !important;
color: black !important;
}
a {
color: black !important;
text-decoration: none !important;
}
.container-fluid {
max-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
hr {
border-color: black !important;
}
}
@media (max-width: 600px) {
body {
padding: 10px;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.2em;
}
}

66
docker/resume/theme.js Normal file
View File

@ -0,0 +1,66 @@
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('themeToggle');
const html = document.documentElement;
// Check for saved theme preference, default to auto
const savedTheme = localStorage.getItem('theme') || 'auto';
// Set initial value for aria-checked attribute
themeToggle.setAttribute('aria-checked', savedTheme !== 'auto');
updateTheme(savedTheme);
function updateTheme(theme) {
// Update button state and labels
const themeLabels = {
light: 'Theme mode: Light',
dark: 'Theme mode: Dark',
auto: 'Theme mode: Auto'
};
themeToggle.setAttribute('aria-label', themeLabels[theme]);
themeToggle.setAttribute('aria-checked', theme !== 'auto');
// Update button icon
const themeIcons = {
light: '🌞',
dark: '🌙',
auto: '🌓'
};
themeToggle.textContent = themeIcons[theme];
if (theme === 'auto') {
html.removeAttribute('data-theme');
} else {
html.setAttribute('data-theme', theme);
}
}
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme') || 'auto';
let newTheme;
switch(currentTheme) {
case 'light':
newTheme = 'dark';
break;
case 'dark':
newTheme = 'auto';
break;
default:
newTheme = 'light';
}
updateTheme(newTheme);
localStorage.setItem('theme', newTheme);
});
// Handle keyboard navigation
themeToggle.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
themeToggle.click();
}
});
});

60
docker/resume/utils.js Normal file
View File

@ -0,0 +1,60 @@
// Utility functions for the resume website
/**
* Debounce a function to limit its execution rate
* @param {Function} func - The function to debounce
* @param {number} wait - The number of milliseconds to delay
* @returns {Function} - The debounced function
*/
export function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Check if an element is in the viewport
* @param {Element} element - The element to check
* @returns {boolean} - Whether the element is in the viewport
*/
export function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
/**
* Format a date in a consistent way
* @param {Date|string} date - The date to format
* @returns {string} - The formatted date string
*/
export function formatDate(date) {
if (!date) return '';
const d = new Date(date);
return d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long'
});
}
/**
* Safely get a value from an object using a path
* @param {Object} obj - The object to traverse
* @param {string} path - The path to the value
* @param {*} defaultValue - The default value if not found
* @returns {*} - The value at the path or the default value
*/
export function get(obj, path, defaultValue = undefined) {
return path.split('.')
.reduce((acc, part) => acc && acc[part], obj) ?? defaultValue;
}

3226
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "resume",
"version": "1.0.0",
"description": "Colin Knapp's professional resume website",
"main": "index.js",
"scripts": {
"serve": "node tests/serve.js",
"test": "npm run test:lighthouse && npm run test:playwright",
"test:playwright": "npx playwright test",
"test:lighthouse": "node tests/lighthouse.js",
"test:headers": "playwright test tests/headers.spec.js",
"setup": "npx playwright install"
},
"repository": {
"type": "git",
"url": "git@git.nixc.us:colin/resume.git"
},
"keywords": [
"resume",
"portfolio",
"accessibility"
],
"author": "Colin Knapp",
"license": "ISC",
"devDependencies": {
"@axe-core/playwright": "^4.10.1",
"@playwright/test": "^1.52.0",
"chrome-launcher": "^1.1.2",
"lighthouse": "^11.4.0",
"puppeteer": "^22.4.1"
},
"dependencies": {
"express": "^4.18.2",
"lighthouse": "^11.6.0",
"playwright": "^1.42.1"
}
}

46
playwright.config.js Normal file
View File

@ -0,0 +1,46 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');
/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './tests',
timeout: 30 * 1000,
expect: {
timeout: 5000
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
actionTimeout: 0,
trace: 'on-first-retry',
baseURL: 'http://localhost:8080',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
webServer: {
command: 'node tests/serve.js',
port: 8080,
reuseExistingServer: true,
},
});

View File

@ -1,14 +1,15 @@
# build 0
networks:
traefik:
external: true
services:
lucky:
image: git.nixc.us/nixius/lucky-ddg:production
resume:
image: git.nixc.us/colin/resume:production
deploy:
# placement:
# constraints:
# - node.hostname == ingress.nixc.us
placement:
constraints:
- node.hostname == ingress.nixc.us
update_config:
order: start-first
# failure_action: rollback
@ -19,12 +20,12 @@ services:
labels:
us.nixc.autodeploy: "true"
traefik.enable: "true"
traefik.http.routers.production_lucky-ddg.tls: "true"
traefik.http.services.production_lucky-ddg.loadbalancer.server.port: "5000"
traefik.http.routers.production_lucky-ddg.rule: "Host(`ddg.nixc.us`)"
traefik.http.routers.production_lucky-ddg.entrypoints: "websecure"
traefik.http.routers.production_lucky-ddg.tls.certresolver: "letsencryptresolver"
traefik.http.routers.production_lucky-ddg.service: "production_lucky-ddg"
traefik.http.routers.production_resume.tls: "true"
traefik.http.services.production_resume.loadbalancer.server.port: "8080"
traefik.http.routers.production_resume.rule: "Host(`resume.colinknapp.com`) || Host(`colinknapp.com`)"
traefik.http.routers.production_resume.entrypoints: "websecure"
traefik.http.routers.production_resume.tls.certresolver: "letsencryptresolver"
traefik.http.routers.production_resume.service: "production_resume"
traefik.docker.network: "traefik"
networks:
traefik:

View File

@ -1,17 +1,16 @@
networks:
traefik:
external: true
services:
resume:
image: git.nixc.us/nixius/lucky-ddg:production
image: git.nixc.us/colin/resume:staging
deploy:
# placement:
# constraints:
# - node.hostname == ingress.nixc.us
placement:
constraints:
- node.hostname == ingress.nixc.us
update_config:
order: start-first
# failure_action: rollback
failure_action: rollback
delay: 0s
parallelism: 1
restart_policy:
@ -19,12 +18,19 @@ services:
labels:
us.nixc.autodeploy: "true"
traefik.enable: "true"
traefik.http.routers.production_lucky-ddg.tls: "true"
traefik.http.services.production_lucky-ddg.loadbalancer.server.port: "5000"
traefik.http.routers.production_lucky-ddg.rule: "Host(`ddg.staging.nixc.us`)"
traefik.http.routers.production_lucky-ddg.entrypoints: "websecure"
traefik.http.routers.production_lucky-ddg.tls.certresolver: "letsencryptresolver"
traefik.http.routers.production_lucky-ddg.service: "production_lucky-ddg"
traefik.http.routers.staging_resume.tls: "true"
traefik.http.services.staging_resume.loadbalancer.server.port: "8080"
traefik.http.routers.staging_resume.rule: "Host(`staging.resume.colinknapp.com`) || Host(`staging.colinknapp.com`)"
traefik.http.routers.staging_resume.entrypoints: "websecure"
traefik.http.routers.staging_resume.tls.certresolver: "letsencryptresolver"
traefik.http.routers.staging_resume.service: "staging_resume"
traefik.docker.network: "traefik"
# traefik.http.routers.staging_resume.middlewares: "authelia@docker"
networks:
traefik:
# logging:
# driver: "gelf"
# options:
# gelf-address: "udp://log.nixc.us:15124"
# tag: "resume_resume"

222
tests/accessibility.test.js Normal file
View File

@ -0,0 +1,222 @@
const { test, expect } = require('@playwright/test');
const { AxeBuilder } = require('@axe-core/playwright');
const PRODUCTION_URL = 'https://colinknapp.com';
const LOCAL_URL = 'http://localhost:8080';
const viewports = [
{ width: 375, height: 667, name: 'mobile' },
{ width: 1024, height: 768, name: 'desktop' }
];
async function getPageUrl(page) {
try {
// Try production first
await page.goto(PRODUCTION_URL, { timeout: 60000 });
return PRODUCTION_URL;
} catch (error) {
console.log('Production site not available, falling back to local');
await page.goto(LOCAL_URL, { timeout: 60000 });
return LOCAL_URL;
}
}
viewports.forEach(viewport => {
test.describe(`Accessibility Tests (${viewport.name})`, () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize(viewport);
});
test('should pass WCAG 2.1 Level AAA standards', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running accessibility tests against ${url} on ${viewport.name}`);
// Run axe accessibility tests
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag2aaa'])
.analyze();
// Check for any violations
expect(results.violations).toHaveLength(0);
});
test('should have proper ARIA attributes for theme toggle', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running ARIA tests against ${url} on ${viewport.name}`);
// Check theme toggle button
const themeToggle = await page.locator('#themeToggle');
expect(await themeToggle.getAttribute('aria-label')).toBe('Theme mode: Auto');
expect(await themeToggle.getAttribute('role')).toBe('switch');
expect(await themeToggle.getAttribute('aria-checked')).toBe('false');
expect(await themeToggle.getAttribute('title')).toBe('Toggle between light, dark, and auto theme modes');
expect(await themeToggle.getAttribute('tabindex')).toBe('0');
});
test('should have proper heading structure', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running heading structure tests against ${url} on ${viewport.name}`);
// Check main content area
const mainContent = await page.locator('.container-fluid');
expect(await mainContent.getAttribute('role')).toBe('main');
// Check heading hierarchy
const h1 = await page.locator('h1');
expect(await h1.count()).toBe(1);
const h2s = await page.locator('h2');
expect(await h2s.count()).toBeGreaterThan(0);
});
test('should have working external links', async ({ page, request }) => {
const url = await getPageUrl(page);
console.log(`Running link validation tests against ${url} on ${viewport.name}`);
await page.goto(url, { timeout: 60000 });
await page.waitForLoadState('networkidle');
// Get all external links
const externalLinks = await page.$$('a[href^="http"]:not([href^="http://localhost"])');
// Skip test if no external links found
if (externalLinks.length === 0) {
console.log('No external links found, skipping test');
return;
}
// Set a longer timeout for external link checks
test.setTimeout(120000);
const brokenLinks = [];
for (const link of externalLinks) {
const href = await link.getAttribute('href');
if (!href) continue;
let attempts = 0;
const maxAttempts = 3;
let success = false;
while (attempts < maxAttempts && !success) {
attempts++;
try {
const response = await request.head(href, { timeout: 15000 });
if (response.status() >= 400) {
brokenLinks.push({
href,
status: response.status(),
attempt: attempts
});
} else {
success = true;
}
} catch (error) {
if (attempts === maxAttempts) {
brokenLinks.push({
href,
error: error.message,
attempt: attempts
});
}
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
if (brokenLinks.length > 0) {
console.log('\nBroken or inaccessible links:');
brokenLinks.forEach(link => {
if (link.error) {
console.log(`- ${link.href}: ${link.error} (Attempt ${link.attempt}/${maxAttempts})`);
} else {
console.log(`- ${link.href}: HTTP ${link.status} (Attempt ${link.attempt}/${maxAttempts})`);
}
});
throw new Error('Some external links are broken or inaccessible');
}
});
test('should have proper color contrast', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running color contrast tests against ${url} on ${viewport.name}`);
await page.goto(url, { timeout: 60000 });
await page.waitForLoadState('networkidle');
// Check text color contrast in both light and dark modes
const contrastInfo = await page.evaluate(() => {
const getContrastRatio = (color1, color2) => {
const getLuminance = (r, g, b) => {
const [rs, gs, bs] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
};
const parseColor = (color) => {
const rgb = color.match(/\d+/g).map(Number);
return rgb.length === 3 ? rgb : [0, 0, 0];
};
const l1 = getLuminance(...parseColor(color1));
const l2 = getLuminance(...parseColor(color2));
const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
return ratio.toFixed(2);
};
const style = getComputedStyle(document.body);
const textColor = style.color;
const backgroundColor = style.backgroundColor;
const contrastRatio = getContrastRatio(textColor, backgroundColor);
return {
textColor,
backgroundColor,
contrastRatio: parseFloat(contrastRatio)
};
});
console.log('Color contrast information:', contrastInfo);
// WCAG 2.1 Level AAA requires a contrast ratio of at least 7:1 for normal text
expect(contrastInfo.contrastRatio).toBeGreaterThanOrEqual(7);
});
test('should have alt text for all images', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running image alt text tests against ${url} on ${viewport.name}`);
await page.goto(url, { timeout: 60000 });
await page.waitForLoadState('networkidle');
// Get all images
const images = await page.$$('img');
// Skip test if no images found
if (images.length === 0) {
console.log('No images found, skipping test');
return;
}
const missingAlt = [];
for (const img of images) {
const alt = await img.getAttribute('alt');
const src = await img.getAttribute('src');
// Skip decorative images (empty alt is fine)
const role = await img.getAttribute('role');
if (role === 'presentation' || role === 'none') {
continue;
}
if (!alt) {
missingAlt.push(src);
}
}
if (missingAlt.length > 0) {
console.log('\nImages missing alt text:');
missingAlt.forEach(src => console.log(`- ${src}`));
throw new Error('Some images are missing alt text');
}
});
});
});

81
tests/headers.spec.js Normal file
View File

@ -0,0 +1,81 @@
const { test, expect } = require('@playwright/test');
test.describe('Security Headers Tests', () => {
test('should have all required security headers', async ({ page }) => {
try {
// Navigate to the page with a timeout
await page.goto('http://localhost:8080', { timeout: 30000 });
// Get response headers
const response = await page.waitForResponse('http://localhost:8080', { timeout: 30000 });
const headers = response.headers();
// Define required headers and their expected values
const requiredHeaders = {
'Content-Security-Policy': expect.stringContaining("default-src 'self'"),
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': expect.stringContaining('geolocation=()'),
'Strict-Transport-Security': expect.stringContaining('max-age=31536000'),
};
// Check each required header
for (const [header, expectedValue] of Object.entries(requiredHeaders)) {
const headerValue = headers[header.toLowerCase()];
console.log(`Checking header: ${header}, Value: ${headerValue}`);
expect(headerValue, `${header} is not defined`).toBeDefined();
if (typeof expectedValue === 'string') {
expect(headerValue, `${header} does not match expected value`).toBe(expectedValue);
} else {
expect(headerValue, `${header} does not match expected pattern`).toMatch(expectedValue);
}
}
} catch (error) {
console.error('Error in security headers test:', error);
throw new Error(`Failed to load local server for security headers test: ${error.message}`);
}
});
test('should have correct CSP directives with hash', async ({ page }) => {
try {
await page.goto('http://localhost:8080', { timeout: 30000 });
const response = await page.waitForResponse('http://localhost:8080', { timeout: 30000 });
const headers = response.headers();
const csp = headers['content-security-policy'];
// Check for essential CSP directives
console.log('CSP Header:', csp);
expect(csp, 'CSP header is not defined').toBeDefined();
expect(csp, 'CSP does not contain default-src directive').toContain("default-src 'none'");
expect(csp, 'CSP does not contain script-src with hash').toContain("script-src 'self' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='");
expect(csp, 'CSP does not contain style-src with hash').toContain("style-src 'self' 'sha256-Mo+7o3oPEKpX7fqRvTtunvQHlIDhJ0SxAMG1PCNniCI='");
expect(csp, 'CSP does not contain img-src directive').toContain("img-src 'self' data:");
expect(csp, 'CSP does not contain font-src directive').toContain("font-src 'self' data:");
expect(csp, 'CSP does not contain connect-src directive').toContain("connect-src 'self'");
} catch (error) {
console.error('Error in CSP directives test:', error);
throw new Error(`Failed to load local server for CSP test: ${error.message}`);
}
});
test('should have integrity attributes on script tags', async ({ page }) => {
try {
await page.goto('http://localhost:8080', { timeout: 30000 });
// Check that all script tags have integrity attributes
const scripts = await page.$$('script');
console.log(`Found ${scripts.length} script tags`);
for (const script of scripts) {
const hasIntegrity = await script.evaluate(el => el.hasAttribute('integrity'));
const src = await script.evaluate(el => el.getAttribute('src') || 'inline script');
console.log(`Script ${src} has integrity: ${hasIntegrity}`);
expect(hasIntegrity, `Script tag ${src} missing integrity attribute`).toBeTruthy();
}
} catch (error) {
console.error('Error in integrity attributes test:', error);
throw new Error(`Failed to load local server for integrity test: ${error.message}`);
}
});
});

85
tests/headers.test.js Normal file
View File

@ -0,0 +1,85 @@
const { test, expect } = require('@playwright/test');
const PRODUCTION_URL = 'https://colinknapp.com';
const LOCAL_URL = 'http://localhost:8080';
async function getPageUrl(page) {
try {
// Try production first
await page.goto(PRODUCTION_URL, { timeout: 60000 });
return PRODUCTION_URL;
} catch (error) {
console.log('Production site not available, falling back to local');
await page.goto(LOCAL_URL, { timeout: 60000 });
return LOCAL_URL;
}
}
test.describe('Security Headers Tests', () => {
test('should have all required security headers', async ({ page, request }) => {
const url = await getPageUrl(page);
console.log(`Running security header tests against ${url}`);
// Get headers directly from the main page
const response = await request.get(url);
const headers = response.headers();
// Check Content Security Policy
const csp = headers['content-security-policy'];
expect(csp).toBeTruthy();
expect(csp).toContain("default-src 'none'");
expect(csp).toContain("script-src 'self'");
expect(csp).toContain("style-src 'self'");
expect(csp).toContain("img-src 'self' data:");
expect(csp).toContain("font-src 'self' data:");
expect(csp).toContain("connect-src 'self'");
expect(csp).toContain("object-src 'none'");
expect(csp).toContain("frame-ancestors 'none'");
expect(csp).toContain("base-uri 'none'");
expect(csp).toContain("form-action 'none'");
// Check other security headers
expect(headers['x-content-type-options']).toBe('nosniff');
expect(headers['x-frame-options']).toBe('DENY');
expect(headers['referrer-policy']).toBe('strict-origin-when-cross-origin');
});
test('should have correct Subresource Integrity (SRI) attributes', async ({ page }) => {
const url = await getPageUrl(page);
console.log(`Running SRI tests against ${url}`);
await page.goto(url, { timeout: 60000 });
await page.waitForLoadState('networkidle');
// Check stylesheet
const stylesheet = await page.locator('link[rel="stylesheet"]').first();
const stylesheetIntegrity = await stylesheet.getAttribute('integrity');
const stylesheetCrossorigin = await stylesheet.getAttribute('crossorigin');
expect(stylesheetIntegrity).toBeTruthy();
expect(stylesheetCrossorigin).toBe('anonymous');
// Check script
const script = await page.locator('script[src]').first();
const scriptIntegrity = await script.getAttribute('integrity');
const scriptCrossorigin = await script.getAttribute('crossorigin');
expect(scriptIntegrity).toBeTruthy();
expect(scriptCrossorigin).toBe('anonymous');
});
test('should have correct caching headers for static assets', async ({ request }) => {
const url = await getPageUrl({ goto: async () => {} });
console.log(`Running caching header tests against ${url}`);
const baseUrl = url.replace(/\/$/, '');
// Check styles.css
const stylesResponse = await request.get(`${baseUrl}/styles.css`);
const stylesCacheControl = stylesResponse.headers()['cache-control'];
expect(stylesCacheControl).toContain('public');
expect(stylesCacheControl).toContain('max-age=');
// Check theme.js
const scriptResponse = await request.get(`${baseUrl}/theme.js`);
const scriptCacheControl = scriptResponse.headers()['cache-control'];
expect(scriptCacheControl).toContain('public');
expect(scriptCacheControl).toContain('max-age=');
});
});

89
tests/lighthouse.js Normal file
View File

@ -0,0 +1,89 @@
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
// Set threshold values for each category
const THRESHOLDS = {
performance: 90,
accessibility: 90,
'best-practices': 90,
seo: 90,
};
async function runLighthouse(url) {
// Create directory for reports if it doesn't exist
const reportsDir = path.join(__dirname, 'reports');
if (!fs.existsSync(reportsDir)) {
fs.mkdirSync(reportsDir);
}
// Generate report filename
const reportPath = path.join(reportsDir, `lighthouse-report-${Date.now()}.json`);
try {
// Run Lighthouse using CLI
const command = `npx lighthouse "${url}" --output=json --output-path="${reportPath}" --only-categories=performance,accessibility,best-practices,seo --form-factor=desktop --throttling-method=simulate --screenEmulation.mobile=false`;
execSync(command, { stdio: 'inherit' });
// Read and parse the report
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
console.log('\nLighthouse Results:');
console.log('--------------------');
// Process and display results
let allPassed = true;
Object.keys(THRESHOLDS).forEach(category => {
const score = Math.round(report.categories[category].score * 100);
const threshold = THRESHOLDS[category];
const passed = score >= threshold;
if (!passed) {
allPassed = false;
}
console.log(`${category}: ${score}/100 - ${passed ? 'PASS' : 'FAIL'} (Threshold: ${threshold})`);
});
// Display audits that failed
console.log('\nFailed Audits:');
console.log('--------------');
let hasFailedAudits = false;
Object.values(report.audits).forEach(audit => {
if (audit.score !== null && audit.score < 0.9) {
hasFailedAudits = true;
console.log(`- ${audit.title}: ${Math.round(audit.score * 100)}/100`);
console.log(` ${audit.description}`);
}
});
if (!hasFailedAudits) {
console.log('No significant audit failures!');
}
console.log(`\nDetailed report saved to: ${reportPath}`);
return allPassed;
} catch (error) {
console.error('Error running Lighthouse:', error);
return false;
}
}
// URL to test - this should be your local server address
const url = process.argv[2] || 'http://localhost:8080';
// Start the tests
console.log(`Starting Lighthouse tests on ${url}`);
runLighthouse(url)
.then(passed => {
console.log(`\nOverall: ${passed ? 'PASSED' : 'FAILED'}`);
process.exit(passed ? 0 : 1);
})
.catch(error => {
console.error('Error running Lighthouse:', error);
process.exit(1);
});

73
tests/serve.js Normal file
View File

@ -0,0 +1,73 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
const PORT = 8080;
const RESUME_DIR = path.join(__dirname, '..', 'docker', 'resume');
// MIME types for common file extensions
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.txt': 'text/plain',
};
// Create a simple HTTP server
const server = http.createServer((req, res) => {
// Parse the request URL
const parsedUrl = url.parse(req.url);
let pathname = parsedUrl.pathname;
// Set default file to index.html
if (pathname === '/') {
pathname = '/index.html';
}
// Construct the file path
const filePath = path.join(RESUME_DIR, pathname);
// Get the file extension
const ext = path.extname(filePath);
// Set the content type based on the file extension
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
// Read the file and serve it
fs.readFile(filePath, (err, data) => {
if (err) {
// If the file doesn't exist, return 404
if (err.code === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 Not Found');
console.log(`404: ${pathname}`);
return;
}
// For other errors, return 500
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('500 Internal Server Error');
console.error(`Error serving ${pathname}:`, err);
return;
}
// If file is found, serve it with the correct content type
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
console.log(`200: ${pathname}`);
});
});
// Start the server
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
console.log(`Serving files from: ${RESUME_DIR}`);
});

60
tests/server.js Normal file
View File

@ -0,0 +1,60 @@
const express = require('express');
const path = require('path');
const crypto = require('crypto');
const fs = require('fs');
const app = express();
const port = 8080;
// Generate a random nonce
function generateNonce() {
return crypto.randomBytes(16).toString('base64');
}
// Security headers middleware
app.use((req, res, next) => {
const nonce = generateNonce();
res.locals.nonce = nonce;
// Content Security Policy with nonce and hash
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; " +
`script-src 'self' 'nonce-${nonce}' 'sha256-ryQsJ+aghKKD/CeXgx8jtsnZT3Epp3EjIw8RyHIq544='; ` +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https: http:; " +
"font-src 'self'; " +
"connect-src 'self'"
);
// Other security headers
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
// res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
next();
});
// Custom middleware to inject nonce into HTML
app.use((req, res, next) => {
if (req.path.endsWith('.html')) {
const filePath = path.join(__dirname, '../docker/resume', req.path);
let html = fs.readFileSync(filePath, 'utf8');
// Add nonce to all script tags
html = html.replace(/<script/g, `<script nonce="${res.locals.nonce}"`);
res.send(html);
} else {
next();
}
});
// Serve static files from the docker/resume directory
app.use(express.static(path.join(__dirname, '../docker/resume')));
app.listen(port, () => {
console.log(`Local development server running at http://localhost:${port}`);
});

View File

@ -0,0 +1,62 @@
const { test, expect } = require('@playwright/test');
test.describe('Theme Toggle Tests', () => {
test('theme toggle should cycle through modes', async ({ page }) => {
// Serve the site locally for testing
await page.goto('http://localhost:8080');
// Get the theme toggle button
const themeToggle = await page.locator('#themeToggle');
// Verify initial state (should be auto)
let ariaLabel = await themeToggle.getAttribute('aria-label');
expect(ariaLabel).toBe('Theme mode: Auto');
// Click to change to light mode
await themeToggle.click();
ariaLabel = await themeToggle.getAttribute('aria-label');
expect(ariaLabel).toBe('Theme mode: Light');
// Verify data-theme attribute is set to light
const dataTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme'));
expect(dataTheme).toBe('light');
// Click to change to dark mode
await themeToggle.click();
ariaLabel = await themeToggle.getAttribute('aria-label');
expect(ariaLabel).toBe('Theme mode: Dark');
// Verify data-theme attribute is set to dark
const darkDataTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme'));
expect(darkDataTheme).toBe('dark');
// Click to change back to auto mode
await themeToggle.click();
ariaLabel = await themeToggle.getAttribute('aria-label');
expect(ariaLabel).toBe('Theme mode: Auto');
// Verify data-theme attribute is removed
const autoDataTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme'));
expect(autoDataTheme).toBeNull();
});
test('theme toggle should be keyboard accessible', async ({ page }) => {
await page.goto('http://localhost:8080');
// Focus the theme toggle button using Tab
await page.keyboard.press('Tab');
// Verify the button is focused
const isFocused = await page.evaluate(() => {
return document.activeElement.id === 'themeToggle';
});
expect(isFocused).toBeTruthy();
// Activate with space key
await page.keyboard.press('Space');
// Verify it changes to light mode
const ariaLabel = await page.locator('#themeToggle').getAttribute('aria-label');
expect(ariaLabel).toBe('Theme mode: Light');
});
});