Merge pull request 'develop' (#2) from develop into master
Reviewed-on: matthew.binning/www#2
This commit is contained in:
commit
2db3ec3da4
14 changed files with 86 additions and 959 deletions
22
.forgejo/workflows/deploy.yaml
Normal file
22
.forgejo/workflows/deploy.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: nix shell nixpkgs#mdbook --command ./deploy.sh build
|
||||
|
||||
- name: Deploy
|
||||
run: nix shell nixpkgs#rsync --command ./deploy.sh local
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1 +1,4 @@
|
|||
blog/*
|
||||
blog/
|
||||
src
|
||||
content/*
|
||||
example/*
|
||||
|
|
|
|||
27
404.html
27
404.html
|
|
@ -1,27 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 - Page Not Found</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/includes/navbar.html" -->
|
||||
|
||||
<main class="container">
|
||||
<section class="hero">
|
||||
<h1>404 - Page Not Found</h1>
|
||||
<p>The page you're looking for doesn't exist.</p>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>What happened?</h2>
|
||||
<p>The page you requested could not be found. It may have been moved, deleted, or never existed.</p>
|
||||
<p><a href="/" style="color: #3498db; text-decoration: none; font-weight: bold;">← Go back to homepage</a></p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/includes/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
127
blog.html
127
blog.html
|
|
@ -1,127 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blog - WWW</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/includes/navbar.html" -->
|
||||
|
||||
<main class="container">
|
||||
<section class="hero">
|
||||
<h1>Blog</h1>
|
||||
<p>Stories, adventures, and reflections from life's journey</p>
|
||||
</section>
|
||||
|
||||
<!-- The Copper Chronicle Section -->
|
||||
<section class="content-section">
|
||||
<h2>The Copper Chronicle</h2>
|
||||
<p class="section-description">Adventures and travels with Copper, exploring the beautiful outdoors.</p>
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/copper-chronicle/big-sur.html">Big Sur Adventure</a></h3>
|
||||
<p class="blog-meta">April 2024</p>
|
||||
<p>Exploring the stunning coastline and trails of Big Sur with Copper.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/copper-chronicle/dove-hunting.html">Dove Hunting</a></h3>
|
||||
<p class="blog-meta">September 2024</p>
|
||||
<p>A day in the field with friends, shotguns, and good company.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/copper-chronicle/shasta-trip.html">Shasta & Dunsmuir</a></h3>
|
||||
<p class="blog-meta">September 2024</p>
|
||||
<p>Mountain adventures in Northern California.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Life & Reflections Section -->
|
||||
<section class="content-section">
|
||||
<h2>Life & Reflections</h2>
|
||||
<p class="section-description">Personal thoughts and journeys on health, faith, and life.</p>
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card private">
|
||||
<h3><a href="/blog/health-journey.html">Health Journey</a></h3>
|
||||
<p class="blog-meta">Private Article</p>
|
||||
<p>Reflections on physical and mental wellness.</p>
|
||||
<span class="private-badge">🔒 Private</span>
|
||||
</article>
|
||||
<article class="blog-card private">
|
||||
<h3><a href="/blog/faith-journey.html">Faith Journey</a></h3>
|
||||
<p class="blog-meta">Private Article</p>
|
||||
<p>Personal spiritual reflections and growth.</p>
|
||||
<span class="private-badge">🔒 Private</span>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Events & Experiences Section -->
|
||||
<section class="content-section">
|
||||
<h2>Events & Experiences</h2>
|
||||
<p class="section-description">Special moments and memorable experiences.</p>
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card private">
|
||||
<h3><a href="/blog/defcon.html">DefCon</a></h3>
|
||||
<p class="blog-meta">Private Article</p>
|
||||
<p>Notes and reflections from DefCon.</p>
|
||||
<span class="private-badge">🔒 Private</span>
|
||||
</article>
|
||||
<article class="blog-card private">
|
||||
<h3><a href="/blog/oktoberfest.html">Oktoberfest</a></h3>
|
||||
<p class="blog-meta">Private Article</p>
|
||||
<p>Celebrating traditions and community.</p>
|
||||
<span class="private-badge">🔒 Private</span>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/independence-day.html">Independence Day</a></h3>
|
||||
<p class="blog-meta">July 2024</p>
|
||||
<p>A celebration of freedom and fireworks.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Recipes Section -->
|
||||
<section class="content-section">
|
||||
<h2>Recipes</h2>
|
||||
<p class="section-description">Family recipes and culinary experiments.</p>
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/st-patricks-day.html">St. Patrick's Day</a></h3>
|
||||
<p class="blog-meta">Holiday</p>
|
||||
<p>Traditional Irish recipes - corned beef, lamb stew, soda bread, and more.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/christmas.html">Christmas</a></h3>
|
||||
<p class="blog-meta">Holiday</p>
|
||||
<p>Holiday favorites including eggnog, gingerbread fruitcake, and meatloaf.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/earth-day.html">Earth Day</a></h3>
|
||||
<p class="blog-meta">Special Event</p>
|
||||
<p>Lamb stir fry and berry compote from April 2024.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/camping.html">Camping & Backpacking</a></h3>
|
||||
<p class="blog-meta">Outdoor</p>
|
||||
<p>Trail-ready recipes and energy bars for adventures.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/misc.html">Miscellaneous</a></h3>
|
||||
<p class="blog-meta">Various</p>
|
||||
<p>Drinks, snacks, sides, and external recipe links.</p>
|
||||
</article>
|
||||
<article class="blog-card">
|
||||
<h3><a href="/blog/public/recipe-book/">Full Recipe Book</a></h3>
|
||||
<p class="blog-meta">Index</p>
|
||||
<p>Browse all recipe collections in one place.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/includes/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
19
book.toml
Normal file
19
book.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[book]
|
||||
title = "The Bin"
|
||||
authors = ["Matthew Binning"]
|
||||
language = "en"
|
||||
|
||||
[build]
|
||||
build-dir = "blog"
|
||||
|
||||
[output.html]
|
||||
default-theme = "rust"
|
||||
smart-punctuation = true
|
||||
git-repository-url = "https://forgejo.binning.net/matthew.binning/www"
|
||||
edit-url-template = "https://forgejo.binning.net/matthew.binning/www/src/branch/main/{path}"
|
||||
no-section-label = true
|
||||
fold.enable = true
|
||||
fold.level = 0
|
||||
|
||||
[output.html.search]
|
||||
enable = true
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
# Common configuration shared between staging and prod
|
||||
|
||||
# This file contains nginx settings that are identical across environments
|
||||
# Import this in both staging.nginx.nix and prod.nginx.nix
|
||||
|
||||
services.nginx = {
|
||||
# Recommended settings
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
};
|
||||
|
||||
# Common location configurations that can be reused
|
||||
# These are defined as library functions
|
||||
lib.nginxLocations = {
|
||||
# Standard root location with SSI support
|
||||
rootLocation = {
|
||||
index = "index.html";
|
||||
tryFiles = "$uri $uri/ =404";
|
||||
extraConfig = ''
|
||||
# Enable Server Side Includes for navbar/footer includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Private blog articles location with HTTP basic authentication
|
||||
privateLocation = {
|
||||
extraConfig = ''
|
||||
auth_basic "Private Articles";
|
||||
auth_basic_user_file /srv/nginx/.htpasswd;
|
||||
|
||||
# Enable Server Side Includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Common extraConfig for custom 404
|
||||
custom404 = ''
|
||||
error_page 404 /404.html;
|
||||
'';
|
||||
};
|
||||
}
|
||||
90
deploy.sh
90
deploy.sh
|
|
@ -2,83 +2,75 @@
|
|||
|
||||
set -e
|
||||
|
||||
# Common files to deploy (shared between staging and prod)
|
||||
DEPLOY_FILES="blog includes index.html blog.html resume.html style.css 404.html"
|
||||
|
||||
# Usage information
|
||||
usage() {
|
||||
printf "Usage: %s [staging|prod]\n\n staging - Deploy to local staging environment (/srv/www/stage.binning.net)\n prod - Deploy to production server via SSH (www.binning.net)\n\nExample:\n %s staging\n %s prod\n" "$0" "$0" "$0"
|
||||
printf "Usage: %s [build|staging|prod|local]\n\n build - Build the blog with mdbook\n staging - Deploy to local staging environment (/srv/www/stage.binning.net)\n prod - Deploy to production server via SSH (www.binning.net)\n local - Deploy directly to /srv/www/binning.net (used by Forgejo CI runner)\n\nExample:\n %s build\n %s staging\n %s prod\n %s local\n" "$0" "$0" "$0" "$0" "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if argument provided
|
||||
if [ $# -eq 0 ]; then
|
||||
printf "Error: No environment specified\n"
|
||||
printf "Error: No command specified\n"
|
||||
usage
|
||||
fi
|
||||
|
||||
ENV=$1
|
||||
CMD=$1
|
||||
|
||||
case $CMD in
|
||||
build)
|
||||
printf "Building blog with mdbook...\n"
|
||||
[ -s src ] || ln -s /var/lib/www src
|
||||
mdbook build
|
||||
printf "✓ Build complete!\n"
|
||||
;;
|
||||
|
||||
case $ENV in
|
||||
staging)
|
||||
printf "Deploying to STAGING environment...\n"
|
||||
|
||||
|
||||
STAGING_PATH="/srv/www/stage.binning.net"
|
||||
|
||||
# Create staging directory if it doesn't exist
|
||||
|
||||
sudo mkdir -p ${STAGING_PATH}
|
||||
|
||||
# Deploy website files via rsync
|
||||
|
||||
printf "Deploying website files...\n"
|
||||
sudo rsync -av --delete ${DEPLOY_FILES} ${STAGING_PATH}/
|
||||
|
||||
# Set proper ownership
|
||||
sudo rsync -av --delete blog/ ${STAGING_PATH}/
|
||||
|
||||
sudo chown -R nginx:nginx ${STAGING_PATH}/
|
||||
|
||||
# Copy nginx config
|
||||
sudo cp -t /etc/nixos/ staging.nginx.nix
|
||||
|
||||
printf "✓ Staging deployment complete!\n Files deployed to: %s\n Nginx config: /etc/nixos/staging.nginx.nix\n\nTo activate, update your NixOS configuration to import staging.nginx.nix\nand run: sudo nixos-rebuild switch\n" "${STAGING_PATH}"
|
||||
|
||||
printf "✓ Staging deployment complete!\n Files deployed to: %s\n\nTo activate nginx, import staging.nginx.nix into your local NixOS config\nand run: sudo nixos-rebuild switch\n" "${STAGING_PATH}"
|
||||
;;
|
||||
|
||||
|
||||
prod)
|
||||
printf "Deploying to PRODUCTION environment...\n"
|
||||
|
||||
# SSH details
|
||||
|
||||
REMOTE_HOST="crossbox"
|
||||
REMOTE_USER="m3b"
|
||||
REMOTE_USER="brimlock"
|
||||
REMOTE_PATH="/srv/www/binning.net"
|
||||
REMOTE_NIXOS="/etc/nixos/"
|
||||
|
||||
# Check if SSH key is set up
|
||||
|
||||
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 ${REMOTE_USER}@${REMOTE_HOST} exit 2>/dev/null; then
|
||||
printf "Warning: SSH connection test failed. Ensure SSH keys are configured.\nYou may be prompted for a password.\n"
|
||||
fi
|
||||
|
||||
# Deploy website files via rsync over SSH
|
||||
# Using just the host from SSH config without user@ prefix
|
||||
|
||||
printf "Deploying website files...\n"
|
||||
ssh ${REMOTE_HOST} "mkdir -p /tmp/${REMOTE_PATH}"
|
||||
rsync -avz --delete ${DEPLOY_FILES} ${REMOTE_HOST}:/tmp/${REMOTE_PATH}/
|
||||
# Set proper permissions and move config on remote server
|
||||
ssh ${REMOTE_HOST} "sudo mv /tmp/${REMOTE_PATH} ${REMOTE_PATH} && \
|
||||
ssh ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p /tmp/blog-deploy"
|
||||
rsync -avz --delete blog/ ${REMOTE_USER}@${REMOTE_HOST}:/tmp/blog-deploy/
|
||||
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo rsync -avz --delete /tmp/blog-deploy/ ${REMOTE_PATH}/ && \
|
||||
sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
|
||||
printf 'Content deployed.\n'"
|
||||
|
||||
# Deploy nginx configuration
|
||||
printf "Deploying nginx configuration...\n"
|
||||
scp prod.nginx.nix ${REMOTE_HOST}:/tmp/nginx.nix
|
||||
|
||||
# Set proper permissions and move config on remote server
|
||||
ssh ${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \
|
||||
sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \
|
||||
printf 'Configuration deployed. Run sudo nixos-rebuild switch to activate.\n'"
|
||||
|
||||
printf "✓ Production deployment complete!\n\nSSH into %s and run: sudo nixos-rebuild switch\n" "${REMOTE_HOST}"
|
||||
|
||||
printf "✓ Production deployment complete!\n\nNginx configuration is managed by the nixos-config flake (hosts/crossbox/nginx.nix).\n"
|
||||
;;
|
||||
|
||||
|
||||
local)
|
||||
printf "Deploying locally to production path...\n"
|
||||
|
||||
LOCAL_PATH="/srv/www/binning.net"
|
||||
|
||||
printf "Deploying website files...\n"
|
||||
rsync -av --delete blog/ ${LOCAL_PATH}/
|
||||
|
||||
printf "✓ Local deployment complete!\n Files deployed to: %s\n" "${LOCAL_PATH}"
|
||||
;;
|
||||
|
||||
*)
|
||||
printf "Error: Invalid environment '%s'\n" "$ENV"
|
||||
printf "Error: Invalid command '%s'\n" "$CMD"
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<footer class="footer">
|
||||
<p>© 2025 WWW - Built with simple HTML and CSS</p>
|
||||
</footer>
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<a href="/" class="nav-brand">Home</a>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/blog.html">Blog</a></li>
|
||||
<li><a href="https://forgejo.binning.net" target="_blank">Git</a></li>
|
||||
<li><a href="/resume.html">Resume</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
37
index.html
37
index.html
|
|
@ -1,37 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WWW - Personal Website</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/includes/navbar.html" -->
|
||||
|
||||
<main class="container">
|
||||
<section class="hero">
|
||||
<h1>Welcome</h1>
|
||||
<p>This is my personal website and blog.</p>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>About & Contact</h2>
|
||||
<div class="contact-info">
|
||||
<p><strong>Email:</strong> example@example.com</p>
|
||||
<p>Feel free to reach out via email for any inquiries.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Genealogy</h2>
|
||||
<div class="genealogy-blurb">
|
||||
<p>I am interested in family history and genealogy. This website serves as a personal space to share stories, chronicles, and musings about life's journey, adventures, and family heritage.</p>
|
||||
<p>The Copper Chronicle contains stories and memories from various trips and experiences. More to come as this site develops.</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/includes/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
151
prod.nginx.nix
151
prod.nginx.nix
|
|
@ -1,151 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
# Read multiple API keys from the secrets file at build time
|
||||
# Note: This embeds the secrets in the Nix store, which is a trade-off
|
||||
# Alternative: Keep secrets file and read via njs module or external auth service
|
||||
secretsFile = "/srv/nginx/secrets";
|
||||
|
||||
# Read API keys from file (one key per line, will be evaluated at build time)
|
||||
# If the file doesn't exist yet, this will fail - create it first
|
||||
apiKeysRaw = builtins.readFile secretsFile;
|
||||
apiKeys = lib.filter (k: k != "") (lib.splitString "\n" apiKeysRaw);
|
||||
|
||||
# Generate map entries for each key
|
||||
mapEntries = lib.concatMapStringsSep "\n "
|
||||
(key: ''"Bearer ${key}" "authorized";'')
|
||||
apiKeys;
|
||||
|
||||
in
|
||||
{
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
# Recommended settings
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
recommendedTlsSettings = true;
|
||||
|
||||
# Increase bucket size for long Bearer tokens
|
||||
mapHashBucketSize = 128;
|
||||
|
||||
# Map directive to check Authorization header against multiple keys
|
||||
appendHttpConfig = ''
|
||||
# Check if the Authorization header matches any expected value
|
||||
map $http_authorization $auth_status {
|
||||
default "unauthorized";
|
||||
"" "no_auth";
|
||||
${mapEntries}
|
||||
}
|
||||
'';
|
||||
|
||||
# Virtual hosts configuration
|
||||
virtualHosts = {
|
||||
|
||||
# Main website - Static HTML/CSS
|
||||
"www.binning.net" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
root = "/srv/www/binning.net";
|
||||
|
||||
locations."/" = {
|
||||
index = "index.html";
|
||||
tryFiles = "$uri $uri/ =404";
|
||||
extraConfig = ''
|
||||
# Enable Server Side Includes for navbar/footer includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Private blog articles with HTTP basic authentication
|
||||
locations."/blog/private/" = {
|
||||
extraConfig = ''
|
||||
auth_basic "Private Articles";
|
||||
auth_basic_user_file /srv/nginx/.htpasswd;
|
||||
|
||||
# Enable Server Side Includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Optional: Custom 404 page
|
||||
extraConfig = ''
|
||||
error_page 404 /404.html;
|
||||
'';
|
||||
};
|
||||
|
||||
# Ollama with Bearer token authentication
|
||||
"ollama.binning.net" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/" = {
|
||||
extraConfig = ''
|
||||
# Check auth status
|
||||
if ($auth_status = "no_auth") {
|
||||
return 401 "Unauthorized: Bearer token required\n";
|
||||
}
|
||||
if ($auth_status = "unauthorized") {
|
||||
return 403 "Forbidden: Invalid API key\n";
|
||||
}
|
||||
|
||||
# Proxy to Ollama (only if authorized)
|
||||
proxy_pass http://localhost:11434;
|
||||
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;
|
||||
|
||||
# Timeouts for long-running requests
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
|
||||
# Allow large request bodies
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/ollama_access.log;
|
||||
error_log /var/log/nginx/ollama_error.log;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Forgejo
|
||||
"forgejo.binning.net" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
# No extraConfig needed - recommendedProxySettings handles headers
|
||||
};
|
||||
};
|
||||
|
||||
# Radicale
|
||||
"radicale.binning.net" = {
|
||||
enableACME = true;
|
||||
forceSSL = true;
|
||||
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:5232";
|
||||
# recommendedProxySettings handles most headers
|
||||
extraConfig = ''
|
||||
proxy_set_header X-Script-Name "";
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Firewall
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
# ACME/Let's Encrypt
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "hvanb@pm.me";
|
||||
};
|
||||
}
|
||||
122
resume.html
122
resume.html
|
|
@ -1,122 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Resume - WWW</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!--#include virtual="/includes/navbar.html" -->
|
||||
|
||||
<main class="container">
|
||||
<section class="hero">
|
||||
<h1>Resume</h1>
|
||||
<p>Professional Experience & Skills</p>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Summary</h2>
|
||||
<p>Experienced software engineer with expertise in systems engineering, cloud infrastructure, and full-stack development. Passionate about building reliable, scalable solutions using modern technologies.</p>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Technical Skills</h2>
|
||||
<div class="skills-grid">
|
||||
<div class="skill-category">
|
||||
<h3>Languages</h3>
|
||||
<ul>
|
||||
<li>Rust</li>
|
||||
<li>Python</li>
|
||||
<li>Go</li>
|
||||
<li>JavaScript/TypeScript</li>
|
||||
<li>HTML/CSS</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="skill-category">
|
||||
<h3>Systems & Infrastructure</h3>
|
||||
<ul>
|
||||
<li>Linux/NixOS</li>
|
||||
<li>Docker</li>
|
||||
<li>Nginx</li>
|
||||
<li>Git</li>
|
||||
<li>CI/CD</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="skill-category">
|
||||
<h3>Frameworks & Tools</h3>
|
||||
<ul>
|
||||
<li>Rocket (Rust)</li>
|
||||
<li>React</li>
|
||||
<li>Node.js</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>RESTful APIs</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Professional Experience</h2>
|
||||
|
||||
<div class="experience-item">
|
||||
<h3>Software Engineer</h3>
|
||||
<p class="job-meta">Example Company • 2020 - Present</p>
|
||||
<ul>
|
||||
<li>Designed and implemented scalable backend services using Rust and Python</li>
|
||||
<li>Managed infrastructure using NixOS and containerization technologies</li>
|
||||
<li>Built and maintained CI/CD pipelines for automated testing and deployment</li>
|
||||
<li>Collaborated with cross-functional teams to deliver high-quality software solutions</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="experience-item">
|
||||
<h3>Systems Administrator</h3>
|
||||
<p class="job-meta">Previous Company • 2018 - 2020</p>
|
||||
<ul>
|
||||
<li>Maintained Linux-based server infrastructure and monitoring systems</li>
|
||||
<li>Automated routine tasks using shell scripting and Python</li>
|
||||
<li>Implemented security best practices and access control policies</li>
|
||||
<li>Provided technical support and documentation for development teams</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Education</h2>
|
||||
<div class="experience-item">
|
||||
<h3>Bachelor of Science in Computer Science</h3>
|
||||
<p class="job-meta">University Name • 2014 - 2018</p>
|
||||
<p>Focus on software engineering, algorithms, and systems programming.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Projects</h2>
|
||||
<div class="experience-item">
|
||||
<h3>Self-Hosted Infrastructure</h3>
|
||||
<p>Designed and deployed a complete self-hosted solution including:</p>
|
||||
<ul>
|
||||
<li>Git hosting with Forgejo</li>
|
||||
<li>Web server with Nginx and SSL/TLS</li>
|
||||
<li>Calendar and contacts sync with Radicale</li>
|
||||
<li>Declarative configuration management with NixOS</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="experience-item">
|
||||
<h3>Personal Website & Blog</h3>
|
||||
<p>Built a simple, reliable website using primitive technologies (HTML/CSS) focused on performance and maintainability.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="content-section">
|
||||
<h2>Contact</h2>
|
||||
<p><strong>Email:</strong> example@example.com</p>
|
||||
<p><strong>GitHub:</strong> <a href="https://forgejo.binning.net" target="_blank">forgejo.binning.net</a></p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!--#include virtual="/includes/footer.html" -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
{
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
|
||||
# Recommended settings
|
||||
recommendedGzipSettings = true;
|
||||
recommendedOptimisation = true;
|
||||
recommendedProxySettings = true;
|
||||
|
||||
# Virtual hosts configuration for local staging
|
||||
virtualHosts = {
|
||||
|
||||
# Main website - Static HTML/CSS
|
||||
# Access via http://localhost or http://localhost:80
|
||||
"localhost" = {
|
||||
# No SSL for local development
|
||||
listen = [
|
||||
{ addr = "127.0.0.1"; port = 80; }
|
||||
{ addr = "0.0.0.0"; port = 80; }
|
||||
];
|
||||
|
||||
root = "/srv/www/stage.binning.net";
|
||||
|
||||
locations."/" = {
|
||||
index = "index.html";
|
||||
tryFiles = "$uri $uri/ =404";
|
||||
extraConfig = ''
|
||||
# Enable Server Side Includes for navbar/footer includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Private blog articles with HTTP basic authentication
|
||||
locations."/blog/private/" = {
|
||||
extraConfig = ''
|
||||
auth_basic "Private Articles";
|
||||
auth_basic_user_file /srv/nginx/.htpasswd;
|
||||
|
||||
# Enable Server Side Includes
|
||||
ssi on;
|
||||
'';
|
||||
};
|
||||
|
||||
# Custom 404 page
|
||||
extraConfig = ''
|
||||
error_page 404 /404.html;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Firewall - allow local HTTP access
|
||||
networking.firewall.allowedTCPPorts = [ 80 ];
|
||||
}
|
||||
331
style.css
331
style.css
|
|
@ -1,331 +0,0 @@
|
|||
/* CSS Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Courier New', 'Courier', 'Monaco', 'Lucida Console', monospace;
|
||||
line-height: 1.7;
|
||||
color: #3e2723;
|
||||
background-color: #f4ecd8;
|
||||
}
|
||||
|
||||
/* Navbar Styles */
|
||||
.navbar {
|
||||
background-color: #5d4037;
|
||||
box-shadow: 0 2px 4px rgba(62, 39, 35, 0.3);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
border-bottom: 2px solid #4e342e;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-brand {
|
||||
color: #f4ecd8;
|
||||
text-decoration: none;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-brand:hover {
|
||||
color: #d7ccc8;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
color: #f4ecd8;
|
||||
text-decoration: none;
|
||||
font-size: 1rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-menu a:hover {
|
||||
color: #d7ccc8;
|
||||
}
|
||||
|
||||
/* Main Container */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
background-color: #efebe9;
|
||||
padding: 3rem 2rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
border: 1px solid #d7ccc8;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
color: #4e342e;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.2rem;
|
||||
color: #6d4c41;
|
||||
}
|
||||
|
||||
/* Content Section */
|
||||
.content-section {
|
||||
background-color: #efebe9;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2);
|
||||
border: 1px solid #d7ccc8;
|
||||
}
|
||||
|
||||
.content-section h2 {
|
||||
color: #4e342e;
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-bottom: 3px solid #8d6e63;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.contact-info p,
|
||||
.genealogy-blurb p {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.8;
|
||||
color: #5d4037;
|
||||
}
|
||||
|
||||
.contact-info strong {
|
||||
color: #4e342e;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #5d4037;
|
||||
color: #f4ecd8;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
margin-top: 3rem;
|
||||
border-top: 2px solid #4e342e;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Blog Styles */
|
||||
.section-description {
|
||||
color: #6d4c41;
|
||||
font-style: italic;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.blog-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
background-color: #faf8f3;
|
||||
border: 1px solid #d7ccc8;
|
||||
border-radius: 4px;
|
||||
padding: 1.5rem;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.blog-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(62, 39, 35, 0.25);
|
||||
}
|
||||
|
||||
.blog-card h3 {
|
||||
color: #4e342e;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.blog-card h3 a {
|
||||
color: #4e342e;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.blog-card h3 a:hover {
|
||||
color: #6d4c41;
|
||||
}
|
||||
|
||||
.blog-meta {
|
||||
color: #8d6e63;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.blog-card p {
|
||||
color: #5d4037;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.blog-card.private {
|
||||
border-color: #a1887f;
|
||||
background-color: #f5f5f0;
|
||||
}
|
||||
|
||||
.private-badge {
|
||||
display: inline-block;
|
||||
background-color: #8d6e63;
|
||||
color: #f4ecd8;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Resume Styles */
|
||||
.skills-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.skill-category h3 {
|
||||
color: #4e342e;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.2rem;
|
||||
border-bottom: 2px solid #8d6e63;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.skill-category ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.skill-category li {
|
||||
padding: 0.5rem 0;
|
||||
color: #5d4037;
|
||||
position: relative;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.skill-category li:before {
|
||||
content: "▸";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #8d6e63;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.experience-item {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.experience-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.experience-item h3 {
|
||||
color: #4e342e;
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.job-meta {
|
||||
color: #8d6e63;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.experience-item ul {
|
||||
margin-top: 1rem;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.experience-item li {
|
||||
color: #5d4037;
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.experience-item p {
|
||||
color: #5d4037;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Recipe Page Styles */
|
||||
.recipe-card {
|
||||
background-color: #faf8f3;
|
||||
border: 1px solid #d7ccc8;
|
||||
border-radius: 4px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.recipe-card h3 {
|
||||
color: #4e342e;
|
||||
border-bottom: 2px solid #8d6e63;
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.recipe-year {
|
||||
color: #8d6e63;
|
||||
font-style: italic;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.nav-container {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.content-section h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.blog-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.skills-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue