Skip to content

Latest commit

 

History

39 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Portfolio OS

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).

CI License: MIT PHP 8.3+ Laravel 13 Livewire 4

Dashboard

Clone it. Seed it. Click around for five minutes. If it clicks, you already know if it fits your partnership.

Try it in two minutes

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 serve

Open 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.


Features

Projects & credentials

  • 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

Work

  • 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/k move, a approve, r reject)
  • Approving an article or link can raise the matching expense for you
  • ⌘K command palette to jump or create from anywhere

People

  • 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

Money

  • 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

Permissions that match real partnerships

  • 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”

Optional AI (off by default)

  • “Ask your data” box and drafted monthly summaries when AI_API_KEY is 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.

Built for shared hosting on purpose

  • 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

Screenshots

Projects
Portfolio — every site, status, monetisation and open work.
Project detail
Project detail — ownership split, team, credential vault.
Tasks
Tasks — checklists, recurring work, bulk assignment.
Approvals
Approvals — one queue for tasks, drafts and links.
Profit and loss
P&L — revenue, direct cost, allocated shared cost, net.
Distributions
Distributions — profit splits by ownership, locked on approval.
Revenue
Revenue — monthly entry or CSV import, FX frozen per row.
Scorecard
Scorecards — monthly output per person, priced.
Command palette
⌘K — jump or create from anywhere.
Dark mode
Dark mode — a full second token set, no flash.
Articles
Articles — brief to published, with cost.
Attendance
Attendance — derived from logins.

Mobile isn’t a cut-down app — same navigation:

Mobile dashboard


Why shared hosting shaped this

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() or symlink(). 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.

Security posture

  • 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 when OPS_TOKEN is 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.

Stack

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.

Tests

php artisan test        # 110 tests, SQLite in-memory
vendor/bin/pint --test  # code style

CI runs the suite on PHP 8.3, 8.4 and 8.5. Money calculations and approval flows require tests — see CONTRIBUTING.md.

Deploying

./deploy/package.sh    # → deploy/dist/app.zip + public.zip

app.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.

Status and known gaps

All seven planned milestones are done and the app is in production use. See CHANGELOG.md.

Honestly:

  • Pick MONEY_BASE_EXPONENT before 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_paisa suffixes. 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

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

Contributing

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.

Licence

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.

About

Self-hosted operations app for teams running a portfolio of websites: projects, an encrypted credential vault, content work, people and money. Laravel + Livewire, runs on PHP/MySQL shared hosting.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

66 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages