Compare commits

...

2 Commits

Author SHA1 Message Date
ducoterra 54746f4636 add cache busting
Build and Push Container / build-and-push (push) Successful in 16s
2026-05-28 16:01:42 -04:00
ducoterra 6a30145171 accessibility updates 2026-05-28 15:44:42 -04:00
6 changed files with 119 additions and 63 deletions
+3
View File
@@ -1,3 +1,6 @@
# Build output
dist/
# Build artifacts
*.tar
*.tar.gz
+6 -8
View File
@@ -1,16 +1,14 @@
FROM docker.io/library/nginx:alpine
RUN rm /etc/nginx/conf.d/default.conf
RUN rm /etc/nginx/conf.d/default.conf && \
apk add --no-cache coreutils
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY src/index.html /usr/share/nginx/html/
COPY src/style.css /usr/share/nginx/html/
COPY src/script.js /usr/share/nginx/html/
COPY src/profile.jpeg /usr/share/nginx/html/
COPY src/ /src/
COPY build.sh /build.sh
RUN chmod +x /build.sh && SRC=/src DIST=/dist /bin/sh /build.sh && cp -r /dist/* /usr/share/nginx/html/ && rm -rf /src /build.sh /dist
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget -qO- http://localhost:8080/ || exit 1
HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -qO- http://localhost:8080/ || exit 1
+1 -1
View File
@@ -13,7 +13,7 @@ podman build -t homepage:latest .
### Run the container
```bash
podman run --rm -p 8080:8080 -v ./src:/usr/share/nginx/html:z homepage:latest
podman run --rm -p 0.0.0.0:8080:8080 -v ./src:/usr/share/nginx/html:z homepage:latest
```
Visit `http://localhost:8080` in your browser.
Executable
+41
View File
@@ -0,0 +1,41 @@
#!/bin/sh
set -e
SRC="${SRC:-src}"
DIST="${DIST:-dist}"
# Clean and create dist
rm -rf "$DIST"
mkdir -p "$DIST"
# Copy index.html
cp "$SRC/index.html" "$DIST/index.html"
# Cache-bust assets by appending a content hash to filenames
for ext in css js jpeg png svg; do
for file in "$SRC"/*."$ext"; do
[ -f "$file" ] || continue
name=$(basename "$file" | sed "s/\.$ext$//")
hash=$(md5sum "$file" | cut -d' ' -f1)
newname="${name}.${hash}.${ext}"
cp "$file" "$DIST/$newname"
# Update references in HTML
case "$ext" in
css)
sed -i "s|href=\"${name}.${ext}\"|href=\"${newname}\"|g" "$DIST/index.html"
;;
js)
sed -i "s|src=\"${name}.${ext}\"|src=\"${newname}\"|g" "$DIST/index.html"
;;
jpeg|png|svg)
sed -i "s|\"${name}.${ext}\"|\"${newname}\"|g" "$DIST/index.html"
;;
esac
done
done
echo "Build complete: $DIST/"
echo "Assets with cache-busting hashes:"
ls -1 "$DIST"
+14
View File
@@ -17,6 +17,20 @@ server {
gzip_min_length 256;
gzip_vary on;
# Never cache index.html so the latest HTML is always served
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
try_files $uri =404;
}
# Cache hashed assets forever (filename contains content hash)
location ~* \.(css|js|jpeg|jpg|png|gif|ico|svg|woff2?)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
+54 -54
View File
@@ -3,9 +3,6 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Reese Wells - Self-Hosting & Infrastructure</title>
<link rel="stylesheet" href="style.css">
</head>
@@ -15,10 +12,11 @@
<div class="server-rack-bg"></div>
<!-- Navigation -->
<nav>
<nav aria-label="Main navigation">
<div class="nav-inner">
<div class="logo">&lt;Reese /&gt;</div>
<ul class="nav-links" id="navLinks">
<ul class="nav-links" id="navLinks" role="list" aria-label="Primary navigation">
<li><a href="#hero" aria-current="page">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#experience">Experience</a></li>
<li><a href="#skills">Skills</a></li>
@@ -32,8 +30,9 @@
</div>
</nav>
<main>
<!-- Hero -->
<section class="hero" id="hero">
<section class="hero" id="hero" aria-label="Introduction">
<div class="hero-content">
<h1>
Hi, I'm <span class="gradient-text">Reese Wells</span><br>
@@ -55,7 +54,7 @@
</section>
<!-- About -->
<section id="about">
<section id="about" aria-label="About">
<div class="about-grid">
<div class="about-image fade-in">
<div class="avatar-frame">
@@ -79,16 +78,16 @@
services across a fleet of servers. Every service runs as a rootless container with
dedicated systemd user sessions, backed by centralized BorgBackup.
</p>
<div class="about-stats">
<div class="stat">
<div class="about-stats" role="list" aria-label="Key statistics">
<div class="stat" role="listitem">
<div class="stat-number">80+</div>
<div class="stat-label">Services</div>
</div>
<div class="stat">
<div class="stat" role="listitem">
<div class="stat-number">2</div>
<div class="stat-label">Domains</div>
</div>
<div class="stat">
<div class="stat" role="listitem">
<div class="stat-number">100%</div>
<div class="stat-label">Self-Hosted</div>
</div>
@@ -98,11 +97,11 @@
</section>
<!-- Experience -->
<section id="experience">
<section id="experience" aria-label="Work Experience">
<p class="section-label fade-in">Experience</p>
<h2 class="section-title fade-in">Where I've worked</h2>
<div class="timeline">
<div class="timeline-item fade-in">
<div class="timeline" role="list" aria-label="Work history">
<div class="timeline-item fade-in" role="listitem">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-header">
@@ -120,7 +119,7 @@
</ul>
</div>
</div>
<div class="timeline-item fade-in">
<div class="timeline-item fade-in" role="listitem">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-header">
@@ -135,7 +134,7 @@
</ul>
</div>
</div>
<div class="timeline-item fade-in">
<div class="timeline-item fade-in" role="listitem">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-header">
@@ -149,7 +148,7 @@
</ul>
</div>
</div>
<div class="timeline-item fade-in">
<div class="timeline-item fade-in" role="listitem">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-header">
@@ -168,15 +167,15 @@
</section>
<!-- Skills -->
<div class="skills-section" id="skills">
<div class="skills-section" id="skills" role="region" aria-label="Skills & Expertise">
<div class="skills-inner">
<p class="section-label fade-in">Skills & Expertise</p>
<h2 class="section-title fade-in">What I work with</h2>
<p class="section-desc fade-in">
A broad toolkit focused on infrastructure, automation, and self-hosted services.
</p>
<div class="skills-grid">
<div class="skill-card fade-in">
<div class="skills-grid" role="list" aria-label="Skills">
<div class="skill-card fade-in" role="listitem">
<div class="skill-icon">&#9881;</div>
<h3>Container Orchestration</h3>
<p>Rootless containers managed via Podman quadlets, Docker Compose, and Kubernetes clusters with Helm charts.</p>
@@ -187,7 +186,7 @@
<span class="skill-tag">k3s</span>
</div>
</div>
<div class="skill-card fade-in">
<div class="skill-card fade-in" role="listitem">
<div class="skill-icon">&#9883;</div>
<h3>Infrastructure Automation</h3>
<p>Ansible playbooks drive deployments across a multi-server fleet with strict SOP ordering and centralized configuration.</p>
@@ -198,7 +197,7 @@
<span class="skill-tag">BorgBackup</span>
</div>
</div>
<div class="skill-card fade-in">
<div class="skill-card fade-in" role="listitem">
<div class="skill-icon">&#9729;</div>
<h3>Networking & DNS</h3>
<p>AWS Route53 powers all DNS management with DDNS auto-updating, Caddy reverse proxy with Route53 DNS-validated TLS, and dual-domain strategy.</p>
@@ -209,7 +208,7 @@
<span class="skill-tag">WireGuard</span>
</div>
</div>
<div class="skill-card fade-in">
<div class="skill-card fade-in" role="listitem">
<div class="skill-icon">&#129302;</div>
<h3>Local AI & ML</h3>
<p>Full local AI stack: Ollama, LiteLLM, LocalAI for inference, Langfuse for observability, with CUDA and ROCm support.</p>
@@ -220,9 +219,9 @@
<span class="skill-tag">ROCm</span>
</div>
</div>
<div class="skill-card fade-in">
<div class="skill-icon">&#10064;</div>
<h3>Python</h3>
<div class="skill-card fade-in" role="listitem">
<div class="skill-icon">&#10064;</div>
<h3>Python</h3>
<p>Python is the backbone of the homelab: DDNS updates, fleet-wide deployment scripts, AWS integration, and automation tooling with boto3, rich, and uv.</p>
<div class="skill-tags">
<span class="skill-tag">Python</span>
@@ -235,15 +234,15 @@
</div>
</div>
<!-- Projects -->
<section id="projects">
<!-- Projects -->
<section id="projects" aria-label="Projects">
<p class="section-label fade-in">Featured Projects</p>
<h2 class="section-title fade-in">What I've built</h2>
<p class="section-desc fade-in">
A selection of projects from my homelab and deployment infrastructure.
</p>
<div class="projects-grid">
<div class="project-card fade-in">
<div class="projects-grid" role="list" aria-label="Projects">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-keyword">version</span>: <span class="code-string">'3.8'</span></div>
@@ -274,7 +273,7 @@
</div>
</div>
</div>
<div class="project-card fade-in">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-keyword">def</span> <span class="code-function">update_record</span>(<span class="code-variable">domain</span>):</div>
@@ -305,7 +304,7 @@
</div>
</div>
</div>
<div class="project-card fade-in">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-keyword">from</span> <span class="code-string">ollama</span> <span class="code-keyword">import</span> <span class="code-function">Client</span></div>
@@ -337,7 +336,7 @@
</div>
</div>
</div>
<div class="project-card fade-in">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Ansible playbook</span></div>
@@ -367,7 +366,7 @@
</div>
</div>
</div>
<div class="project-card fade-in">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Self-hosted services</span></div>
@@ -398,7 +397,7 @@
</div>
</div>
</div>
<div class="project-card fade-in">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Kubernetes with k3s</span></div>
@@ -432,24 +431,24 @@
</section>
<!-- Contact -->
<section class="contact-section" id="contact">
<section class="contact-section" id="contact" aria-label="Contact">
<p class="section-label fade-in">Get In Touch</p>
<h2 class="section-title fade-in">Let's connect</h2>
<p class="section-desc fade-in">
Always open to discussing self-hosting, infrastructure, open source, or just sharing homelab stories.
</p>
<div class="contact-links fade-in">
<a href="mailto:reese.wells@ducoterra.net" class="contact-link">
<div class="contact-links fade-in" role="list" aria-label="Contact links">
<a href="mailto:reese.wells@ducoterra.net" class="contact-link" role="listitem">
<span class="link-icon">&#9993;</span>
<span>Email</span>
</a>
<a href="https://gitea.reeseapps.com/services/homelab" class="contact-link" target="_blank" rel="noopener">
<a href="https://gitea.reeseapps.com/services/homelab" class="contact-link" target="_blank" rel="noopener" role="listitem">
<span class="link-icon">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
</span>
<span>Gitea</span>
</a>
<a href="https://www.linkedin.com/in/reesewells/" class="contact-link" target="_blank" rel="noopener">
<a href="https://www.linkedin.com/in/reesewells/" class="contact-link" target="_blank" rel="noopener" role="listitem">
<span class="link-icon">in</span>
<span>LinkedIn</span>
</a>
@@ -457,18 +456,18 @@
</section>
<!-- GPG Keys -->
<section class="gpg-section" id="gpg">
<section class="gpg-section" id="gpg" aria-label="Public GPG Keys">
<p class="section-label fade-in">Trust</p>
<h2 class="section-title fade-in">Public GPG Keys</h2>
<p class="section-desc fade-in" style="margin-left:auto;margin-right:auto;text-align:center;">
Use these keys to verify signed commits and communications. Both keys belong to Reese Wells.
</p>
<div class="gpg-keys-grid">
<div class="gpg-key-card fade-in">
<div class="gpg-key-header">
<span class="gpg-key-icon">&#128274;</span>
<div>
<h3>Primary Key</h3>
<div class="gpg-keys-grid" role="list" aria-label="GPG keys">
<div class="gpg-key-card fade-in" role="listitem">
<div class="gpg-key-header">
<span class="gpg-key-icon">&#128274;</span>
<div>
<h3>Primary Key</h3>
<p class="gpg-key-email">reese.wells@ducoterra.net</p>
</div>
</div>
@@ -527,11 +526,11 @@ BQ==
=U3eP
-----END PGP PUBLIC KEY BLOCK-----</code></pre>
</div>
<div class="gpg-key-card fade-in">
<div class="gpg-key-header">
<span class="gpg-key-icon">&#128274;</span>
<div>
<h3>Git Signing Key</h3>
<div class="gpg-key-card fade-in" role="listitem">
<div class="gpg-key-header">
<span class="gpg-key-icon">&#128274;</span>
<div>
<h3>Git Signing Key</h3>
<p class="gpg-key-email">git@ducoterra.net</p>
</div>
</div>
@@ -563,7 +562,7 @@ XwEAnes79w4eYeMUjIytQWACEvy4QoO7X2MLTKliSqc4Ag8=
</section>
<!-- Achievements Tracker -->
<section id="achievements" class="achievements-section" style="display: none;">
<section id="achievements" class="achievements-section" hidden aria-label="Achievements">
<div class="section-label">Achievements</div>
<div class="section-title">Terminal Achievements</div>
<p class="section-desc" style="margin: 0 auto 2rem;">Explore every corner of the terminal to unlock achievements.</p>
@@ -572,7 +571,8 @@ XwEAnes79w4eYeMUjIytQWACEvy4QoO7X2MLTKliSqc4Ag8=
</section>
<!-- Footer -->
<footer>
</main>
<footer role="contentinfo">
<p style="font-size: 0.75rem; color: var(--text-muted);">
Built with a 100% self-hosted LLM stack running
<a href="https://github.com/ggml-org/llama.cpp" target="_blank" rel="noopener" style="color: var(--accent-hover); text-decoration: none;">llama.cpp</a>,