Agiladmin is a Clojure web application for payroll, timesheet, and
project administration. It imports monthly .xlsx timesheets, loads
project definitions from YAML files in a Git-backed budgets
repository, computes hours and costs, and renders HTML reports for
personnel and projects.
The current codebase targets org.clojure/clojure 1.12.4 and starts
with the Clojure CLI. Authentication is backend-driven: PocketBase is
supported for real deployments, and a development-only fallback
backend is available for local manual testing.
- Runtime: Ring + Compojure on Jetty
- Data processing: core.matrix, Docjure / Apache POI, YAML files
- Storage model:
- project metadata and uploaded spreadsheets live in a Git-managed budgets directory
- authentication is handled through a backend abstraction, with PocketBase currently implemented
- Build: Clojure CLI with
deps.edn - Tests: Midje
The app is well tested and fairly stateful. Startup performs real side effects:
- configuration is loaded from YAML
- an SSH keypair is generated if the configured private key does not exist
- the authentication backend is initialized and health-checked
- Java (JRE) and the Clojure CLI
- a writable budgets Git checkout or clone target in
budgets/ - a valid
agiladmin.yamlconfiguration file, or an explicit config path viaAGILADMIN_CONF - for real auth flows: a reachable PocketBase instance
The main entry point is:
clj -M:runThat runs agiladmin.main, which initializes the app and starts Jetty with the host and port from config.
For manual testing without PocketBase:
make runThis sets AGILADMIN_DEV_AUTH=1 and enables a simple in-memory development auth backend.
Use these credentials:
admin:adminmanager:managerguest:guest
The dev auth backend only supports sign-in. Sign-up and pending-user flows are intentionally disabled there.
The repository ships an example config at doc/agiladmin.pocketbase.yaml.
Run with it using:
make run-pocketbase CONF=doc/agiladmin.pocketbase.yamlOr directly:
AGILADMIN_CONF=doc/agiladmin.pocketbase.yaml clj -M:runPocketBase is optional in config, but without either PocketBase or AGILADMIN_DEV_AUTH=1, authentication is not initialized and login will not work.
Agiladmin expects the PocketBase users auth collection to have a role select field. Supported values are admin, manager, or empty.
Role-based features are enabled from the PocketBase user record returned at login. If a user role changes in PocketBase, they need to log out and log back in before Agiladmin sees the change.
Current role logic:
admin: can reach the personnel list, project views, reload, configuration, and project editingmanager: can reach project views and is routed to their own personnel page when opening the personnel landing- empty role or regular user: is limited to their own personnel page
- the shared home entry point for authenticated users is
/persons/list; that route sends admins to the personnel list and everyone else to their own person view
Initialize the users collection field on a fresh PocketBase instance with:
AGILADMIN_CONF=doc/agiladmin.pocketbase.yaml clj -M -m agiladmin.pocketbase-initIf agiladmin.pocketbase.manage-process is true, Agiladmin starts PocketBase itself, serves it with the configured migrations directory, waits for health, applies the role bootstrap when the installed Agiladmin version changes, and stops PocketBase again on exit.
PocketBase HTTP calls use bounded timeouts by default so startup does not hang forever if the service is unreachable. Override them with agiladmin.pocketbase.connect-timeout-ms and agiladmin.pocketbase.socket-timeout-ms if needed.
Packaging and systemd deployment notes live in packaging/README.md.
Run the test suite with:
clj -M:testOr:
make testThe current test suite covers:
- config loading and validation
- spreadsheet ingestion and cost derivation
- auth backends and session behavior
- selected view logic
ring/initstartup behavior
It does not comprehensively cover route behavior, Git push side effects, or frontend rendering.
Build the standalone jar with:
make buildThis produces:
target/0.4.0-SNAPSHOT-standalone.jar
Run the built jar with:
java -jar target/<version>-standalone.jarOr with an explicit config file:
AGILADMIN_CONF=doc/agiladmin.pocketbase.yaml java -jar target/<version>-standalone.jarThe jar uses the same config lookup as clj -M:run: by default it looks for agiladmin.yaml in the standard locations, including the current working directory.
By default the app looks for agiladmin.yaml in standard locations,
including the current working directory. You can also point to an
explicit YAML file path with AGILADMIN_CONF.
Example:
appname: agiladmin
agiladmin:
webserver:
host: localhost
port: 8000
anti-forgery: false
ssl-redirect: false
budgets:
git: ssh://git@example.org/admin-budgets
ssh-key: id_rsa
path: budgets/
source:
git: https://github.com/dyne/agiladmin
update: false
pocketbase:
base-url: http://127.0.0.1:8090
users-collection: users
superuser-email: admin@example.org
superuser-password: change-meNotes:
budgets.ssh-keyis the private key path used for Git access; if it does not exist, Agiladmin generates a new keypair and exposes the public key in the/configpage- project names are discovered from
*.yamlfiles inbudgets.path, using the part of the filename before the first. pocketbaseis optional only if you are using dev auth locally
Each project is discovered from a YAML file in the budgets path, usually
<PROJECT>.yaml.
Minimal example:
CORE:
start_date: 01-01-2025
duration: 12
cph: 50
rates:
A.User: 55
B.User: 45
tasks: []Task-based example:
CORE:
start_date: 01-01-2025
duration: 12
cph: 50
rates:
A.User: 55
tasks:
- id: T1
text: Coordination
start_date: 01-01-2025
duration: 12
pm: 1Notes:
- task ids are normalized to uppercase internally
- the loader also accepts a direct-entry file shape where the file contains the project entry itself rather than a top-level project key
- spreadsheet parsing is position-based and depends on the current Excel template layout
- src/agiladmin/handlers.clj: main HTTP routes
- src/agiladmin/ring.clj: initialization and middleware defaults
- src/agiladmin/core.clj: spreadsheet and project logic
- src/agiladmin/view_timesheet.clj: upload and Git commit flow
- src/agiladmin/auth/pocketbase.clj: PocketBase auth backend
- pb_migrations/: PocketBase schema migrations kept for future schema changes
- test/agiladmin/: Midje test suite
- Timesheet upload and commit logic writes temporary files under
/tmp/... - The budgets repository is mutable application state; timesheet submission performs Git operations
- PocketBase-backed role-aware access depends on a
roleselect field on the auth users collection - Managed PocketBase mode uses a local version marker file to record that the current Agiladmin version has applied its bootstrap step
- The app serves a bundled static HTML README on
/, so updating this file does not automatically change the in-app landing page
Copyright (C) 2016-2026 Dyne.org foundation
Sourcecode written and maintained by Denis Roio jaromil@dyne.org
Designed in cooperation with Manuela Annibali manuela@dyne.org
