230 lines
7.4 KiB
Markdown
230 lines
7.4 KiB
Markdown
# Phase 1: Dev Environment — Plan
|
|
|
|
**Goal:** Developer can run `nix develop` and have a working Kimai instance with test data, ready for plugin development.
|
|
**Requirements:** DEV-01, DEV-02, DEV-03
|
|
**Research:** phase-1/RESEARCH.md
|
|
|
|
## Success Criteria
|
|
|
|
1. `nix develop` drops into a shell with PHP 8.2+, Composer, and Node available
|
|
2. A local Kimai instance starts and is accessible in the browser
|
|
3. The Kimai instance contains seeded time entry data spanning multiple months
|
|
4. A plugin directory is symlinked so code changes are reflected without reinstallation
|
|
|
|
## Waves
|
|
|
|
### Wave 1: Nix Flake + Infrastructure Scripts (autonomous)
|
|
|
|
#### Plan 01-01: Nix Flake and Dev Infrastructure
|
|
|
|
**Objective:** Create a reproducible dev environment with all dependencies and a one-command bootstrap.
|
|
|
|
**Task 1: Create Nix flake and devshell**
|
|
|
|
Create `flake.nix` at project root:
|
|
- Input: `nixpkgs` (use `github:NixOS/nixpkgs/nixpkgs-unstable`)
|
|
- DevShell packages:
|
|
- PHP 8.2 with extensions: `gd`, `intl`, `mbstring`, `pdo_mysql`, `xml`, `xsl`, `zip`, `tokenizer` — use `php82.buildEnv { extensions = { enabled, all }: enabled ++ (with all; [ xsl pdo_mysql ]); }`
|
|
- `php82Packages.composer`
|
|
- `nodejs_22`
|
|
- `mariadb` (11.4.x)
|
|
- `symfony-cli`
|
|
- `process-compose`
|
|
- Shell hook: print a message pointing to `dev/setup.sh` for first-time setup and `process-compose -f dev/process-compose.yaml up` for starting the stack
|
|
|
|
Create `.envrc` with `use flake` for direnv integration.
|
|
|
|
**Task 2: Create process-compose config**
|
|
|
|
Create `dev/process-compose.yaml`:
|
|
- `mariadb` process:
|
|
- Command: `mysqld --datadir=$PWD/dev/.mariadb-data --socket=$PWD/dev/.mariadb.sock --port=3307 --skip-grant-tables --skip-networking=0 --bind-address=127.0.0.1`
|
|
- Readiness probe: `mysqladmin --socket=$PWD/dev/.mariadb.sock ping` (initial delay 2s, period 1s)
|
|
- `kimai` process:
|
|
- Command: `symfony server:start --no-tls --dir=$PWD/dev/kimai --port=8010`
|
|
- Depends on: `mariadb` (condition: process_healthy)
|
|
|
|
**Task 3: Create setup script**
|
|
|
|
Create `dev/setup.sh` (executable):
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
KIMAI_DIR="$SCRIPT_DIR/kimai"
|
|
DATA_DIR="$SCRIPT_DIR/.mariadb-data"
|
|
|
|
# Step 1: Initialize MariaDB data directory
|
|
if [ ! -d "$DATA_DIR" ]; then
|
|
echo "==> Initializing MariaDB data directory..."
|
|
mysql_install_db --datadir="$DATA_DIR" --auth-root-authentication-method=normal
|
|
fi
|
|
|
|
# Step 2: Start MariaDB temporarily for setup
|
|
echo "==> Starting MariaDB for setup..."
|
|
mysqld --datadir="$DATA_DIR" --socket="$SCRIPT_DIR/.mariadb.sock" --port=3307 --skip-grant-tables --skip-networking=0 --bind-address=127.0.0.1 &
|
|
MARIADB_PID=$!
|
|
trap "kill $MARIADB_PID 2>/dev/null || true" EXIT
|
|
|
|
# Wait for MariaDB to be ready
|
|
for i in $(seq 1 30); do
|
|
if mysqladmin --socket="$SCRIPT_DIR/.mariadb.sock" ping 2>/dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Create database
|
|
mysql --socket="$SCRIPT_DIR/.mariadb.sock" -e "CREATE DATABASE IF NOT EXISTS kimai;"
|
|
|
|
# Step 3: Clone Kimai
|
|
if [ ! -d "$KIMAI_DIR" ]; then
|
|
echo "==> Cloning Kimai 2.52.0..."
|
|
git clone -b 2.52.0 --depth 1 https://github.com/kimai/kimai.git "$KIMAI_DIR"
|
|
fi
|
|
|
|
# Step 4: Configure Kimai
|
|
cat > "$KIMAI_DIR/.env.local" <<EOF
|
|
DATABASE_URL=mysql://root@127.0.0.1:3307/kimai?charset=utf8mb4&serverVersion=11.4.8-MariaDB
|
|
APP_SECRET=$(openssl rand -hex 16)
|
|
APP_ENV=dev
|
|
MAILER_URL=null://null
|
|
EOF
|
|
|
|
# Step 5: Install Composer dependencies
|
|
echo "==> Installing Composer dependencies..."
|
|
cd "$KIMAI_DIR"
|
|
composer install --no-interaction
|
|
|
|
# Step 6: Install Kimai (creates schema, runs migrations)
|
|
echo "==> Installing Kimai..."
|
|
bin/console kimai:install -n
|
|
|
|
# Step 7: Load test fixtures
|
|
echo "==> Loading test fixtures..."
|
|
bin/console kimai:reset:dev -n
|
|
|
|
# Step 8: Symlink plugin
|
|
echo "==> Symlinking plugin..."
|
|
mkdir -p var/plugins
|
|
ln -sfn "$PROJECT_DIR" var/plugins/KimaiHeatmapBundle
|
|
|
|
# Step 9: Clear cache
|
|
bin/console cache:clear
|
|
|
|
echo ""
|
|
echo "==> Setup complete!"
|
|
echo " Run: process-compose -f dev/process-compose.yaml up"
|
|
echo " Then open: http://127.0.0.1:8010"
|
|
echo " Login: susan_super / password"
|
|
```
|
|
|
|
**Task 4: Create .gitignore**
|
|
|
|
Create/update `.gitignore`:
|
|
```
|
|
dev/kimai/
|
|
dev/.mariadb-data/
|
|
dev/.mariadb.sock
|
|
dev/.mariadb.sock.lock
|
|
node_modules/
|
|
vendor/
|
|
.direnv/
|
|
```
|
|
|
|
**Commit:** `feat: add nix flake, process-compose, and setup script for dev environment`
|
|
|
|
**Verification:**
|
|
- `nix develop --command bash -c "php --version && composer --version && node --version"` succeeds
|
|
- `nix develop --command bash -c "which mariadb && which symfony && which process-compose"` succeeds
|
|
|
|
---
|
|
|
|
### Wave 2: Kimai Instance + Plugin Scaffold (checkpoint required)
|
|
|
|
#### Plan 01-02: Bootstrap Kimai and Create Plugin Scaffold
|
|
|
|
**Objective:** Get Kimai running with test data and the plugin recognized.
|
|
|
|
**Task 1: Create minimal plugin scaffold**
|
|
|
|
Create these files at project root (the project root IS the plugin bundle):
|
|
|
|
`KimaiHeatmapBundle.php`:
|
|
```php
|
|
<?php
|
|
|
|
namespace KimaiPlugin\KimaiHeatmapBundle;
|
|
|
|
use App\Plugin\PluginInterface;
|
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
|
|
|
class KimaiHeatmapBundle extends Bundle implements PluginInterface
|
|
{
|
|
}
|
|
```
|
|
|
|
`composer.json`:
|
|
```json
|
|
{
|
|
"name": "kimai-plugin/heatmap-bundle",
|
|
"type": "kimai-plugin",
|
|
"description": "GitHub-style activity heatmap dashboard widget for Kimai",
|
|
"license": "MIT",
|
|
"autoload": {
|
|
"psr-4": {
|
|
"KimaiPlugin\\KimaiHeatmapBundle\\": ""
|
|
}
|
|
},
|
|
"extra": {
|
|
"kimai": {
|
|
"require": 25200,
|
|
"name": "Activity Heatmap"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Commit:** `feat: add minimal plugin scaffold (bundle class + composer.json)`
|
|
|
|
**Task 2: Run setup and verify (CHECKPOINT — requires manual browser verification)**
|
|
|
|
1. Enter devshell: `nix develop`
|
|
2. Run setup: `bash dev/setup.sh`
|
|
3. Start stack: `process-compose -f dev/process-compose.yaml up`
|
|
4. Open browser: `http://127.0.0.1:8010`
|
|
5. Login: `susan_super` / `password`
|
|
|
|
**Verification checklist:**
|
|
- [ ] Kimai dashboard loads in browser
|
|
- [ ] `cd dev/kimai && bin/console kimai:plugins` lists "Activity Heatmap"
|
|
- [ ] `cd dev/kimai && bin/console doctrine:query:sql "SELECT COUNT(*) FROM kimai2_timesheet"` returns > 0 rows
|
|
- [ ] Plugin appears in Kimai admin > Plugins page
|
|
|
|
## Requirement Coverage
|
|
|
|
| Requirement | Plan | Verified By |
|
|
|-------------|------|-------------|
|
|
| DEV-01 | 01-01 | `nix develop` provides PHP 8.2+, Composer, Node |
|
|
| DEV-02 | 01-02 | `kimai:reset:dev` loads fixtures; SQL count check |
|
|
| DEV-03 | 01-02 | `kimai:plugins` lists the plugin; plugin visible in admin |
|
|
|
|
## Risks
|
|
|
|
| Risk | Mitigation |
|
|
|------|------------|
|
|
| MariaDB local datadir doesn't work | Fall back to `--socket` only or use `mysql_install_db --user=$(whoami)` |
|
|
| PHP extension missing in Nix | Add explicit extensions to `php.buildEnv`; iterate based on `composer install` errors |
|
|
| Kimai 2.52.0 incompatible with MariaDB 11.4 | Try `mariadb_1011` (10.11.x) from nixpkgs as fallback |
|
|
| `kimai:reset:dev` fixture count too low | Acceptable for dev — supplement with API calls later if needed |
|
|
|
|
## Notes
|
|
|
|
- Timebox this phase to 1 day. If Nix+MariaDB setup takes longer, consider Docker fallback.
|
|
- The plugin scaffold is intentionally minimal — just enough for Kimai to recognize it. Real functionality comes in Phase 2.
|
|
- `APP_ENV=dev` enables Symfony's auto-recompilation, so cache:clear is only needed after initial plugin symlink.
|
|
|
|
---
|
|
*Plan created: 2026-04-08*
|