mifi 4a79266a27
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
Tweaks and updates
2026-02-13 18:21:18 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 14:50:12 -03:00
2026-02-13 15:56:36 -03:00
2026-02-13 14:27:14 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 15:17:59 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 18:21:18 -03:00
2026-02-13 18:12:52 -03:00
2026-02-13 18:12:52 -03:00

mifi.holdings — Landing Page

Static landing site for mifi.holdings (and www). Plain HTML/CSS/JS source; a build step produces minified assets and inlines critical CSS. Served by nginx in Docker behind Traefik, with CI/CD via Woodpecker and deployment via Portainer.


Quick reference

What Where
Site mifi.holdings, www.mifi.holdings (HTTPS)
Runtime nginx (Alpine) in Docker
Reverse proxy / TLS Traefik (Let's Encrypt)
CI/CD Woodpecker (ci → build → deploy)
Registry git.mifi.dev/mifi-holdings/landing
Package manager pnpm
Build output dist/ (gitignored)

Architecture

                    ┌─────────────────┐
                    │   Traefik       │  (routing, TLS, websecure)
                    └────────┬────────┘
                             │
                    ┌────────▼────────┐
                    │  Docker container │  mifi-holdings-landing
                    │  nginx:alpine    │  port 80
                    │  /usr/share/    │
                    │  nginx/html ←   │  built output from dist/
                    └─────────────────┘
  • Build: pnpm build copies src/dist/, minifies JS/CSS, inlines critical CSS (Critters). Docker image = nginx:alpine + nginx/conf.d/ + dist/ into /usr/share/nginx/html.
  • Run: Single service on external network marina-net; Traefik routes mifi.holdings and www.mifi.holdings to this container (HTTPS, security-prison@file middleware).

Repo structure

.
├── src/                    # Source (HTML, CSS, JS)
│   ├── index.html
│   └── assets/
│       ├── css/style.css
│       ├── js/ga-init.js
│       └── images/         # favicon, etc.
├── scripts/
│   └── build.js            # Build: copy, minify, inline critical CSS
├── dist/                   # Build output (gitignored)
├── nginx/
│   └── conf.d/
│       └── default.conf    # nginx server config (cache rules, SPA fallback)
├── .woodpecker/
│   ├── ci.yml              # Lint, format, build check (PR + push to main)
│   ├── build.yml           # Site build → Docker image → push (main)
│   └── deploy.yml          # Trigger Portainer redeploy (main)
├── docker-compose.yml      # Service + Traefik labels for production
├── Dockerfile              # nginx + config + dist (not src)
├── package.json            # pnpm scripts: build, format, lint, docker
└── README.md

Build (pnpm build)

  • Output: dist/ (gitignored). Do not edit dist/; it is generated.
  • Steps (see scripts/build.js):
    1. Clean and copy src/dist/.
    2. Minify all .js (terser) and .css (clean-css).
    3. Inline critical CSS with Critters (lazy-loads the rest; no browser required, so it works in CI).
  • When: Run before docker build. CI and the build pipeline both run pnpm build before packaging.

Tech stack

  • Frontend: Static HTML + CSS + JS in src/; production build minifies and inlines critical CSS.
  • Server: nginx (Alpine) in Docker.
  • Tooling: pnpm; Prettier (format); ESLint (JS), Stylelint (CSS), yamllint (YAML); terser, clean-css, beasties (build).
  • Deployment: Docker image (from dist/) → Gitea registry → Portainer stack redeploy.

Local development

  • Dependencies: pnpm install.
  • Format: pnpm format / pnpm format:check.
  • Lint: pnpm lint (ESLint for src/**/*.js, Stylelint for src/**/*.css, yamllint for Woodpecker + docker-compose). Use pnpm lint:fix to auto-fix where supported.
  • Build: pnpm build → produces dist/. Required before building the Docker image.
  • Run locally (Docker):
    • Build site: pnpm build.
    • Image: pnpm docker:build (or docker build --platform linux/amd64 -t git.mifi.dev/mifi-holdings/landing:latest .).
    • Run: use docker-compose up only on a host that has marina-net and Traefik; otherwise run the image with a port map and open http://localhost:<port>.

No dev server; edit src/ and run pnpm build (and optionally docker run or a local static server on dist/) to test.


Docker

  • Dockerfile: Copies nginx/conf.d/ and dist/ (not src/) into an nginx:alpine image. Run pnpm build first so dist/ exists.
  • Image: Tagged as git.mifi.dev/mifi-holdings/landing:latest (and :<commit-sha> in CI).
  • Local build/push: pnpm buildpnpm docker:buildpnpm docker:push (requires login to git.mifi.dev).

CI/CD (Woodpecker)

Three pipelines:

  1. ci (.woodpecker/ci.yml)

    • When: Every PR and every push to main.
    • Steps: Install deps → Prettier format check → lint (ESLint, Stylelint, yamllint) → Build (pnpm build). Mattermost notifications on success/failure.
  2. build (.woodpecker/build.yml)

    • When: Push/tag/manual on main (and deployment to production); depends_on: ci.
    • Steps: Site build (pnpm install, pnpm build) → Docker image build (linux/amd64, up to 3 retries) → push :latest and :<CI_COMMIT_SHA> to git.mifi.dev/mifi-holdings/landing. Mattermost notifications.
  3. deploy (.woodpecker/deploy.yml)

    • When: Same as build (main / production deploy); depends_on: ci.
    • Steps: Call Portainer webhook to redeploy the stack. Mattermost notifications.

Secrets used: gitea_registry_username, gitea_package_token, portainer_webhook_url, mattermost_* (bot token, channel IDs, API URL).


Deployment (production)

  • Orchestration: Stack defined in docker-compose.yml, deployed via Portainer (webhook triggered by Woodpecker deploy pipeline).
  • Network: Container joins external network marina-net (shared with Traefik).
  • Traefik:
    • Hosts: mifi.holdings, www.mifi.holdings.
    • Entrypoint: websecure (HTTPS).
    • TLS: Let's Encrypt (tls.certresolver=letsencrypt).
    • Middleware: security-prison@file.
    • Backend: this service, port 80.
  • Healthcheck: wget --spider -q http://localhost/ every 20s (timeout 3s, 3 retries).

To deploy manually: pull the latest image and redeploy the stack in Portainer, or trigger the Portainer webhook (e.g. same URL as in portainer_webhook_url).


nginx behavior

  • Root: /usr/share/nginx/html (contents of dist/ after build).
  • HTML: Cache-Control: public, no-cache so updates are visible quickly.
  • JS/CSS: Long cache, immutable for hashed/versioned assets.
  • Images/fonts: Cached (30d / 1y).
  • SPA-style fallback: / tries $uri, $uri/, then index.html, then 404.

Summary

  • What it is: Static landing for mifi.holdings; source in src/, built output in dist/.
  • How it runs: nginx in Docker serving dist/, fronted by Traefik on marina-net.
  • How its updated: Push to main → Woodpecker runs ci (lint, format, build), then build pipeline (site build → Docker image → push), then deploy (Portainer webhook); Mattermost reports status.
Description
The landing page for the (www.)mifi.holdings domain. Because the root of my digital empire needs a page.
https://mifi.holdings
Readme 184 KiB
Languages
JavaScript 40.6%
HTML 39%
CSS 19.3%
Dockerfile 1.1%