taskemon

module
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 8, 2026 License: EUPL-1.2

README

Taskemon

Taskemon is a self-hosted task manager with hidden Pokémon rewards and optional ESC/POS thermal printing. Creating a task generates a reward through PokéAPI; completing the task reveals it and adds it to the user's collection.

This repository contains the Go backend and its command-line setup client. Web and Home Assistant clients can use the HTTP API, but they are not included here.

Project status

Taskemon is pre-1.0 software. Its API, configuration, and database schema may still change between minor releases.

The backend currently includes account management, browser sessions, API keys, task ownership, rewards, statistics, SQLite persistence, and USB thermal-printer support. It does not include an official web frontend, Home Assistant integration, Docker image, OpenAPI specification, or CI workflow.

Features

  • First-run setup for the initial administrator and API key
  • Intentional public registration for standard user accounts
  • Argon2id password hashing and login rate limiting
  • Browser sessions with CSRF protection
  • Bearer API keys suitable for scripts and Home Assistant
  • Administrator account and credential management
  • Per-user task creation, bulk creation, listing, updating, completion, deletion, and printing
  • Hidden Pokémon rewards revealed only when tasks are completed
  • Pokémon collection and task statistics
  • SQLite storage with embedded Goose migrations
  • Optional direct USB ESC/POS printing with configurable ticket layout
  • Explicit CORS origins, security headers, and trusted-proxy handling

Requirements

  • Go 1.25.7 or newer
  • A C compiler, pkg-config, and the libusb 1.0 development files when building the current project
  • Network access to https://pokeapi.co when creating tasks
  • Optional: a compatible USB ESC/POS printer and permission to access its USB device

The SQLite driver is implemented in Go. A separately installed SQLite library is not required.

Installation

Clone and build the project:

git clone https://github.com/Renan-M-Fernandes/taskemon.git
cd taskemon
go mod download
make build

The binary is written to build/taskemon.

To install it under /usr/local/bin:

sudo make install

The installation prefix can be overridden with the Make variable PREFIX.

Quick start

Copy the example configuration:

cp config.example.json config.json

The example disables physical printing. Review the server address, CORS origins, cookie security, and database path before exposing the service outside a trusted local network.

Build and start the server:

make build
./build/taskemon serve --config config.json

On an empty database, the server logs a one-time initial setup token. Keep the server running, open another terminal, and run:

./build/taskemon setup \
  --url http://localhost:8080 \
  --username admin \
  --display-name "Administrator" \
  --timezone America/Sao_Paulo \
  --api-key-name "Home Assistant"

The command securely prompts for the setup token and password, then asks for confirmation. The generated API key is displayed once; save it before closing the terminal.

Check the service:

curl http://localhost:8080/api/v1/health

Use the saved API key:

curl \
  -H 'Authorization: Bearer tsk_change-me' \
  http://localhost:8080/api/v1/tasks/open

Configuration

Configuration file

Taskemon reads JSON configuration:

  • taskemon starts the server with config.json.
  • taskemon serve also uses config.json.
  • taskemon serve --config /path/to/config.json uses another file.
  • If the selected file does not exist, Taskemon runs with built-in defaults.

A partial configuration overrides the corresponding defaults. Unknown fields, invalid JSON, and multiple JSON values are rejected. Relative database paths are resolved from the server's working directory.

No configuration field is required because every field has a default. Initial administrator setup is still required before protected API routes can be used.

Environment variables

The application does not currently read environment variables. Use the configuration file and CLI flags. Variables such as VERSION, PREFIX, DESTDIR, and GO are Makefile inputs, not runtime application settings.

Core defaults
Setting Default Notes
server.host 0.0.0.0 Listens on every interface.
server.port 8080 Must be between 1 and 65535.
server.corsOrigins ["http://localhost:8123"] Exact HTTP or HTTPS origins only; wildcards are rejected.
server.trustedProxyCIDRs [] Proxies allowed to supply client addresses through X-Forwarded-For.
auth.secureCookies false Set to true when browser sessions are served through HTTPS.
auth.sessionLifetimeDays 30 Allowed range: 1–365 days.
auth.sessionIdleHours 168 Must be positive and no longer than the total session lifetime.
auth.maximumConcurrentPasswordWork 4 Allowed range: 1–64. Limits concurrent password hashing and verification.
database.path ./database/taskemon.db Parent directories are created automatically.

Only configure trustedProxyCIDRs for proxies you control. Forwarding headers from other clients are ignored.

Printer defaults

Printing is optional. When printer.enabled is false, Taskemon uses the no-op printer and task print requests return successfully without sending data to hardware.

Setting Default Notes
printer.enabled false Enables physical printer initialization.
printer.transport noop Supported values: noop and usb.
printer.vendorID 0x0418 Hexadecimal USB vendor ID.
printer.productID 0x5011 Hexadecimal USB product ID.
printer.endpoint 1 USB output endpoint; allowed range: 1–15.
printer.devicePath /dev/usb/lp0 Parsed for compatibility, but the current USB implementation selects devices by vendor ID, product ID, and endpoint.
printer.cutCommand gs_v_0 Supported values: gs_v_0, full, partial, and feed.
printer.qrMode pokemon_placeholder Use pokemon_placeholder or task_completion.
printer.baseURL empty Base URL for task_completion QR values. Empty uses taskemon://tasks/{id}/complete.
printer.layout.paperWidth 80 Retained in configuration; line wrapping is currently controlled by charsPerLine.
printer.layout.charsPerLine 48 Must be positive.
printer.layout.feedLinesBeforeCut 1 Allowed range: 0–255.

Layout sections are header, title, description, tag, qr, shinyHint, and footer. Their full defaults are in config.example.json.

  • Text size values must be between 1 and 8.
  • justify accepts left, center, or right.
  • header.style must be between 0 and 5.
  • qr.size must be between 1 and 16.
  • qr.correction accepts L, M, Q, or H.
  • maxLines: 0 means no line limit.
  • The existing JSON key is spelled linesAllwaysVisible; use that spelling in configuration.

The two QR modes behave as follows:

Mode Printed value
pokemon_placeholder A Pokémon.com URL for the task's generated reward. This can reveal the reward before task completion.
task_completion {baseURL}/tasks/{taskID}/complete, or the taskemon:// form when baseURL is empty.

CLI usage

taskemon
taskemon serve [options]
taskemon setup [options]
taskemon help

Running taskemon without arguments is equivalent to starting the server with config.json.

serve
taskemon serve [--config path]
Flag Default Description
--config config.json Path to the JSON configuration file.

Example:

taskemon serve --config /etc/taskemon/config.json

The server handles SIGINT and SIGTERM and allows up to 10 seconds for graceful HTTP shutdown.

setup
taskemon setup [options]

The server must already be running with an empty user database. The password is always read from an interactive terminal and cannot be supplied through a flag or redirected standard input.

Flag Default Description
--url http://localhost:8080 Base URL of the running API; paths, queries, and fragments are rejected.
--token prompt One-time setup token printed by the server.
--username prompt Initial administrator username.
--display-name username Administrator display name.
--timezone UTC IANA timezone, for example America/Sao_Paulo.
--api-key-name Initial API key Name assigned to the generated API key.
--yes false Skip the final confirmation. Password input is still interactive.

Fully specified example:

taskemon setup \
  --url https://taskemon.example.com \
  --token change-me \
  --username admin \
  --display-name "Administrator" \
  --timezone America/Sao_Paulo \
  --api-key-name "Home Assistant" \
  --yes

API

All routes are under /api/v1. JSON request bodies must use Content-Type: application/json, cannot exceed 1 MiB, cannot contain unknown fields, and must contain exactly one JSON value. Date-time fields use RFC 3339.

Errors use:

{
  "error": "error message"
}
Authentication labels
Label Requirement
Public No authentication.
Session A valid taskemon_session cookie. Mutating requests also require X-CSRF-Token.
Either A browser session or Authorization: Bearer <api-key>. API-key requests do not use CSRF.
Admin session An administrator browser session. API keys are not accepted for admin routes.
Public and setup routes
Method Route Auth Request Success response
GET /health Public 200 health object
GET /setup Public `200 {"required":true
POST /setup Setup token SetupRequest plus X-Taskemon-Setup-Token 201 user and initial API key
POST /users Public CreateUserRequest 201 standard user
POST /auth/login Public LoginRequest 200 user and CSRF token; sets session and CSRF cookies
POST /auth/logout Public Current session cookie when present 204; revokes that session and clears cookies

Public registration is intentional. POST /users creates a non-administrator account. Complete initial setup before exposing the API because setup is only available while no users exist.

Current account and credentials
Method Route Auth Request Success response
GET /me Either 200 current user
PATCH /me Session UserUpdateRequest 200 updated user
PUT /me/password Session PasswordChangeRequest 204; revokes all sessions and clears cookies
POST /me/disable Session 204; revokes credentials and clears cookies
DELETE /me Session 204; deletes the account and owned data
GET /me/api-keys Session 200 API-key metadata array
POST /me/api-keys Session CreateAPIKeyRequest 201 metadata and one-time token
PATCH /me/api-keys/{keyID} Session APIKeyUpdateRequest 200 updated metadata
DELETE /me/api-keys/{keyID} Session 204
GET /me/sessions Session 200 active session array
DELETE /me/sessions Session 204; revokes all sessions and clears cookies
DELETE /me/sessions/{sessionID} Session 204; clears cookies if the current session was revoked
GET /me/collection Either 200 collection array
GET /me/stats Either 200 task and collection statistics
Administrator routes

Every administrator route requires an administrator browser session and CSRF on mutations.

Method Route Request Success response
GET /admin/users 200 enabled user array
POST /admin/users CreateUserRequest 201 standard user
GET /admin/users/disabled 200 disabled user array
GET /admin/users/lookup/by-username/{username} 200 user
GET /admin/users/{userID} 200 user
POST /admin/users/{userID}/enable 200 enabled user
POST /admin/users/{userID}/disable 204
POST /admin/users/{userID}/promote 204
POST /admin/users/{userID}/demote 204
DELETE /admin/users/{userID} 204
GET /admin/users/{userID}/api-keys 200 API-key metadata array
DELETE /admin/users/{userID}/api-keys/{keyID} 204

Taskemon prevents disabling, deleting, or demoting the last enabled administrator. Administrators manage accounts and credentials but do not receive access to another user's tasks.

Task routes

These routes accept a browser session or API key. The authenticated user ID is taken from the credential, not from the URL or request body.

Method Route Request Success response
GET /tasks 200 all owned tasks
GET /tasks/open 200 open owned tasks
GET /tasks/completed 200 completed owned tasks
GET /tasks/{taskID} 200 task
POST /tasks CreateTaskRequest 201 task
POST /tasks/bulk Array of 1–100 CreateTaskRequest values 201 all succeeded, 207 mixed result, or 400 all failed
PATCH /tasks/{taskID} TaskUpdateRequest 200 updated task
DELETE /tasks/{taskID} 204; completed tasks cannot be deleted
POST /tasks/{taskID}/complete 200 completed task with revealed reward
POST /tasks/{taskID}/print 204 after the configured printer returns
Request bodies
Name JSON fields
SetupRequest username, displayName, password, apiKeyName, timezone
CreateUserRequest username, displayName, password, timezone
LoginRequest username, password
UserUpdateRequest At least one of displayName or timezone
PasswordChangeRequest currentPassword, newPassword
CreateAPIKeyRequest name, optional future expiresAt
APIKeyUpdateRequest name
CreateTaskRequest Required title; optional description, dueAt, and tag
TaskUpdateRequest Any of title, description, dueAt, or tag; send "dueAt": null to clear the due date

Usernames are normalized to lowercase and must contain 3–64 characters. Passwords must contain 15–128 valid UTF-8 characters. Empty display names default to the username during account creation, empty timezones default to UTC, and timezones must be valid IANA names. API-key names must contain 1–100 characters.

An omitted or empty task tag becomes misc. Task tags are normalized to lowercase. A task response always contains reward.revealed; Pokémon details are omitted until completion.

API-key examples

Create a task:

curl --fail-with-body \
  -X POST http://localhost:8080/api/v1/tasks \
  -H 'Authorization: Bearer tsk_change-me' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Clean the desk",
    "description": "Clear the desk before coding",
    "dueAt": "2026-08-08T18:00:00-03:00",
    "tag": "home"
  }'

The initial response hides the reward:

{
  "id": 1,
  "userID": 1,
  "title": "Clean the desk",
  "description": "Clear the desk before coding",
  "completed": false,
  "dueAt": "2026-08-08T21:00:00Z",
  "tag": "home",
  "createdAt": "2026-08-07T12:00:00Z",
  "completedAt": null,
  "reward": {
    "revealed": false
  }
}

Complete it:

curl --fail-with-body \
  -X POST http://localhost:8080/api/v1/tasks/1/complete \
  -H 'Authorization: Bearer tsk_change-me'

List the collection:

curl --fail-with-body \
  -H 'Authorization: Bearer tsk_change-me' \
  http://localhost:8080/api/v1/me/collection
Browser-session example

Log in and store the cookies:

curl --fail-with-body \
  -c taskemon.cookies \
  -X POST http://localhost:8080/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{
    "username": "admin",
    "password": "change-me-to-a-long-password"
  }'

Copy the csrfToken from the response. Use it for unsafe methods:

curl --fail-with-body \
  -b taskemon.cookies \
  -X PATCH http://localhost:8080/api/v1/me \
  -H 'Content-Type: application/json' \
  -H 'X-CSRF-Token: change-me' \
  -d '{"displayName":"New display name"}'

Logging in while the browser already has a Taskemon session cookie replaces only that session after the new credentials are accepted. Sessions on other devices are not revoked.

Authentication and authorization

  • Passwords are stored with Argon2id.
  • Browser authentication uses the taskemon_session HttpOnly cookie.
  • The taskemon_csrf cookie and login response expose the CSRF token that browser clients must echo in X-CSRF-Token for unsafe methods.
  • Both cookies use SameSite=Lax; auth.secureCookies controls their Secure flag.
  • API keys use the tsk_ prefix and are stored as hashes. The full token is returned only when created.
  • API keys can access task routes and read current-user data. They cannot manage browser sessions, passwords, API keys, accounts, or administrators.
  • Disabled users cannot authenticate. Disabling an account revokes its API keys and sessions.
  • Login attempts are limited in memory by source IP and username.
  • Expired browser sessions are cleaned at startup and every 24 hours.

Database and migrations

Taskemon uses SQLite in WAL mode with foreign keys enabled, a five-second busy timeout, immediate write transactions, UTC timestamps, and embedded Goose migrations.

On startup, the server:

  1. Creates the database parent directory when needed.
  2. Connects to SQLite.
  3. Applies pending embedded migrations.
  4. Refuses to start if it detects Taskemon tables without Goose migration history.

The schema contains:

  • users
  • api_keys
  • web_sessions
  • tasks
  • task_rewards
  • collection_entries
  • user_statistics

Deleting a user cascades to their credentials, tasks, rewards, collection, and statistics.

Upgrading from v0.3

The pre-authentication v0.3 schema used text user IDs. The current server cannot safely map those IDs to authenticated accounts and intentionally refuses to migrate that database automatically.

Back up the existing database and start this version with an empty database. There is currently no supported in-place v0.3 data migration.

Stop the server before making a simple file-level SQLite backup, and preserve the database together with any active -wal and -shm files if they exist.

Thermal printer

The current physical printer transport uses gousb to select a device by vendor ID and product ID, claim its default interface, open the configured output endpoint, and send ESC/POS commands.

Taskemon attempts USB kernel-driver auto-detach. On Linux, a busy printer may require:

sudo modprobe -r usblp

The service account must also have permission to access the USB device. The repository does not currently include a udev rule or printer diagnostic command.

Development

Run the standard local checks:

make check

This runs gofmt -w ., go mod tidy, go test ./..., go vet ./..., and the release-style build. Because it writes formatting and module changes, review the working tree afterward.

Individual targets:

make test
make vet
make build
make lint
make lint/licenses
make licenses
  • make lint runs golangci-lint using .golangci.yml.
  • make lint/licenses requires the REUSE tool.
  • make licenses uses go-licenses 1.6.0 and updates LICENSE-3rdparty.csv.

SQL queries are defined under internal/database/queries, the schema is under internal/database/migrations, and generated sqlc code is committed under internal/database/db. After changing SQL, regenerate it with a compatible sqlc generate command.

The repository does not currently contain automated CI workflows.

Running tests

go test ./...
go vet ./...

The test suite covers command parsing, setup, configuration, authentication, middleware, account lifecycle, SQLite integration and constraints, API routes, task ownership, rewards, printer rendering, ESC/POS output, and task printing. Hardware-independent printer behavior is tested without requiring a physical USB printer.

Run the race detector when changing shared state, authentication, or HTTP code:

go test -race ./...

Building release binaries

Build the default binary:

make build

Supply another version:

make build VERSION=0.9.0

The Makefile builds ./cmd/taskemon as a position-independent executable with paths trimmed and symbols stripped. Release builds still depend on the target platform's libusb runtime.

Useful Make variables:

Variable Default Purpose
VERSION 0.9.0 Version value passed to the linker.
OUTDIR build Build output directory.
PREFIX /usr/local Installation prefix.
DESTDIR empty Packaging root used by make install.
GO go Go command.

Deployment

No Dockerfile, Compose file, systemd unit, or packaged container image is included.

For a production deployment:

  • Build the binary on a system with the required Go, C, pkg-config, and libusb dependencies.
  • Store config.json and the SQLite database in persistent locations.
  • Run behind an HTTPS reverse proxy when using browser sessions.
  • Set auth.secureCookies to true.
  • Configure exact frontend origins in server.corsOrigins.
  • Configure only the reverse proxy's networks in server.trustedProxyCIDRs.
  • Restrict access to the setup token, API keys, database, and server logs.
  • Grant USB access only when physical printing is enabled.

The API sets X-Frame-Options: DENY. Use a separate client that calls the API rather than embedding the API directly in an iframe.

Project structure

cmd/taskemon/                   CLI dispatcher, server command, and setup client
internal/api/                   HTTP routes, handlers, DTOs, CORS, and security headers
internal/auth/                  Accounts, passwords, sessions, API keys, and middleware
internal/config/                JSON configuration defaults and validation
internal/database/              SQLite connection, Goose migrations, SQL, and sqlc output
internal/escpos/                ESC/POS command implementation
internal/printer/               Printer transports and ticket rendering
internal/task/                  Task, reward, collection, and statistic services
internal/taskprint/             Task-to-printer integration and QR construction
database/                       Default runtime database directory
Makefile                        Build, test, lint, install, and license targets
config.example.json             Complete safe example configuration
sqlc.yaml                       sqlc generation configuration

Security considerations

  • Complete initial setup before exposing the server. Public account creation is enabled, and setup stops being available after any user exists.
  • The setup token is generated at startup, kept in memory, and printed to server logs only while setup is required. It changes when the server restarts.
  • Use HTTPS and secure cookies for browser clients outside local development.
  • Store API keys as secrets. They remain valid until revoked or expired.
  • Use explicit CORS origins. Wildcard origins are rejected because credentialed browser requests are supported.
  • Do not trust proxy CIDRs you do not control.
  • The default host exposes the server on all interfaces.
  • The default configuration is intended for local development, not direct Internet exposure.
  • Pokémon reward creation sends requests to the external PokéAPI service.

Troubleshooting

Build fails for gousb or pkg-config

Install a C compiler, pkg-config, and your platform's libusb 1.0 development package. These are build requirements even when runtime printer support is disabled.

Server reports a legacy or unmanaged database

The server refuses to modify pre-authentication or untracked schemas. Back up the database and use an empty database path. Automatic migration from v0.3 is not supported.

Setup token is rejected

Use the latest token printed by the currently running server. Restarting an unconfigured server generates a new token. Setup returns a conflict after any user exists.

API returns 401 unauthorized

For API-key requests, use exactly:

Authorization: Bearer tsk_change-me

For browser requests, include the session cookie.

Browser mutation returns 403 invalid csrf token

Read the csrfToken from the login response or taskemon_csrf cookie and send it in X-CSRF-Token. API-key requests do not need this header.

Browser request fails CORS preflight

Add the frontend's exact origin, including scheme and port, to server.corsOrigins. Paths and wildcards are invalid.

Task creation returns a service error

Task creation calls PokéAPI to obtain the current species count, Pokémon data, and species data. Confirm outbound HTTPS access to pokeapi.co.

Printer is busy or access is denied

Check the USB IDs and endpoint. On Linux, release usblp if it claimed the interface and grant the service account access to the USB device.

Contributing

Read CONTRIBUTING.md, keep changes focused, update tests and documentation with behavioral changes, and run make check before submitting a pull request.

The active repository is github.com/Renan-M-Fernandes/taskemon.

License

Taskemon is distributed under the EUPL-1.2. Third-party dependency licenses are listed in LICENSE-3rdparty.csv, with license texts under LICENSES/.

Credits

Taskemon started from the thermal-printer task manager created by James Pond. This version expands the original project into a multi-user API with authentication, gamified Pokémon rewards, SQLite persistence, and optional thermal printing.

Pokémon data is retrieved from PokéAPI.

Directories

Path Synopsis
cmd
taskemon command
internal
api
escpos
Package escpos is a small vendored ESC/POS implementation based on github.com/hennedo/escpos, patched for generic POS-80 thermal printers.
Package escpos is a small vendored ESC/POS implementation based on github.com/hennedo/escpos, patched for generic POS-80 thermal printers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL