From 12e954a8a6697a85c545f475a5a79fa0e001a47f Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 15:38:41 -0800 Subject: [PATCH 01/42] init: Restart blog Bootstrapped the site from scratch following a data loss event. Set up a simple HTML/CSS page served from nginx. Added a navbar with the index page anchored in the top-left, a blog page link, and a link to the Forgejo instance. Added contact info and a genealogy blurb to the index page. Added an example resume page linked from the navbar. --- .gitignore | 1 + 404.html | 27 ++++ README.md | 35 +++++ blog.html | 89 +++++++++++++ deploy.sh | 15 +++ includes/footer.html | 3 + includes/navbar.html | 10 ++ index.html | 37 ++++++ nginx.nix | 140 ++++++++++++++++++++ resume.html | 122 +++++++++++++++++ style.css | 305 +++++++++++++++++++++++++++++++++++++++++++ www.code-workspace | 7 + 12 files changed, 791 insertions(+) create mode 100644 .gitignore create mode 100644 404.html create mode 100644 README.md create mode 100644 blog.html create mode 100755 deploy.sh create mode 100644 includes/footer.html create mode 100644 includes/navbar.html create mode 100644 index.html create mode 100644 nginx.nix create mode 100644 resume.html create mode 100644 style.css create mode 100644 www.code-workspace diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2858192 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +blog/* diff --git a/404.html b/404.html new file mode 100644 index 0000000..33c62f8 --- /dev/null +++ b/404.html @@ -0,0 +1,27 @@ + + + + + + 404 - Page Not Found + + + + + +
+
+

404 - Page Not Found

+

The page you're looking for doesn't exist.

+
+ +
+

What happened?

+

The page you requested could not be found. It may have been moved, deleted, or never existed.

+

← Go back to homepage

+
+
+ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2cb2fc5 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# WWW + +Www is my personal website, blog project, and portfolio page. +It should be available at https://wwww.binning.dev + +# History + +## 2025-02-28 + +I restarted this in [Rocket](https://rocket.rs) after yet another data loss event. + +## 2024-06-21 + +I migrated from cgit to forgejo and from kuberentes to simply nginx. + +## 2023-10-07 + +I merged the deployment repository into this one. +I also moved to self-hosting at some point. + +## 2022-01-05 + +I moved hosting to Vultr and moved to a Cobalt. +One neat command to unhide files was: + +`for i in \.*; do if [ ${i} != '.' ] && [ ${i} != '..' ]; then mv {.,}${i/\./} ; fi; done;` + +## 2018-12-16 + +I started this blog with [Namecheap][Namecheap], [Ruby][Ruby], [Jekyll][Jekyll], and [Github-Pages][Github-Pages]. + +[Namecheap]: https://www.namecheap.com/ +[Ruby]: https://www.ruby-lang.org/ +[Jekyll]: https://jekyllrb.com/ +[Github-Pages]: https://pages.github.com/ \ No newline at end of file diff --git a/blog.html b/blog.html new file mode 100644 index 0000000..7c4e8fd --- /dev/null +++ b/blog.html @@ -0,0 +1,89 @@ + + + + + + Blog - WWW + + + + + +
+
+

Blog

+

Stories, adventures, and reflections from life's journey

+
+ + +
+

The Copper Chronicle

+

Adventures and travels with Copper, exploring the beautiful outdoors.

+
+
+

Big Sur Adventure

+

April 2024

+

Exploring the stunning coastline and trails of Big Sur with Copper.

+
+
+

Dove Hunting

+

September 2024

+

A day in the field with friends, shotguns, and good company.

+
+
+

Shasta & Dunsmuir

+

September 2024

+

Mountain adventures in Northern California.

+
+
+
+ + +
+

Life & Reflections

+

Personal thoughts and journeys on health, faith, and life.

+
+
+

Health Journey

+

Private Article

+

Reflections on physical and mental wellness.

+ 🔒 Private +
+
+

Faith Journey

+

Private Article

+

Personal spiritual reflections and growth.

+ 🔒 Private +
+
+
+ + +
+

Events & Experiences

+

Special moments and memorable experiences.

+
+
+

DefCon

+

Private Article

+

Notes and reflections from DefCon.

+ 🔒 Private +
+
+

Oktoberfest

+

Private Article

+

Celebrating traditions and community.

+ 🔒 Private +
+ +
+
+
+ + + + diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..daf400e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +sudo cp -rt /srv/www/binning.net \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html + +sudo chown -R nginx:nginx /srv/www/binning.net/ + +sudo cp -t /etc/nixos/ \ + nginx.nix \ No newline at end of file diff --git a/includes/footer.html b/includes/footer.html new file mode 100644 index 0000000..d78ea74 --- /dev/null +++ b/includes/footer.html @@ -0,0 +1,3 @@ + diff --git a/includes/navbar.html b/includes/navbar.html new file mode 100644 index 0000000..4cccc2f --- /dev/null +++ b/includes/navbar.html @@ -0,0 +1,10 @@ + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae2384c --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + WWW - Personal Website + + + + + +
+
+

Welcome

+

This is my personal website and blog.

+
+ +
+

About & Contact

+
+

Email: example@example.com

+

Feel free to reach out via email for any inquiries.

+
+
+ +
+

Genealogy

+
+

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.

+

The Copper Chronicle contains stories and memories from various trips and experiences. More to come as this site develops.

+
+
+
+ + + + diff --git a/nginx.nix b/nginx.nix new file mode 100644 index 0000000..a2f5b7d --- /dev/null +++ b/nginx.nix @@ -0,0 +1,140 @@ +{ 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; + ''; + }; + + # 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"; + }; +} diff --git a/resume.html b/resume.html new file mode 100644 index 0000000..cc726f1 --- /dev/null +++ b/resume.html @@ -0,0 +1,122 @@ + + + + + + Resume - WWW + + + + + +
+
+

Resume

+

Professional Experience & Skills

+
+ +
+

Summary

+

Experienced software engineer with expertise in systems engineering, cloud infrastructure, and full-stack development. Passionate about building reliable, scalable solutions using modern technologies.

+
+ +
+

Technical Skills

+
+
+

Languages

+
    +
  • Rust
  • +
  • Python
  • +
  • Go
  • +
  • JavaScript/TypeScript
  • +
  • HTML/CSS
  • +
+
+
+

Systems & Infrastructure

+
    +
  • Linux/NixOS
  • +
  • Docker
  • +
  • Nginx
  • +
  • Git
  • +
  • CI/CD
  • +
+
+
+

Frameworks & Tools

+
    +
  • Rocket (Rust)
  • +
  • React
  • +
  • Node.js
  • +
  • PostgreSQL
  • +
  • RESTful APIs
  • +
+
+
+
+ +
+

Professional Experience

+ +
+

Software Engineer

+

Example Company • 2020 - Present

+
    +
  • Designed and implemented scalable backend services using Rust and Python
  • +
  • Managed infrastructure using NixOS and containerization technologies
  • +
  • Built and maintained CI/CD pipelines for automated testing and deployment
  • +
  • Collaborated with cross-functional teams to deliver high-quality software solutions
  • +
+
+ +
+

Systems Administrator

+

Previous Company • 2018 - 2020

+
    +
  • Maintained Linux-based server infrastructure and monitoring systems
  • +
  • Automated routine tasks using shell scripting and Python
  • +
  • Implemented security best practices and access control policies
  • +
  • Provided technical support and documentation for development teams
  • +
+
+
+ +
+

Education

+
+

Bachelor of Science in Computer Science

+

University Name • 2014 - 2018

+

Focus on software engineering, algorithms, and systems programming.

+
+
+ +
+

Projects

+
+

Self-Hosted Infrastructure

+

Designed and deployed a complete self-hosted solution including:

+
    +
  • Git hosting with Forgejo
  • +
  • Web server with Nginx and SSL/TLS
  • +
  • Calendar and contacts sync with Radicale
  • +
  • Declarative configuration management with NixOS
  • +
+
+ +
+

Personal Website & Blog

+

Built a simple, reliable website using primitive technologies (HTML/CSS) focused on performance and maintainability.

+
+
+ +
+

Contact

+

Email: example@example.com

+

GitHub: forgejo.binning.net

+
+
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..41b685a --- /dev/null +++ b/style.css @@ -0,0 +1,305 @@ +/* CSS Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + line-height: 1.6; + color: #333; + background-color: #f5f5f5; +} + +/* Navbar Styles */ +.navbar { + background-color: #2c3e50; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: sticky; + top: 0; + z-index: 1000; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + color: #fff; + text-decoration: none; + font-size: 1.5rem; + font-weight: bold; +} + +.nav-brand:hover { + color: #3498db; +} + +.nav-menu { + list-style: none; + display: flex; + gap: 2rem; +} + +.nav-menu a { + color: #fff; + text-decoration: none; + font-size: 1rem; + transition: color 0.3s ease; +} + +.nav-menu a:hover { + color: #3498db; +} + +/* Main Container */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +/* Hero Section */ +.hero { + background-color: #fff; + padding: 3rem 2rem; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + margin-bottom: 2rem; + text-align: center; +} + +.hero h1 { + font-size: 2.5rem; + color: #2c3e50; + margin-bottom: 1rem; +} + +.hero p { + font-size: 1.2rem; + color: #555; +} + +/* Content Section */ +.content-section { + background-color: #fff; + padding: 2rem; + margin-bottom: 2rem; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.content-section h2 { + color: #2c3e50; + font-size: 2rem; + margin-bottom: 1.5rem; + border-bottom: 3px solid #3498db; + padding-bottom: 0.5rem; +} + +.contact-info p, +.genealogy-blurb p { + margin-bottom: 1rem; + line-height: 1.8; + color: #555; +} + +.contact-info strong { + color: #2c3e50; +} + +/* Footer */ +.footer { + background-color: #2c3e50; + color: #fff; + text-align: center; + padding: 2rem; + margin-top: 3rem; +} + +.footer p { + font-size: 0.9rem; +} + +/* Blog Styles */ +.section-description { + color: #666; + 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: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 1.5rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + position: relative; +} + +.blog-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +.blog-card h3 { + color: #2c3e50; + margin-bottom: 0.5rem; + font-size: 1.3rem; +} + +.blog-card h3 a { + color: #2c3e50; + text-decoration: none; + transition: color 0.3s ease; +} + +.blog-card h3 a:hover { + color: #3498db; +} + +.blog-meta { + color: #999; + font-size: 0.9rem; + margin-bottom: 0.75rem; +} + +.blog-card p { + color: #555; + line-height: 1.6; +} + +.blog-card.private { + border-color: #f39c12; + background-color: #fff9f0; +} + +.private-badge { + display: inline-block; + background-color: #f39c12; + color: #fff; + 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: #2c3e50; + margin-bottom: 1rem; + font-size: 1.2rem; + border-bottom: 2px solid #3498db; + padding-bottom: 0.5rem; +} + +.skill-category ul { + list-style: none; + padding-left: 0; +} + +.skill-category li { + padding: 0.5rem 0; + color: #555; + position: relative; + padding-left: 1.5rem; +} + +.skill-category li:before { + content: "▸"; + position: absolute; + left: 0; + color: #3498db; + font-weight: bold; +} + +.experience-item { + margin-bottom: 2rem; +} + +.experience-item:last-child { + margin-bottom: 0; +} + +.experience-item h3 { + color: #2c3e50; + font-size: 1.4rem; + margin-bottom: 0.5rem; +} + +.job-meta { + color: #999; + font-size: 0.95rem; + margin-bottom: 1rem; + font-style: italic; +} + +.experience-item ul { + margin-top: 1rem; + padding-left: 1.5rem; +} + +.experience-item li { + color: #555; + margin-bottom: 0.5rem; + line-height: 1.7; +} + +.experience-item p { + color: #555; + line-height: 1.7; +} + +/* 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; + } +} diff --git a/www.code-workspace b/www.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/www.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file From 00a73b421b78f95cb017d33e75b2906a782dd19d Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 15:38:41 -0800 Subject: [PATCH 02/42] init: Restart blog Bootstrapped the site from scratch following a data loss event. Set up a simple HTML/CSS page served from nginx. Added a navbar with the index page anchored in the top-left, a blog page link, and a link to the Forgejo instance. Added contact info and a genealogy blurb to the index page. Added an example resume page linked from the navbar. --- .gitignore | 1 + 404.html | 27 ++++ README.md | 35 +++++ blog.html | 89 +++++++++++++ deploy.sh | 15 +++ includes/footer.html | 3 + includes/navbar.html | 10 ++ index.html | 37 ++++++ nginx.nix | 140 ++++++++++++++++++++ resume.html | 122 +++++++++++++++++ style.css | 305 +++++++++++++++++++++++++++++++++++++++++++ www.code-workspace | 7 + 12 files changed, 791 insertions(+) create mode 100644 .gitignore create mode 100644 404.html create mode 100644 README.md create mode 100644 blog.html create mode 100755 deploy.sh create mode 100644 includes/footer.html create mode 100644 includes/navbar.html create mode 100644 index.html create mode 100644 nginx.nix create mode 100644 resume.html create mode 100644 style.css create mode 100644 www.code-workspace diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2858192 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +blog/* diff --git a/404.html b/404.html new file mode 100644 index 0000000..33c62f8 --- /dev/null +++ b/404.html @@ -0,0 +1,27 @@ + + + + + + 404 - Page Not Found + + + + + +
+
+

404 - Page Not Found

+

The page you're looking for doesn't exist.

+
+ +
+

What happened?

+

The page you requested could not be found. It may have been moved, deleted, or never existed.

+

← Go back to homepage

+
+
+ + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..2cb2fc5 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# WWW + +Www is my personal website, blog project, and portfolio page. +It should be available at https://wwww.binning.dev + +# History + +## 2025-02-28 + +I restarted this in [Rocket](https://rocket.rs) after yet another data loss event. + +## 2024-06-21 + +I migrated from cgit to forgejo and from kuberentes to simply nginx. + +## 2023-10-07 + +I merged the deployment repository into this one. +I also moved to self-hosting at some point. + +## 2022-01-05 + +I moved hosting to Vultr and moved to a Cobalt. +One neat command to unhide files was: + +`for i in \.*; do if [ ${i} != '.' ] && [ ${i} != '..' ]; then mv {.,}${i/\./} ; fi; done;` + +## 2018-12-16 + +I started this blog with [Namecheap][Namecheap], [Ruby][Ruby], [Jekyll][Jekyll], and [Github-Pages][Github-Pages]. + +[Namecheap]: https://www.namecheap.com/ +[Ruby]: https://www.ruby-lang.org/ +[Jekyll]: https://jekyllrb.com/ +[Github-Pages]: https://pages.github.com/ \ No newline at end of file diff --git a/blog.html b/blog.html new file mode 100644 index 0000000..7c4e8fd --- /dev/null +++ b/blog.html @@ -0,0 +1,89 @@ + + + + + + Blog - WWW + + + + + +
+
+

Blog

+

Stories, adventures, and reflections from life's journey

+
+ + +
+

The Copper Chronicle

+

Adventures and travels with Copper, exploring the beautiful outdoors.

+
+
+

Big Sur Adventure

+

April 2024

+

Exploring the stunning coastline and trails of Big Sur with Copper.

+
+
+

Dove Hunting

+

September 2024

+

A day in the field with friends, shotguns, and good company.

+
+
+

Shasta & Dunsmuir

+

September 2024

+

Mountain adventures in Northern California.

+
+
+
+ + +
+

Life & Reflections

+

Personal thoughts and journeys on health, faith, and life.

+
+
+

Health Journey

+

Private Article

+

Reflections on physical and mental wellness.

+ 🔒 Private +
+
+

Faith Journey

+

Private Article

+

Personal spiritual reflections and growth.

+ 🔒 Private +
+
+
+ + +
+

Events & Experiences

+

Special moments and memorable experiences.

+
+
+

DefCon

+

Private Article

+

Notes and reflections from DefCon.

+ 🔒 Private +
+
+

Oktoberfest

+

Private Article

+

Celebrating traditions and community.

+ 🔒 Private +
+ +
+
+
+ + + + diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..daf400e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +sudo cp -rt /srv/www/binning.net \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html + +sudo chown -R nginx:nginx /srv/www/binning.net/ + +sudo cp -t /etc/nixos/ \ + nginx.nix \ No newline at end of file diff --git a/includes/footer.html b/includes/footer.html new file mode 100644 index 0000000..d78ea74 --- /dev/null +++ b/includes/footer.html @@ -0,0 +1,3 @@ +
+

© 2025 WWW - Built with simple HTML and CSS

+
diff --git a/includes/navbar.html b/includes/navbar.html new file mode 100644 index 0000000..4cccc2f --- /dev/null +++ b/includes/navbar.html @@ -0,0 +1,10 @@ + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae2384c --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + WWW - Personal Website + + + + + +
+
+

Welcome

+

This is my personal website and blog.

+
+ +
+

About & Contact

+
+

Email: example@example.com

+

Feel free to reach out via email for any inquiries.

+
+
+ +
+

Genealogy

+
+

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.

+

The Copper Chronicle contains stories and memories from various trips and experiences. More to come as this site develops.

+
+
+
+ + + + diff --git a/nginx.nix b/nginx.nix new file mode 100644 index 0000000..a2f5b7d --- /dev/null +++ b/nginx.nix @@ -0,0 +1,140 @@ +{ 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; + ''; + }; + + # 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"; + }; +} diff --git a/resume.html b/resume.html new file mode 100644 index 0000000..cc726f1 --- /dev/null +++ b/resume.html @@ -0,0 +1,122 @@ + + + + + + Resume - WWW + + + + + +
+
+

Resume

+

Professional Experience & Skills

+
+ +
+

Summary

+

Experienced software engineer with expertise in systems engineering, cloud infrastructure, and full-stack development. Passionate about building reliable, scalable solutions using modern technologies.

+
+ +
+

Technical Skills

+
+
+

Languages

+
    +
  • Rust
  • +
  • Python
  • +
  • Go
  • +
  • JavaScript/TypeScript
  • +
  • HTML/CSS
  • +
+
+
+

Systems & Infrastructure

+
    +
  • Linux/NixOS
  • +
  • Docker
  • +
  • Nginx
  • +
  • Git
  • +
  • CI/CD
  • +
+
+
+

Frameworks & Tools

+
    +
  • Rocket (Rust)
  • +
  • React
  • +
  • Node.js
  • +
  • PostgreSQL
  • +
  • RESTful APIs
  • +
+
+
+
+ +
+

Professional Experience

+ +
+

Software Engineer

+

Example Company • 2020 - Present

+
    +
  • Designed and implemented scalable backend services using Rust and Python
  • +
  • Managed infrastructure using NixOS and containerization technologies
  • +
  • Built and maintained CI/CD pipelines for automated testing and deployment
  • +
  • Collaborated with cross-functional teams to deliver high-quality software solutions
  • +
+
+ +
+

Systems Administrator

+

Previous Company • 2018 - 2020

+
    +
  • Maintained Linux-based server infrastructure and monitoring systems
  • +
  • Automated routine tasks using shell scripting and Python
  • +
  • Implemented security best practices and access control policies
  • +
  • Provided technical support and documentation for development teams
  • +
+
+
+ +
+

Education

+
+

Bachelor of Science in Computer Science

+

University Name • 2014 - 2018

+

Focus on software engineering, algorithms, and systems programming.

+
+
+ +
+

Projects

+
+

Self-Hosted Infrastructure

+

Designed and deployed a complete self-hosted solution including:

+
    +
  • Git hosting with Forgejo
  • +
  • Web server with Nginx and SSL/TLS
  • +
  • Calendar and contacts sync with Radicale
  • +
  • Declarative configuration management with NixOS
  • +
+
+ +
+

Personal Website & Blog

+

Built a simple, reliable website using primitive technologies (HTML/CSS) focused on performance and maintainability.

+
+
+ +
+

Contact

+

Email: example@example.com

+

GitHub: forgejo.binning.net

+
+
+ + + + diff --git a/style.css b/style.css new file mode 100644 index 0000000..41b685a --- /dev/null +++ b/style.css @@ -0,0 +1,305 @@ +/* CSS Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + line-height: 1.6; + color: #333; + background-color: #f5f5f5; +} + +/* Navbar Styles */ +.navbar { + background-color: #2c3e50; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + position: sticky; + top: 0; + z-index: 1000; +} + +.nav-container { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.nav-brand { + color: #fff; + text-decoration: none; + font-size: 1.5rem; + font-weight: bold; +} + +.nav-brand:hover { + color: #3498db; +} + +.nav-menu { + list-style: none; + display: flex; + gap: 2rem; +} + +.nav-menu a { + color: #fff; + text-decoration: none; + font-size: 1rem; + transition: color 0.3s ease; +} + +.nav-menu a:hover { + color: #3498db; +} + +/* Main Container */ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +/* Hero Section */ +.hero { + background-color: #fff; + padding: 3rem 2rem; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + margin-bottom: 2rem; + text-align: center; +} + +.hero h1 { + font-size: 2.5rem; + color: #2c3e50; + margin-bottom: 1rem; +} + +.hero p { + font-size: 1.2rem; + color: #555; +} + +/* Content Section */ +.content-section { + background-color: #fff; + padding: 2rem; + margin-bottom: 2rem; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.content-section h2 { + color: #2c3e50; + font-size: 2rem; + margin-bottom: 1.5rem; + border-bottom: 3px solid #3498db; + padding-bottom: 0.5rem; +} + +.contact-info p, +.genealogy-blurb p { + margin-bottom: 1rem; + line-height: 1.8; + color: #555; +} + +.contact-info strong { + color: #2c3e50; +} + +/* Footer */ +.footer { + background-color: #2c3e50; + color: #fff; + text-align: center; + padding: 2rem; + margin-top: 3rem; +} + +.footer p { + font-size: 0.9rem; +} + +/* Blog Styles */ +.section-description { + color: #666; + 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: #f9f9f9; + border: 1px solid #e0e0e0; + border-radius: 8px; + padding: 1.5rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + position: relative; +} + +.blog-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +.blog-card h3 { + color: #2c3e50; + margin-bottom: 0.5rem; + font-size: 1.3rem; +} + +.blog-card h3 a { + color: #2c3e50; + text-decoration: none; + transition: color 0.3s ease; +} + +.blog-card h3 a:hover { + color: #3498db; +} + +.blog-meta { + color: #999; + font-size: 0.9rem; + margin-bottom: 0.75rem; +} + +.blog-card p { + color: #555; + line-height: 1.6; +} + +.blog-card.private { + border-color: #f39c12; + background-color: #fff9f0; +} + +.private-badge { + display: inline-block; + background-color: #f39c12; + color: #fff; + 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: #2c3e50; + margin-bottom: 1rem; + font-size: 1.2rem; + border-bottom: 2px solid #3498db; + padding-bottom: 0.5rem; +} + +.skill-category ul { + list-style: none; + padding-left: 0; +} + +.skill-category li { + padding: 0.5rem 0; + color: #555; + position: relative; + padding-left: 1.5rem; +} + +.skill-category li:before { + content: "▸"; + position: absolute; + left: 0; + color: #3498db; + font-weight: bold; +} + +.experience-item { + margin-bottom: 2rem; +} + +.experience-item:last-child { + margin-bottom: 0; +} + +.experience-item h3 { + color: #2c3e50; + font-size: 1.4rem; + margin-bottom: 0.5rem; +} + +.job-meta { + color: #999; + font-size: 0.95rem; + margin-bottom: 1rem; + font-style: italic; +} + +.experience-item ul { + margin-top: 1rem; + padding-left: 1.5rem; +} + +.experience-item li { + color: #555; + margin-bottom: 0.5rem; + line-height: 1.7; +} + +.experience-item p { + color: #555; + line-height: 1.7; +} + +/* 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; + } +} diff --git a/www.code-workspace b/www.code-workspace new file mode 100644 index 0000000..362d7c2 --- /dev/null +++ b/www.code-workspace @@ -0,0 +1,7 @@ +{ + "folders": [ + { + "path": "." + } + ] +} \ No newline at end of file From e9bdac4ed7229c89909894a0bedb92008b37972e Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 16:44:54 -0800 Subject: [PATCH 03/42] feat: Add deployment to a staging environment Added staging.nginx.nix modeled on prod.nginx.nix, configuring nginx as a local staging server. Updated deploy.sh to target either staging or prod over SSH. Consolidated shared configuration between the two nginx nix files and between the staging and prod deploy paths in deploy.sh. --- deploy.sh | 99 ++++++++++++++++++++++++++++++++----- nginx.nix => prod.nginx.nix | 11 +++++ staging.nginx.nix | 56 +++++++++++++++++++++ 3 files changed, 155 insertions(+), 11 deletions(-) rename nginx.nix => prod.nginx.nix (92%) create mode 100644 staging.nginx.nix diff --git a/deploy.sh b/deploy.sh index daf400e..a6b2f21 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,15 +1,92 @@ #!/usr/bin/env sh -sudo cp -rt /srv/www/binning.net \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html +set -e -sudo chown -R nginx:nginx /srv/www/binning.net/ +# 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" + exit 1 +} -sudo cp -t /etc/nixos/ \ - nginx.nix \ No newline at end of file +# Check if argument provided +if [ $# -eq 0 ]; then + printf "Error: No environment specified\n" + usage +fi + +ENV=$1 + +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 \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html \ + ${STAGING_PATH}/ + + # Set proper ownership + 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}" + ;; + + prod) + printf "Deploying to PRODUCTION environment...\n" + + # SSH details + REMOTE_HOST="binning.net" + REMOTE_USER="matthew.binning" + 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 + printf "Deploying website files...\n" + rsync -avz --delete \ + -e ssh \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html \ + ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/ + + # Deploy nginx configuration + printf "Deploying nginx configuration...\n" + scp prod.nginx.nix ${REMOTE_USER}@${REMOTE_HOST}:/tmp/nginx.nix + + # Set proper permissions and move config on remote server + ssh ${REMOTE_USER}@${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 "Error: Invalid environment '%s'\n" "$ENV" + usage + ;; +esac diff --git a/nginx.nix b/prod.nginx.nix similarity index 92% rename from nginx.nix rename to prod.nginx.nix index a2f5b7d..d3f84ff 100644 --- a/nginx.nix +++ b/prod.nginx.nix @@ -59,6 +59,17 @@ in ''; }; + # 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; diff --git a/staging.nginx.nix b/staging.nginx.nix new file mode 100644 index 0000000..174a7f4 --- /dev/null +++ b/staging.nginx.nix @@ -0,0 +1,56 @@ +{ 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 ]; +} From 5b25211618cddf6a36b9142b9597a9bba7f79939 Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 16:44:54 -0800 Subject: [PATCH 04/42] feat: Add deployment to a staging environment Added staging.nginx.nix modeled on prod.nginx.nix, configuring nginx as a local staging server. Updated deploy.sh to target either staging or prod over SSH. Consolidated shared configuration between the two nginx nix files and between the staging and prod deploy paths in deploy.sh. --- deploy.sh | 99 ++++++++++++++++++++++++++++++++----- nginx.nix => prod.nginx.nix | 11 +++++ staging.nginx.nix | 56 +++++++++++++++++++++ 3 files changed, 155 insertions(+), 11 deletions(-) rename nginx.nix => prod.nginx.nix (92%) create mode 100644 staging.nginx.nix diff --git a/deploy.sh b/deploy.sh index daf400e..a6b2f21 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,15 +1,92 @@ #!/usr/bin/env sh -sudo cp -rt /srv/www/binning.net \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html +set -e -sudo chown -R nginx:nginx /srv/www/binning.net/ +# 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" + exit 1 +} -sudo cp -t /etc/nixos/ \ - nginx.nix \ No newline at end of file +# Check if argument provided +if [ $# -eq 0 ]; then + printf "Error: No environment specified\n" + usage +fi + +ENV=$1 + +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 \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html \ + ${STAGING_PATH}/ + + # Set proper ownership + 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}" + ;; + + prod) + printf "Deploying to PRODUCTION environment...\n" + + # SSH details + REMOTE_HOST="binning.net" + REMOTE_USER="matthew.binning" + 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 + printf "Deploying website files...\n" + rsync -avz --delete \ + -e ssh \ + blog \ + includes \ + index.html \ + blog.html \ + resume.html \ + style.css \ + 404.html \ + ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/ + + # Deploy nginx configuration + printf "Deploying nginx configuration...\n" + scp prod.nginx.nix ${REMOTE_USER}@${REMOTE_HOST}:/tmp/nginx.nix + + # Set proper permissions and move config on remote server + ssh ${REMOTE_USER}@${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 "Error: Invalid environment '%s'\n" "$ENV" + usage + ;; +esac diff --git a/nginx.nix b/prod.nginx.nix similarity index 92% rename from nginx.nix rename to prod.nginx.nix index a2f5b7d..d3f84ff 100644 --- a/nginx.nix +++ b/prod.nginx.nix @@ -59,6 +59,17 @@ in ''; }; + # 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; diff --git a/staging.nginx.nix b/staging.nginx.nix new file mode 100644 index 0000000..174a7f4 --- /dev/null +++ b/staging.nginx.nix @@ -0,0 +1,56 @@ +{ 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 ]; +} From 982de5ab56107f05b2d9663c63c7224b977ad6a7 Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 17:48:00 -0800 Subject: [PATCH 05/42] feat: Add a recipe book Created a recipe section in the blog from existing markdown content. --- blog.html | 38 ++++++++++++++ common.nginx.nix | 45 ++++++++++++++++ deploy.sh | 38 ++++++-------- includes/navbar.html | 2 +- style.css | 120 ++++++++++++++++++++++++++----------------- 5 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 common.nginx.nix diff --git a/blog.html b/blog.html index 7c4e8fd..2c99c60 100644 --- a/blog.html +++ b/blog.html @@ -82,6 +82,44 @@ + + +
+

Recipes

+

Family recipes and culinary experiments.

+
+
+

St. Patrick's Day

+

Holiday

+

Traditional Irish recipes - corned beef, lamb stew, soda bread, and more.

+
+
+

Christmas

+

Holiday

+

Holiday favorites including eggnog, gingerbread fruitcake, and meatloaf.

+
+
+

Earth Day

+

Special Event

+

Lamb stir fry and berry compote from April 2024.

+
+ +
+

Miscellaneous

+

Various

+

Drinks, snacks, sides, and external recipe links.

+
+ +
+
diff --git a/common.nginx.nix b/common.nginx.nix new file mode 100644 index 0000000..02a5c24 --- /dev/null +++ b/common.nginx.nix @@ -0,0 +1,45 @@ +{ 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; + ''; + }; +} diff --git a/deploy.sh b/deploy.sh index a6b2f21..94fd863 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,6 +2,9 @@ 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" @@ -27,15 +30,7 @@ case $ENV in # Deploy website files via rsync printf "Deploying website files...\n" - sudo rsync -av --delete \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html \ - ${STAGING_PATH}/ + sudo rsync -av --delete ${DEPLOY_FILES} ${STAGING_PATH}/ # Set proper ownership sudo chown -R nginx:nginx ${STAGING_PATH}/ @@ -50,8 +45,8 @@ case $ENV in printf "Deploying to PRODUCTION environment...\n" # SSH details - REMOTE_HOST="binning.net" - REMOTE_USER="matthew.binning" + REMOTE_HOST="crossbox" + REMOTE_USER="m3b" REMOTE_PATH="/srv/www/binning.net" REMOTE_NIXOS="/etc/nixos/" @@ -61,24 +56,21 @@ case $ENV in fi # Deploy website files via rsync over SSH + # Using just the host from SSH config without user@ prefix printf "Deploying website files...\n" - rsync -avz --delete \ - -e ssh \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html \ - ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/ + 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} && \ + sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \ + printf 'Content deployed.\n'" # Deploy nginx configuration printf "Deploying nginx configuration...\n" - scp prod.nginx.nix ${REMOTE_USER}@${REMOTE_HOST}:/tmp/nginx.nix + scp prod.nginx.nix ${REMOTE_HOST}:/tmp/nginx.nix # Set proper permissions and move config on remote server - ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \ + 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'" diff --git a/includes/navbar.html b/includes/navbar.html index 4cccc2f..7286aa7 100644 --- a/includes/navbar.html +++ b/includes/navbar.html @@ -3,8 +3,8 @@ Home diff --git a/style.css b/style.css index 41b685a..1189e54 100644 --- a/style.css +++ b/style.css @@ -6,19 +6,20 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - line-height: 1.6; - color: #333; - background-color: #f5f5f5; + font-family: 'Courier New', 'Courier', 'Monaco', 'Lucida Console', monospace; + line-height: 1.7; + color: #3e2723; + background-color: #f4ecd8; } /* Navbar Styles */ .navbar { - background-color: #2c3e50; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + 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 { @@ -31,14 +32,14 @@ body { } .nav-brand { - color: #fff; + color: #f4ecd8; text-decoration: none; font-size: 1.5rem; font-weight: bold; } .nav-brand:hover { - color: #3498db; + color: #d7ccc8; } .nav-menu { @@ -48,14 +49,14 @@ body { } .nav-menu a { - color: #fff; + color: #f4ecd8; text-decoration: none; font-size: 1rem; transition: color 0.3s ease; } .nav-menu a:hover { - color: #3498db; + color: #d7ccc8; } /* Main Container */ @@ -67,39 +68,41 @@ body { /* Hero Section */ .hero { - background-color: #fff; + background-color: #efebe9; padding: 3rem 2rem; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + 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: #2c3e50; + color: #4e342e; margin-bottom: 1rem; } .hero p { font-size: 1.2rem; - color: #555; + color: #6d4c41; } /* Content Section */ .content-section { - background-color: #fff; + background-color: #efebe9; padding: 2rem; margin-bottom: 2rem; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + border-radius: 4px; + box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2); + border: 1px solid #d7ccc8; } .content-section h2 { - color: #2c3e50; + color: #4e342e; font-size: 2rem; margin-bottom: 1.5rem; - border-bottom: 3px solid #3498db; + border-bottom: 3px solid #8d6e63; padding-bottom: 0.5rem; } @@ -107,20 +110,21 @@ body { .genealogy-blurb p { margin-bottom: 1rem; line-height: 1.8; - color: #555; + color: #5d4037; } .contact-info strong { - color: #2c3e50; + color: #4e342e; } /* Footer */ .footer { - background-color: #2c3e50; - color: #fff; + background-color: #5d4037; + color: #f4ecd8; text-align: center; padding: 2rem; margin-top: 3rem; + border-top: 2px solid #4e342e; } .footer p { @@ -129,7 +133,7 @@ body { /* Blog Styles */ .section-description { - color: #666; + color: #6d4c41; font-style: italic; margin-bottom: 1.5rem; } @@ -142,55 +146,55 @@ body { } .blog-card { - background-color: #f9f9f9; - border: 1px solid #e0e0e0; - border-radius: 8px; + 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(-4px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(62, 39, 35, 0.25); } .blog-card h3 { - color: #2c3e50; + color: #4e342e; margin-bottom: 0.5rem; font-size: 1.3rem; } .blog-card h3 a { - color: #2c3e50; + color: #4e342e; text-decoration: none; transition: color 0.3s ease; } .blog-card h3 a:hover { - color: #3498db; + color: #6d4c41; } .blog-meta { - color: #999; + color: #8d6e63; font-size: 0.9rem; margin-bottom: 0.75rem; } .blog-card p { - color: #555; + color: #5d4037; line-height: 1.6; } .blog-card.private { - border-color: #f39c12; - background-color: #fff9f0; + border-color: #a1887f; + background-color: #f5f5f0; } .private-badge { display: inline-block; - background-color: #f39c12; - color: #fff; + background-color: #8d6e63; + color: #f4ecd8; padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; @@ -206,10 +210,10 @@ body { } .skill-category h3 { - color: #2c3e50; + color: #4e342e; margin-bottom: 1rem; font-size: 1.2rem; - border-bottom: 2px solid #3498db; + border-bottom: 2px solid #8d6e63; padding-bottom: 0.5rem; } @@ -220,7 +224,7 @@ body { .skill-category li { padding: 0.5rem 0; - color: #555; + color: #5d4037; position: relative; padding-left: 1.5rem; } @@ -229,7 +233,7 @@ body { content: "▸"; position: absolute; left: 0; - color: #3498db; + color: #8d6e63; font-weight: bold; } @@ -242,13 +246,13 @@ body { } .experience-item h3 { - color: #2c3e50; + color: #4e342e; font-size: 1.4rem; margin-bottom: 0.5rem; } .job-meta { - color: #999; + color: #8d6e63; font-size: 0.95rem; margin-bottom: 1rem; font-style: italic; @@ -260,16 +264,38 @@ body { } .experience-item li { - color: #555; + color: #5d4037; margin-bottom: 0.5rem; line-height: 1.7; } .experience-item p { - color: #555; + 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 { From d2421d41f36c9993b779720d9f54c0ebd2e51552 Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Thu, 1 Jan 2026 17:48:00 -0800 Subject: [PATCH 06/42] feat: Add a recipe book Created a recipe section in the blog from existing markdown content. --- blog.html | 38 ++++++++++++++ common.nginx.nix | 45 ++++++++++++++++ deploy.sh | 38 ++++++-------- includes/navbar.html | 2 +- style.css | 120 ++++++++++++++++++++++++++----------------- 5 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 common.nginx.nix diff --git a/blog.html b/blog.html index 7c4e8fd..2c99c60 100644 --- a/blog.html +++ b/blog.html @@ -82,6 +82,44 @@ + + +
+

Recipes

+

Family recipes and culinary experiments.

+
+
+

St. Patrick's Day

+

Holiday

+

Traditional Irish recipes - corned beef, lamb stew, soda bread, and more.

+
+
+

Christmas

+

Holiday

+

Holiday favorites including eggnog, gingerbread fruitcake, and meatloaf.

+
+
+

Earth Day

+

Special Event

+

Lamb stir fry and berry compote from April 2024.

+
+ +
+

Miscellaneous

+

Various

+

Drinks, snacks, sides, and external recipe links.

+
+ +
+
diff --git a/common.nginx.nix b/common.nginx.nix new file mode 100644 index 0000000..02a5c24 --- /dev/null +++ b/common.nginx.nix @@ -0,0 +1,45 @@ +{ 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; + ''; + }; +} diff --git a/deploy.sh b/deploy.sh index a6b2f21..94fd863 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,6 +2,9 @@ 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" @@ -27,15 +30,7 @@ case $ENV in # Deploy website files via rsync printf "Deploying website files...\n" - sudo rsync -av --delete \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html \ - ${STAGING_PATH}/ + sudo rsync -av --delete ${DEPLOY_FILES} ${STAGING_PATH}/ # Set proper ownership sudo chown -R nginx:nginx ${STAGING_PATH}/ @@ -50,8 +45,8 @@ case $ENV in printf "Deploying to PRODUCTION environment...\n" # SSH details - REMOTE_HOST="binning.net" - REMOTE_USER="matthew.binning" + REMOTE_HOST="crossbox" + REMOTE_USER="m3b" REMOTE_PATH="/srv/www/binning.net" REMOTE_NIXOS="/etc/nixos/" @@ -61,24 +56,21 @@ case $ENV in fi # Deploy website files via rsync over SSH + # Using just the host from SSH config without user@ prefix printf "Deploying website files...\n" - rsync -avz --delete \ - -e ssh \ - blog \ - includes \ - index.html \ - blog.html \ - resume.html \ - style.css \ - 404.html \ - ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/ + 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} && \ + sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \ + printf 'Content deployed.\n'" # Deploy nginx configuration printf "Deploying nginx configuration...\n" - scp prod.nginx.nix ${REMOTE_USER}@${REMOTE_HOST}:/tmp/nginx.nix + scp prod.nginx.nix ${REMOTE_HOST}:/tmp/nginx.nix # Set proper permissions and move config on remote server - ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo mv /tmp/nginx.nix ${REMOTE_NIXOS}nginx.nix && \ + 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'" diff --git a/includes/navbar.html b/includes/navbar.html index 4cccc2f..7286aa7 100644 --- a/includes/navbar.html +++ b/includes/navbar.html @@ -3,8 +3,8 @@ Home diff --git a/style.css b/style.css index 41b685a..1189e54 100644 --- a/style.css +++ b/style.css @@ -6,19 +6,20 @@ } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - line-height: 1.6; - color: #333; - background-color: #f5f5f5; + font-family: 'Courier New', 'Courier', 'Monaco', 'Lucida Console', monospace; + line-height: 1.7; + color: #3e2723; + background-color: #f4ecd8; } /* Navbar Styles */ .navbar { - background-color: #2c3e50; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + 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 { @@ -31,14 +32,14 @@ body { } .nav-brand { - color: #fff; + color: #f4ecd8; text-decoration: none; font-size: 1.5rem; font-weight: bold; } .nav-brand:hover { - color: #3498db; + color: #d7ccc8; } .nav-menu { @@ -48,14 +49,14 @@ body { } .nav-menu a { - color: #fff; + color: #f4ecd8; text-decoration: none; font-size: 1rem; transition: color 0.3s ease; } .nav-menu a:hover { - color: #3498db; + color: #d7ccc8; } /* Main Container */ @@ -67,39 +68,41 @@ body { /* Hero Section */ .hero { - background-color: #fff; + background-color: #efebe9; padding: 3rem 2rem; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + 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: #2c3e50; + color: #4e342e; margin-bottom: 1rem; } .hero p { font-size: 1.2rem; - color: #555; + color: #6d4c41; } /* Content Section */ .content-section { - background-color: #fff; + background-color: #efebe9; padding: 2rem; margin-bottom: 2rem; - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + border-radius: 4px; + box-shadow: 0 2px 8px rgba(62, 39, 35, 0.2); + border: 1px solid #d7ccc8; } .content-section h2 { - color: #2c3e50; + color: #4e342e; font-size: 2rem; margin-bottom: 1.5rem; - border-bottom: 3px solid #3498db; + border-bottom: 3px solid #8d6e63; padding-bottom: 0.5rem; } @@ -107,20 +110,21 @@ body { .genealogy-blurb p { margin-bottom: 1rem; line-height: 1.8; - color: #555; + color: #5d4037; } .contact-info strong { - color: #2c3e50; + color: #4e342e; } /* Footer */ .footer { - background-color: #2c3e50; - color: #fff; + background-color: #5d4037; + color: #f4ecd8; text-align: center; padding: 2rem; margin-top: 3rem; + border-top: 2px solid #4e342e; } .footer p { @@ -129,7 +133,7 @@ body { /* Blog Styles */ .section-description { - color: #666; + color: #6d4c41; font-style: italic; margin-bottom: 1.5rem; } @@ -142,55 +146,55 @@ body { } .blog-card { - background-color: #f9f9f9; - border: 1px solid #e0e0e0; - border-radius: 8px; + 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(-4px); - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(62, 39, 35, 0.25); } .blog-card h3 { - color: #2c3e50; + color: #4e342e; margin-bottom: 0.5rem; font-size: 1.3rem; } .blog-card h3 a { - color: #2c3e50; + color: #4e342e; text-decoration: none; transition: color 0.3s ease; } .blog-card h3 a:hover { - color: #3498db; + color: #6d4c41; } .blog-meta { - color: #999; + color: #8d6e63; font-size: 0.9rem; margin-bottom: 0.75rem; } .blog-card p { - color: #555; + color: #5d4037; line-height: 1.6; } .blog-card.private { - border-color: #f39c12; - background-color: #fff9f0; + border-color: #a1887f; + background-color: #f5f5f0; } .private-badge { display: inline-block; - background-color: #f39c12; - color: #fff; + background-color: #8d6e63; + color: #f4ecd8; padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.85rem; @@ -206,10 +210,10 @@ body { } .skill-category h3 { - color: #2c3e50; + color: #4e342e; margin-bottom: 1rem; font-size: 1.2rem; - border-bottom: 2px solid #3498db; + border-bottom: 2px solid #8d6e63; padding-bottom: 0.5rem; } @@ -220,7 +224,7 @@ body { .skill-category li { padding: 0.5rem 0; - color: #555; + color: #5d4037; position: relative; padding-left: 1.5rem; } @@ -229,7 +233,7 @@ body { content: "▸"; position: absolute; left: 0; - color: #3498db; + color: #8d6e63; font-weight: bold; } @@ -242,13 +246,13 @@ body { } .experience-item h3 { - color: #2c3e50; + color: #4e342e; font-size: 1.4rem; margin-bottom: 0.5rem; } .job-meta { - color: #999; + color: #8d6e63; font-size: 0.95rem; margin-bottom: 1rem; font-style: italic; @@ -260,16 +264,38 @@ body { } .experience-item li { - color: #555; + color: #5d4037; margin-bottom: 0.5rem; line-height: 1.7; } .experience-item p { - color: #555; + 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 { From 723c02be838f7e449010ceba2aa8c21162c06e47 Mon Sep 17 00:00:00 2001 From: Matthew Binning Date: Fri, 2 Jan 2026 07:16:46 -0800 Subject: [PATCH 07/42] feat: Convert the blog to mdbook Replaced the custom HTML/CSS blog with mdbook. Gained a sidebar for navigating sections, subsections, and pages. Applied a sepia-tone, monospace theme. Blog sections now populate from SUMMARY.md rather than manually maintained HTML includes. --- .gitignore | 1 + book.toml | 30 ++++++++ deploy.sh | 8 +- includes/navbar.html | 2 +- theme/sepia.css | 179 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 book.toml create mode 100644 theme/sepia.css diff --git a/.gitignore b/.gitignore index 2858192..9d11bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +content/* blog/* diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..709a0e7 --- /dev/null +++ b/book.toml @@ -0,0 +1,30 @@ +[book] +title = "Blog" +description = "Stories, adventures, and reflections from life's journey" +authors = ["Matthew Binning"] +language = "en" +multilingual = false +src = "content" + +[build] +build-dir = "blog" +create-missing = false + +[output.html] +no-section-label = true +git-repository-url = "https://forgejo.binning.net/matthew.binning/www" +site-url = "/blog/" +default-theme = "light" +preferred-dark-theme = "light" +curly-quotes = true +mathjax-support = false +copy-fonts = false +additional-css = ["theme/sepia.css"] + +[output.html.fold] +enable = true +level = 0 + +[output.html.playground] +editable = false +copyable = false diff --git a/deploy.sh b/deploy.sh index 94fd863..5890ec8 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,8 +2,12 @@ set -e +# Build the blog with mdbook before deploying +printf "Building blog with mdbook...\n" +mdbook build + # Common files to deploy (shared between staging and prod) -DEPLOY_FILES="blog includes index.html blog.html resume.html style.css 404.html" +DEPLOY_FILES="blog includes index.html resume.html style.css 404.html" # Usage information usage() { @@ -61,7 +65,7 @@ case $ENV in 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_HOST} "sudo rsync -avz --delete /tmp/${REMOTE_PATH} ${REMOTE_PATH} && \ sudo chown -R nginx:nginx ${REMOTE_PATH}/ && \ printf 'Content deployed.\n'" diff --git a/includes/navbar.html b/includes/navbar.html index 7286aa7..7885fb8 100644 --- a/includes/navbar.html +++ b/includes/navbar.html @@ -2,7 +2,7 @@