# mail-landing Static landing site for **mail.mifi.holdings** (HTML/CSS/JS). Source lives in `src/`, is built to `dist/`, and is served in production by **nginx** inside a Docker container, with **Traefik** as the reverse proxy. --- ## Tech stack & frameworks | Layer | Technology | | --------------------- | -------------------------------------------------------------------------------------- | | **Runtime** | Node 22 (dev/build only); production is static files only | | **Package manager** | pnpm 10.x | | **Source** | Plain HTML, CSS, JS — no framework (Vite/React/etc.) | | **Build** | Custom script (`scripts/build.js`): copy, minify, inline critical CSS | | **Minification** | **Terser** (JS), **clean-css** (CSS) | | **Critical CSS** | **Beasties** — inlines above-the-fold CSS into HTML (no browser/headless; works in CI) | | **Lint / format** | ESLint, Stylelint, Prettier, yamllint (for Woodpecker & compose) | | **Production server** | nginx (Alpine) in Docker | | **Reverse proxy** | Traefik (via `docker-compose` labels; TLS, gzip, security middlewares) | | **CI/CD** | Woodpecker CI (Gitea); images pushed to `git.mifi.dev` registry | --- ## Project structure ``` mail-landing/ ├── src/ # Source (edited by hand) │ ├── index.html │ ├── help/index.html │ └── assets/ │ ├── css/site.css │ ├── js/ga-init.js │ └── images/favicon.svg ├── dist/ # Build output (gitignored in practice; produced by pnpm build) ├── scripts/ │ └── build.js # Build: copy → minify JS/CSS → Beasties inline critical CSS ├── nginx/conf.d/ # nginx config for the container ├── docker-compose.yml # Service definition + Traefik labels for mail.mifi.holdings ├── Dockerfile # nginx:alpine + config + dist/ ├── .woodpecker/ │ ├── ci.yml # Lint, format check, build (PR + push to main) │ ├── build.yml # Build site → Docker image → push to registry │ └── deploy.yml # Trigger Portainer webhook + Mattermost notifications ├── .devcontainer/ # Dev Container (Node 22 + pnpm) for Cursor/VS Code └── package.json ``` --- ## Architecture (high level) 1. **Develop** in `src/` (HTML/CSS/JS). No bundler; structure mirrors output. 2. **Build** (`pnpm build`): `src/` → `dist/` (copy, minify JS/CSS, inline critical CSS via Beasties). 3. **Docker image**: `Dockerfile` copies `nginx/conf.d/` and `dist/` into `nginx:alpine`; no Node in the image. 4. **Run**: Container serves `/usr/share/nginx/html` on port 80. Traefik (external) terminates TLS for `mail.mifi.holdings`, applies gzip and security middlewares, and routes to this service on `marina-net`. 5. **Deploy**: Woodpecker runs **ci** → **build** (site + image + push) → **deploy** (Portainer webhook redeploy + Mattermost). --- ## Local development (Dev Container) 1. **Open in Dev Container** In Cursor/VS Code: **Command Palette** → **Dev Containers: Reopen in Container** (or **Clone Repository in Container Volume** when opening the repo). First time: builds the container (Node 22 + pnpm), runs `pnpm install`. 2. **Preview the site** In the container terminal: - **Quick preview** (serves `src/` as-is, no build): `pnpm preview` - **Production-like** (build then serve `dist/`): `pnpm preview:prod` Port **3000** is forwarded; open **http://localhost:3000** (or use the “Preview” port notification). 3. **Other commands** - `pnpm build` — build `src/` → `dist/` (minify JS/CSS, inline critical CSS) - `pnpm lint` / `pnpm format` — lint and format - `pnpm docker:build` — build production image (for local testing; image: `git.mifi.dev/mifi-holdings/mail-landing:latest`) --- ## Build pipeline (what `pnpm build` does) 1. **Clean & copy** — `dist/` is removed; `src/` is copied recursively to `dist/`. 2. **Minify JS** — All `.js` files in `dist/` are minified with Terser (no comments). 3. **Minify CSS** — All `.css` files are minified with clean-css (level 2). 4. **Inline critical CSS** — Beasties runs on every `.html` file in `dist/` (default preload behavior; no headless browser). Output: `dist/` ready to be served or copied into the Docker image. --- ## Deployment (Woodpecker CI/CD) Pipelines live under `.woodpecker/`. Execution order: | Pipeline | When | What | | ------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **ci** (`ci.yml`) | Every PR and every push to `main` | Install → Prettier check → Lint (JS, CSS, YAML) → Build. Mattermost notifications on failure/success. | | **build** (`build.yml`) | Push/tag/manual on `main`, or deployment event for production | Depends on **ci**. Site build → Docker image build (linux/amd64, tagged with commit SHA + `latest`) → Push to `git.mifi.dev/mifi-holdings/mail-landing`. Mattermost on success/failure. | | **deploy** (`deploy.yml`) | Same as build (runs after build) | `skip_clone: true`; triggers Portainer webhook to redeploy the stack. Mattermost deploy success/failure. | **Secrets** (in Woodpecker): `portainer_webhook_url`, `mattermost_*`, `gitea_registry_username`, `gitea_package_token`. **Production**: Stack is defined in Portainer (using this repo’s `docker-compose.yml`). Redeploy pulls `git.mifi.dev/mifi-holdings/mail-landing:latest` and restarts the service. Traefik (on `marina-net`) routes `mail.mifi.holdings` to this container with TLS (e.g. Let’s Encrypt) and middlewares (gzip, security). --- ## Production runtime (Docker + Traefik) - **Image**: `git.mifi.dev/mifi-holdings/mail-landing:latest` (nginx:alpine + `nginx/conf.d/` + `dist/`). - **Compose**: `docker-compose.yml` defines one service, `marina-net`, Traefik labels for `Host(\`mail.mifi.holdings\`)`, TLS, gzip, and security middlewares; healthcheck via `wget`on`/`. - **nginx**: Serves `/usr/share/nginx/html`; HTML no-cache, JS/CSS long-lived cache; static assets and directory/index handling as in `nginx/conf.d/default.conf`. --- ## Quick reference (pnpm scripts) | Command | Description | | ------------------- | ---------------------------------------------- | | `pnpm build` | Build `src/` → `dist/` (minify + critical CSS) | | `pnpm preview` | Serve `src/` on port 3000 (no build) | | `pnpm preview:prod` | Build then serve `dist/` on 3000 | | `pnpm lint` | Lint JS, CSS, and Woodpecker/compose YAML | | `pnpm lint:fix` | Auto-fix lint where supported | | `pnpm format` | Prettier write | | `pnpm format:check` | Prettier check only | | `pnpm docker:build` | Build Docker image (linux/amd64) | | `pnpm docker:push` | Push image to registry (manual) |