Files

78 lines
5.5 KiB
Markdown

# SSH Portfolio
A public, anonymous Node.js SSH server that turns a terminal into a customizable portfolio. Visitors connect with:
```sh
ssh -p 2222 localhost
```
## Quick start
```sh
npm install
npm start
```
The first run creates `data/config.json` from [`config.example.json`](config.example.json). Edit persona, pages, colors, ports, and admin keys, then use `reload` as an admin or restart the server. Pages are content-only; actions are registered in `src/commands.js`, so adding a command does not require changing the SSH transport.
## PM2 deployment
The included [`ecosystem.config.cjs`](ecosystem.config.cjs) watches `src/` and automatically restarts the SSH server after code changes. Runtime data, SQLite files, dependencies, tests, and logs are ignored to prevent restart loops.
```sh
mkdir -p logs
pm2 start ecosystem.config.cjs
pm2 save
```
Use `pm2 logs ssh-portfolio` to inspect output. Set `PORT` or `NOTIFICATION_PORT` when starting PM2 if the defaults are not suitable, for example `PORT=2222 NOTIFICATION_PORT=7777 pm2 start ecosystem.config.cjs`.
## Authentication model
Anonymous `none` authentication is intentionally accepted for visitors. Add one or more base64 public-key blobs to `adminPublicKeys` to enable administrator mode; key authentication is optional and never required. Admins get `reload`, `theme`, and `announce` commands.
Because this is an intentionally open service, run it in a container or restricted account, use a dedicated port, and do not expose private data through configured pages.
## Owner notifications
Run `npm run notify` on the owner machine (or use `NOTIFICATION_PORT`) to receive chat and announcement notifications over a localhost-only socket. The protocol is newline-delimited JSON, so it is straightforward to replace with a desktop/mobile notification client. For a separate owner machine, forward the port with `ssh -N -L 7777:127.0.0.1:7777 <server>`.
## SSH monitoring dashboard
The server also starts a small, localhost-only web dashboard (default `http://127.0.0.1:3000`). On first start it creates a random bearer token in `data/dashboard-token` (mode 0600) and prints a tokenized URL to the server log. Set `DASHBOARD_PORT`, `DASHBOARD_TOKEN`, or the `dashboard.host`, `dashboard.port`, and `dashboard.tokenFile` config fields to customize it. Keep the dashboard bound to localhost and access it through an SSH tunnel when needed, for example `ssh -N -L 3000:127.0.0.1:3000 <server>`.
The dashboard shows active/recent connections, source IPs, SSH client identifiers, and recorded commands. An administrator can close a connection, ban its IP (which also closes matching active connections), or remove a ban. Bans and audit history are stored in SQLite; command history is retained for operational monitoring, so protect the database and dashboard token.
## Bulletin board and SQLite
Persistent data is stored in `data/portfolio.sqlite` by default using SQLite in WAL mode. The server creates `board_posts` and `leaderboard_scores` automatically. [`src/database.js`](src/database.js) provides small parameterized methods for reading/writing both tables; use those methods for extensions instead of building SQL from visitor input.
Bulletin commands:
- `board` or `board list` — read the latest notes.
- `board post <message>` — leave a note.
- `board delete <id>` — delete a note as an authenticated administrator.
Posts are limited by character count and UTF-8 byte size. Control characters, terminal escapes, pictographs, and excessive combining marks are rejected. IP addresses are stored only as SHA-256 hashes and are subject to a cooldown and daily posting limit. Configure these limits with `board.maxCharacters`, `board.cooldownSeconds`, and `board.dailyLimit`.
## Games
- `flappy` starts a live game; press Space to flap and `q` to quit.
- `pong` or `pong bot` starts a live bot match; use the Up/Down arrow keys and `q` to quit.
- `pong online` waits for or joins another connected visitor. Both players use the Up/Down arrow keys.
- `leaderboard flappy` and `leaderboard pong` show server-recorded scores.
Both games are ASCII-only and calculate scores on the server; visitors cannot submit leaderboard values directly.
## Extending
Pages and commands are source modules. Page metadata (`name`, `title`, optional `order`, `aliases`, `visible`, `requiredRole`, and `description`) lives in the module and is authoritative:
- `src/pages/home.js`, `about.js`, `projects.js`, and `links.js` are the premade editable pages.
- `src/pages/index.js` is the page registry. Add an import and registry entry when creating a new page; its exported `name` becomes the SSH command. Aliases are also accepted.
- `src/commands/` contains the premade command modules (`chat`, `contact`, `announce`, and so on).
- Each game is self-contained in its command module: `src/commands/flappy.js` owns Flappy physics/live state, and `src/commands/pong.js` owns Pong physics/matchmaking/live state. The shared [`src/database.js`](src/database.js) is intentionally separate because the bulletin board and both games persist data through it.
- `src/commands.js` is the command registry. Add an import there to expose a new command.
Each page exports `name`, `title`, and `render({ config, ctx, api })`. The renderer returns terminal text, so you can build extensive custom pages while inheriting the shared header, theme, and prompt. The `config.pages` entries in the generated JSON are retained only as a legacy content fallback for the premade projects/links pages; titles and navigation metadata come from modules.