Add comprehensive in-depth README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
637
README.md
637
README.md
@@ -0,0 +1,637 @@
|
|||||||
|
# RemoteLink
|
||||||
|
|
||||||
|
A self-hosted remote support platform similar to ScreenConnect / ConnectWise Control. Connect to and control remote machines through a browser — no port forwarding required.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Browser-based viewer** — full mouse, keyboard, and scroll control over WebSocket; no plugins
|
||||||
|
- **Multi-monitor** — switch between displays mid-session
|
||||||
|
- **Clipboard sync** — paste from your clipboard to the remote machine and vice versa
|
||||||
|
- **File transfer** — drag-and-drop upload, directory browser, one-click download from remote
|
||||||
|
- **Script execution** — run PowerShell, cmd, or bash commands and stream output back (admin only)
|
||||||
|
- **Session chat** — text chat forwarded to the remote machine during a session
|
||||||
|
- **Wake-on-LAN** — send a magic packet to wake an offline machine
|
||||||
|
- **Attended mode** — agent prompts the local user to accept before the session starts
|
||||||
|
- **Agent auto-update** — agents check their version on each heartbeat and self-replace when a new binary is published
|
||||||
|
- **Session recording** — relay can archive JPEG frame streams to disk (opt-in)
|
||||||
|
- **Machine tags & notes** — label and annotate machines; filter by tag or group
|
||||||
|
- **Machine groups** — organise machines and control technician access
|
||||||
|
- **Invite-only registration** — no open sign-up; admin sends email invites
|
||||||
|
- **Open enrollment** — agents register without a token when `OPEN_ENROLLMENT=true`
|
||||||
|
- **Light / dark / system theme**
|
||||||
|
- **Mass deployment** — NSIS silent installer for Windows; shell one-liner for Linux
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Browser ──── HTTPS ────▶ Next.js App (port 3000)
|
||||||
|
│ │
|
||||||
|
│ PostgreSQL 17
|
||||||
|
│
|
||||||
|
Browser ──── WSS ─────▶ Relay (port 8765)
|
||||||
|
│
|
||||||
|
Agent ──── WSS ─────▶ Relay ─────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
| Component | Technology | Purpose |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| **Web app** | Next.js 16 / NextAuth v5 / Drizzle ORM | Dashboard, auth, API |
|
||||||
|
| **Relay** | FastAPI + asyncpg (Python) | WebSocket bridge between agent and browser |
|
||||||
|
| **Database** | PostgreSQL 17 | Machines, sessions, tokens, groups |
|
||||||
|
| **Agent** | Python + PyInstaller | Screen capture, input control, file transfer |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start (Docker)
|
||||||
|
|
||||||
|
### 1. Clone and configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.pathcore.org/monoadmin/remotelink-docker.git
|
||||||
|
cd remotelink-docker
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit `.env`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
POSTGRES_PASSWORD=a_strong_random_password
|
||||||
|
AUTH_SECRET= # generate: openssl rand -base64 32
|
||||||
|
NEXT_PUBLIC_APP_URL=http://your-server-ip:3000
|
||||||
|
NEXT_PUBLIC_RELAY_URL=your-server-ip:8765
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start the stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Three containers start:
|
||||||
|
- `remotelink-app` — web dashboard on port **3000**
|
||||||
|
- `remotelink-relay` — WebSocket relay on port **8765**
|
||||||
|
- `remotelink-db` — PostgreSQL (internal only)
|
||||||
|
|
||||||
|
### 3. Create your first account
|
||||||
|
|
||||||
|
Open `http://your-server:3000` and sign up. The first account is automatically given the `admin` role.
|
||||||
|
|
||||||
|
> After the first account is created, registration is **invite-only** by default. Additional users require an invite link from Admin → User Invites.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Deployment (Reverse Proxy)
|
||||||
|
|
||||||
|
### Nginx example
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# Web app
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name remotelink.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/remotelink.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/remotelink.example.com/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# WebSocket relay
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name relay.remotelink.example.com;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/remotelink.example.com/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/remotelink.example.com/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8765;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `.env` for production:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_APP_URL=https://remotelink.example.com
|
||||||
|
NEXT_PUBLIC_RELAY_URL=relay.remotelink.example.com # no port — nginx handles 443
|
||||||
|
ALLOWED_ORIGINS=https://remotelink.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Rebuild the app container after changing `NEXT_PUBLIC_*` variables (they are baked into the Next.js build):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build app
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### App (`docker-compose.yml` → `app` service)
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `POSTGRES_PASSWORD` | — | **Required.** PostgreSQL password |
|
||||||
|
| `AUTH_SECRET` | — | **Required.** NextAuth signing secret (`openssl rand -base64 32`) |
|
||||||
|
| `NEXT_PUBLIC_APP_URL` | `http://localhost:3000` | Public URL shown in invite links |
|
||||||
|
| `NEXT_PUBLIC_RELAY_URL` | `localhost:8765` | Relay address as seen from the **browser** (host:port or host if behind proxy on 443) |
|
||||||
|
| `OPEN_ENROLLMENT` | `false` | Set `true` to allow agents to register without a token |
|
||||||
|
| `OPEN_ENROLLMENT_USER_EMAIL` | — | Email of the user who owns auto-registered machines. Falls back to first admin. |
|
||||||
|
|
||||||
|
### Relay (`docker-compose.yml` → `relay` service)
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `DATABASE_URL` | — | **Required.** PostgreSQL connection string |
|
||||||
|
| `ALLOWED_ORIGINS` | `*` | Comma-separated allowed CORS origins. Set to your app URL in production. |
|
||||||
|
| `RECORDING_DIR` | — | Path inside the container to save session recordings. Leave empty to disable. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent Installation
|
||||||
|
|
||||||
|
### Linux (one-liner)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With enrollment token
|
||||||
|
curl -L https://your-server.com/downloads/remotelink-agent-linux -o remotelink-agent
|
||||||
|
chmod +x remotelink-agent
|
||||||
|
./remotelink-agent --server https://your-server.com --enroll YOUR_TOKEN
|
||||||
|
|
||||||
|
# Open enrollment (no token)
|
||||||
|
./remotelink-agent --server https://your-server.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Config is saved to `/etc/remotelink/agent.json`. On subsequent runs, just:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./remotelink-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Systemd service (run on boot)
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/remotelink-agent.service
|
||||||
|
[Unit]
|
||||||
|
Description=RemoteLink Agent
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/remotelink-agent
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp remotelink-agent /usr/local/bin/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable --now remotelink-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows (NSIS installer)
|
||||||
|
|
||||||
|
Download `RemoteLink-Setup.exe` from your dashboard (Admin → Agent Enrollment) or build it yourself (see [Building the Windows Installer](#building-the-windows-installer)).
|
||||||
|
|
||||||
|
**Interactive install** — double-click and follow the wizard. Enter your server URL and optional enrollment token when prompted.
|
||||||
|
|
||||||
|
**Silent mass deployment** — no UI, suitable for GPO / RMM / Intune:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
:: Open enrollment (no token required)
|
||||||
|
RemoteLink-Setup.exe /S /SERVER=https://your-server.com
|
||||||
|
|
||||||
|
:: With enrollment token
|
||||||
|
RemoteLink-Setup.exe /S /SERVER=https://your-server.com /ENROLL=YOUR_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer:
|
||||||
|
1. Copies binaries to `C:\Program Files\RemoteLink\`
|
||||||
|
2. Enrolls the machine with the server
|
||||||
|
3. Installs and starts a Windows service (`RemoteLinkAgent`) that runs on boot
|
||||||
|
|
||||||
|
**Uninstall:**
|
||||||
|
```batch
|
||||||
|
"C:\Program Files\RemoteLink\Uninstall.exe" /S
|
||||||
|
```
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L https://your-server.com/downloads/remotelink-agent-macos -o remotelink-agent
|
||||||
|
chmod +x remotelink-agent
|
||||||
|
./remotelink-agent --server https://your-server.com --enroll YOUR_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
> macOS binaries require the agent to be built on a Mac (PyInstaller limitation). See [Building the Agent](#building-the-agent).
|
||||||
|
|
||||||
|
### Agent CLI Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
remotelink-agent [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--server URL Server URL (required for first-time registration)
|
||||||
|
--relay URL Relay WebSocket URL (auto-derived from --server if omitted)
|
||||||
|
--enroll TOKEN Enrollment token (omit if open enrollment is enabled)
|
||||||
|
--attended Prompt local user to accept each incoming connection
|
||||||
|
--run-once Exit when the session ends (don't save config)
|
||||||
|
--verbose, -v Verbose logging
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent Enrollment
|
||||||
|
|
||||||
|
### Option A — Enrollment Tokens (default)
|
||||||
|
|
||||||
|
1. Go to **Admin → Agent Enrollment**
|
||||||
|
2. Create a token (optional: label, expiry, max uses)
|
||||||
|
3. Copy the install command shown in the UI and run it on the target machine
|
||||||
|
|
||||||
|
Tokens can be single-use (max uses = 1) for one machine, or unlimited for mass deployment.
|
||||||
|
|
||||||
|
### Option B — Open Enrollment
|
||||||
|
|
||||||
|
Set `OPEN_ENROLLMENT=true` in `.env` and restart the app container. Agents can then register without any token:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./remotelink-agent --server https://your-server.com
|
||||||
|
```
|
||||||
|
|
||||||
|
Machines appear immediately in the dashboard under the account set by `OPEN_ENROLLMENT_USER_EMAIL` (or the first admin user).
|
||||||
|
|
||||||
|
> **Security note:** Open enrollment allows any machine that can reach your server to register. Only enable it on private/VPN-accessible deployments or temporarily during a rollout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Connecting to a Machine
|
||||||
|
|
||||||
|
### Session code (from the agent's tray / terminal)
|
||||||
|
|
||||||
|
1. The agent generates a 6-character session code on demand
|
||||||
|
2. Go to **Quick Connect** in the dashboard and enter the code
|
||||||
|
3. The viewer opens directly — no relay polling required
|
||||||
|
|
||||||
|
### Direct connect (from Machines page)
|
||||||
|
|
||||||
|
Click **Connect** next to any online machine. A session is created and the viewer opens.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Viewer Controls
|
||||||
|
|
||||||
|
| Action | How |
|
||||||
|
|--------|-----|
|
||||||
|
| Mouse | Move / click / right-click / scroll normally on the canvas |
|
||||||
|
| Keyboard | Type directly — all keystrokes are forwarded |
|
||||||
|
| Ctrl+Alt+Del | Toolbar → **CAD** button |
|
||||||
|
| Paste clipboard | Toolbar → clipboard icon (reads your local clipboard and sends it to the remote) |
|
||||||
|
| Switch monitor | Toolbar → monitor dropdown (appears when agent has >1 display) |
|
||||||
|
| File transfer | Toolbar → folder icon → sidebar opens |
|
||||||
|
| Chat | Toolbar → message icon → sidebar opens |
|
||||||
|
| Quality | Toolbar → signal icon → High / Medium / Low |
|
||||||
|
| Fullscreen | Toolbar → maximise icon (toolbar auto-hides after 3 s, reappears on mouse move) |
|
||||||
|
| Wake-on-LAN | Toolbar → lightning bolt (shown when agent is offline and MAC address is known) |
|
||||||
|
| End session | Toolbar → X button |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Transfer
|
||||||
|
|
||||||
|
The file transfer sidebar has two areas:
|
||||||
|
|
||||||
|
**Remote file browser (top half)**
|
||||||
|
- Navigate directories on the remote machine
|
||||||
|
- Click a file's download button to pull it to your browser
|
||||||
|
- The agent's home directory (`~`) is the default starting location
|
||||||
|
|
||||||
|
**Upload drop zone (bottom half)**
|
||||||
|
- Drag files from your computer onto the drop zone, or click to browse
|
||||||
|
- Files are sent to the remote machine's current directory (shown in the browser path)
|
||||||
|
|
||||||
|
Files are transferred as base64-encoded chunks through the existing WebSocket relay — no separate HTTP upload server needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Machine Management
|
||||||
|
|
||||||
|
### Tags
|
||||||
|
|
||||||
|
Add colour-coded tags to machines (e.g. `server`, `windows`, `client-acme`). Tags appear as badges on each machine card and can be used to filter the machines list.
|
||||||
|
|
||||||
|
- Click the three-dot menu on a machine card → **Edit**
|
||||||
|
- Type a tag in the tag input and press Enter
|
||||||
|
- Click the **×** on a tag badge to remove it
|
||||||
|
|
||||||
|
Built-in tag colours: `server` (blue), `windows` (cyan), `linux` (orange), `mac` (grey), `workstation` (purple), `laptop` (green). Unknown tags use the default muted style.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
Free-text notes per machine — useful for recording IP ranges, owner contacts, software installed, etc.
|
||||||
|
|
||||||
|
- Click the three-dot menu → **Show notes** to view inline
|
||||||
|
- Click **Edit** to modify
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
Groups let you organise machines by client, location, or team.
|
||||||
|
|
||||||
|
1. Go to **Admin → Groups** and create a group
|
||||||
|
2. Edit a machine card and select a group from the dropdown
|
||||||
|
3. Use the group filter on the Machines page to view only machines in a group
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Recording
|
||||||
|
|
||||||
|
To enable recording, set `RECORDING_DIR` in the relay service environment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
relay:
|
||||||
|
environment:
|
||||||
|
RECORDING_DIR: /recordings
|
||||||
|
volumes:
|
||||||
|
- ./recordings:/recordings
|
||||||
|
```
|
||||||
|
|
||||||
|
Each session produces a `.remrec` file in that directory. The format is:
|
||||||
|
|
||||||
|
```
|
||||||
|
Header: "RLREC\x01" (6 bytes)
|
||||||
|
Frames: [8-byte ms timestamp][4-byte length][JPEG bytes] × N
|
||||||
|
```
|
||||||
|
|
||||||
|
A simple Python replay script:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import struct, time
|
||||||
|
|
||||||
|
with open("session.remrec", "rb") as f:
|
||||||
|
assert f.read(6) == b"RLREC\x01"
|
||||||
|
while True:
|
||||||
|
hdr = f.read(12)
|
||||||
|
if not hdr: break
|
||||||
|
ts, length = struct.unpack(">QI", hdr)
|
||||||
|
frame = f.read(length)
|
||||||
|
# frame is a JPEG — display with Pillow, cv2, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Building the Agent
|
||||||
|
|
||||||
|
### Linux / macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd agent
|
||||||
|
pip install pyinstaller -r requirements.txt
|
||||||
|
pyinstaller agent.spec
|
||||||
|
# Output: dist/remotelink-agent
|
||||||
|
```
|
||||||
|
|
||||||
|
The Linux binary requires `xclip` or `xsel` on the target machine for clipboard sync.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Building must be done on a **Windows machine** — PyInstaller cannot cross-compile Windows executables from Linux.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
- Python 3.11+ from [python.org](https://python.org)
|
||||||
|
- [NSIS 3.x](https://nsis.sourceforge.io) (for the installer)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd agent
|
||||||
|
pip install pyinstaller
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pyinstaller agent.spec
|
||||||
|
# Output: dist\remotelink-agent.exe + dist\remotelink-agent-service.exe
|
||||||
|
|
||||||
|
makensis installer.nsi
|
||||||
|
# Output: RemoteLink-Setup.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `agent.spec` references `assets\icon.ico`. Either add your own `.ico` to `agent\assets\` or remove the `icon=` and `!define MUI_ICON` lines from `agent.spec` and `installer.nsi` respectively to use the defaults.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- Invite-only user registration — no public sign-up
|
||||||
|
- Passwords hashed with bcrypt (via NextAuth credentials provider)
|
||||||
|
- JWT sessions with configurable secret (`AUTH_SECRET`)
|
||||||
|
|
||||||
|
### Agent authentication
|
||||||
|
- Each machine has a unique 256-bit `access_key` generated at enrollment
|
||||||
|
- The relay validates the access key against the database before accepting any agent connection
|
||||||
|
- Viewer connections are authenticated via a per-session `viewer_token` (UUID)
|
||||||
|
|
||||||
|
### Script execution
|
||||||
|
- `exec_script` commands from the viewer are only forwarded to the agent if the viewer's user has the `admin` role
|
||||||
|
- Checked at the relay level — non-admin viewers receive an error; the command never reaches the agent
|
||||||
|
|
||||||
|
### Transport
|
||||||
|
- All connections should be run behind TLS in production (nginx + Let's Encrypt)
|
||||||
|
- The relay enforces `ALLOWED_ORIGINS` CORS to prevent cross-site WebSocket hijacking
|
||||||
|
|
||||||
|
### Attended mode
|
||||||
|
- Running the agent with `--attended` makes it prompt the local user (via zenity / kdialog on Linux, PowerShell MessageBox on Windows) before any session starts
|
||||||
|
- The connection is rejected if the user declines or doesn't respond within 30 seconds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Node.js 22+ with pnpm (`npm i -g pnpm`)
|
||||||
|
- Docker + Docker Compose
|
||||||
|
- Python 3.12+
|
||||||
|
|
||||||
|
### Run locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start database only
|
||||||
|
docker compose up -d postgres
|
||||||
|
|
||||||
|
# Install JS dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Set up .env (copy from .env.example, fill in values)
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Start the Next.js dev server
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# In another terminal — start the relay
|
||||||
|
cd relay
|
||||||
|
pip install -r requirements.txt
|
||||||
|
DATABASE_URL=postgresql://remotelink:password@localhost:5432/remotelink uvicorn main:app --port 8765 --reload
|
||||||
|
|
||||||
|
# In another terminal — run the agent
|
||||||
|
cd agent
|
||||||
|
pip install -r requirements.txt
|
||||||
|
python agent.py --server http://localhost:3000 --enroll YOUR_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database schema
|
||||||
|
|
||||||
|
| Table | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `users` | User accounts with roles (`admin`, `user`) |
|
||||||
|
| `machines` | Registered agent machines (online status, tags, notes, group) |
|
||||||
|
| `groups` | Machine groups for access control |
|
||||||
|
| `sessions` | Active and historical viewer sessions |
|
||||||
|
| `session_codes` | Short-lived 6-character codes for Quick Connect |
|
||||||
|
| `enrollment_tokens` | Admin-generated tokens for agent registration |
|
||||||
|
| `invites` | Email invite tokens for new user sign-up |
|
||||||
|
|
||||||
|
Migrations are in `db/migrations/` and applied automatically when the PostgreSQL container initialises from scratch.
|
||||||
|
|
||||||
|
### Project structure
|
||||||
|
|
||||||
|
```
|
||||||
|
remotelink-docker/
|
||||||
|
├── app/ # Next.js App Router pages and API routes
|
||||||
|
│ ├── (dashboard)/ # Authenticated dashboard pages
|
||||||
|
│ │ └── dashboard/
|
||||||
|
│ │ ├── admin/ # Admin panel (invites, enrollment, groups)
|
||||||
|
│ │ ├── connect/ # Quick Connect (session code entry)
|
||||||
|
│ │ ├── machines/ # Machine list with search/filter
|
||||||
|
│ │ ├── sessions/ # Session history
|
||||||
|
│ │ └── settings/ # User profile settings
|
||||||
|
│ ├── api/ # REST API handlers
|
||||||
|
│ │ ├── agent/ # Agent: register, heartbeat, session-code
|
||||||
|
│ │ ├── groups/ # Machine groups CRUD
|
||||||
|
│ │ ├── machines/ # Machine CRUD + Wake-on-LAN
|
||||||
|
│ │ ├── sessions/ # Session read/update
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── viewer/[sessionId]/ # Full-screen remote viewer
|
||||||
|
├── agent/ # Python agent
|
||||||
|
│ ├── agent.py # Main agent (capture, input, file transfer)
|
||||||
|
│ ├── service_win.py # Windows Service wrapper
|
||||||
|
│ ├── agent.spec # PyInstaller build spec
|
||||||
|
│ ├── installer.nsi # NSIS Windows installer script
|
||||||
|
│ └── requirements.txt
|
||||||
|
├── components/
|
||||||
|
│ ├── dashboard/ # Dashboard UI components
|
||||||
|
│ │ ├── machine-card.tsx # Machine card with inline edit
|
||||||
|
│ │ └── machines-filter.tsx # Search/tag/group filter bar
|
||||||
|
│ └── viewer/ # Viewer UI components
|
||||||
|
│ ├── toolbar.tsx # Session toolbar
|
||||||
|
│ ├── connection-status.tsx
|
||||||
|
│ ├── file-transfer-panel.tsx
|
||||||
|
│ └── chat-panel.tsx
|
||||||
|
├── db/migrations/ # SQL migration files
|
||||||
|
├── lib/db/schema.ts # Drizzle ORM schema
|
||||||
|
├── relay/
|
||||||
|
│ ├── main.py # FastAPI WebSocket relay
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ └── requirements.txt
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── Dockerfile # Multi-stage Next.js production build
|
||||||
|
└── .env.example
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relay WebSocket Protocol
|
||||||
|
|
||||||
|
### Agent → Relay (`/ws/agent?machine_id=&access_key=`)
|
||||||
|
|
||||||
|
| Message | Direction | Type | Description |
|
||||||
|
|---------|-----------|------|-------------|
|
||||||
|
| JPEG frame | agent→relay | binary | Raw screen frame forwarded to all viewers |
|
||||||
|
| `{"type":"ping"}` | agent→relay | JSON | Updates `last_seen`; relay responds with online status |
|
||||||
|
| `{"type":"monitor_list","monitors":[...]}` | agent→relay | JSON | Sent after `start_stream`; relayed to viewer |
|
||||||
|
| `{"type":"clipboard_content","content":"..."}` | agent→relay | JSON | Clipboard change; relayed to viewer |
|
||||||
|
| `{"type":"file_chunk",...}` | agent→relay | JSON | File download chunk; relayed to viewer |
|
||||||
|
| `{"type":"file_list","entries":[...]}` | agent→relay | JSON | Directory listing; relayed to viewer |
|
||||||
|
| `{"type":"script_output",...}` | agent→relay | JSON | Script stdout line; relayed to viewer |
|
||||||
|
| `{"type":"chat_message","text":"..."}` | agent→relay | JSON | Chat message; relayed to viewer |
|
||||||
|
|
||||||
|
### Viewer → Relay (`/ws/viewer?session_id=&viewer_token=`)
|
||||||
|
|
||||||
|
| Message | Direction | Type | Description |
|
||||||
|
|---------|-----------|------|-------------|
|
||||||
|
| `{"type":"start_stream"}` | relay→agent | JSON | Sent by relay when viewer connects |
|
||||||
|
| `{"type":"stop_stream"}` | relay→agent | JSON | Sent by relay when viewer disconnects |
|
||||||
|
| `{"type":"mouse_move","x":...,"y":...}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"mouse_click","button":"left",...}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"mouse_scroll","dx":...,"dy":...}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"key_press","key":"a"}` | viewer→agent | JSON | Printable character |
|
||||||
|
| `{"type":"key_special","key":"enter"}` | viewer→agent | JSON | Special key |
|
||||||
|
| `{"type":"exec_key_combo","keys":["ctrl_l","alt_l","delete"]}` | viewer→agent | JSON | Key combo (admin only) |
|
||||||
|
| `{"type":"set_monitor","index":1}` | viewer→agent | JSON | Switch capture monitor |
|
||||||
|
| `{"type":"set_quality","quality":"high"}` | viewer→agent | JSON | `high` / `medium` / `low` |
|
||||||
|
| `{"type":"clipboard_paste","content":"..."}` | viewer→agent | JSON | Set remote clipboard |
|
||||||
|
| `{"type":"exec_script","script":"...","shell":"bash"}` | viewer→agent | JSON | Run script (admin only) |
|
||||||
|
| `{"type":"file_download","path":"/tmp/log.txt"}` | viewer→agent | JSON | Request file |
|
||||||
|
| `{"type":"file_upload_start","filename":"...","dest_path":"..."}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"file_upload_chunk","chunk":"<base64>","seq":0}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"file_upload_end"}` | viewer→agent | JSON | |
|
||||||
|
| `{"type":"list_files","path":"~"}` | viewer→agent | JSON | Browse remote filesystem |
|
||||||
|
| `{"type":"chat_message","text":"..."}` | viewer→agent | JSON | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Agent connects but viewer shows blank screen (Linux)
|
||||||
|
|
||||||
|
The agent can't find a display. Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo $DISPLAY # should be :0 or similar
|
||||||
|
xdpyinfo # should list display info
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent auto-detects `$DISPLAY` by scanning `/tmp/.X11-unix/`. If you're running the agent as a system service, make sure `DISPLAY` is set in the service environment.
|
||||||
|
|
||||||
|
### Relay shows "Cannot call receive once a disconnect message has been received"
|
||||||
|
|
||||||
|
This was a known bug — it is fixed. If you see it on an older version, update the relay container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build relay
|
||||||
|
```
|
||||||
|
|
||||||
|
### File transfer fails for large files
|
||||||
|
|
||||||
|
The relay holds chunks in memory. For files over ~100 MB, consider increasing the Docker memory limit or using an alternative file transfer method for large transfers. Chunk size is 64 KB.
|
||||||
|
|
||||||
|
### Agent auto-update isn't working
|
||||||
|
|
||||||
|
The relay's `CURRENT_AGENT_VERSION` in `app/api/agent/heartbeat/route.ts` must be bumped and the new binary placed at `/public/downloads/remotelink-agent-linux` (and `/windows.exe`). The version check is a simple string equality — it won't auto-update unless the constant is changed.
|
||||||
|
|
||||||
|
### Wake-on-LAN doesn't work
|
||||||
|
|
||||||
|
- The agent must have connected at least once since the MAC address feature was added (it's reported on registration)
|
||||||
|
- WoL only works if the target machine and the server are on the same L2 network segment, or your router forwards broadcast packets
|
||||||
|
- The machine's BIOS must have WoL enabled, and the NIC must be configured to wake on magic packet
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
Reference in New Issue
Block a user