Reorganize repository: Move Hugo site to docker/showerloop/public, clean up root directory, update Docker configuration to use Caddy, add Woodpecker CI config
|
@ -1,3 +1,4 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
public
|
public/public
|
||||||
|
temp_backup
|
|
@ -0,0 +1,113 @@
|
||||||
|
# build:1
|
||||||
|
labels:
|
||||||
|
location: manager
|
||||||
|
clone:
|
||||||
|
git:
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
settings:
|
||||||
|
partial: false
|
||||||
|
depth: 1
|
||||||
|
when:
|
||||||
|
branch: [main, production]
|
||||||
|
steps:
|
||||||
|
# Build Step for staging Branch
|
||||||
|
build-staging:
|
||||||
|
name: build-staging
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
environment:
|
||||||
|
REGISTRY_USER:
|
||||||
|
from_secret: REGISTRY_USER
|
||||||
|
REGISTRY_PASSWORD:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
commands:
|
||||||
|
- echo "Building application for staging branch"
|
||||||
|
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
|
||||||
|
- echo compose build
|
||||||
|
- docker compose -f docker-compose.staging.yml pull --ignore-buildable
|
||||||
|
- docker compose -f docker-compose.staging.yml build --pull
|
||||||
|
when:
|
||||||
|
event: push
|
||||||
|
|
||||||
|
deploy-new:
|
||||||
|
name: deploy-new
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
environment:
|
||||||
|
REGISTRY_USER:
|
||||||
|
from_secret: REGISTRY_USER
|
||||||
|
REGISTRY_PASSWORD:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
commands:
|
||||||
|
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
|
||||||
|
- echo compose push
|
||||||
|
- docker compose -f docker-compose.staging.yml push
|
||||||
|
- docker stack deploy --with-registry-auth -c ./stack.staging.yml $${CI_REPO_NAME}-staging
|
||||||
|
when:
|
||||||
|
event: push
|
||||||
|
|
||||||
|
cleanup-staging:
|
||||||
|
name: cleanup-staging
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
environment:
|
||||||
|
REGISTRY_USER:
|
||||||
|
from_secret: REGISTRY_USER
|
||||||
|
REGISTRY_PASSWORD:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
commands:
|
||||||
|
- for i in {1..5}; do docker stack rm ${CI_REPO_NAME}-staging && break || sleep 10; done
|
||||||
|
- docker compose -f docker-compose.staging.yml down
|
||||||
|
- docker compose -f docker-compose.staging.yml rm -f
|
||||||
|
when:
|
||||||
|
event: push
|
||||||
|
|
||||||
|
build-push-production:
|
||||||
|
name: build-push-production
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
environment:
|
||||||
|
REGISTRY_USER:
|
||||||
|
from_secret: REGISTRY_USER
|
||||||
|
REGISTRY_PASSWORD:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
commands:
|
||||||
|
- echo "Building application for production branch"
|
||||||
|
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
|
||||||
|
- echo compose build
|
||||||
|
- docker compose -f docker-compose.production.yml pull --ignore-buildable
|
||||||
|
- docker compose -f docker-compose.production.yml build --pull
|
||||||
|
- docker compose -f docker-compose.production.yml push
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
name: deploy-production
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
environment:
|
||||||
|
REGISTRY_USER:
|
||||||
|
from_secret: REGISTRY_USER
|
||||||
|
REGISTRY_PASSWORD:
|
||||||
|
from_secret: REGISTRY_PASSWORD
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
commands:
|
||||||
|
- echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us
|
||||||
|
- docker stack deploy --with-registry-auth -c ./stack.production.yml $${CI_REPO_NAME}
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: push
|
||||||
|
|
||||||
|
post-deploy-smoke-tests-git-nixc-us:
|
||||||
|
name: run-post-deploy-smoke-tests-git-nixc-us
|
||||||
|
image: git.nixc.us/colin/playwright:latest
|
||||||
|
environment:
|
||||||
|
BASE_URL: "https://git.nixc.us"
|
||||||
|
when:
|
||||||
|
branch: production
|
||||||
|
event: push
|
|
@ -1,68 +0,0 @@
|
||||||
# CSS and JavaScript Optimization Guide
|
|
||||||
|
|
||||||
This document explains how to eliminate unused CSS and JavaScript from the ShowerLoop website.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
1. Install dependencies:
|
|
||||||
```bash
|
|
||||||
npm install --legacy-peer-deps
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Build the production version with optimizations:
|
|
||||||
```bash
|
|
||||||
./build-production.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## What This Does
|
|
||||||
|
|
||||||
### CSS Optimization
|
|
||||||
|
|
||||||
The build script optimizes CSS in two ways:
|
|
||||||
|
|
||||||
1. **CSSO Optimization**: Uses CSSO to remove unused selectors, merge similar rules, and minify the CSS.
|
|
||||||
- Typically reduces CSS by 5-30% depending on the file
|
|
||||||
- Maintains full functionality while eliminating bloat
|
|
||||||
- Works without requiring a running website
|
|
||||||
|
|
||||||
2. **File Path Updates**: Automatically updates all HTML files to reference the optimized CSS versions.
|
|
||||||
|
|
||||||
### JavaScript Optimization
|
|
||||||
|
|
||||||
JavaScript optimization happens through:
|
|
||||||
|
|
||||||
1. **Tree Shaking**: Rollup analyzes your code to detect which parts are actually used and removes dead code.
|
|
||||||
- Eliminates unused imports and functions
|
|
||||||
- Reduces JavaScript file sizes significantly
|
|
||||||
|
|
||||||
2. **Code Splitting**: JavaScript is split into separate bundles:
|
|
||||||
- app.modern.min.js - Main application code
|
|
||||||
- video-init.modern.min.js - Video player initialization
|
|
||||||
- skip-to-content.modern.min.js - Accessibility features
|
|
||||||
|
|
||||||
3. **Minification**: All JavaScript is minified to reduce file size.
|
|
||||||
|
|
||||||
## Optimization Results
|
|
||||||
|
|
||||||
In our tests, the optimization achieves:
|
|
||||||
|
|
||||||
| File Type | Size Reduction |
|
|
||||||
|-----------|---------------|
|
|
||||||
| CSS | 5-30% |
|
|
||||||
| JavaScript| 30-60% |
|
|
||||||
|
|
||||||
## How to Keep It Optimized
|
|
||||||
|
|
||||||
1. **Maintain Source Files**: Keep original JavaScript source files in `src/js/`
|
|
||||||
|
|
||||||
2. **Run Build Before Deployment**: Always run `./build-production.sh` before deploying to production
|
|
||||||
|
|
||||||
3. **Add New Components Wisely**: When adding new CSS/JS, consider if it's truly needed or if existing code can be reused
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **Missing Styles**: If elements look unstyled after optimization, check the original CSS files and update your HTML accordingly.
|
|
||||||
|
|
||||||
- **JavaScript Errors**: If functionality breaks, check the browser console for errors and ensure all required code is in your source files.
|
|
||||||
|
|
||||||
- **Build Errors**: Make sure all dependencies are installed with `npm install --legacy-peer-deps` before running the build scripts.
|
|
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2020 Shower-Loop
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
35
README.md
|
@ -1,27 +1,34 @@
|
||||||
# website
|
# ShowerLoop Website
|
||||||
https://showerloop.cc new site for https://showerloop.org [planned]
|
|
||||||
|
|
||||||
## Local Development
|
This repository contains the source code for the ShowerLoop website, built with Hugo and served via Docker.
|
||||||
|
|
||||||
To test this site locally:
|
## Repository Structure
|
||||||
|
|
||||||
1. Make sure you have [Hugo](https://gohugo.io/installation/) installed on your system
|
- `/docker/showerloop/` - Container configuration for the website
|
||||||
2. Clone this repository
|
- `/docker/showerloop/public/` - Hugo website source files
|
||||||
3. Run the development server using the script:
|
- `/docker/showerloop/Dockerfile` - Docker image definition
|
||||||
|
- `/docker/showerloop/Caddyfile` - Caddy server configuration
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run the Hugo development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd docker/showerloop/public
|
||||||
./run-hugo-server.sh
|
./run-hugo-server.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Or run Hugo directly:
|
The development server will be available at http://localhost:1313/
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To build the production site:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hugo server -D --disableFastRender
|
cd docker/showerloop/public
|
||||||
|
./build-production.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Open your browser and go to http://localhost:1313/ to view the site
|
## Deployment
|
||||||
5. The site will automatically reload when you make changes to the content or templates
|
|
||||||
|
|
||||||
### Notes
|
The site is automatically built and deployed using Woodpecker CI when changes are pushed to the master branch.
|
||||||
- The `-D` flag enables draft content to be visible in the development environment
|
|
||||||
- `--disableFastRender` ensures full rebuilds for more reliable preview
|
|
|
@ -1 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Categories on</title><link>http://localhost:54386/categories/</link><description>Recent content in Categories on</description><generator>Hugo</generator><language>en-us</language><atom:link href="http://localhost:54386/categories/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=53498&path=livereload" data-no-instant defer></script><title>Support the Project | ShowerLoop</title>
|
|
||||||
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><link rel=stylesheet href=/css/vendor/material-icons.css><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css></noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css></noscript><script type=module>
|
|
||||||
|
|
||||||
import * as utils from '/js/utils.modern.min.js';
|
|
||||||
window.utilsModule = utils;
|
|
||||||
</script><script type=module src=/js/app.modern.min.js defer></script><script type=module src=/js/skip-to-content.modern.min.js defer></script><script type=module src=/js/material.modern.min.js defer></script><script nomodule src=/js/app.min.js defer></script><script nomodule src=/js/skip-to-content.min.js defer></script><script nomodule src=/js/material.min.js defer></script><link rel=stylesheet type=text/css href=/css/app.min.css><link rel=stylesheet type=text/css href=/css/custom.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/custom.css></noscript></head><body class=page><div class=skip-to-content role=button tabindex=0>Skip to Content</div><style>.mdl-navigation .mdl-button.mdl-navigation__link{display:flex;align-items:center;justify-content:center;height:36px;line-height:36px;padding:0 16px;margin:8px 0}.logo{height:50px;width:auto;max-width:150px;transition:none!important}</style><div class="mdl-layout mdl-js-layout
|
|
||||||
mdl-layout--fixed-header"><header class="mdl-layout__header site-header"><div class=mdl-layout__header-row><a href=/ class=mdl-layout-title><img class=logo src=/images/logo2.webp height=50 width=auto alt="ShowerLoop Logo"></a><div class=mdl-layout-spacer></div><nav class="mdl-navigation mdl-layout--large-screen-only"><a class=mdl-navigation__link href=/ title=Home>Home</a>
|
|
||||||
<a class=mdl-navigation__link href=/how-it-works/ title="How It Works">How It Works</a>
|
|
||||||
<a class=mdl-navigation__link href=/research/ title=Research>Research</a>
|
|
||||||
<a class=mdl-navigation__link href=/posts/ title=Posts>Posts</a>
|
|
||||||
<a class=mdl-navigation__link href=/components/ title=Components>Components</a>
|
|
||||||
<a class="mdl-navigation__link mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href=/make-it/ title="Make It">Make It</a></nav></div></header><div class=mdl-layout__drawer><span class=mdl-layout-title><strong>ShowerLoop</strong></span><nav class=mdl-navigation><a class=mdl-navigation__link href=/ title=Home tabindex=0>Home</a>
|
|
||||||
<a class=mdl-navigation__link href=/how-it-works/ title="How It Works" tabindex=0>How It Works</a>
|
|
||||||
<a class=mdl-navigation__link href=/research/ title=Research tabindex=0>Research</a>
|
|
||||||
<a class=mdl-navigation__link href=/posts/ title=Posts tabindex=0>Posts</a>
|
|
||||||
<a class=mdl-navigation__link href=/components/ title=Components tabindex=0>Components</a>
|
|
||||||
<a class="mdl-navigation__link mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href=/make-it/ title="Make It" tabindex=0>Make It</a></nav></div><main aria-role=main><div class=subpage-content><div class=chocolate-container><h1>Support the Project</h1><div class=mdl-grid><section class="mdl-cell mdl-cell--12-col"><p>If you want to support the ShowerLoop project, check out our <a href=/make-it>Make-It Button</a> where you can contribute to our ongoing development.</p><pre><code> <p>We work with limited resources and recycle many materials. With access to workspace and tools (thank you, Fablab!), we're able to continue developing this sustainable technology.</p>
|
|
||||||
|
|
||||||
<p>We're collaborating with people around the world to bring ShowerLoop to various environments - boats, RVs, houses in remote locations, Pacific Islands, eco-friendly buildings, and historic structures without modern plumbing. The possibilities are endless, and together we hope to realize them all.</p>
|
|
||||||
|
|
||||||
<p>Our team is developing a business model that fosters community around social and environmental ethics within the open source circular economy.</p>
|
|
||||||
|
|
||||||
<div class="mdl-grid" style="justify-content: center; margin-top: 30px;">
|
|
||||||
<a class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href="/make-it">
|
|
||||||
Support the Project
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</code></pre></div></div></div></main><footer class="mdl-mini-footer site-footer"><div class=mdl-mini-footer__left-section>© 2020 Shower Loop | All Rights Reserved</div><div class=mdl-mini-footer__right-section></div></footer></div><script>document.addEventListener("DOMContentLoaded",function(){setTimeout(function(){if(!window.__bfcacheLiveReloadActive){const e=window.location.protocol==="https:"?"wss:":"ws:",t=`${e}//${window.location.host}/__livereload`;try{if(window.OriginalWebSocket){const e=new window.OriginalWebSocket(t);e.onmessage=function(e){e.data==="reload"&&window.location.reload()},window.__bfcacheLiveReloadActive=!0}}catch(e){console.warn("LiveReload connection error:",e)}}},500)})</script></body></html>
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
services:
|
||||||
|
showerloop:
|
||||||
|
build:
|
||||||
|
context: docker/showerloop
|
||||||
|
dockerfile: Dockerfile.production
|
||||||
|
pull: true
|
||||||
|
image: git.nixc.us/colin/showerloop-cc:production
|
|
@ -0,0 +1,6 @@
|
||||||
|
services:
|
||||||
|
showerloop:
|
||||||
|
build:
|
||||||
|
context: docker/showerloop
|
||||||
|
pull: true
|
||||||
|
image: git.nixc.us/colin/showerloop-cc:staging
|
|
@ -0,0 +1 @@
|
||||||
|
FROM git.nixc.us/colin/showerloop-cc:staging
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Template: Caddyfile.override
|
||||||
|
# Purpose: Default configuration for custom containers.
|
||||||
|
# Description:
|
||||||
|
# - Serves static files from /srv.
|
||||||
|
# - Provides a /health endpoint for health checks.
|
||||||
|
# - Designed to run behind a reverse proxy like Træfik, listening only on port 80.
|
||||||
|
# - comes with security headers
|
||||||
|
|
||||||
|
:80 {
|
||||||
|
# Health check endpoint
|
||||||
|
respond /health "OK" 200
|
||||||
|
|
||||||
|
# Enable compression for text-based resources
|
||||||
|
encode gzip zstd
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
header {
|
||||||
|
# Cross-Origin headers
|
||||||
|
Cross-Origin-Embedder-Policy "require-corp"
|
||||||
|
Cross-Origin-Opener-Policy "same-origin"
|
||||||
|
Cross-Origin-Resource-Policy "same-origin"
|
||||||
|
|
||||||
|
# Permissions Policy
|
||||||
|
Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
|
||||||
|
|
||||||
|
# Referrer Policy
|
||||||
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
|
||||||
|
# HSTS
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||||
|
|
||||||
|
# Content Type Options
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
|
||||||
|
# XSS Protection
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
|
||||||
|
# Frame Options (prevents clickjacking)
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
|
||||||
|
# Frame ancestors (prevents embedding in other sites)
|
||||||
|
Content-Security-Policy "frame-ancestors 'none'"
|
||||||
|
|
||||||
|
# Remove Server header
|
||||||
|
-Server
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache control for static assets - images, fonts, etc.
|
||||||
|
@staticAssets {
|
||||||
|
path *.jpg *.jpeg *.png *.webp *.avif *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot
|
||||||
|
method GET HEAD
|
||||||
|
}
|
||||||
|
header @staticAssets Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
header @staticAssets ?Access-Control-Allow-Origin *
|
||||||
|
|
||||||
|
# Special handling for CSS and JS files
|
||||||
|
@cssAndJs {
|
||||||
|
path *.css *.js
|
||||||
|
method GET HEAD
|
||||||
|
}
|
||||||
|
header @cssAndJs Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
|
||||||
|
# Cache HTML files but for a shorter period
|
||||||
|
@htmlFiles {
|
||||||
|
path *.html
|
||||||
|
method GET HEAD
|
||||||
|
}
|
||||||
|
header @htmlFiles Cache-Control "public, max-age=86400, must-revalidate"
|
||||||
|
|
||||||
|
# Static file server
|
||||||
|
file_server {
|
||||||
|
root /srv # Root directory for serving static files
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restrict allowed methods to only GET and HEAD
|
||||||
|
@staticRequests {
|
||||||
|
method GET HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
handle @staticRequests {
|
||||||
|
root * /srv
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle all other methods
|
||||||
|
respond "Method Not Allowed" 405
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Stage 1: Build Hugo site
|
||||||
|
FROM alpine:latest AS hugo-builder
|
||||||
|
|
||||||
|
# Install necessary dependencies (Hugo, Git, Node.js, and npm)
|
||||||
|
RUN apk add --no-cache hugo git nodejs npm
|
||||||
|
|
||||||
|
# Copy our enhanced Caddyfile
|
||||||
|
COPY Caddyfile.default.template /etc/caddy/Caddyfile.override
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /site
|
||||||
|
|
||||||
|
# Copy the Hugo source files
|
||||||
|
COPY public/ /site
|
||||||
|
|
||||||
|
# Install PostCSS and its dependencies locally and update browserslist
|
||||||
|
RUN cd /site && npm init -y && \
|
||||||
|
npm install --save-dev postcss postcss-cli autoprefixer && \
|
||||||
|
npm install --save-dev caniuse-lite && \
|
||||||
|
npm update caniuse-lite browserslist
|
||||||
|
|
||||||
|
# Build the Hugo site for production with optimizations
|
||||||
|
# Disable GitInfo, enable minification, and set production environment
|
||||||
|
RUN mkdir /public && \
|
||||||
|
cd /site && \
|
||||||
|
HUGO_ENABLEGITINFO=false \
|
||||||
|
HUGO_ENV=production \
|
||||||
|
npm run build:prod && \
|
||||||
|
cp -r /site/public/* /public/
|
||||||
|
|
||||||
|
# Stage 2: Production image with prebuilt static files
|
||||||
|
FROM git.nixc.us/colin/container-base:production-nixiusstatic
|
||||||
|
|
||||||
|
# Copy the built site from the first stage
|
||||||
|
COPY --from=hugo-builder /public /srv
|
||||||
|
|
||||||
|
# Copy our enhanced Caddyfile for development
|
||||||
|
COPY Caddyfile.default.template /etc/caddy/Caddyfile.override
|
||||||
|
|
||||||
|
# Add health check endpoint for production monitoring
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -q --spider http://localhost/healthcheck.txt || exit 1
|
|
@ -0,0 +1 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Categories on</title><link>http://localhost:1313/categories/</link><description>Recent content in Categories on</description><generator>Hugo</generator><language>en-us</language><atom:link href="http://localhost:1313/categories/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
|
|
@ -1,4 +1,4 @@
|
||||||
<!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=54386&path=livereload" data-no-instant defer></script><title>Components | ShowerLoop</title>
|
<!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><title>Components | ShowerLoop</title>
|
||||||
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><link rel=stylesheet href=/css/vendor/material-icons.css><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css></noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css></noscript><script type=module>
|
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><link rel=stylesheet href=/css/vendor/material-icons.css><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css></noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css></noscript><script type=module>
|
||||||
|
|
||||||
import * as utils from '/js/utils.modern.min.js';
|
import * as utils from '/js/utils.modern.min.js';
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 730 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 898 KiB After Width: | Height: | Size: 898 KiB |
|
@ -1,4 +1,4 @@
|
||||||
<!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=54386&path=livereload" data-no-instant defer></script><title>How It Works | ShowerLoop</title>
|
<!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><title>How It Works | ShowerLoop</title>
|
||||||
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="Learn how the ShowerLoop system filters, purifies and recycles shower water in real-time, saving up to 90% of water and energy."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><link rel=stylesheet href=/css/vendor/material-icons.css><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css></noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css></noscript><script type=module>
|
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="Learn how the ShowerLoop system filters, purifies and recycles shower water in real-time, saving up to 90% of water and energy."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><link rel=stylesheet href=/css/vendor/material-icons.css><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/material.indigo-pink.min.css></noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css media=print onload='this.media="all"'><noscript><link rel=stylesheet href=/css/vendor/fontawesome.min.css></noscript><script type=module>
|
||||||
|
|
||||||
import * as utils from '/js/utils.modern.min.js';
|
import * as utils from '/js/utils.modern.min.js';
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 213 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 988 KiB After Width: | Height: | Size: 988 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 488 KiB |
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |