Operator-focused PM2 monitoring, remote process inspection, and live log streaming over SSH.
PM2 Operator is a full-stack operations console for teams running Node.js services under PM2 on remote hosts.
Instead of asking every operator to open separate SSH sessions, remember host details, and run PM2 commands by hand, the project provides a shared web workspace for:
- managing SSH hosts
- validating connectivity and host fingerprints
- discovering PM2 processes
- opening a compact runtime dashboard
- streaming merged logs in real time
- managing users, themes, and operator shortcuts
The project is aimed at developers, operators, and small teams who want a focused PM2 control surface rather than a general observability stack or an additional agent running on every server.
This repository is a deployable application, not a published npm package. It is designed to be run from source or via Docker Compose.
- Multi-user workspace with bootstrap owner flow and three roles:
OWNER,ADMIN, andMEMBER - Shared SSH host inventory with support for password or private-key authentication
- Encrypted SSH secrets at rest using AES-256-GCM
- SSH host fingerprint pinning with a repin workflow for expected host key changes
- Remote PM2 process discovery using
pm2 jlist - Live multi-process log streaming over Socket.IO from server-side SSH sessions
- Operator dashboard with CPU, memory, restart counts, runtime metadata, and PM2
restart/reloadactions - Host tagging, host search, and process filtering
- Regex include/exclude log filters, configurable tail length, scroll lock, and log download
- Per-user settings for theme, panel layout, and keyboard shortcuts
- Docker Compose setup for PostgreSQL and the application runtime
PM2 is widely used to run Node.js services, but day-to-day operations often still depend on direct shell access. That works for individuals, but it does not scale well for teams that need shared visibility, consistent workflows, and basic access control.
This project matters because it brings those workflows into a single, typed, auditable application without introducing an extra host-side agent. It uses standard SSH access plus PM2 itself, which keeps rollout straightforward and reduces operational overhead on managed hosts.
It is also meaningfully different from generic log viewers:
- it understands PM2 process identity and process metadata
- it exposes PM2-specific actions such as
restartandreload - it treats host fingerprint verification as part of the workflow
- it combines operator UX with workspace-level user and host management
From an engineering perspective, the repository demonstrates end-to-end ownership across backend security, cryptography, realtime transport, SSH automation, data modelling, and frontend operator tooling. The codebase uses typed boundaries, explicit validation, and automated tests for the server-side core.
For the default Docker-based setup:
- Docker Engine or Docker Desktop
- Docker Compose
For local development without Docker:
- Node.js 22+
- PostgreSQL 16+
Copy the example environment file:
cp .env.example .envOn Windows PowerShell:
Copy-Item .env.example .envInstall dependencies:
npm install
npm run prisma:generatedocker compose up -d --buildThe application will be available at http://localhost:3000.
The container startup command runs Prisma migrations automatically before starting the server.
If you are running the app directly on your machine, update DATABASE_URL in .env to use localhost instead of the Docker service hostname:
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/pm2_log_viewer?schema=publicThen run:
npm run prisma:migrate
npm run devDevelopment URLs:
- frontend: http://localhost:5173
- backend: http://localhost:3000
The smallest working setup is the Docker flow:
npm install
cp .env.example .env
npm run prisma:generate
docker compose up -d --buildAfter the app starts:
- Open http://localhost:3000.
- If this is the first run, create the owner account.
- Add an SSH host from the workspace.
- Test the host connection and confirm the SSH fingerprint.
- Load PM2 processes for that host.
- Open either the dashboard or live logs for one or more processes.
- Create or edit a host with either password-based or private-key authentication.
- Optionally apply tags to organise hosts by environment, service, or team.
- Test the host to verify SSH connectivity and pin the current fingerprint.
- Open the Processes view to fetch the remote PM2 inventory.
- Select one or more PM2 processes.
- Open:
Dashboardfor runtime summary and PM2 actionsLogsfor live merged streaming output
- In the log view, use include/exclude regex filters, tail size, pause, scroll lock, clear, and download.
Bootstrap the first owner:
curl -X POST http://localhost:3000/auth/bootstrap \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.com",
"password": "ChangeMe123!"
}'Create a host after signing in and obtaining an access token:
curl -X POST http://localhost:3000/hosts \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "app-prod-1",
"host": "203.0.113.10",
"port": 22,
"username": "deploy",
"authType": "PRIVATE_KEY",
"privateKey": "-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----",
"passphrase": "",
"tagIds": []
}'Test a host and discover its PM2 processes:
curl -X POST http://localhost:3000/hosts/HOST_ID/test \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repinFingerprint": false}'
curl http://localhost:3000/hosts/HOST_ID/processes \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"The browser uses Socket.IO for operational views. The current event model includes:
logs:start->logs:line,logs:status,logs:errordashboard:start->dashboard:snapshot,dashboard:status,dashboard:errordashboard:action->dashboard:action-result
This makes the UI responsive without polling for individual log lines, while keeping PM2 runtime polling and PM2 actions explicit and session-scoped.
The backend is an Express application with a clear split between routes, services, utilities, and persistence:
apps/server/src/routes- HTTP surface for
auth,hosts,tags, andusers - request validation with
zod - authentication and role checks at the route boundary
- HTTP surface for
apps/server/src/services- authentication and token rotation
- audit logging
- SSH host secret management
- SSH and PM2 execution
- user preferences and workspace serialization
- dashboard snapshot construction
apps/server/prisma/schema.prisma- users
- SSH hosts
- tags
- audit logs
- refresh tokens
- user preferences
The realtime layer is implemented in apps/server/src/socket.ts.
Each connected client can start:
- a live log stream for one or more PM2 targets
- a runtime dashboard session for selected PM2 process IDs
Important implementation details:
- SSH sessions are opened server-side using
ssh2 - commands are wrapped in a login shell so PM2 can be found in user shell profiles
- host keys are fingerprinted and verified before commands run
- PM2 JSON is parsed from
pm2 jlist - log streams are buffered with a simple ring buffer so the UI can reason about visible versus buffered lines
The frontend is a React + Vite application in apps/web.
Its main responsibilities are:
- session restoration and bootstrap flow
- host inventory and tag filtering
- PM2 process selection
- switching between Processes, Dashboard, Logs, and Settings
- rendering operator-focused panels for runtime state and live logs
- persisting per-user dashboard view state and settings
The primary composition lives in:
apps/web/src/App.tsxapps/web/src/components/Dashboard.tsxapps/web/src/components/MonitorDashboard.tsxapps/web/src/components/LogPanel.tsxapps/web/src/components/SettingsPanel.tsx
- The project uses SSH rather than a custom agent on each host. That keeps host rollout simple, but it means remote shell access and PM2 availability are prerequisites.
- Dashboard sessions are on-demand and tied to active users rather than being stored as a long-running telemetry pipeline.
- The application is intentionally PM2-specific. That focus keeps the UX practical for PM2 operators, but it is not a general-purpose observability platform.
- Add new HTTP capabilities by extending the route and service layers in
apps/server/src/routesandapps/server/src/services. - Add new PM2 or SSH operations in
apps/server/src/services/ssh.service.ts. - Extend runtime data in
apps/server/src/services/monitor.service.tsand the matching frontend types. - Add new user-level preferences through
user_preferencesand the settings UI.
The project reads its runtime configuration from .env.
| Variable | Purpose |
|---|---|
DATABASE_URL |
PostgreSQL connection string used by Prisma |
MASTER_KEY |
Base64-encoded 32-byte key used for AES-256-GCM encryption of SSH secrets |
JWT_ACCESS_SECRET |
Secret used to sign access tokens |
JWT_REFRESH_SECRET |
Secret used to sign and rotate refresh sessions |
ACCESS_TOKEN_TTL_MINUTES |
Access token lifetime in minutes |
REFRESH_TOKEN_TTL_DAYS |
Refresh token lifetime in days |
COOKIE_SECURE |
Enables secure cookies when running behind HTTPS |
APP_PORT |
Express and Socket.IO port |
LOG_BUFFER_MAX_LINES |
Maximum buffered log lines kept per active log session |
CORS_ORIGIN |
Allowed frontend origin for HTTP and Socket.IO |
Notes:
MASTER_KEYmust decode to exactly 32 bytes.- The Docker setup uses
postgresas the database hostname. - Local development usually needs
localhostinDATABASE_URL. - In production, replace the development secrets from
.env.examplebefore starting the app.
- Create one
OWNERaccount during bootstrap. - Add
ADMINusers for operators who manage hosts and tags. - Add
MEMBERusers for read-only process and log access.
This model allows teams to share infrastructure visibility without sharing SSH credentials between people.
When a server is rebuilt and its SSH host key changes:
- Test the host again from the UI.
- Review the fingerprint mismatch prompt.
- Repin the new fingerprint only if the change is expected.
This keeps host trust explicit rather than silently accepting new keys.
- Select one or more PM2 processes
- open the Logs view
- increase or reduce tail size
- filter with include/exclude regex patterns
- lock scroll while reading older lines
- download the current filtered output when needed
From the dashboard, select the PM2 services you want to inspect and use the built-in reload or restart action. The UI refreshes the dashboard session after the action completes so the operator sees updated runtime state without opening a separate shell.
Current validation is centered on build verification and the server test suite.
Run the full build:
npm run buildRun the server tests:
npm run test --workspace @pm2-operator/serverThe server tests cover:
- authentication routes
- encrypted secret handling
- host secret update logic
- SSH helper behavior
- log stream parsing
- PM2 JSON parsing
- dashboard snapshot aggregation
There is no dedicated lint script in the repository yet. At the moment, npm run build is the main type-check and production-build validation command for both workspaces.
- Expand automated coverage beyond the server suite, especially for frontend and Socket.IO flows
- Continue refining the dense operator UI for dashboards and long-running log reading sessions
- Broaden deployment and production-hardening documentation
- Formalise contributor-facing project policy for public collaboration
Contributions should stay practical, well-scoped, and consistent with the existing codebase.
Open an issue in this repository with:
- the problem you observed
- environment details
- steps to reproduce
- expected behaviour
- actual behaviour
Feature requests are most useful when they describe:
- the operator or developer workflow
- why the current behaviour is limiting
- the expected UI, API, or runtime outcome
Before opening a pull request:
npm run build
npm run test --workspace @pm2-operator/serverPlease keep pull requests focused and aligned with the existing conventions:
- use TypeScript throughout
- validate request payloads with
zod - keep persistence changes explicit in Prisma schema and migrations
- keep backend behavior in services, not embedded in routes
- keep frontend changes consistent with the current React + Tailwind component patterns
TODO: add a dedicated
CONTRIBUTING.mdand contribution policy.
This project is licensed under the MIT License. See LICENSE for details.
Maintained through this repository by the project author.
The codebase reflects hands-on ownership across product design, backend services, authentication, SSH automation, realtime delivery, and operator-facing UI.
TODO: add maintainer name, organisation, and preferred contact details.
