# Docker Project Generator — Usage Guide

This is the step-by-step guide for developers *using* the tool described in
[README.md](README.md) to spin up a new local WordPress project. If you're
looking for how to host/configure the tool itself, see README.md's
[Server-side setup](README.md#server-side-setup) section instead.

## 1. Prerequisites (your machine)

- **Docker Desktop** — if it isn't installed, `start.ps1` will offer to
  install it for you (via `winget`, falling back to a direct download).
  Docker Engine 20.10.0 or newer is required.
- **Windows + PowerShell** to use `start.bat`/`start.ps1` as generated. On
  macOS/Linux, skip straight to `docker compose up -d` — see
  [Section 7](#7-running-without-startps1-macoslinux).
- A **GitHub account that is a member of the `Kilowott-HQ` organization**.
  Sign-in will be rejected otherwise.

## 2. Sign in

1. Open the tool's URL in your browser (it will redirect you to HTTPS
   automatically if you land on plain HTTP).
2. Click **Sign in with GitHub**.
3. Authorize the OAuth app if prompted — this asks for the `repo` scope in
   addition to your basic profile/org-membership info. That's needed so
   repos you create through this tool are created under your own GitHub
   account rather than a shared service account (see
   [Section 5](#5-repo-check--create)); it's not used for anything else.
4. You'll be redirected back once GitHub confirms your identity and your
   `Kilowott-HQ` membership.

If sign-in fails, the page shows why:

| Error | Meaning |
|---|---|
| "GitHub sign-in is not configured yet" | The server admin hasn't set up the OAuth client ID/secret yet. |
| "Sign-in session expired or was invalid" | The OAuth CSRF state didn't match — just try signing in again. |
| Membership error | Your GitHub account isn't a member of `Kilowott-HQ`, or your membership is private. Ask an org owner. |

## 3. Choose a project name

Enter the name you want for both the GitHub repository and the local
project folder. Rules:

- Letters, numbers, and hyphens only (e.g. `client-landing-page`).
- No spaces, underscores, or path separators — the form validates this
  client-side and the server re-validates it (`sanitizeRepoName()`).

This name becomes:
- The GitHub repo name under `Kilowott-HQ`.
- The Docker container/network/volume name prefix.
- The theme folder name (`wp-content/themes/<name>`) — the Base Theme
  itself if that's your theme choice, or the GeneratePress/Hello Elementor
  child theme otherwise (see [Section 4](#4-theme-choice--optional-plugins)).
- The site title, converted to Title Case (e.g. `client-landing-page` →
  "Client Landing Page").

## 4. Theme choice & optional plugins

Below the project name field are two groups: **Theme** (pick exactly one)
and **Plugins** (pick any number).

### Theme (pick one)

- **Base Theme** — selected automatically, and can't be left unselected
  (it's the fallback if you don't pick GeneratePress or Hello Elementor
  instead). A genuinely minimal, standalone theme generated fresh for your
  project — no parent theme, no child theme split, just a small,
  functioning classic WordPress theme (`style.css`, `functions.php`,
  `index.php`, `header.php`, `footer.php`, `sidebar.php`) slugged the same
  as your project name. Comes with WooCommerce theme support already
  declared (`add_theme_support('woocommerce')` plus the product gallery
  zoom/lightbox/slider features), so installing the WooCommerce plugin
  (below) works cleanly against it out of the box.
- **GeneratePress** — installs the GeneratePress theme plus a project-named
  child theme, and activates the child theme.
- **Hello Elementor** — installs the Hello Elementor theme plus a
  project-named child theme, and activates the child theme. Checking this
  automatically checks the **Elementor** plugin too, since the theme is
  designed to be used with it — this only runs one-directional: switching
  the theme choice away from Hello Elementor afterward leaves Elementor
  checked, in case you still want it on its own.

Only one theme is ever active, so picking GeneratePress or Hello Elementor
means Base Theme (or whichever you'd picked before) is *not* installed
alongside it — it's a straight substitution. All three end up in the exact
same theme folder (`wp-content/themes/<project-name>`), since that's what
the Base Theme's slug is required to be; `wp-cli-init.sh` tracks which one
is currently there and rebuilds that folder from scratch if you regenerate
the project with a different choice, so switching theme choices across
regenerations doesn't leave stale files from the previous choice behind.

### Plugins (pick any number)

- **Elementor**
- **WooCommerce**

Both are unchecked by default. Checked selections are installed and
activated by `wp-cli-init.sh` on first boot, the same way Yoast SEO/Wordfence
already are — each is skipped with a warning (not a hard failure) if it
can't be installed for any reason, since all of this is ordinary public
wordpress.org listings.

## 5. Repo check / create

When you submit the name, the tool checks GitHub for
`Kilowott-HQ/<name>`:

- **Already exists** — the tool tells you, and you can still generate the
  local Docker project against that existing repo name (useful if you
  already have the repo and just need the local dev environment).
- **Doesn't exist** — the tool offers to create it, using **your own GitHub
  account** (not a shared service token). Creating it:
  - Makes a new **private** repo under `Kilowott-HQ`, initialized with a
    README.
  - Grants **you** admin access on it immediately and automatically — this
    is standard GitHub behavior for anyone who creates a repo under an org
    they belong to, so there's no invitation to accept.

This requires `Kilowott-HQ`'s org settings to allow members to create
repositories. If they don't, repo creation will fail with a permissions
error — ask an org owner to either create the repo for you, or enable
member repo creation under the org's settings.

## 6. Generate and download the project

Click **Generate Project**. The tool builds and downloads `<name>.zip`
containing:

```
<name>.zip
├── docker-compose.yaml     # your project's compose file, values already filled in
├── wp-cli-init.sh          # WordPress bootstrap script (runs inside the wpcli container)
├── start.ps1               # one-click launcher (Windows)
├── start.bat               # double-click wrapper for start.ps1
├── .gitignore              # tracks only wp-content/themes and wp-content/plugins
├── .gitleaks.toml          # secret-scanning config
├── .phpcs.xml              # PHP coding standard (WordPress)
├── .eslintrc.json          # JS lint config
├── .stylelintrc.json       # CSS lint config
├── .git-hooks/
│   └── pre-push            # Gitleaks → PHPCS → ESLint → Stylelint, run before every push
└── install-hooks.sh        # copies .git-hooks/pre-push into .git/hooks (run once)
```

**Where to find your generated credentials:** open the downloaded
`docker-compose.yaml` and look at the `wpcli` service's `environment:`
block — `WP_ADMIN_USER` and `WP_ADMIN_PASSWORD` are randomly generated per
download and only ever recorded in that file. `MYSQL_USER`/`MYSQL_PASSWORD`
are deterministic (derived from the project name), so regenerating the zip
later for the same project name won't lock you out of an existing database
volume.

Those are your **local** credentials. Your **staging** site has a separate
set — see the next section.

### Staging site access (wp-admin)

When your repo is created, a Servebolt staging site is provisioned with
WordPress already installed. Its wp-admin credentials appear **once**, in an
amber panel on the generator page, immediately after the zip downloads:

| Field | What it is |
|---|---|
| Staging URL | your staging site's address |
| wp-admin | the login page for it |
| Username | the admin account Servebolt created (not `admin`) |
| Password | randomly generated by Servebolt |

**Copy the password before you leave that page.** It is shown once and
deliberately never stored anywhere — not in the zip, not in the repo, not in
the project README, not in Slack, not in any server-side file. Keeping a live
staging admin password on disk next to the hosting API credentials would be a
worse trade than making people reset it, so that is the choice made here.

Slack does get a notification when a project is created, but it contains only
the repo URL and the staging URL — never credentials.

**If you lose it** (closed the tab, joined the project later, inherited it from
someone else), use the **"Reset its staging admin password"** link under the
generator form:

1. Type the project's repository name into the name field.
2. Click the reset link and confirm.
3. A new password appears in the same amber panel — again, shown once.

Two things to know about resetting:

- The old password stops working immediately. On a **live client site**, check
  with whoever is working on it before you reset, in case they are relying on
  the current one.
- You must have access to that repository on GitHub. Being signed in is not
  enough — the check is per-repo, so you cannot reset the staging site of a
  project you have no access to.

Projects created before staging-access tracking existed have no environment on
record, and the reset will tell you so plainly; those have to be reset from the
Servebolt panel directly.

### What to do with the zip

1. Unzip it into an empty folder — this folder is your project root.
2. That's it for manual git setup — `start.ps1` now handles `git init`,
   wiring up the `origin` remote, and the initial commit/push automatically
   the first time your site finishes setting up (see
   [Section 7](#7-start-the-project)). If you'd rather do this yourself
   (e.g. the GitHub repo already has content you want to `git clone` in
   first), see the note in Section 7 about skipping the automated step.
3. Run `sh install-hooks.sh` once, from a shell that has `.git` available,
   to enable the pre-push quality gate locally — this part is still
   manual.

## 7. Start the project

**Windows:** double-click `start.bat`. It will:

1. Check whether Docker Desktop is installed (offering to install it via
   `winget` if not).
2. Verify the installed Docker version is 20.10.0+.
3. Launch Docker Desktop if it isn't already running, and wait for the
   engine to come up (up to ~3 minutes).
4. Run `docker compose pull` to explicitly download the four images this
   project needs (`mysql:5.7`, `wordpress:latest`, `wordpress:cli`,
   `phpmyadmin/phpmyadmin`) before doing anything else — as its own step
   with its own progress output and its own failure message, rather than
   leaving it folded into the next step below.
5. Run `docker compose up -d`.
6. Poll for a `.wp-cli-ready` marker file, showing a live progress bar
   (7 stages: admin account → WordPress.org plugins → Kilowott + optional
   plugins → theme setup (Base Theme, or GeneratePress/Hello Elementor with
   a child theme) → default theme cleanup → plugin updates → core update).
7. **The first time only**, once WordPress is ready: automatically set up
   Git for this project using **your own git credentials** (whatever's
   already configured on your machine for github.com — this tool never
   supplies or handles any git credential itself) —
   - `git init` (if `.git` doesn't already exist)
   - Add `origin` pointing at `https://github.com/Kilowott-HQ/<name>.git`
     (if not already set)
   - `git pull origin main`
   - Commit the generated theme folder (`wp-content/themes/<name>`) with
     the message "Base theme setup", then push it
   - `git checkout staging-branch` and `git pull origin main` on top of it,
     if that branch already exists in the repo (skipped with a note if it
     doesn't)

   Every step here is best-effort — a failure (no git installed, no
   credentials configured, network issue, no `staging-branch` in a
   brand-new repo) is logged to `error.log` and shown on screen, but never
   blocks your site from coming up. A hidden `.git-setup-done` marker file
   in the project folder means this only ever runs once per project; if
   you'd rather set Git up yourself (e.g. `git clone` an existing repo with
   content into place instead), create an empty `.git-setup-done` file in
   the project folder *before* running `start.bat` the first time, and this
   step will skip itself entirely.
8. Open your default browser at the site's URL once ready.

If it fails partway, the console window stays open (doesn't just vanish)
and tells you what to check — usually `docker compose logs wpcli`. This
holds even for failures the script doesn't specifically anticipate (a
Docker/PowerShell edge case, a missing command, a network hiccup): every
failure is caught, shown on screen, and appended to **`error.log`** in the
same project folder (next to `docker-compose.yaml`) — check that file first
if something goes wrong and you're not sure why, especially if the window
closed faster than you could read it.

**Note:** an `error.log` entry starting with `UNEXPECTED ERROR: WARNING: ...`
or mentioning `Internal Server Error` from Docker's own API is very likely
just Docker Desktop's engine still starting up, or a harmless backend
warning (e.g. `No blkio throttle.read_bps_device support` on some Linux/WSL2
setups) — not a real failure. `start.ps1` treats these correctly now and
lets its retry loop ride them out instead of aborting; if you still see one
of these lines abort the script, that's worth reporting since it means the
fix for that specific case missed a spot.

### 7. Running without start.ps1 (macOS/Linux)

`start.ps1` is PowerShell-only. On macOS/Linux, run the equivalent steps
manually from the project folder:

```sh
docker compose up -d
# then poll for readiness:
until [ -f .wp-cli-ready ]; do sleep 3; done
open http://localhost:<WP_PORT>   # or `xdg-open` on Linux
```

`<WP_PORT>` is whatever `docker-compose.yaml`'s `wordpress` service maps
(check the `ports:` line, or `WP_URL` in the `wpcli` service's environment).

## 8. What gets installed automatically

`wp-cli-init.sh` does all of this on first boot (and reconciles it on every
subsequent restart):

- Creates `wp-config.php`, waits for the database, installs the requested
  locale (default `en_GB`).
- Runs `wp core install` with your generated admin account, or reconciles
  the admin account/title/visibility if WordPress was already installed.
- Sets the site to discourage search engine indexing (local dev only).
- Installs and activates **Yoast SEO** and **Wordfence** from
  wordpress.org.
- Installs **kw-security** and **kw-performance**, both from their own
  public GitHub releases, if reachable — either is skipped with a warning,
  not a hard failure, if unavailable.
- Installs and activates **Elementor** and/or **WooCommerce** if you checked
  them (see [Section 4](#4-theme-choice--optional-plugins)) — skipped
  otherwise.
- Removes the default **Akismet** and **Hello Dolly** plugins.
- Sets up whichever theme was chosen: the **Base Theme** (default, minimal,
  standalone, no child theme), **GeneratePress**, or **Hello Elementor**
  (the latter two each with a project-named child theme) — only one theme
  ends up active.
- Removes all default WordPress themes (Twenty Twenty* series).
- Updates all plugins and WordPress core itself.

Every plugin/theme install above is a separate wordpress.org (or GitHub, for
kw-security/kw-performance) download — WP-CLI's own package cache is pointed
at `/tmp/.wp-cli-cache` inside the `wpcli` container to keep these fast on
repeat runs; older projects generated before this fix will instead see
harmless `Failed to create directory '/.wp-cli/cache/': Permission denied`
warnings in `docker compose logs wpcli`, and simply re-download every plugin
from scratch each run.

## 9. Regenerating a project

You can re-run **Generate Project** for the same name at any time — e.g. to
pick up a newer version of the templates. Notes:

- MySQL credentials stay the same (deterministic), so a regenerated zip
  still matches an existing `db_data` Docker volume.
- The WordPress admin username/password are **re-randomized** every time.
  If WordPress is already installed in that volume, `wp-cli-init.sh`
  reconciles the existing admin account to match the new password on next
  `docker compose up -d` — so re-download and restart if you've lost track
  of admin credentials for an existing project.

## 10. Troubleshooting

| Symptom | Likely cause / fix |
|---|---|
| `start.bat` window closes instead of staying open with a message | Shouldn't happen — every failure path now pauses the window and logs to `error.log` in the project folder. `start.bat` itself also pauses (via a plain `pause`, no PowerShell needed) if `start.ps1` exits with an error code, covering the case where PowerShell couldn't even run the script at all (execution policy locked down by Group Policy, antivirus quarantine, a corrupted download). If you still see the window vanish with nothing, check `error.log` first, then try opening a PowerShell window in that folder and running `.\start.ps1` directly to see the raw error. |
| Outdated Docker (older than 20.10.0) | `start.ps1` detects this and shows a red message asking you to update Docker Desktop, then stops — update it from [docker.com](https://www.docker.com/products/docker-desktop) and re-run `start.bat`. |
| "Could not determine your Docker version" (yellow warning) | `start.ps1` couldn't parse `docker --version`'s output — it continues anyway, but this project still needs Docker 20.10.0+; if a later step fails, check your Docker version manually first. |
| Stuck at "Waiting for Docker Desktop to finish starting..." | Open Docker Desktop manually and wait for "Engine running" before re-running `start.bat`. |
| "Failed to download one or more required Docker images" | The `docker compose pull` step failed — check the console output printed just above that message for which image and why (not duplicated into `error.log`, since Docker's own progress output would otherwise flood it); usually a network issue or Docker Hub rate limit. Re-running `start.bat` retries it. |
| Stuck at "Waiting for WordPress setup to finish" past a few minutes | This step can genuinely take 15-20+ minutes on a first run, especially with several optional theme/plugins checked — `start.ps1` waits up to 30 minutes before giving up. Run `docker compose logs wpcli` in the project folder to see which step it's on (or whether it already finished and just took a while). |
| `kw-performance` plugin not installed | Non-fatal by design. It now installs from its own public GitHub release, the same way `kw-security` does — check the release exists at `github.com/Kilowott-HQ/kw-performance/releases/latest` and that the container has internet access to GitHub. |
| Elementor / WooCommerce didn't get installed even though I checked the box | Non-fatal by design — check `docker compose logs wpcli` for a "could not install" warning, usually a wordpress.org connectivity issue from inside the container. Confirm the checkbox was actually checked before clicking Generate, since an unchecked box is indistinguishable from a failed install. |
| GeneratePress / Hello Elementor didn't get installed even though I selected it | If installing the chosen theme itself fails (network issue, wordpress.org connectivity from inside the container), `wp-cli-init.sh` falls back to the Base Theme automatically rather than leaving the site with no active theme — check `docker compose logs wpcli` for a "could not install ... falling back to the base theme" warning. |
| Hello Dolly / Akismet still present after setup | Fixed — an earlier version of `wp-cli-init.sh` used the wrong internal name (`hello-dolly` instead of `hello`) for Hello Dolly, which silently matched nothing. Re-download the project zip to pick up the fix. |
| "Invalid repository name" | Only letters, numbers, and hyphens are allowed in the project name. |
| Repo creation fails with a permissions error | `Kilowott-HQ`'s org settings likely restrict repository creation to owners only. Ask an org owner to create the repo for you, or to enable member repo creation under the org's Member privileges settings. |
| "Your sign-in session is missing a permission this action needs now" | You signed in before the `repo` OAuth scope was added to this tool. Sign out and sign back in to pick it up. |
| Pre-push hook says a tool "not installed — skipping" | Expected if you don't have `gitleaks`/`phpcs`/`eslint`/`stylelint` installed locally; install whichever ones you want enforced. |
| "Git automation skipped: 'git' is not installed" | Install [Git for Windows](https://git-scm.com/download/win), then delete `.git-setup-done` in the project folder and re-run `start.bat` to retry. |
| Git automation warns it "could not pull/push origin" | Usually means git isn't authenticated for github.com on your machine yet (no credential manager entry / SSH key), or you don't have access to that repo. Set up your git credentials normally (however you'd authenticate for any other GitHub clone/push), delete `.git-setup-done`, and re-run `start.bat`. |
| "No 'staging-branch' found in this repo" | Expected for a brand-new repo — nothing creates a `staging-branch` automatically. Not an error; the rest of Git setup already completed. |
| Git setup didn't run at all / I wanted to `git clone` an existing repo myself instead | Create an empty `.git-setup-done` file in the project folder before running `start.bat` for the first time to skip the automated step entirely, then set Git up however you prefer. |

## Related

- [WORKFLOW.html](WORKFLOW.html) — **the day-to-day branch and PR workflow**:
  `feature/*` → `staging` → `main`, which checks block a merge, the bypass label
  rules, and the current enforcement gaps.
- [README.md](README.md) — architecture, server-side/admin setup, security notes.
