matomo-bootstrap 1.1.13
Headless bootstrap tooling for Matomo (installation + API token provisioning)
Requires Python: >=3.10

# matomo-bootstrap
[![GitHub Sponsors](https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-blue?logo=github)](https://github.com/sponsors/kevinveenbirkenbach) [![Patreon](https://img.shields.io/badge/Support-Patreon-orange?logo=patreon)](https://www.patreon.com/c/kevinveenbirkenbach) [![Buy Me a Coffee](https://img.shields.io/badge/Buy%20me%20a%20Coffee-Funding-yellow?logo=buymeacoffee)](https://buymeacoffee.com/kevinveenbirkenbach) [![PayPal](https://img.shields.io/badge/Donate-PayPal-blue?logo=paypal)](https://s.veen.world/paypaldonate)


Headless bootstrap tooling for **Matomo**. Automates **first-time installation** and **API token provisioning** for fresh Matomo instances.
---

## Features

- 🚀 **Fully headless Matomo installation**
  - Drives the official Matomo web installer using **Playwright**
  - Automatically skips the installer if Matomo is already installed
- 🔐 **API token provisioning**
  - Creates an **app-specific token** via an authenticated Matomo session
  - Compatible with **Matomo 5.3.x** Docker images
- 🧪 **E2E-tested**
  - Docker-based end-to-end tests included
- ❄️ **First-class Nix support**
  - Flake-based packaging and pinned `flake.lock`
  - Uses `nixpkgs` browsers via `playwright-driver` (no Playwright downloads)
- 🧼 **Token-only stdout contract**
  - **stdout contains only the token** (safe for scripting)
  - Logs go to **stderr**

---

## Requirements

- A running Matomo instance (e.g. via Docker)
- For fresh installs:
  - Chromium (provided by Playwright or by the Playwright base container image)

---

## Installation

### Nix (recommended)

Run directly from the repository:

```bash
nix run github:kevinveenbirkenbach/matomo-bootstrap
```

In Nix mode, browsers are provided via `nixpkgs` (`playwright-driver`) and Playwright downloads are disabled.

---

### Python / pip

Requires **Python ≥ 3.10**:

```bash
pip install matomo-bootstrap
python -m playwright install chromium
```

---

### Docker image (GHCR)

Pull the prebuilt image:

```bash
docker pull ghcr.io/kevinveenbirkenbach/matomo-bootstrap:stable
# or:
docker pull ghcr.io/kevinveenbirkenbach/matomo-bootstrap:latest
```

---

## Usage

### CLI

```bash
matomo-bootstrap \
  --base-url http://127.0.0.1:8080 \
  --admin-user administrator \
  --admin-password 'AdminSecret123!' \
  --admin-email administrator@example.org \
  --token-description my-ci-token
```

On success, the command prints **only the token** to stdout:

```text
6c7a8c2b0e9e4a3c8e1d0c4e8a6b9f21
```

---

### Environment variables

All options can be provided via environment variables:

```bash
export MATOMO_URL=http://127.0.0.1:8080
export MATOMO_ADMIN_USER=administrator
export MATOMO_ADMIN_PASSWORD='AdminSecret123!'
export MATOMO_ADMIN_EMAIL=administrator@example.org
export MATOMO_TOKEN_DESCRIPTION=my-ci-token

matomo-bootstrap
```

---

### Debug mode

Enable verbose logs (**stderr only**):

```bash
matomo-bootstrap --debug
```

---

## Docker Compose integration (one-shot bootstrap)

### Why “one-shot”?

The bootstrap container is meant to:

1. Run once,
2. Print the token to stdout,
3. Exit with code `0`.

You should **not** start it automatically on every `docker compose up`.
Instead, start Matomo normally, then run the bootstrap via `docker compose run`.

The cleanest Compose pattern is to put `bootstrap` behind a **profile**.

---

### Example `docker-compose.yml` (recommended: `profiles`)

```yaml
services:
  db:
    image: mariadb:11
    container_name: matomo-db
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: matomo
      MARIADB_USER: matomo
      MARIADB_PASSWORD: matomo_pw
      MARIADB_ROOT_PASSWORD: root_pw
    volumes:
      - mariadb_data:/var/lib/mysql
    healthcheck:
      test: ["CMD-SHELL", "mariadb-admin ping -uroot -proot_pw --silent"]
      interval: 5s
      timeout: 3s
      retries: 60

  matomo:
    image: matomo:5.3.2
    container_name: matomo
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "${MATOMO_PORT:-8080}:80"
    environment:
      MATOMO_DATABASE_HOST: db
      MATOMO_DATABASE_ADAPTER: mysql
      MATOMO_DATABASE_USERNAME: matomo
      MATOMO_DATABASE_PASSWORD: matomo_pw
      MATOMO_DATABASE_DBNAME: matomo
    volumes:
      - matomo_data:/var/www/html
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://127.0.0.1/ >/dev/null || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 60

  bootstrap:
    # This prevents automatic startup during a normal `docker compose up`
    profiles: ["bootstrap"]

    # Option A: use the published image (recommended)
    image: ghcr.io/kevinveenbirkenbach/matomo-bootstrap:1.0.1

    # Option B: build locally from the repository checkout
    # build:
    #   context: .
    #   dockerfile: Dockerfile
    # image: matomo-bootstrap:local

    container_name: matomo-bootstrap
    depends_on:
      matomo:
        condition: service_started
    environment:
      # inside the compose network, Matomo is reachable via the service name
      MATOMO_URL: "http://matomo"

      MATOMO_ADMIN_USER: "administrator"
      MATOMO_ADMIN_PASSWORD: "AdminSecret123!"
      MATOMO_ADMIN_EMAIL: "administrator@example.org"
      MATOMO_TOKEN_DESCRIPTION: "docker-compose-bootstrap"

      # Values used by the recorded installer flow
      MATOMO_SITE_NAME: "Matomo (docker-compose)"
      MATOMO_SITE_URL: "http://127.0.0.1:${MATOMO_PORT:-8080}"
      MATOMO_TIMEZONE: "Germany - Berlin"

      # Optional stability knobs
      MATOMO_TIMEOUT: "60"
      MATOMO_PLAYWRIGHT_HEADLESS: "1"
      MATOMO_PLAYWRIGHT_NAV_TIMEOUT_MS: "60000"
      MATOMO_PLAYWRIGHT_SLOWMO_MS: "0"
      MATOMO_INSTALLER_READY_TIMEOUT_S: "240"
      MATOMO_INSTALLER_STEP_TIMEOUT_S: "45"
      MATOMO_INSTALLER_STEP_DEADLINE_S: "240"
      MATOMO_INSTALLER_TABLES_CREATION_TIMEOUT_S: "240"
      MATOMO_INSTALLER_TABLES_ERASE_TIMEOUT_S: "180"
      MATOMO_INSTALLER_DEBUG_DIR: "/tmp/matomo-bootstrap"

    restart: "no"

volumes:
  mariadb_data:
  matomo_data:
```

---

### Commands

Start DB + Matomo **without** bootstrap:

```bash
docker compose up -d db matomo
```

Run bootstrap once (prints token to stdout):

```bash
docker compose --profile bootstrap run --rm bootstrap
```

Re-run bootstrap (creates a new token by default):

```bash
docker compose --profile bootstrap run --rm bootstrap
```

---

## Idempotency / avoiding new tokens on every run

By default, `UsersManager.createAppSpecificTokenAuth` creates a new token each time.

If you want strictly idempotent runs in automation, you can provide an existing token
and make the bootstrap return it instead of creating a new one:

```bash
export MATOMO_BOOTSTRAP_TOKEN_AUTH="0123456789abcdef..."
matomo-bootstrap
```

> This is useful for CI re-runs or configuration management tools.

---

## How it works

1. **Reachability check**

   * waits until Matomo responds via HTTP (any status is considered “reachable”)
2. **Installation (if needed)**

   * uses a recorded Playwright flow to complete the Matomo web installer
   * waits until installer controls are interactive before clicking next steps
   * writes screenshot/HTML debug artifacts on installer failure
3. **Authentication**

   * logs in using Matomo’s `Login.logme` controller (cookie session)
4. **Token creation**

   * calls `UsersManager.createAppSpecificTokenAuth`
5. **Output**

   * prints the token to stdout (token-only contract)

---

## End-to-end tests

Run the full E2E cycle locally:

```bash
make e2e
```

This will:

1. Start Matomo + MariaDB via Docker
2. Install Matomo headlessly
3. Create an API token
4. Validate the token via the Matomo API
5. Tear everything down again

---

## Author

**Kevin Veen-Birkenbach**
[https://www.veen.world/](https://www.veen.world/)

---

## License

MIT — see [LICENSE](LICENSE)