Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f270a5c7c6 | ||
|
|
5e5b6c8933 | ||
|
|
1af480ee91 |
@@ -1,3 +1,10 @@
|
|||||||
|
## [1.0.1] - 2025-12-23
|
||||||
|
|
||||||
|
* * Support for running `matomo-bootstrap` **fully via Nix** in a clean, containerized environment.
|
||||||
|
* A **token-only stdout contract**: the bootstrap command now prints only the API token, making it safe for automation.
|
||||||
|
* Reproducible Nix builds via a pinned `flake.lock`.
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0] - 2025-12-23
|
## [1.0.0] - 2025-12-23
|
||||||
|
|
||||||
* 🥳
|
* 🥳
|
||||||
|
|||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1766309749,
|
||||||
|
"narHash": "sha256-3xY8CZ4rSnQ0NqGhMKAy5vgC+2IVK0NoVEzDoOh4DA4=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a6531044f6d0bef691ea18d4d4ce44d0daa6e816",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
25
flake.nix
25
flake.nix
@@ -19,16 +19,19 @@
|
|||||||
rec {
|
rec {
|
||||||
matomo-bootstrap = python.pkgs.buildPythonApplication {
|
matomo-bootstrap = python.pkgs.buildPythonApplication {
|
||||||
pname = "matomo-bootstrap";
|
pname = "matomo-bootstrap";
|
||||||
version = "1.0.0"; # keep in sync with pyproject.toml
|
version = "1.0.1"; # keep in sync with pyproject.toml
|
||||||
pyproject = true;
|
pyproject = true;
|
||||||
src = self;
|
src = self;
|
||||||
|
|
||||||
# Runtime deps (Python)
|
nativeBuildInputs = with python.pkgs; [
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
];
|
||||||
|
|
||||||
propagatedBuildInputs = with python.pkgs; [
|
propagatedBuildInputs = with python.pkgs; [
|
||||||
playwright
|
playwright
|
||||||
];
|
];
|
||||||
|
|
||||||
# Optional: keep tests off in nix build by default
|
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
|
|
||||||
meta = with pkgs.lib; {
|
meta = with pkgs.lib; {
|
||||||
@@ -46,16 +49,22 @@
|
|||||||
apps = forAllSystems (system:
|
apps = forAllSystems (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
python = pkgs.python312;
|
||||||
|
|
||||||
|
pythonPlaywright = python.withPackages (ps: [
|
||||||
|
ps.playwright
|
||||||
|
]);
|
||||||
|
|
||||||
matomo = self.packages.${system}.matomo-bootstrap;
|
matomo = self.packages.${system}.matomo-bootstrap;
|
||||||
|
|
||||||
playwright-install = pkgs.writeShellApplication {
|
playwright-install = pkgs.writeShellApplication {
|
||||||
name = "matomo-bootstrap-playwright-install";
|
name = "matomo-bootstrap-playwright-install";
|
||||||
runtimeInputs = [ matomo ];
|
runtimeInputs = [ pythonPlaywright ];
|
||||||
|
|
||||||
text = ''
|
text = ''
|
||||||
# Installs the Playwright Chromium browser into the user cache.
|
# Install Playwright browsers.
|
||||||
# This is needed when the Matomo instance is not installed yet and
|
# IMPORTANT: Do not print anything to stdout (tests expect token-only stdout).
|
||||||
# the web installer must be driven via Playwright.
|
exec ${pythonPlaywright}/bin/python -m playwright install chromium 1>&2
|
||||||
exec ${matomo}/bin/python -m playwright install chromium
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "matomo-bootstrap"
|
name = "matomo-bootstrap"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
description = "Headless bootstrap tooling for Matomo (installation + API token provisioning)"
|
description = "Headless bootstrap tooling for Matomo (installation + API token provisioning)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@@ -27,3 +27,50 @@ services:
|
|||||||
MATOMO_DATABASE_USERNAME: matomo
|
MATOMO_DATABASE_USERNAME: matomo
|
||||||
MATOMO_DATABASE_PASSWORD: matomo_pw
|
MATOMO_DATABASE_PASSWORD: matomo_pw
|
||||||
MATOMO_DATABASE_DBNAME: matomo
|
MATOMO_DATABASE_DBNAME: matomo
|
||||||
|
|
||||||
|
nix:
|
||||||
|
image: nixos/nix:latest
|
||||||
|
container_name: e2e-nix
|
||||||
|
depends_on:
|
||||||
|
matomo:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
# Run as root to avoid /nix big-lock permission issues
|
||||||
|
user: "0:0"
|
||||||
|
working_dir: /work
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# Project root as flake
|
||||||
|
- ../../:/work:ro
|
||||||
|
|
||||||
|
# Nix store (removed by docker compose down -v)
|
||||||
|
- e2e_nix_store:/nix
|
||||||
|
|
||||||
|
# HOME/XDG for nix + playwright
|
||||||
|
- e2e_nix_home:/tmp/home
|
||||||
|
|
||||||
|
environment:
|
||||||
|
NIX_CONFIG: "experimental-features = nix-command flakes"
|
||||||
|
TERM: "xterm"
|
||||||
|
|
||||||
|
HOME: "/tmp/home"
|
||||||
|
USER: "root"
|
||||||
|
LOGNAME: "root"
|
||||||
|
XDG_CACHE_HOME: "/tmp/home/.cache"
|
||||||
|
XDG_CONFIG_HOME: "/tmp/home/.config"
|
||||||
|
XDG_DATA_HOME: "/tmp/home/.local/share"
|
||||||
|
|
||||||
|
MATOMO_SITE_NAME: "Matomo E2E"
|
||||||
|
MATOMO_SITE_URL: "http://127.0.0.1:8080"
|
||||||
|
MATOMO_TIMEZONE: "Germany - Berlin"
|
||||||
|
|
||||||
|
command: >
|
||||||
|
sh -lc "mkdir -p /tmp/home/.cache /tmp/home/.config /tmp/home/.local/share;
|
||||||
|
tail -f /dev/null"
|
||||||
|
|
||||||
|
# Allow access to host-published Matomo port
|
||||||
|
network_mode: host
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
e2e_nix_store:
|
||||||
|
e2e_nix_home:
|
||||||
|
|||||||
58
tests/e2e/test_bootstrap_nix.py
Normal file
58
tests/e2e/test_bootstrap_nix.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
MATOMO_URL = os.environ.get("MATOMO_URL", "http://127.0.0.1:8080")
|
||||||
|
ADMIN_USER = os.environ.get("MATOMO_ADMIN_USER", "administrator")
|
||||||
|
ADMIN_PASSWORD = os.environ.get("MATOMO_ADMIN_PASSWORD", "AdminSecret123!")
|
||||||
|
ADMIN_EMAIL = os.environ.get("MATOMO_ADMIN_EMAIL", "administrator@example.org")
|
||||||
|
|
||||||
|
|
||||||
|
class TestMatomoBootstrapE2ENix(unittest.TestCase):
|
||||||
|
def test_bootstrap_creates_api_token_via_nix(self) -> None:
|
||||||
|
script = f"""set -euo pipefail
|
||||||
|
|
||||||
|
export NIX_CONFIG='experimental-features = nix-command flakes'
|
||||||
|
export TERM='xterm'
|
||||||
|
|
||||||
|
# Make sure we have a writable HOME (compose already sets HOME=/tmp/home)
|
||||||
|
mkdir -p "$HOME" "$HOME/.cache" "$HOME/.config" "$HOME/.local/share"
|
||||||
|
|
||||||
|
# IMPORTANT:
|
||||||
|
# Nix flakes read the local repo as git+file:///work.
|
||||||
|
# Git refuses if the repo is not owned by the current user (root in the container).
|
||||||
|
# Mark it as safe explicitly.
|
||||||
|
git config --global --add safe.directory /work
|
||||||
|
|
||||||
|
# 1) Install Playwright Chromium (cached in the container environment)
|
||||||
|
nix run --no-write-lock-file -L .#matomo-bootstrap-playwright-install
|
||||||
|
|
||||||
|
# 2) Run bootstrap (must print ONLY token)
|
||||||
|
nix run --no-write-lock-file -L .#matomo-bootstrap -- \\
|
||||||
|
--base-url '{MATOMO_URL}' \\
|
||||||
|
--admin-user '{ADMIN_USER}' \\
|
||||||
|
--admin-password '{ADMIN_PASSWORD}' \\
|
||||||
|
--admin-email '{ADMIN_EMAIL}' \\
|
||||||
|
--token-description 'e2e-test-token-nix'
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
"docker",
|
||||||
|
"compose",
|
||||||
|
"-f",
|
||||||
|
"tests/e2e/docker-compose.yml",
|
||||||
|
"exec",
|
||||||
|
"-T",
|
||||||
|
"nix",
|
||||||
|
"sh",
|
||||||
|
"-lc",
|
||||||
|
script,
|
||||||
|
]
|
||||||
|
|
||||||
|
token = subprocess.check_output(cmd).decode().strip()
|
||||||
|
self.assertRegex(token, r"^[a-f0-9]{32,64}$")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user