Projects / Note 01
Open Lab Frame Project
In this note
Chronological project record for the design, infrastructure, deployment, security hardening, and continued development of
openlabframe.com.
Homelab Overview | Open Lab Frame | Raspberry Pi 5 | VM101 - OpenLabFrame Dev | Network Topology | Uptime Kuma | Beszel
Project Status
State: Active / In Production
Public Site: https://openlabframe.com
Production Host: Raspberry Pi 5
Development Host: VM101 - OpenLabFrame Dev
Source Repository: <private Open Lab Frame source repository>
Web Server: Nginx
Public Ingress: Cloudflare Tunnel
Production Build: Hardened / hashed / minified dist/
Open Lab Frame is the public portfolio and project-documentation site for the homelab.
It serves as both:
Public portfolio
+
Living record of actual homelab work
Core Project Goal
The goal of Open Lab Frame is to present real technical work being completed in the homelab rather than acting as a generic personal homepage.
The site is intended to demonstrate experience with:
- Linux
- Docker
- Nginx
- Cloudflare
- Git
- GitHub
- JavaScript
- CSS
- HTML
- Build tooling
- Infrastructure deployment
- Monitoring
- Security hardening
- Networking
- Self-hosting
- Troubleshooting
- Production change control
The site should continue evolving alongside the real homelab.
Architecture Overview
Current production architecture:
Public Visitor
↓
Internet
↓
Cloudflare
↓
Cloudflare Tunnel
↓
Raspberry Pi 5
<private Pi 5 host>
↓
Docker
↓
Nginx / ehworld
↓
Open Lab Frame
Development remains separate:
VM101
↓
Readable source
↓
npm run build
↓
dist/
↓
Deployment package
↓
Pi 5 staging
↓
Production
Major Design Principle
One of the most important decisions in the project is:
Readable development source
≠
Public production code
Development source remains readable and maintainable.
Production is generated through the build system and can include:
- Minification
- Obfuscation
- Content hashing
- Selector hardening
- JavaScript fragmentation
This avoids making the private source unnecessarily difficult to work on.
Early Infrastructure Phase
Open Lab Frame began as a self-hosted site running from the Raspberry Pi 5.
The Pi 5 became the primary public web infrastructure node.
Current Pi 5 identity:
Hostname: <pi5-hostname>
User: <admin-user>
Network: Private LAN
RAM: 16 GB
Storage: 1 TB NVMe
The site was deployed through Dockerized Nginx.
Nginx Production Container
Production web server container:
ehworld
Current active website directory:
<PRODUCTION_SITE_DIRECTORY>
This directory contains the built production website served to the public.
Cloudflare Tunnel
The site uses Cloudflare Tunnel rather than exposing the Pi 5 directly to the public Internet.
Architecture:
Nginx
↓
cloudflared
↓
Cloudflare
↓
Visitor
This was especially useful because the home connection uses Starlink behind CGNAT.
No traditional inbound public IPv4 port forwarding is required.
Domain
Primary public domain:
openlabframe.com
Cloudflare handles the public DNS and ingress layer.
Initial Website Direction
Early versions of the website focused on presenting:
- Personal/homelab identity
- Current projects
- Setup information
- Public links
- Technical progress
- Monitoring screenshots
- Project history
The visual design evolved around a dark interface with gold-accent styling.
Early UI Work
Early website work included:
- Main title styling
- Setup panels
- Responsive positioning
- About section layout
- Social/media icons
- Project controls
- History/changelog interface
- Visitor counter
- Dashboard images
- Hover behavior
The project gradually moved from a simple personal site toward a structured homelab portfolio.
Visual Identity
The site developed a consistent visual identity around:
Dark background
Gold accents
Glowing edges
Modern panels
Responsive layouts
A modern gold cube was also selected as the favicon/logo direction.
Main Source Files
Readable source has included:
src/
├── index.html
├── projects/
│ └── index.html
├── style.css
├── setup-menu.js
├── visitor-counter.js
├── owner-tools.js
└── history-modal.js
These remain development-oriented files.
Production transformation occurs during the build.
GitHub Repository
Canonical source repository:
<private Open Lab Frame source repository>
GitHub provides:
- Version control
- Change history
- Recovery point
- Development workflow
- Source organization
The private repository remains the authoritative source.
Development Environment Evolution
Early development work used GitHub / Codespaces workflows.
The project later gained a dedicated development VM:
Current VM101:
VM ID: 101
Network: Private LAN
User: <admin-user>
VM101 is normally:
OFF
when development is not taking place.
VM101 Purpose
VM101 exists specifically for:
- Open Lab Frame source editing
- Git operations
- Build testing
- Local previews
- Production build generation
- Deployment preparation
Production does not depend on VM101 remaining online.
SSH Access
Windows alias:
ssh <development-alias>
Repository path:
<SOURCE_REPOSITORY>
Development Toolchain
Current known environment:
nvm: 0.40.7
Node.js: 24.21.0
npm: 11.19.0
Normal dependency installation:
npm ci
Build:
npm run build
Output:
dist/
Private Development Preview
VM101 can serve the development version internally.
Backend:
<private development host>:<SERVICE_PORT>
Friendly private URL:
<private development URL>
Path:
Client
↓
Pi-hole
↓
VM102 / NPM
↓
<private development host>:<SERVICE_PORT>
↓
VM101
When VM101 is off:
<private development hostname>
being unavailable is expected.
Source / Dist Separation
Development source:
src/
Generated output:
dist/
The correct relationship is:
src/
↓
Build system
↓
dist/
Not:
Edit dist/
The generated production output should never become the primary source of truth.
Build System
The project uses:
build.mjs
as the production build orchestrator.
The build pipeline evolved beyond basic HTML/CSS/JS copying and now includes security/hardening transformations.
Production Goals
The production build aims to:
- Reduce readable implementation details
- Minify assets
- Obfuscate JavaScript
- Fragment production JavaScript
- Harden selectors
- Content-hash filenames
- Prevent stale caching
- Avoid source maps
- Preserve functionality
- Preserve accessibility
- Keep source maintainable
JavaScript Hardening - Phase 2
A major production-hardening milestone was JavaScript fragmentation.
The build began producing multiple independently processed JS files.
Production naming follows patterns such as:
j-XXXXXXXX.min.js
The filenames are intentionally meaningless.
JavaScript Fragmentation Goals
The fragmentation phase aimed to avoid exposing:
One large obvious JavaScript bundle
Instead:
Source modules
↓
Build
↓
Multiple hardened JS outputs
The original execution order is preserved in:
dist/index.html
No Source Maps
Production builds intentionally contain no source maps.
This means files such as:
*.map
should not be generated or deployed publicly.
The readable source remains private instead.
Selector Hardening - Phase 3
The project later added deterministic selector hardening.
Module:
selector-hardening.mjs
Technologies include:
postcss-selector-parser
SHA-256 based deterministic mapping
Class Hardening
Readable classes are transformed to names similar to:
x**********
Example concept:
.project-card
may become:
.x1234567890
The exact value is build-generated.
ID Hardening
Readable IDs are transformed to names similar to:
y**********
This reduces the amount of meaningful naming visible in production HTML.
Shared Selector Map
The build reads:
src/index.html
+
src/style.css
to create a single selector map.
This ensures that selectors remain consistent across:
- HTML
- CSS
- JavaScript
JavaScript Selector Rewrites
The production hardener rewrites references including:
getElementById()
getElementsByClassName()
querySelector()
querySelectorAll()
closest()
matches()
Without this, changing HTML/CSS selectors would break JavaScript behavior.
ARIA Preservation
The hardening process also updates ARIA relationships where applicable.
Examples:
aria-controls
aria-labelledby
This ensures selector hardening does not destroy accessibility relationships.
Font Awesome Preservation
Font Awesome classes are preserved:
fa-*
These are excluded from project selector hardening.
CSS Production Output
CSS production files use hashed/minified names such as:
c-XXXXXXXX.min.css
A changed file gets a changed hash.
Why Content Hashing Matters
Hashed assets allow long-term browser/CDN caching.
Example:
c-ABC12345.min.css
If CSS changes:
New CSS
↓
New hash
↓
New filename
The browser therefore cannot accidentally reuse an old cached asset under the same filename.
Production Cache Strategy
HTML remains refreshable:
Cache-Control: no-cache
Hashed static assets can use:
max-age=31536000
immutable
Images use a shorter cache lifetime.
A previous image cache policy used approximately:
4 hours
Production Cache Validation
Public deployment testing has verified behavior such as:
HTML
→ CF-Cache-Status: DYNAMIC
Hashed assets
→ CF-Cache-Status: HIT
→ Age increasing
This confirms the intended separation between dynamic HTML references and immutable hashed assets.
September 5 Hardening Deployment
A significant Phase 3 production deployment occurred on September 5, 2026.
Before deployment:
- Selector hardening passed
- Responsive behavior passed
- Modal/menu behavior passed
- CSS/JS references passed
- Public output was reviewed
September 5 Production ZIP
Deployment archive:
<DEPLOYMENT_ARCHIVE>
Observed size:
877K
Recorded SHA-256:
676b07b29c4fa7d3d2d34433b87dd050755c1e71c7dadc4e2954a56f7e7f5891
ZIP integrity validation:
OK
September 5 Production Backup
A production backup was created before deployment.
Example:
ehlabframe-backup-20260905-072800
Approximate size:
956K
This provided a rollback point.
September 5 New Assets
The production sync included hashed files such as:
c-7350C8F2.min.css
c-E1D2ADA6.min.css
c-E763DD50.min.css
j-31D7E46F.min.js
j-427D344E.min.js
j-8CEBBA48.min.js
j-A3C106A2.min.js
Old obsolete hashed assets were removed through controlled rsync --delete.
Deployment Safety Workflow
The project uses a deliberate production workflow:
Readable source
↓
npm run build
↓
dist/
↓
Local validation
↓
ZIP
↓
Transfer to Pi 5
↓
ZIP integrity check
↓
Extract to staging
↓
Timestamped production backup
↓
rsync dry run
↓
Review
↓
Live rsync
↓
nginx -t
↓
Reload
↓
Public validation
This workflow should continue to be treated as canonical.
Staging Directory
Production staging:
<STAGING_DIRECTORY>
The staging directory gives the new build a place to exist before touching the live site.
Active Directory
Live production:
<PRODUCTION_SITE_DIRECTORY>
Backup Directories
Production backups:
<PRODUCTION_BACKUP_DIRECTORY>
Each major deployment should preserve a known-good previous state.
rsync Dry Run
A dry run should always occur before live replacement.
Conceptually:
rsync -av --delete --dry-run \
<STAGING_DIRECTORY>/ \
<PRODUCTION_SITE_DIRECTORY>/
Review:
- Additions
- Changes
- Deletions
before running the real command.
Live Deployment
Only after reviewing the dry run:
rsync -av --delete \
<STAGING_DIRECTORY>/ \
<PRODUCTION_SITE_DIRECTORY>/
Nginx Validation
Before reload:
docker exec ehworld nginx -t
Expected:
syntax is ok
test is successful
Only then:
docker exec ehworld nginx -s reload
Public Validation
After deployment, verify:
Homepage
Projects page
Navigation
Images
Menus
Modals
History
Responsive behavior
HTTPS
Cloudflare Tunnel
Hashed assets
Browser caching
A hard refresh should also be used when validating changes.
Hover Redesign
The visual hover behavior was redesigned during the hardening phase.
Current direction includes:
Gold icon fill
Gold glow
Gold text
Letter-edge glow
The previous card lift / scale behavior was removed.
Panel animations remain.
CSS Reorganization
The main:
src/style.css
was reorganized to improve maintainability while preserving the visual design.
Readable source remains organized even though production CSS is minified and hashed.
Source Baseline - September 9
The later audited:
src(4).zip
became the canonical readable-source baseline.
It superseded:
src(3).zip
for ongoing work.
Audited Source Inventory
The September 9 baseline included approximately:
src/index.html
→ 545 lines
src/projects/index.html
→ 49 lines at that audit point
src/style.css
→ 1,155 lines
JavaScript included:
setup-menu.js
visitor-counter.js
owner-tools.js
history-modal.js
The project later continued evolving beyond the earlier lightweight Projects page.
Projects Page Evolution
The Projects page originally existed as a lightweight placeholder.
Earlier public content included:
Under Development
This was eventually replaced by a real homelab-project page.
Current Projects Page
The current production Projects page contains sections for real devices and projects.
Current device sections include:
Raspberry Pi 5
Raspberry Pi 3 Model B
HP ProDesk 600 G4
Raspberry Pi 5 Project Section
Current Pi 5 section covers:
- Docker
- Nginx
- Open Lab Frame hosting
- Cloudflare Tunnel
- Grafana
- Uptime Kuma
- Monitoring
- Traffic/dashboard views
It includes expandable screenshot galleries.
Status:
In use
Raspberry Pi 3 Project Section
The Pi 3 section covers:
- Pi-hole
- Tailscale
- DNS
- Private networking
It includes Pi-hole dashboard imagery.
Status:
In use
HP ProDesk Project Section
The HP ProDesk 600 G4 section documents the media-server project.
Main highlighted application:
Jellyfin
Current site status has shown:
Configuring
Current Service Icons
The Projects page includes locally hosted icons for:
Jellyfin
Docker
Nginx
Grafana
Cloudflare
Tailscale
Pi-hole
Local hosting reduces dependency on external icon sources.
Project Status Badges
Current visual status convention:
Green
→ In use
Gold
→ Configuring
This gives visitors quick context about project maturity.
Responsive Project Layout
The Projects page includes responsive layouts for different screen sizes.
The design was validated for:
- Desktop
- Narrow screens
- Mobile layouts
- Expandable content
Accessibility Improvements
Current project-page improvements include:
- Descriptive alt text
- Keyboard focus indicators
- Keyboard-accessible controls
- Responsive device layouts
- Expandable image galleries
- Lightbox behavior
Version 0.2
The current production build identifies:
Version 0.2 - Latest
Title:
Device Projects and Setup Updates
This represents the transition toward the website showing real device-based project content.
Changelog / History
The website includes a history interface.
Supporting source:
history-modal.js
The history feature allows the public site to show its own development milestones.
Setup Menu
Supporting source:
setup-menu.js
This controls interactive setup/project navigation.
Owner Tools
Owner-specific behavior is handled through:
owner-tools.js
An owner-specific protected path has existed under:
a protected owner-only route
Owner-only functionality remains separate from normal public visitor behavior.
Visitor Counter
The website includes a custom visitor counter.
Frontend source:
visitor-counter.js
API:
a visitor-count API
Visitor Cookie
The site uses a long-lived cookie:
a long-lived visitor cookie
This helps avoid repeatedly incrementing the count for the same browser.
D1 Visitor Data
Cloudflare D1 is used for counter persistence.
The visitor counter uses a small Cloudflare D1-backed persistence layer. Exact internal schema details are omitted from the public copy.
A previously validated API example returned a count such as:
{"count":71}
The exact current count naturally changes over time.
YouTube Click Tracking
A separate tracking endpoint was added:
a click-tracking API
It was designed to track:
- Total clicks
- Unique clicks
This keeps site analytics lightweight and purpose-built.
Git Mirror
The project also has a Pi 5 local Git mirror process.
Timer:
open-lab-frame-mirror.timer
Schedule:
Daily around 03:00
Configuration includes:
Persistent
so the job can recover from a missed scheduled run.
Monitoring
Open Lab Frame infrastructure is monitored through:
and:
Uptime Kuma Role
Uptime Kuma answers:
Is the website / infrastructure reachable?
Alerts can flow through:
Discord
+
ntfy
→ iPhone
Beszel Role
Beszel answers:
How much resource is the Pi 5 using?
The Pi 5 normally has substantial headroom.
Nginx Resource Testing
A direct test of approximately:
500 rapid HTTP requests
showed only approximately:
2–3% CPU
on the Pi 5.
Nginx processes were observed using roughly:
26.7 MiB RSS combined
This confirmed that the static site is extremely lightweight relative to the Pi 5's capacity.
Google Search Indexing
The public Projects page eventually replaced the older placeholder content.
However, Google search results can lag behind the live website.
The correct validation order is:
Check public site
↓
Check live HTML
↓
Check Search Console
↓
Request indexing if appropriate
↓
Wait for Google to refresh
Search Console Workflow
Used workflow:
Google Search Console
↓
URL Inspection
↓
Test Live URL
↓
Request Indexing
A stale Google result does not mean the deployment itself is stale.
Production vs Search Index
Important distinction:
Public website
→ current production truth
Google result
→ cached/indexed representation
Always inspect the real public page before troubleshooting indexing.
Security Philosophy
The project should remain secure without making development painful.
Primary rules:
- Keep readable source private.
- Build production output automatically.
- Do not publish source maps.
- Do not embed secrets in frontend files.
- Keep owner/admin paths protected.
- Use Cloudflare for public ingress.
- Keep production deployment deliberate.
- Maintain rollback copies.
- Do not edit live production directly unless recovering from an emergency.
Production Is Public
Everything inside:
dist/
must be treated as publicly downloadable.
Never place inside production:
Passwords
API secrets
SSH private keys
Cloudflare API tokens
Private webhooks
Recovery codes
Internal credentials
Private environment files
Source Protection Philosophy
Frontend hardening does not make browser code impossible to inspect.
The practical goals are:
Increase casual-copy difficulty
Reduce readable implementation details
Keep production optimized
Preserve private source quality
It is not treated as a replacement for server-side security.
Build Integrity
Production build validation should include searches for unwanted source artifacts.
Examples:
Readable original selector names
Source maps
Old unhashed assets
Unexpected JavaScript function names
Broken ARIA references
The production output should behave correctly while exposing less implementation detail.
Production Deployment Principle
The canonical workflow remains:
Readable private source
↓
Build
↓
Validate
↓
Package
↓
Transfer
↓
Integrity check
↓
Stage
↓
Backup production
↓
Dry run
↓
Deploy
↓
Nginx test
↓
Reload
↓
Public validation
Do not skip steps simply because a change appears small.
Rollback Philosophy
Every meaningful production deployment should have:
Known-good backup
+
Known new build
+
Ability to revert
If a deployment fails:
Stop changing things
↓
Restore last known-good production directory
↓
nginx -t
↓
Reload
↓
Validate
Current Production Baseline
The latest reviewed production direction includes:
- Full device Projects page
- Raspberry Pi 5 project section
- Raspberry Pi 3 project section
- HP ProDesk Jellyfin project section
- Local service icons
- Responsive project layouts
- Keyboard focus improvements
- Descriptive alt text
- Expandable galleries
- Lightbox behavior
- Version 0.2 changelog
- Hashed/minified production assets
- Production selector hardening
- JavaScript fragmentation
This supersedes the earlier simple placeholder Projects page.
Project Roadmap Philosophy
Future website sections should reflect actual homelab progress.
Potential future subjects include:
- NAS Compute Machine
- Managed Omada networking
- Additional Docker services
- Storage architecture
- Monitoring improvements
- Backup systems
- Networking projects
- New infrastructure milestones
The public site should not claim a project is complete before the actual homelab work supports it.
Current High-Level Project Timeline
Initial Pi 5 website
↓
Nginx + Docker
↓
Cloudflare Tunnel
↓
Custom UI / project panels
↓
Visitor counter
↓
Monitoring / owner features
↓
Private GitHub source workflow
↓
Production minification
↓
Hashed assets
↓
JavaScript fragmentation
↓
Selector hardening
↓
Dedicated VM101 dev environment
↓
Full Projects page
↓
Current Version 0.2 production
Project Success Criteria
Open Lab Frame is working correctly when:
Public domain loads
Cloudflare Tunnel works
Pi 5 production host is healthy
Nginx serves correct dist
HTTPS works
Projects page reflects actual homelab state
Hashed assets load
Responsive layout works
JavaScript interactions work
Accessibility remains functional
Monitoring is healthy
Source repository is current
Production can be reproduced from source
Rollback path exists
Related Notes
Homelab Overview
Open Lab Frame
Raspberry Pi 5
VM101 - OpenLabFrame Dev
Network Topology
Uptime Kuma
Beszel
Nginx Proxy Manager
Open Lab Frame Deployment
NAS Compute Machine