19 KiB
Phase 1: Dev Environment - Research
Researched: 2026-04-08 Domain: Nix flake devshell, Kimai 2.x local instance, MariaDB, PHP 8.2+, data seeding Confidence: HIGH
Summary
Phase 1 is about getting a reproducible dev environment where nix develop gives you a working Kimai instance with test data ready for plugin development. The key findings are:
- Kimai requires MySQL/MariaDB -- SQLite support was removed years ago. The Nix flake must provision a local MariaDB instance (11.4.x available in nixpkgs). This is the single biggest complexity driver for this phase.
- Kimai 2.52.0 is the current latest stable release (March 2025), requiring PHP 8.1-8.5 and Symfony 6.4. PHP 8.2.29 is already available in the system nixpkgs as
php82. - Kimai ships built-in fixtures (
bin/console kimai:reset:devloads Doctrine fixtures that create ~100-1000 timesheet entries per user across 3 years of data). No need to hand-roll a seed script. - Plugins live in
var/plugins/with a symlink from the project directory. The namespace must beKimaiPlugin\<BundleName>and the bundle class must implementApp\Plugin\PluginInterface.
Primary recommendation: Use a shell script (dev/start.sh) invoked from the Nix devshell that starts MariaDB in a local data directory, runs Kimai install + fixture loading, symlinks the plugin, and starts the Symfony dev server. Use process-compose to manage MariaDB + web server lifecycle.
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| DEV-01 | Nix flake/devshell provides a running local Kimai instance | Nix flake with php82, mariadb, composer, nodejs, symfony-cli, process-compose; shell hook bootstraps Kimai |
| DEV-02 | Local Kimai has a seeded database with realistic time entry data | bin/console kimai:reset:dev loads built-in Doctrine fixtures with thousands of timesheet entries |
| DEV-03 | Plugin is loadable in the local Kimai instance for manual testing | Symlink project dir into Kimai's var/plugins/KimaiHeatmapBundle, clear cache |
| </phase_requirements> |
Standard Stack
Core (Nix Flake Packages)
| Package | Nixpkgs Attr | Version | Purpose |
|---|---|---|---|
| PHP | php82 |
8.2.29 | Kimai runtime (with extensions: gd, intl, mbstring, pdo_mysql, xml, xsl, zip, tokenizer) |
| MariaDB | mariadb |
11.4.8 | Database server (Kimai dropped SQLite support) |
| Composer | php82Packages.composer |
2.8.x | PHP dependency management |
| Node.js | nodejs |
22.x | JS tooling for d3/esbuild (later phases) |
| npm | (bundled with nodejs) | 10.x | JS package manager |
| symfony-cli | symfony-cli |
5.16.0 | Dev web server with PHP integration |
| process-compose | process-compose |
1.78.0 | Process manager for MariaDB + web server |
Kimai
| Property | Value | Source |
|---|---|---|
| Latest stable | 2.52.0 | [VERIFIED: github.com/kimai/kimai/releases] |
| PHP support | 8.1 - 8.5 | [VERIFIED: composer.json require] |
| Symfony version | 6.4 LTS | [VERIFIED: composer.json extra] |
| Database | MySQL 8.3+ or MariaDB 11.1+ | [CITED: kimai.org/documentation/developers.html] |
| SQLite | NOT supported (removed 2021) | [VERIFIED: kimai.org/en/blog/2021/sqlite-and-ftp-support-removed] |
Required PHP Extensions
From Kimai's composer.json [VERIFIED]:
ext-gd, ext-intl, ext-json, ext-mbstring, ext-pdo, ext-tokenizer, ext-xml, ext-xsl, ext-zip
Plus pdo_mysql for MariaDB connectivity.
Architecture Patterns
Nix Flake Structure
flake.nix # PHP, MariaDB, Composer, Node, symfony-cli, process-compose
flake.lock
dev/
process-compose.yaml # MariaDB + symfony web server process definitions
setup.sh # One-time: clone Kimai, composer install, DB setup, fixtures, symlink plugin
seed.sh # Re-run fixtures without full setup
Pattern 1: Local MariaDB with Data Directory in Project
What: MariaDB runs from a local data directory (./dev/.mariadb-data/) using mysqld --datadir without needing root or system-level MySQL installation. [ASSUMED]
When to use: Always for this project -- avoids polluting the system.
Example:
# Initialize MariaDB data directory
mysql_install_db --datadir=./dev/.mariadb-data --auth-root-authentication-method=normal
# Start MariaDB on a non-standard port (to avoid conflicts)
mysqld --datadir=./dev/.mariadb-data --socket=./dev/.mariadb.sock --port=3307 --skip-grant-tables
Pattern 2: Kimai Clone as Git-Ignored Dependency
What: Clone Kimai into ./dev/kimai/ (git-ignored), run composer install there, then symlink the plugin directory into ./dev/kimai/var/plugins/KimaiHeatmapBundle.
Why: Keeps the host application separate from plugin source. Kimai is a dependency, not part of the plugin repo.
dev/
kimai/ # .gitignore'd -- cloned Kimai instance
var/
plugins/
KimaiHeatmapBundle -> ../../../../ # symlink to project root
Pattern 3: Plugin Symlink for Live Development
What: Symlink the project root (which IS the plugin bundle) into Kimai's var/plugins/ directory. [CITED: kimai.org/documentation/plugins.html]
Why: Code changes in the plugin are immediately reflected. No copy/install step needed.
ln -sf "$(pwd)" ./dev/kimai/var/plugins/KimaiHeatmapBundle
Critical: The symlink target directory name MUST match the bundle class name (KimaiHeatmapBundle).
Pattern 4: process-compose for Multi-Service Dev
What: process-compose manages MariaDB and Symfony dev server as a single dev stack.
Why: Both services need to run simultaneously. process-compose handles startup order, log aggregation, and clean shutdown. Better than manual terminal management.
# dev/process-compose.yaml
version: "0.5"
processes:
mariadb:
command: mysqld --datadir=./dev/.mariadb-data --socket=./dev/.mariadb.sock --port=3307
readiness_probe:
exec:
command: mysqladmin --socket=./dev/.mariadb.sock ping
initial_delay_seconds: 2
period_seconds: 1
kimai:
command: symfony server:start --no-tls --dir=./dev/kimai
depends_on:
mariadb:
condition: process_healthy
Anti-Patterns to Avoid
- Using SQLite for dev: Kimai dropped SQLite support. Migrations will fail. No workaround. [VERIFIED]
- Installing Kimai globally or via Composer require: Kimai is the host application, not a library. Clone it.
- Trying to Nixify Composer dependencies: Let Composer manage
vendor/normally inside the Kimai clone. Nix provides the PHP binary and extensions only. - Running MariaDB as a system service: Use a local data directory. No root, no systemd, no conflicts with existing databases.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| Test data seeding | Custom SQL insert scripts | bin/console kimai:reset:dev |
Kimai ships Doctrine fixtures that create users, customers, projects, activities, and 100-1000+ timesheet entries per user spanning 3 years [VERIFIED: TimesheetFixtures.php] |
| PHP dev web server | nginx/Apache config | symfony server:start |
Handles PHP-FPM, routing, and .env loading automatically [VERIFIED: nixpkgs symfony-cli 5.16.0] |
| Process management | Shell scripts with & and trap |
process-compose |
Handles startup ordering, health checks, log aggregation, clean shutdown [VERIFIED: nixpkgs process-compose 1.78.0] |
| MariaDB initialization | Manual SQL DDL | mysql_install_db + kimai:install |
Standard tooling that handles system tables, migrations, and schema creation |
Database Schema (for Seeding Context)
Key tables for timesheet data [VERIFIED: Kimai source Entity classes]:
| Table | Key Columns | Notes |
|---|---|---|
kimai2_timesheet |
id, start_time, end_time, duration, timezone, date_tz, user (FK), project_id (FK), activity_id (FK), rate, billable, exported |
Duration in seconds |
kimai2_customers |
id, name, country, currency, timezone, visible |
Required parent for projects |
kimai2_projects |
id, name, customer_id (FK), visible, billable |
Required parent for activities |
kimai2_activities |
id, name, project_id (FK, nullable), visible, billable |
Can be global (null project) or project-specific |
kimai2_users |
id, username, email, roles, password, timezone |
Created via kimai:user:create |
The kimai:reset:dev command loads Doctrine fixtures that populate ALL these tables with realistic data, including:
- 5 users (clara_customer, john_user, tony_teamlead, anna_admin, susan_super) -- all with password "password" [CITED: kimai.org/documentation/developers.html]
- Multiple customers, projects, and activities
- 100-1000 timesheet entries per user spanning ~3 years back [VERIFIED: TimesheetFixtures.php source]
Kimai Setup Commands (Exact Sequence)
# 1. Clone Kimai at specific version
git clone -b 2.52.0 --depth 1 https://github.com/kimai/kimai.git dev/kimai
# 2. Configure environment
cp dev/kimai/.env.dist dev/kimai/.env
# Edit .env: set DATABASE_URL, APP_SECRET
# 3. Install PHP dependencies (--no-dev is fine for plugin dev; add --dev if you need Kimai's own test tools)
cd dev/kimai && composer install --no-dev --optimize-autoloader
# 4. Create database and run migrations
bin/console kimai:install -n
# 5. Load demo fixtures (drops and recreates schema with test data!)
bin/console kimai:reset:dev
# 6. Symlink plugin
ln -sf "$(pwd)/../../" var/plugins/KimaiHeatmapBundle
# 7. Clear cache (required after adding plugin)
bin/console cache:clear
DATABASE_URL format for local MariaDB:
DATABASE_URL=mysql://root@127.0.0.1:3307/kimai?charset=utf8mb4&serverVersion=11.4.8-MariaDB
Common Pitfalls
Pitfall 1: MariaDB Socket vs TCP Connection
What goes wrong: Kimai tries to connect via Unix socket (default MySQL behavior) but the local MariaDB is listening on a custom socket path or TCP port.
Why it happens: Default MySQL client config looks for /var/run/mysqld/mysqld.sock which doesn't exist in a Nix devshell.
How to avoid: Use TCP connection (127.0.0.1:3307) in DATABASE_URL, not localhost (which triggers socket mode on Linux). Or set --socket path explicitly.
Warning signs: "Can't connect to local MySQL server through socket" errors.
Pitfall 2: Kimai Cache Issues After Plugin Symlink
What goes wrong: After symlinking the plugin, Kimai doesn't see it or throws container compilation errors.
Why it happens: Symfony caches the container, routes, and bundle list. A new plugin requires cache clear.
How to avoid: Always run bin/console cache:clear after symlinking. In dev, set APP_ENV=dev to enable auto-recompilation.
Warning signs: Plugin not listed in admin, or DI errors referencing old cached container.
Pitfall 3: PHP Extension Mismatch in Nix
What goes wrong: Kimai fails at runtime with "extension not found" errors even though you listed extensions in flake.nix.
Why it happens: Nix PHP needs explicit extension configuration via php.withExtensions or php.buildEnv. Simply adding php82 doesn't include all extensions.
How to avoid: Build a custom PHP with required extensions:
php = pkgs.php82.buildEnv {
extensions = { enabled, all }: enabled ++ (with all; [ xsl ]);
};
The default enabled set includes most common extensions (gd, intl, mbstring, pdo, xml, zip, etc.) but xsl is often missing. [ASSUMED]
Warning signs: composer install fails, or Kimai throws "class not found" for Intl/GD operations.
Pitfall 4: kimai:reset:dev Drops Everything
What goes wrong: Running kimai:reset:dev drops the entire database schema and reloads fixtures. Developers run it thinking it adds data on top of existing data.
Why it happens: The command runs doctrine:fixtures:load which purges all tables first.
How to avoid: Only run during initial setup or when you explicitly want a clean slate. For adding entries, write a separate seed script or use Kimai's API.
Warning signs: Custom data disappears after running the command.
Pitfall 5: Plugin Directory Name Must Match Bundle Class
What goes wrong: Symlink named heatmap-plugin or KimaiHeatmap instead of KimaiHeatmapBundle. Kimai's plugin loader silently ignores it.
Why it happens: Kimai expects the directory name in var/plugins/ to match the bundle class name exactly.
How to avoid: Name the symlink KimaiHeatmapBundle to match the class KimaiHeatmapBundle.php.
Warning signs: Plugin not appearing in Kimai admin, no errors in logs.
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | PHPUnit (match Kimai's version) |
| Config file | None yet -- Wave 0 task |
| Quick run command | php vendor/bin/phpunit --filter TestName |
| Full suite command | php vendor/bin/phpunit |
Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| DEV-01 | Nix devshell provides PHP, Composer, Node | smoke | nix develop --command bash -c "php --version && composer --version && node --version" |
N/A (manual) |
| DEV-02 | Seeded database has timesheet entries | smoke | cd dev/kimai && bin/console doctrine:query:sql "SELECT COUNT(*) FROM kimai2_timesheet" |
N/A (manual) |
| DEV-03 | Plugin is loadable | smoke | cd dev/kimai && bin/console kimai:plugins (should list the plugin) |
N/A (manual) |
Wave 0 Gaps
- None for this phase -- DEV-01/02/03 are infrastructure tasks verified by smoke commands, not unit tests.
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| Nix | Flake/devshell | Yes | 2.93.3 (Lix) | -- |
| PHP 8.2 | Kimai runtime | Yes (system) | 8.2.29 | Provided by Nix flake |
| Composer | PHP deps | Yes (system) | 2.8.12 | Provided by Nix flake |
| Node.js | JS tooling | Yes (system) | 22.21.1 | Provided by Nix flake |
| MariaDB | Database | No (not installed) | -- | Provided by Nix flake (11.4.8 available) |
| symfony-cli | Dev server | No (not installed) | -- | Provided by Nix flake (5.16.0 available) |
| process-compose | Process mgmt | No (not installed) | -- | Provided by Nix flake (1.78.0 available) |
Missing dependencies with no fallback: None -- all provided by the Nix flake.
Plugin Loading Mechanism
Kimai discovers plugins automatically from var/plugins/. Key details [CITED: kimai.org/documentation/plugins.html]:
- Plugin directory must contain a bundle class that
implements PluginInterface - Namespace must be
KimaiPlugin\<BundleName>(theKimaiPluginvendor prefix is mandatory) - Routes auto-load from
Resources/config/routes.{php,yaml}inside the plugin directory - Plugins load in ALL environments without modifying
config/bundles.php - The
composer.jsonmust have"type": "kimai-plugin"and anextra.kimaisection withrequire(minimum Kimai version as integer: major10000 + minor100 + patch) andname
Minimum Plugin composer.json
{
"name": "kimai-plugin/heatmap-bundle",
"type": "kimai-plugin",
"description": "GitHub-style activity heatmap dashboard widget",
"license": "MIT",
"autoload": {
"psr-4": {
"KimaiPlugin\\KimaiHeatmapBundle\\": ""
}
},
"extra": {
"kimai": {
"require": 25200,
"name": "Activity Heatmap"
}
}
}
Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|---|---|---|
| A1 | MariaDB can run with --datadir pointing to a local project directory without root |
Architecture Patterns | HIGH -- would need alternative DB setup approach (Docker, system service) |
| A2 | Nix php82 default enabled extensions cover most of Kimai's requirements except xsl |
Common Pitfalls | MEDIUM -- may need to explicitly list more extensions |
| A3 | symfony server:start works with the Nix-provided PHP binary |
Architecture Patterns | LOW -- symfony-cli auto-detects PHP; fallback is php -S |
Open Questions
-
Exact
php.buildEnvextension list needed- What we know: Kimai requires gd, intl, mbstring, pdo_mysql, xml, xsl, zip, tokenizer, json
- What's unclear: Which of these are in Nix's default
enabledset vs. needing explicit addition - Recommendation: Start with
enabled ++ [xsl pdo_mysql]and iterate ifcomposer installfails
-
MariaDB version compatibility with Kimai migrations
- What we know: Kimai docs say MariaDB 11.1+; nixpkgs has 11.4.8
- What's unclear: Whether any migration uses MySQL-specific syntax that breaks on MariaDB 11.4
- Recommendation: Use 11.4.8 (latest in nixpkgs); if issues arise, try 10.11.14 (
mariadb_1011)
Sources
Primary (HIGH confidence)
- GitHub kimai/kimai releases -- version 2.52.0 confirmed
- Kimai composer.json -- PHP/Symfony versions, extensions
- Kimai Entity/Timesheet.php -- database schema
- Kimai Entity/Customer.php -- customer table schema
- Kimai Entity/Project.php -- project table schema
- Kimai Entity/Activity.php -- activity table schema
- Kimai WidgetInterface.php -- widget API
- Kimai TimesheetFixtures.php -- fixture data details
- Kimai DemoBundle -- reference plugin implementation
Secondary (MEDIUM confidence)
- Kimai plugin documentation -- plugin loading mechanism
- Kimai installation docs -- setup commands
- Kimai developer docs -- dev environment, fixture commands
- Kimai SQLite removal announcement -- database requirements
- loophp/nix-shell -- Nix PHP packaging reference
Tertiary (LOW confidence)
- None
Metadata
Confidence breakdown:
- Standard stack: HIGH -- versions verified against nixpkgs and Kimai source
- Architecture: HIGH for Kimai plugin mechanics (verified against source), MEDIUM for Nix MariaDB setup (assumed pattern)
- Pitfalls: HIGH -- based on verified Kimai constraints (no SQLite, cache clearing, fixture behavior)
Research date: 2026-04-08 Valid until: 2026-05-08 (Kimai releases roughly monthly; pin to 2.52.0)