diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1600130a..00000000 --- a/Dockerfile +++ /dev/null @@ -1,59 +0,0 @@ -# syntax=docker/dockerfile:1 -FROM rust:bookworm as chef -WORKDIR /app -# Install cargo-binstall for faster tool installation -RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash -# Install cargo-chef -RUN cargo binstall cargo-chef -y -# Install cargo-leptos and sass (needed for build) -RUN cargo binstall cargo-leptos -y -# Install sqlx-cli (needed for pre-build prepare step) -RUN cargo binstall sqlx-cli -y --force -RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm pkg-config libssl-dev && rm -rf /var/lib/apt/lists/* -RUN npm install -g sass -# Add WASM target -RUN rustup target add wasm32-unknown-unknown - -FROM chef as planner -COPY . . -RUN cargo chef prepare --recipe-path recipe.json - -FROM chef as builder -COPY --from=planner /app/recipe.json recipe.json -# Build dependencies - this is the caching Docker layer! -RUN cargo chef cook --release --recipe-path recipe.json -RUN cargo chef cook --release --target wasm32-unknown-unknown --package frontend --recipe-path recipe.json - -# Build application -COPY . . -# Build the actual app -RUN cargo leptos build --release -vv && \ - cp -r target/release/backend /tmp/ && \ - cp -r target/site /tmp/ -# Install sqlx-cli for runtime migrations/prep if needed (or just copy binary if available) -RUN cargo binstall sqlx-cli -y --force - -# Runtime Stage -FROM debian:bookworm-slim as runtime -WORKDIR /app - -# Install runtime dependencies (OpenSSL, ca-certificates) -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends openssl ca-certificates \ - && apt-get autoremove -y \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -# Copy artifacts from builder -COPY --from=builder /usr/local/cargo/bin/sqlx /usr/local/bin/sqlx -COPY --from=builder /tmp/backend /app/backend -COPY --from=builder /tmp/site /app/site -COPY --from=builder /app/data /app/data - -# Set environment -ENV LEPTOS_SITE_ADDR="0.0.0.0:3000" -ENV LEPTOS_SITE_ROOT="site" - -EXPOSE 3000 - -CMD ["/app/backend"] diff --git a/GEMINI.md b/GEMINI.md index 4adcde1a..837df14a 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -9,10 +9,10 @@ The application is a modern, full-stack Rust web application utilizing Server-Si - **Frontend**: Leptos (Rust WASM framework) - **Backend**: Axum (Rust async web framework) -- **Database**: PostgreSQL (managed via SQLx) +- **Database**: SQLite (managed via SQLx) - **Styling**: SASS / SCSS - **Environment**: Nix (via `flake.nix` and `direnv`) -- **Deployment**: Docker Compose with Nginx reverse proxy and Let's Encrypt SSL +- **Deployment**: Managed externally via a meta repo. ## Directory Structure @@ -20,7 +20,7 @@ The application is a modern, full-stack Rust web application utilizing Server-Si - `frontend/`: Client-side Rust code. Contains Leptos components, routing, and UI logic. - `shared/`: Shared types, models, and utilities used by both frontend and backend. - `migrations/` & `migration/`: SQLx database migration files. -- `scripts/`: Automation scripts for local development database setup and remote deployment. +- `scripts/`: Automation scripts for local development database setup. - `style/`: SASS stylesheets. - `.github/workflows/`: CI/CD pipelines (Formatting, Linting, Testing, Security Audits, and AI reviews). @@ -41,7 +41,7 @@ The recommended way to run the application in development is via `cargo-leptos`, cargo leptos watch ``` -*Note: Make sure your local PostgreSQL database is running via `docker-compose up -d db` (which is typically handled by `setup-dev.sh`).* +*Note: Make sure your local SQLite database is initialized via `./scripts/setup-dev.sh`.* ## Important Architectural Guidelines for AI Assistants @@ -59,8 +59,6 @@ cargo leptos watch 4. **Styling**: Global styling is handled via SASS. When adding new components, add corresponding styles to the `style/` directory and ensure they are compiled correctly by the Leptos build pipeline. -5. **Deployment**: - Deployment is managed by `./scripts/deploy.sh`. Changes to infrastructure should be mirrored in both `compose.yaml` and `compose.prod.yaml` where applicable. - Tick off tasks in the roadmap as they are completed. - Update the roadmap as the project progresses. diff --git a/README.md b/README.md index c534036a..03ab3f15 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,25 @@ # jakewray.ca -My personal portfolio website built with Rust, Leptos, and PostgreSQL. +My personal portfolio website built with Rust, Leptos, and SQLite. ## Live Site - [jakewray.dev](https://jakewray.dev) -## Deployment - -See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) for complete deployment instructions. - -Quick start: -```bash -./scripts/deploy.sh all -``` - -For first-time SSL setup on the server: -```bash -./scripts/init_ssl.sh -``` - ## Architecture - **Backend**: Rust with Leptos (SSR) -- **Database**: PostgreSQL -- **Reverse Proxy**: Nginx with Let's Encrypt SSL -- **Deployment**: Docker Compose +- **Database**: SQLite ### Known Limitations + - **Database Concurrency**: The application uses embedded SQLite in WAL mode with a small connection pool (`max_connections(5)`). SQLite only allows one concurrent writer. Concurrent write bursts will queue (up to a 5s busy timeout) and could fail under heavy write load. This is acceptable for a personal blog/portfolio, but must be accounted for if write traffic scales. - **Reverse Proxy Setup**: When deploying behind a reverse proxy (such as Nginx), you **MUST** configure the `TRUSTED_PROXY_IPS` environment variable with the proxy's IP address. If left unset, all client requests will appear to come from the proxy's IP, effectively disabling per-client rate limiting and causing all users to share the same rate limit bucket. ## Development ### Quick Start with Nix (Recommended) + ```bash direnv allow # Load development environment ./scripts/setup-dev.sh # Setup database @@ -43,6 +29,7 @@ cargo leptos watch # Start dev server See [docs/LOCAL_DEV.md](docs/LOCAL_DEV.md) for detailed setup. ### Without Nix + ```bash cargo install cargo-leptos ./scripts/setup-dev.sh @@ -50,14 +37,15 @@ cargo leptos watch ``` ## Project Structure + - `backend/` - Server-side Rust code - `frontend/` - Client-side Leptos components - `shared/` - Shared types and utilities - `flake.nix` - Nix development environment - `.envrc` - direnv configuration - ## Development Roadmap + - [x] **HTTPS/SSL** - Let's Encrypt certificates - [x] **Authentication** - Password-protected admin panel - [x] **Theme** - Modern indigo design diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml deleted file mode 100644 index 93b54dd4..00000000 --- a/docker-compose.prod.yaml +++ /dev/null @@ -1,44 +0,0 @@ -services: - portfolio: - build: . - restart: always - user: "1000:1000" - environment: - # 4 slashes are intentional for an absolute path: sqlite:////app/data/sqlite.db - - DATABASE_URL=sqlite:////app/data/sqlite.db - - LEPTOS_SITE_ADDR=0.0.0.0:3000 - - RUST_LOG=info - networks: - - jake_net - volumes: - - ./media_mount:/app/media - - ./data:/app/data - - # Embedded SQLite replaces separate db and migration services - - nginx: - image: nginx:stable-alpine - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf - - ./certbot/conf:/etc/letsencrypt - - ./certbot/www:/var/www/certbot - command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" - depends_on: - - portfolio - networks: - - jake_net - - certbot: - image: certbot/certbot - restart: unless-stopped - volumes: - - ./certbot/conf:/etc/letsencrypt - - ./certbot/www:/var/www/certbot - entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" - -networks: - jake_net: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md deleted file mode 100644 index 574ab064..00000000 --- a/docs/DEPLOYMENT.md +++ /dev/null @@ -1,147 +0,0 @@ -# Deployment Guide - -## Overview - -This application uses Docker Compose for production deployment with Nginx as a reverse proxy and Let's Encrypt for SSL certificates. - -## Architecture - -- **Backend**: Rust/Leptos application (port 3000) -- **Database**: PostgreSQL 15 -- **Reverse Proxy**: Nginx (with automatic HTTPS via Let's Encrypt) -- **Certificate Management**: Certbot (automatic renewal) - -## Domain Configuration - -The application is configured to serve traffic for `jakewray.dev` through Nginx, which handles: -- HTTPS certificate provisioning via Let's Encrypt/Certbot -- HTTP to HTTPS redirection -- Reverse proxy to the backend service -- Security headers and rate limiting - -### Nginx Configuration - -The `nginx.conf` configures: -- `jakewray.dev` - Main domain, proxies to the backend on port 3000 -- `www.jakewray.dev` - Redirects to the main domain (non-www) -- SSL/TLS with modern security settings -- Gzip compression for performance -- Rate limiting (10 requests/second with burst of 20) - -## First-Time Deployment - -1. Ensure DNS A records point to your server IP: - - `jakewray.dev` → Server IP - - `www.jakewray.dev` → Server IP - -2. Run the deployment script: - ```bash - ./scripts/deploy.sh all - ``` - -3. Initialize SSL certificates (first time only): - ```bash - # SSH into the server, then run: - cd ~/app - ./scripts/init_ssl.sh - ``` - - This script will: - - Create temporary self-signed certificates - - Start Nginx - - Request real certificates from Let's Encrypt - - Reload Nginx with the new certificates - -## Subsequent Deployments - -For code updates, simply run: -```bash -./scripts/deploy.sh all -``` - -Certificates will be automatically renewed by the certbot service (checks twice daily). - -## Services - -- `portfolio` - Main application (internal port 3000) -- `db` - PostgreSQL database -- `nginx` - Nginx reverse proxy (ports 80/443) -- `certbot` - Certificate renewal service -- `migration` - Database migration service (runs once) - -## Troubleshooting - -### Site Not Loading - -1. Check if all services are running: - ```bash - docker compose -f compose.prod.yaml ps - ``` - -2. View Nginx logs: - ```bash - docker compose -f compose.prod.yaml logs nginx - ``` - -3. View backend logs: - ```bash - docker compose -f compose.prod.yaml logs portfolio - ``` - -4. Verify DNS records: - ```bash - dig jakewray.dev - ``` - -### Certificate Issues - -1. Check certbot logs: - ```bash - docker compose -f compose.prod.yaml logs certbot - ``` - -2. Manually renew certificates: - ```bash - docker compose -f compose.prod.yaml run --rm certbot renew - docker compose -f compose.prod.yaml exec nginx nginx -s reload - ``` - -3. Ensure ports 80 and 443 are accessible from the internet - -### Connection Timeout - -If you're experiencing timeouts: - -1. Check if the portfolio service is running and healthy: - ```bash - docker compose -f compose.prod.yaml exec portfolio curl http://localhost:3000/health - ``` - -2. Check Nginx can reach the backend: - ```bash - docker compose -f compose.prod.yaml exec nginx wget -O- http://portfolio:3000/health - ``` - -3. Verify firewall rules allow traffic on ports 80 and 443 - -## Migration from nginx-proxy-manager - -This deployment previously used nginx-proxy-manager. The migration to Nginx + Certbot provides: -- Configuration as code (no manual UI setup required) -- Automatic certificate renewal -- Better performance and control -- Industry-standard setup - -If upgrading from the old setup: -1. The old volumes (`npm_data`, `npm_letsencrypt`) can be safely removed after verifying the new setup works -2. Run `init_ssl.sh` to set up new certificates -3. The migration is seamless - no data loss - -## Security Features - -The Nginx configuration includes: -- TLS 1.2 and 1.3 only -- Strong cipher suites -- Security headers (X-Frame-Options, X-Content-Type-Options, etc.) -- Rate limiting to prevent abuse -- HTTP to HTTPS redirection diff --git a/docs/LOCAL_DEV.md b/docs/LOCAL_DEV.md index 13f50e6f..c62993c9 100644 --- a/docs/LOCAL_DEV.md +++ b/docs/LOCAL_DEV.md @@ -237,15 +237,7 @@ Edit files in `frontend/src/pages/admin/login.rs` 4. **Check form validation**: Try submitting without credentials 5. **Test token expiration**: Manually edit localStorage token to test -## Production Deployment -When ready to deploy: -```bash -cargo leptos build --release -./scripts/deploy.sh all -``` - -See [docs/DEPLOYMENT.md](../DEPLOYMENT.md) for full deployment guide. ## Troubleshooting diff --git a/docs/deployment.md b/docs/deployment.md deleted file mode 100644 index 574ab064..00000000 --- a/docs/deployment.md +++ /dev/null @@ -1,147 +0,0 @@ -# Deployment Guide - -## Overview - -This application uses Docker Compose for production deployment with Nginx as a reverse proxy and Let's Encrypt for SSL certificates. - -## Architecture - -- **Backend**: Rust/Leptos application (port 3000) -- **Database**: PostgreSQL 15 -- **Reverse Proxy**: Nginx (with automatic HTTPS via Let's Encrypt) -- **Certificate Management**: Certbot (automatic renewal) - -## Domain Configuration - -The application is configured to serve traffic for `jakewray.dev` through Nginx, which handles: -- HTTPS certificate provisioning via Let's Encrypt/Certbot -- HTTP to HTTPS redirection -- Reverse proxy to the backend service -- Security headers and rate limiting - -### Nginx Configuration - -The `nginx.conf` configures: -- `jakewray.dev` - Main domain, proxies to the backend on port 3000 -- `www.jakewray.dev` - Redirects to the main domain (non-www) -- SSL/TLS with modern security settings -- Gzip compression for performance -- Rate limiting (10 requests/second with burst of 20) - -## First-Time Deployment - -1. Ensure DNS A records point to your server IP: - - `jakewray.dev` → Server IP - - `www.jakewray.dev` → Server IP - -2. Run the deployment script: - ```bash - ./scripts/deploy.sh all - ``` - -3. Initialize SSL certificates (first time only): - ```bash - # SSH into the server, then run: - cd ~/app - ./scripts/init_ssl.sh - ``` - - This script will: - - Create temporary self-signed certificates - - Start Nginx - - Request real certificates from Let's Encrypt - - Reload Nginx with the new certificates - -## Subsequent Deployments - -For code updates, simply run: -```bash -./scripts/deploy.sh all -``` - -Certificates will be automatically renewed by the certbot service (checks twice daily). - -## Services - -- `portfolio` - Main application (internal port 3000) -- `db` - PostgreSQL database -- `nginx` - Nginx reverse proxy (ports 80/443) -- `certbot` - Certificate renewal service -- `migration` - Database migration service (runs once) - -## Troubleshooting - -### Site Not Loading - -1. Check if all services are running: - ```bash - docker compose -f compose.prod.yaml ps - ``` - -2. View Nginx logs: - ```bash - docker compose -f compose.prod.yaml logs nginx - ``` - -3. View backend logs: - ```bash - docker compose -f compose.prod.yaml logs portfolio - ``` - -4. Verify DNS records: - ```bash - dig jakewray.dev - ``` - -### Certificate Issues - -1. Check certbot logs: - ```bash - docker compose -f compose.prod.yaml logs certbot - ``` - -2. Manually renew certificates: - ```bash - docker compose -f compose.prod.yaml run --rm certbot renew - docker compose -f compose.prod.yaml exec nginx nginx -s reload - ``` - -3. Ensure ports 80 and 443 are accessible from the internet - -### Connection Timeout - -If you're experiencing timeouts: - -1. Check if the portfolio service is running and healthy: - ```bash - docker compose -f compose.prod.yaml exec portfolio curl http://localhost:3000/health - ``` - -2. Check Nginx can reach the backend: - ```bash - docker compose -f compose.prod.yaml exec nginx wget -O- http://portfolio:3000/health - ``` - -3. Verify firewall rules allow traffic on ports 80 and 443 - -## Migration from nginx-proxy-manager - -This deployment previously used nginx-proxy-manager. The migration to Nginx + Certbot provides: -- Configuration as code (no manual UI setup required) -- Automatic certificate renewal -- Better performance and control -- Industry-standard setup - -If upgrading from the old setup: -1. The old volumes (`npm_data`, `npm_letsencrypt`) can be safely removed after verifying the new setup works -2. Run `init_ssl.sh` to set up new certificates -3. The migration is seamless - no data loss - -## Security Features - -The Nginx configuration includes: -- TLS 1.2 and 1.3 only -- Strong cipher suites -- Security headers (X-Frame-Options, X-Content-Type-Options, etc.) -- Rate limiting to prevent abuse -- HTTP to HTTPS redirection diff --git a/flake.nix b/flake.nix index 86aa46ca..050b3d5e 100644 --- a/flake.nix +++ b/flake.nix @@ -40,26 +40,16 @@ openssl # Database - postgresql + sqlite sqlx-cli # Styling sass dart-sass - # Container tools - docker - docker-compose - podman - podman-compose - colima # Lightweight Docker daemon for macOS - # Web compilation (optional) nodejs - # Cloud deployment - google-cloud-sdk - # Development tools git just @@ -68,21 +58,16 @@ shellHook = '' export RUST_SRC_PATH=${pkgs.rustPlatform.rustLibSrc} export RUST_LOG=info - export DATABASE_URL="postgres://admin:password@127.0.0.1:5432/portfolio" - - # Set gcloud project for this repo - gcloud config set project jakewray-portfolio 2>/dev/null || true + export DATABASE_URL="sqlite://jakewray.db" echo "🚀 jakewray.dev development environment loaded" echo " Rust: $(rustc --version)" echo " Cargo: $(cargo --version)" - echo " GCloud: $(gcloud config get-value project)" - echo " Database: PostgreSQL (docker-compose up -d db)" + echo " Database: SQLite" echo "" echo "📚 Quick commands:" echo " cargo leptos watch - Start dev server" echo " ./scripts/setup-dev.sh - Setup local database" - echo " docker-compose down - Stop services" ''; }; } diff --git a/hgen/src/main.rs b/hgen/src/main.rs index 9b70aee0..d0392507 100644 --- a/hgen/src/main.rs +++ b/hgen/src/main.rs @@ -12,7 +12,8 @@ use argon2::{ }; fn main() { let mut password = String::new(); - std::io::Read::read_to_string(&mut std::io::stdin(), &mut password).expect("Failed to read password"); + std::io::Read::read_to_string(&mut std::io::stdin(), &mut password) + .expect("Failed to read password"); let password = password.trim_end_matches(['\r', '\n']); let salt = SaltString::generate(&mut OsRng); let params = argon2::Params::new( @@ -20,12 +21,9 @@ fn main() { shared::auth::ARGON2_T_COST, shared::auth::ARGON2_P_COST, Some(argon2::Params::DEFAULT_OUTPUT_LEN), - ).unwrap(); - let argon2 = Argon2::new( - argon2::Algorithm::Argon2id, - argon2::Version::V0x13, - params, - ); + ) + .unwrap(); + let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params); let hash = argon2.hash_password(password.as_bytes(), &salt).unwrap(); println!("{}", hash); } diff --git a/nginx/nginx.conf b/nginx/nginx.conf deleted file mode 100644 index d3c57516..00000000 --- a/nginx/nginx.conf +++ /dev/null @@ -1,134 +0,0 @@ -events { - worker_connections 1024; -} - -http { - # Basic Settings - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - client_max_body_size 20M; - - # MIME types - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Logging - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_proxied any; - gzip_comp_level 6; - gzip_types - text/plain - text/css - text/xml - text/javascript - text/html - application/json - application/javascript - application/xml+rss - application/rss+xml - font/truetype - font/opentype - application/vnd.ms-fontobject - image/svg+xml; - - # Rate limiting - limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; - - # Upstream backend - upstream portfolio_backend { - server portfolio:3000; - } - - # HTTP server - redirect to HTTPS - server { - listen 80; - listen [::]:80; - server_name jakewray.dev www.jakewray.dev; - - # ACME challenge for Let's Encrypt (if using certbot) - location /.well-known/acme-challenge/ { - root /var/www/certbot; - } - - # Redirect all other traffic to HTTPS - location / { - return 301 https://$host$request_uri; - } - } - - # HTTPS server for jakewray.dev - server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name jakewray.dev; - - # SSL certificates (will be mounted via volumes) - ssl_certificate /etc/letsencrypt/live/jakewray.dev/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/jakewray.dev/privkey.pem; - - # SSL configuration - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - - # Proxy settings - location / { - limit_req zone=general burst=20 nodelay; - - proxy_pass http://portfolio_backend; - proxy_http_version 1.1; - - # Headers - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - - # WebSocket support (if needed) - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - # Timeouts - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - } - } - - # HTTPS server for www.jakewray.dev - redirect to non-www - server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name www.jakewray.dev; - - # SSL certificates - ssl_certificate /etc/letsencrypt/live/jakewray.dev/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/jakewray.dev/privkey.pem; - - # SSL configuration - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; - - # Redirect to non-www - return 301 https://jakewray.dev$request_uri; - } -} diff --git a/scripts/deploy.sh b/scripts/deploy.sh deleted file mode 100755 index aee3dca8..00000000 --- a/scripts/deploy.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -set -e - -TARGET=${1:-all} -PROJECT_ID="jakewray-portfolio" -INSTANCE_NAME="jakewray-portfolio" -ZONE="us-west1-a" - -echo "Deploying target: $TARGET" - - - -# 0. Clean remote directory (preserving persistent data) -# We NO LONGER wipe the directory to preserve Docker cache and valid files. -# rsync/scp will overwrite changed files. -echo "Preparing remote directory..." -gcloud compute ssh jake-user@$INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE --command=" - mkdir -p ~/app/data && \ - chmod 700 ~/app/data && \ - sudo chown -R jake-user:jake-user ~/app -" - - -# 1. Copy files to VM (Delta sync) -echo "Getting instance IP..." -IP=$(gcloud compute instances describe $INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE --format='get(networkInterfaces[0].accessConfigs[0].natIP)') -echo "Instance IP: $IP" - -echo "Ensuring rsync is installed on remote..." -gcloud compute ssh jake-user@$INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE --command=" - if ! command -v rsync &> /dev/null; then - echo 'rsync not found, installing...' - sudo apt-get update && sudo apt-get install -y rsync - else - echo 'rsync is already installed.' - fi -" - -echo "Syncing project files (rsync)..." -# We exclude things that are large, ignored, or platform-specific -rsync -avz --info=progress2 \ - --exclude '.git' \ - --exclude 'target' \ - --exclude 'node_modules' \ - --exclude '.postgres_local' \ - --exclude 'postgres.log' \ - --exclude '.env' \ - --exclude '.DS_Store' \ - -e "ssh -i ~/.ssh/google_compute_engine -o StrictHostKeyChecking=no" \ - ./ \ - "jake-user@$IP:~/app/" - -# 2. SSH and Deploy -echo "Starting remote configuration and build..." -gcloud compute ssh jake-user@$INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE --command=" - cd ~/app && - chmod +x scripts/*.sh && - ./scripts/remote_setup.sh && - ./scripts/remote_build.sh $TARGET -" - -echo "Deployment of $TARGET complete!" diff --git a/scripts/init_certbot.sh b/scripts/init_certbot.sh deleted file mode 100644 index e3d924c9..00000000 --- a/scripts/init_certbot.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -domains=(jakewray.dev www.jakewray.dev) -rsa_key_size=4096 -data_path="./certbot" -email="jake@bitnorth.ca" # Adding a valid email is important -staging=0 # Set to 1 if you're testing your setup to avoid hitting request limits - -if [ -d "$data_path" ]; then - read -p "Existing data found for $data_path. Continue and replace existing certificate? (y/N) " decision - if [ "$decision" != "Y" ] && [ "$decision" != "y" ]; then - exit - fi -fi - -if [ ! -e "$data_path/conf/options-ssl-nginx.conf" ] || [ ! -e "$data_path/conf/ssl-dhparams.pem" ]; then - echo "### Downloading recommended TLS parameters ..." - mkdir -p "$data_path/conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" - curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem" - echo -fi - -echo "### Creating dummy certificate for $domains ..." -path="/etc/letsencrypt/live/$domains" -mkdir -p "$data_path/conf/live/$domains" -docker compose -f compose.prod.yaml run --rm --entrypoint "\ - openssl req -x509 -nodes -newkey rsa:$rsa_key_size -days 1\ - -keyout '$path/privkey.pem' \ - -out '$path/fullchain.pem' \ - -subj '/CN=localhost'" certbot -echo - -echo "### Starting nginx ..." -docker compose -f compose.prod.yaml up --force-recreate -d nginx -echo - -echo "### Deleting dummy certificate for $domains ..." -docker compose -f compose.prod.yaml run --rm --entrypoint "\ - rm -Rf /etc/letsencrypt/live/$domains && \ - rm -Rf /etc/letsencrypt/archive/$domains && \ - rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot -echo - -echo "### Requesting Let's Encrypt certificate for $domains ..." -#Join $domains to -d args -domain_args="" -for domain in "${domains[@]}"; do - domain_args="$domain_args -d $domain" -done - -# Select appropriate email arg -case "$email" in - "") email_arg="--register-unsafely-without-email" ;; - *) email_arg="-m $email" ;; -esac - -# Enable staging mode if needed -if [ $staging != "0" ]; then staging_arg="--staging"; fi - -docker compose -f compose.prod.yaml run --rm --entrypoint "\ - certbot certonly --webroot -w /var/www/certbot \ - $staging_arg \ - $email_arg \ - $domain_args \ - --rsa-key-size $rsa_key_size \ - --agree-tos \ - --force-renewal" certbot -echo - -echo "### Reloading nginx ..." -docker compose -f compose.prod.yaml exec nginx nginx -s reload diff --git a/scripts/init_infra.sh b/scripts/init_infra.sh deleted file mode 100644 index 03911a4a..00000000 --- a/scripts/init_infra.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Configuration -PROJECT_ID="jakewray-portfolio" -INSTANCE_NAME="jakewray-portfolio" -ZONE="us-west1-a" -MACHINE_TYPE="c4a-standard-4" # Google Axion (ARM): 4 vCPU, 16GB RAM -IMAGE_FAMILY="debian-12-arm64" -IMAGE_PROJECT="debian-cloud" - -echo "Checking Google Cloud Infrastructure..." - -# 1. Setup Static IP -ADDRESS_NAME="$INSTANCE_NAME-ip" -if ! gcloud compute addresses describe $ADDRESS_NAME --project=$PROJECT_ID --region=us-west1 &>/dev/null; then - echo "Creating static IP address..." - gcloud compute addresses create $ADDRESS_NAME --project=$PROJECT_ID --region=us-west1 -fi -STATIC_IP=$(gcloud compute addresses describe $ADDRESS_NAME --project=$PROJECT_ID --region=us-west1 --format='get(address)') -echo "Using Static IP: $STATIC_IP" - -# 2. Create VM if not exists -if ! gcloud compute instances describe $INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE &>/dev/null; then - echo "Creating VM instance..." - gcloud compute instances create $INSTANCE_NAME \ - --project=$PROJECT_ID \ - --zone=$ZONE \ - --machine-type=$MACHINE_TYPE \ - --image-family=$IMAGE_FAMILY \ - --image-project=$IMAGE_PROJECT \ - --boot-disk-size=64GB \ - --address=$STATIC_IP \ - --tags=http-server,https-server \ - --metadata=startup-script='#! /bin/bash - # Install Docker - curl -fsSL https://get.docker.com -o get-docker.sh - sh get-docker.sh - - # Install Docker Compose (standalone) - COMPOSE_VERSION="v2.24.6" - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - ARCH=$(uname -m) - curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-${OS}-${ARCH}" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose - - # Install Docker Compose (plugin) - mkdir -p /usr/local/lib/docker/cli-plugins - ln -s /usr/local/bin/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose - ' - - echo "Waiting for VM to initialize..." - sleep 30 -else - echo "VM $INSTANCE_NAME already exists." -fi - -# 2. Get IP -IP_ADDRESS=$(gcloud compute instances describe $INSTANCE_NAME --project=$PROJECT_ID --zone=$ZONE --format='get(networkInterfaces[0].accessConfigs[0].natIP)') -echo "VM IP Address: $IP_ADDRESS" -echo "Ensure your DNS (A Record) for jakewray.dev points to $IP_ADDRESS" diff --git a/scripts/init_ssl.sh b/scripts/init_ssl.sh deleted file mode 100644 index 57b21d99..00000000 --- a/scripts/init_ssl.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -set -e - -# This script initializes SSL certificates for the first deployment -# Run this on the server after the first deployment - -DOMAIN="jakewray.dev" -EMAIL="admin@jakewray.dev" - -echo "Initializing SSL certificates for $DOMAIN..." -cd ~/app - -# Ensure services are up (except nginx which needs certs) -echo "Starting backend services..." -sudo docker compose -f compose.prod.yaml up -d db portfolio - -# Wait for backend to be ready -echo "Waiting for backend to be ready..." -sleep 10 - -# Stop nginx if running -sudo docker compose -f compose.prod.yaml stop nginx 2>/dev/null || true - -# Get certificates using certbot standalone mode (since nginx isn't running yet) -echo "Requesting Let's Encrypt certificates using standalone mode..." -mkdir -p ~/app/certbot/conf ~/app/certbot/www -sudo docker run --rm -p 80:80 -v ~/app/certbot/conf:/etc/letsencrypt -v ~/app/certbot/www:/var/www/certbot certbot/certbot certonly \ - --standalone \ - --preferred-challenges http \ - --email $EMAIL \ - --agree-tos \ - --no-eff-email \ - -d $DOMAIN \ - -d www.$DOMAIN - -# Start nginx with the new certificates -echo "Starting nginx with SSL certificates..." -sudo docker compose -f compose.prod.yaml up -d nginx - -# Start certbot renewal service -echo "Starting certbot renewal service..." -sudo docker compose -f compose.prod.yaml up -d certbot - -echo "SSL certificates initialized successfully!" -echo "Nginx is now running with HTTPS enabled." -echo "Certificates will auto-renew via certbot service (checks twice daily)." diff --git a/scripts/install_compose_plugin.sh b/scripts/install_compose_plugin.sh deleted file mode 100644 index 84bb8690..00000000 --- a/scripts/install_compose_plugin.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash -set -e - -# This script installs/fixes Docker Compose as both a standalone binary and a plugin. -# It handles architecture detection and ensures lowercase names for URLs. - -COMPOSE_VERSION="v2.24.6" -OS=$(uname -s | tr '[:upper:]' '[:lower:]') -ARCH=$(uname -m) - -# Normalize architecture names -if [ "$ARCH" = "x86_64" ]; then - BINARY_ARCH="x86_64" -elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then - BINARY_ARCH="aarch64" -else - echo "Unknown architecture: $ARCH" - exit 1 -fi - -BINARY_URL="https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-${OS}-${BINARY_ARCH}" - -echo "Installing Docker Compose ${COMPOSE_VERSION} for ${OS}-${BINARY_ARCH}..." -echo "Downloading from ${BINARY_URL}..." - -# 1. Download to /usr/local/bin -sudo curl -SL "${BINARY_URL}" -o /usr/local/bin/docker-compose -sudo chmod +x /usr/local/bin/docker-compose - -# 2. Link as a CLI plugin in multiple standard paths -sudo mkdir -p /usr/local/lib/docker/cli-plugins -sudo ln -sf /usr/local/bin/docker-compose /usr/local/lib/docker/cli-plugins/docker-compose - -sudo mkdir -p /usr/lib/docker/cli-plugins -sudo ln -sf /usr/local/bin/docker-compose /usr/lib/docker/cli-plugins/docker-compose - -echo "Docker Compose installed successfully!" -docker-compose version -docker compose version diff --git a/scripts/remote_build.sh b/scripts/remote_build.sh deleted file mode 100644 index 4dda0826..00000000 --- a/scripts/remote_build.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -set -e - -TARGET=${1:-all} # Default to 'all' if no argument provided -cd ~/app - -echo "Remote Build Target: $TARGET" - -# Enable Docker BuildKit for better caching -export DOCKER_BUILDKIT=1 -export COMPOSE_DOCKER_CLI_BUILD=1 - -ensure_data_dir() { - echo "Ensuring data directory exists..." - mkdir -p data && chmod 700 data && sudo chown 1000:1000 data -} - -if [ ! -f .env ]; then - echo "Generating new .env file with defaults..." - cat < .env -DOMAIN_NAME=jakewray.dev -LEPTOS_SITE_ADDR=0.0.0.0:3000 -RUST_LOG=info -DATABASE_URL=sqlite:////app/data/sqlite.db -ENVIRONMENT=production -JWT_SECRET=$(openssl rand -base64 48 | tr -d '\n') -# Warning: Ephemeral Docker Bridge IPs change on restart. -# Run `docker network inspect jakewraydev_default` to find the proxy IP, -# and manually add TRUSTED_PROXY_IPS= to this file if using rate limiting. -EOF -chmod 600 .env -else - echo "Using existing .env file." -fi - -if [ "$TARGET" = "all" ] || [ "$TARGET" = "backend" ]; then - echo "Building chef base image (with cache)..." - sudo DOCKER_BUILDKIT=1 docker build \ - --target chef \ - --cache-from portfolio-chef:latest \ - -t portfolio-chef . - - ensure_data_dir -fi - -if [ "$TARGET" = "all" ]; then - echo "Building and starting ALL services with BuildKit caching..." - sudo DOCKER_BUILDKIT=1 docker compose -f compose.prod.yaml build \ - --build-arg BUILDKIT_INLINE_CACHE=1 - ensure_data_dir - sudo docker compose -f compose.prod.yaml up -d --remove-orphans -elif [ "$TARGET" = "backend" ]; then - echo "Building and restarting BACKEND (portfolio) service with caching..." - sudo DOCKER_BUILDKIT=1 docker compose -f compose.prod.yaml build \ - --build-arg BUILDKIT_INLINE_CACHE=1 portfolio - ensure_data_dir - sudo docker compose -f compose.prod.yaml up -d --no-deps portfolio -elif [ "$TARGET" = "frontend" ]; then - echo "Frontend is part of the backend binary in this setup (SSR)." - echo "Please use 'backend' or 'all' target." - exit 1 -else - echo "Unknown target: $TARGET" - exit 1 -fi - -echo "=====================================================================" -echo "WARNING: Check your .env file for TRUSTED_PROXY_IPS." -echo "Docker bridge IPs may change. Verify them if using rate limiting!" -echo "=====================================================================" diff --git a/scripts/remote_setup.sh b/scripts/remote_setup.sh deleted file mode 100644 index 2fc4d9ad..00000000 --- a/scripts/remote_setup.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -set -e - -echo "Running Remote Setup..." -# Ensure Docker Compose is installed correctly -./scripts/install_compose_plugin.sh - -# Install gcsfuse if not installed -if ! command -v gcsfuse &> /dev/null; then - echo 'Installing gcsfuse...' - export GCSFUSE_REPO=gcsfuse-`lsb_release -c -s` - echo "deb https://packages.cloud.google.com/apt $GCSFUSE_REPO main" | sudo tee /etc/apt/sources.list.d/gcsfuse.list - curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - - sudo apt-get update - sudo apt-get install -y gcsfuse -fi - -# Create mount point -mkdir -p ~/media_mount - -# Mount bucket (if not already mounted) -if ! mount | grep -q 'media_mount'; then - echo 'Mounting GCS bucket...' - gcsfuse --implicit-dirs jakewray-portfolio-media ~/media_mount -fi - -# Configure Swap (4GB) -if [ ! -f /swapfile ]; then - echo "Creating 4GB swap file..." - sudo fallocate -l 4G /swapfile - sudo chmod 600 /swapfile - sudo mkswap /swapfile - sudo swapon /swapfile - echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab - echo "Swap created." -else - echo "Swap file already exists." -fi