Status: built, not released. OpsDesk Lite has never been published. There is no download, no live install, no customers, nobody trialling it, and the license is still to be decided. What exists is a working, tested v0.1 codebase, a buyer-package release pipeline that refuses to ship anything half-finished, and a clear position on who this is for.
It is parked, not dead — and the distinction matters. The build reached its acceptance criteria and then stopped because I chose to put the time elsewhere, not because it hit a wall. The tests still run, the packaging pipeline still produces a package, and the decisions that would need making before a release are known and written down. Reviving it is a decision, not a rescue. This page is a product exploration, written the way I would want to read it: what was decided, what was built, and what is deliberately not being claimed.
The problem it targets
Small organizations lose operational requests everywhere: email threads, texts, hallway conversations, sticky notes, vendor calls, and one overloaded spreadsheet. Senior living communities, churches, schools, clinics, property and facility teams, and small IT shops all have the same shape of problem — a printer that jams, a badge reader that stops unlocking a side entrance, a Wi-Fi complaint in one wing — and the same objection to the obvious answer: per-seat helpdesk SaaS priced for teams ten times their size, holding data they would rather keep on a server they control.
OpsDesk Lite is the small, boring answer to that. One place to capture, route, resolve, and remember the work. Own your requests, own your data, run it on a $5 VPS, and stop paying per seat for a ticket queue.
What I built, and how
This was a spec-first build. Before any code existed I wrote the product brief: the thesis, the target users, the non-goals (no billing, no license enforcement, no real AI calls, no multi-tenant SaaS architecture, no telemetry), the preferred stack, the deployment philosophy, the data model, the role model, the security requirements, the documentation set, and sixteen acceptance criteria that define what "done" means for v0.1.
An AI coding agent then implemented the application against that brief. The initial build — application, seed data, docs, feature tests, and packaging scripts — landed on 2026-06-17. My job was direction and verification: authoring the contract, reviewing what came back against the acceptance criteria and the security requirements, and running the tests and the release-readiness pipeline. That division of labor is the honest description, and it is also the point: it is the same operating model I describe under Practical AI Implementation, applied to a real product.
The result runs on PHP 8.3 and Laravel with Blade server-rendered views. SQLite is the default database, with MySQL-compatible migrations behind an environment switch. There is no Node runtime, Redis, queue worker, external CDN, SaaS API, telemetry, or AI key required in production — a deliberate constraint from the brief, because the deployment target is a basic PHP web host or a small VPS, not a platform team.
Architecture and decisions
- One migration, eight tables. Settings (key–value), categories, locations, assets, request forms (with JSON custom fields), tickets, ticket comments, and knowledge-base articles. Tickets are indexed on status, priority, source, and due date, and foreign keys null on delete so a retired asset never orphans its history.
- Four roles, enforced at the route. Owner, admin, agent, and requester are enforced by middleware groups and a custom role guard, so a requester account can log in, see its own status page, and never reach staff routes.
- Public intake without public exposure. Anyone can submit a request through a slugged public form; requesters can look up ticket status by ticket number and email and see only public replies. Internal notes live on the same comment model but are isolated by relation, and every ticket update forces an explicit public-reply-or-internal-note choice.
- Ticket numbering as a service. Human-readable
OD-000001style numbers, generated by a dedicated service so the format is one decision, not scattered string formatting. - A "Facility Edition" template pack. Categories, request forms, locations, assets, KB articles, and realistic demo tickets seed a fresh install so it is demoable in minutes rather than empty.
- AI-ready, disabled by default. An AI service interface with a null implementation (summarize, suggest reply, classify, draft KB) sits behind a config flag. Nothing calls a model until someone decides to.
Security requirements, as shipped
The brief listed the security requirements up front, and the implementation was checked against them:
- Public request forms are rate-limited per IP through a named limiter, and their default intake status cannot start as resolved or closed. That default is an administrator setting, and the two states it must never take are the two that hide a ticket from the open queue — a public form defaulting to closed still accepts requests and then silently drops them, which is worse than a form that is switched off, so the intake path checks the configured value against the allowed opening states and falls back to open.
- Internal notes are excluded from every public and requester view by relation, not by template discipline, and a feature test asserts it. A template rule has to be repeated in every view that ever renders a comment, so it holds only until someone adds a view and forgets; the public and requester pages instead read a relation that filters internal notes at the query, which means a new template cannot render one by accident.
- CSV export neutralizes formula-leading cells so an exported ticket title cannot become a spreadsheet payload.
- Admin self-lockout guards: admins cannot strip their own admin role, users linked to ticket history cannot be deleted, and the last privileged account cannot be removed.
- Tickets cannot be assigned to requester accounts, and referenced categories, locations, and assets are deactivated rather than deleted.
- A
php artisan opsdesk:backupcommand zips the SQLite database and uploads with a manifest, resolves the active database path from config, and warns when the active driver is not SQLite. - Standard Laravel posture underneath: CSRF, session auth, hashed passwords, validation on every form, escaped Blade output, and a
SECURITY.mdcovering hardening, file permissions, and backup handling.
Proof and verification
- 48 PHPUnit feature tests across eight files cover the ticket workflow, public forms and status lookup, auth and roles, admin safety guards, marketing pages, the public knowledge base, reports (including the CSV neutralization case), and a settings-plus-backup smoke test. I am not going to publish a pass count from memory; the honest statement is that the suite exists, is part of the release checklist, and is run before packaging.
- Allowlisted release packaging. Three PowerShell scripts build a buyer package from an explicit allowlist, strip
.env, vendor, SQLite files, logs, and cache, assert that forbidden paths and internal-only docs are absent, and reject unsafe version strings. - Release-blocking-wording preflight. Before a buyer package is produced, every packaged text and Blade file is scanned for placeholder or unfinished release wording, and packaging refuses to proceed while the license is a placeholder — internal-QA builds are named as such. One internal-QA source package exists as proof the pipeline runs end to end.
Boundaries and status
- v0.1 has never been published. No download, no live install, no customers, no revenue, nobody evaluating it, and commercial terms are not final.
- Known v0.1 gaps: no email notifications, inbound mail is a placeholder command, no attachments, no real license enforcement.
- Seeded demo users exist for local evaluation only and are documented as something to replace before any real deployment.
- It is parked and revivable rather than abandoned: the codebase, the test suite and the packaging pipeline all still run, and the remaining work is known. If this becomes a real offer, the next steps are settling the license, a public demo instance, and a scrubbed source export — none of which has happened yet, and none of which is blocked by anything technical.
Related
- Technical Operations — the self-hosting and backup judgment this product is built around.
- Business Systems — how I think about small-organization operating systems.
- Runbook Composer — a local tool for the same kind of ops-desk discipline.
- Ventures — the rest of the project roster.