No description
  • PHP 90.8%
  • Shell 9.2%
Find a file
Nicky Hendriks c29a78b6d5
All checks were successful
Build and deploy / deploy (push) Successful in 1s
feat: add sudo to privileged commands for forgejo runner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 22:26:23 +02:00
.forgejo/workflows feat: add sudo to privileged commands for forgejo runner 2026-04-07 22:26:23 +02:00
api PDF export, year overview, calendar fixes, language strings cleanup 2026-04-05 08:54:39 +02:00
cron Initial commit 2026-03-30 15:31:05 +02:00
includes Update includes/caldav.php 2026-04-04 21:47:38 +02:00
install Add install/migrations/002_add_times.sql 2026-04-04 21:48:20 +02:00
lang Language file cleanup, year overview, PDF export 2026-04-05 08:06:38 +02:00
lib PDF export, year overview, calendar fixes, language strings cleanup 2026-04-05 08:54:39 +02:00
static/fonts Ship setup.php with self-lock, update README 2026-03-30 17:25:17 +02:00
templates Update templates/styles.php 2026-04-05 09:04:09 +02:00
.gitignore Ship setup.php with self-lock, update README 2026-03-30 17:25:17 +02:00
.gitkeep Initial commit 2026-03-30 15:31:05 +02:00
.htaccess Allow cron endpoint through .htaccess 2026-03-31 08:30:47 +02:00
admin.php Add bilingual support (NL/EN) and logos 2026-03-30 17:36:12 +02:00
bootstrap.sh Add functionalities: skip all-day events, add options to skip categories as well 2026-03-30 16:50:38 +02:00
config.example.php Make english by default, Dutch as option 2026-03-30 17:43:28 +02:00
dashboard.php Add bilingual support (NL/EN) and logos 2026-03-30 17:36:12 +02:00
deploy.sh Skip all-day events, add category exclude rules, add deploy script 2026-03-30 16:46:48 +02:00
entries.php Fixed font errors in pdf export 2026-04-05 08:49:16 +02:00
index.php Initial commit 2026-03-30 15:31:05 +02:00
login.php Add bilingual support (NL/EN) and logos 2026-03-30 17:36:12 +02:00
logout.php Initial commit 2026-03-30 15:31:05 +02:00
overview.php Fixed height on stat boxes 2026-04-05 09:05:07 +02:00
README.md Updated readme 2026-03-31 08:11:53 +02:00
settings.php Add bilingual support (NL/EN) and logos 2026-03-30 17:36:12 +02:00

Urentracker

Hour tracking for Dutch freelancers (ZZP) to meet the 1225-hour annual criterion for tax deductions.

Sync your CalDAV calendar, set exclude rules by keyword, calendar name or category, add hours manually, and export to CSV. Multi-user with admin panel. Built on PHP 8.3 + MySQL, no Composer required.

Requirements

  • PHP 8.3+ with extensions: pdo_mysql, openssl, curl
  • MySQL 8.0+ or MariaDB 10.6+
  • Web server: Apache (with mod_rewrite) or nginx

Installation

1. Clone the repo

git clone <repo-url> /var/www/urentracker

2. Create config

cp config.example.php config.php
nano config.php

Fill in: database credentials, app URL, a random cron_token, and your preferred default language:

openssl rand -hex 32

3. Create database

mysql -u root -p <<EOF
CREATE DATABASE urentracker CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'urentracker'@'localhost' IDENTIFIED BY 'strong-password';
GRANT ALL ON urentracker.* TO 'urentracker'@'localhost';
EOF

mysql -u urentracker -p urentracker < install/schema.sql

4. Configure web server

Apache — make sure mod_rewrite is enabled and AllowOverride All is set for the document root. The .htaccess handles the rest.

nginx — use install/nginx.conf.example as a starting point.

5. Create first admin account

Visit /install/setup.php in your browser. The wizard creates the first admin account and then automatically blocks itself — nothing to delete.

6. Set up cron

Add to crontab for automatic CalDAV sync:

0 * * * * curl -sf "https://your-domain.com/cron/sync.php?token=YOUR_CRON_TOKEN"

Or via a Forgejo Actions workflow — see .forgejo/workflows/cron.yml.example.

Usage

Page What
/dashboard.php Progress ring, monthly bars, recent hours
/entries.php All entries, edit, add manually, CSV export
/settings.php CalDAV, exclude rules, password, hours goal
/admin.php Create and manage users

Exclude rules

Three rule types to exclude calendar events from the hours criterion:

Type Behaviour
Keyword Excludes events whose title contains this word (case-insensitive)
Calendar name Excludes all events from a specific calendar
Category Excludes based on the CATEGORIES tag in the calendar event

Excluded entries remain visible but do not count toward the total.

CalDAV URL formats

Server URL
Nextcloud https://cloud.example.com/remote.php/dav/calendars/USER/CALENDAR/
Radicale https://cal.example.com/USER/CALENDAR/
Baikal https://baikal.example.com/cal.php/calendars/USER/CALENDAR/
Fastmail https://caldav.fastmail.com/dav/principals/user/EMAIL/
iCloud https://caldav.icloud.com/ (use app-specific password)

Configuration reference

All configuration lives in config.php (copied from config.example.php):

'db' => [
    'host'    => 'localhost',
    'port'    => 3306,
    'name'    => 'urentracker',
    'user'    => 'urentracker',
    'pass'    => 'your-password',
],
'app' => [
    'url'          => 'https://hours.example.com',
    'cron_token'   => 'random-secret',   // openssl rand -hex 32
    'session_ttl'  => 28800,             // 8 hours
    'default_lang' => 'en',              // 'en' or 'nl'
],

File structure

urentracker/
├── config.php              ← Not in git, create from config.example.php
├── config.example.php
├── index.php
├── login.php / logout.php
├── dashboard.php
├── entries.php
├── settings.php
├── admin.php
├── includes/
│   ├── bootstrap.php       ← DB, auth, i18n helpers
│   └── caldav.php          ← CalDAV sync engine
├── templates/
│   ├── layout.php
│   └── styles.php
├── lang/
│   ├── en.php              ← English strings
│   └── nl.php              ← Dutch strings
├── api/
│   ├── sync.php            ← Manual sync
│   └── export.php          ← CSV export
├── cron/
│   └── sync.php            ← Cron sync endpoint
├── install/
│   ├── schema.sql
│   ├── setup.php           ← Self-locks after first use
│   └── nginx.conf.example
└── static/
    └── fonts/

License

MIT