You run a bunch of websites with partners. Right now that probably means spreadsheets, a shared password doc, and a monthly “who is owed what?” argument.
Portfolio OS puts the sites, the passwords, the work, the people, and the money in one place — and it runs on plain PHP + MySQL shared hosting (the cheap kind with FTP and cron).
Clone it. Seed it. Click around for five minutes. If it clicks, you already know if it fits your partnership.
You need PHP 8.3+, Composer, and Node (only to build CSS/JS on your laptop — nothing Node runs on the server).
git clone https://github.com/tnandla/portfolio-os.git && cd portfolio-os
composer install && cp .env.example .env && php artisan key:generate
touch database/database.sqlite && php artisan migrate --seed
npm install && npm run build
php artisan serveOpen http://127.0.0.1:8000 and sign in:
| Account | Password | What you’ll see |
|---|---|---|
admin@example.com |
password |
Everything |
partner@example.com |
password |
Money + ownership view |
supervisor@example.com |
password |
Approvals + team work |
staff@example.com |
password |
Assigned work only |
accountant@example.com |
password |
Revenue, expenses, P&L |
Switch accounts once. The app changes shape a lot depending on who is signed in — that’s the point.
Stuck? Full setup, MySQL, config and troubleshooting: docs/INSTALL.md.
- Portfolio of every site: status, monetisation, ownership, open work
- Encrypted credential vault per project (hosting, CMS, registrar, ads, analytics…)
- Secrets only decrypt when someone with permission asks — every reveal is logged
- Expiry warnings before domains / certs / access lapse
- Tasks with checklists, assignees, and recurring templates
- Article pipeline from brief → draft → published (with cost)
- Link-building log with per-project budgets
- One approval queue for tasks, articles, and links (
j/kmove,aapprove,rreject) - Approving an article or link can raise the matching expense for you
- ⌘K command palette to jump or create from anywhere
- Login counts as check-in (no separate punch clock)
- Late after a grace window you choose
- Monthly scorecards: tasks, articles, links — priced at each person’s rates
- Mixed pay is normal: salary and/or per article / link / task
- Revenue by month (manual or CSV), with FX frozen per row
- Expenses and shared costs allocated across projects by revenue share
- P&L that partners can actually read
- Ownership per project, enforced to total 100%
- Partner distributions: preview → approve → locked forever (corrections are new entries)
- Every amount stored as integer minor units — no float rounding surprises
- Soft-delete only on financial records
- Roles are many-to-many (partner and supervisor is fine — one merged UI, no role switcher)
- 49 permissions across five seeded roles
- Project access enforced in queries, not “hidden in the view”
- “Ask your data” box and drafted monthly summaries when
AI_API_KEYis set - Credentials, passwords, and bank details never go to the model
- No key → no nav, no route, no outbound call. The rest of the app is complete without it.
- PHP + MySQL only — no Redis, no Node on the server, no websockets, no Docker
- Deploy with two zip files over FTP + two cron lines
- Sessions, cache, and queues on database or files
- Jobs safe to run late, out of order, or twice
Mobile isn’t a cut-down app — same navigation:
The whole app runs on PHP and MySQL: no Node runtime on the server, no Redis, no queue daemon, no websockets, no container. Deployment is two zip files over FTP and a cron entry.
That constraint made a few good habits stick:
- Sessions, cache and queues run on the database or filesystem. Nothing assumes Redis.
- Every queued job is safe to run late, out of order, or twice — a cron drip does all three.
- No reliance on
exec(),proc_open()orsymlink(). Backups are a pure-PHP SQL export. - Assets are built locally and uploaded. Fonts are self-hosted WOFF2 — no CDN in the runtime path.
Deploy it to a normal VPS and none of this hurts you — you just have headroom you are not using.
- Vault secrets are encrypted at rest with
APP_KEY. The key is the lock: back up your vault before rotating it. - Decrypted secrets are never stored in component state, so they are not re-sent to the browser on later interactions.
- Money mutations and every credential reveal are written to an audit log.
- Financial records are soft-deleted only. Approved distribution runs are immutable.
- The optional AI assistant is excluded from credentials in code, not by convention, and an LLM never generates SQL that gets executed — questions map onto a fixed whitelist of read-only reports.
/_ops/{action}is the sharp edge. It runs migrations and cache commands over HTTP for hosts with no SSH. It 404s whenOPS_TOKENis empty or shorter than 32 characters, compares tokens in constant time, is rate-limited per IP, and should be switched off the moment a deploy finishes.- Two-factor authentication is scaffold only: the database columns and a settings toggle exist, but there is no enrolment and no login challenge. Turning the toggle on protects nothing.
Report vulnerabilities privately — see SECURITY.md.
PHP 8.3+ · Laravel 13 · Livewire 4 · Alpine.js · Tailwind CSS 4 · Blade · MySQL 8 (SQLite locally) · Vite 8 · Pest 4 · Pint.
No React, no Vue, no Inertia, no SPA.
php artisan test # 110 tests, SQLite in-memory
vendor/bin/pint --test # code styleCI runs the suite on PHP 8.3, 8.4 and 8.5. Money calculations and approval flows require tests — see CONTRIBUTING.md.
./deploy/package.sh # → deploy/dist/app.zip + public.zipapp.zip goes to laravel_app/ outside the web root, public.zip to public_html/. Migrations run through the token-gated ops route because there is no SSH. Two cron entries handle the scheduler and a queue:work --stop-when-empty drip.
Non-techie Hostinger checklist: docs/SHARED_HOSTING_FOR_BEGINNERS.md. Full technical walkthrough: DEPLOYMENT.md.
All seven planned milestones are done and the app is in production use. See CHANGELOG.md.
Honestly:
- Pick
MONEY_BASE_EXPONENTbefore entering data. Stored integers carry no scale, so changing it later reinterprets every amount. There is no migration for that. - Column names keep their original
_paisa/_pkr_paisasuffixes. They mean "minor units of the base currency" whatever currency you configure; renaming them would break existing installs for no functional gain. - Multi-currency is one base plus one optional input currency, not arbitrary per-project currencies.
- Reports are tables. No charting library.
- Wide financial tables scroll sideways on phones rather than reflowing into cards.
- Editing happens in modals and side forms, not inline.
- 2FA is scaffold only (see Security above).
| docs/INSTALL.md | Local setup, configuration, troubleshooting |
| docs/SHARED_HOSTING_FOR_BEGINNERS.md | Short Hostinger checklist for non-techies |
| docs/USER_GUIDE.md | What each role can do, and the short path to common tasks |
| DEPLOYMENT.md | Shared-hosting deploy, ops route, cron, backups |
| docs/DESIGN_AUDIT.md | The design system and its accepted gaps |
| CONTRIBUTING.md | Setup, non-negotiable constraints, PR process |
| SECURITY.md | Reporting vulnerabilities |
| CHANGELOG.md | Release history |
Welcome. Read CONTRIBUTING.md first — it covers the constraints that are not negotiable (shared hosting, integer money, many-to-many roles, immutable approved distributions) and the tests expected for money and approval changes. By participating you agree to the Code of Conduct.
MIT — see LICENSE. The bundled fonts (Geist, Geist Mono, Instrument Sans) are third-party software under the SIL Open Font License 1.1 and are not covered by the MIT licence; see resources/fonts/LICENSE.













