# 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:`. 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 `:` in CI). - **Local build/push**: `pnpm build` → `pnpm docker:build` → `pnpm 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 `:` 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 it’s 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.