README.md
Rama main · réplica de lectura del repositorio original.
# LabSCH — Lab School Manager
[](https://www.python.org/)
[]()
[](LICENSE)
[]()
[]()
[](https://labsch-manager.pages.dev)
Centralized Windows lab management for 20+ PCs — **100% serverless**. Push block/allow rules, app-block policies, camera/audio controls, remote commands, and secure download tasks to lightweight Python agents on each client. Manage the fleet through `labschctl` or the Cloudflare Pages Web UI.
> **Current status:** `v0.4.0-test10` is an acceptance/test build, not a fleet-wide stable release. Windows behavior must be physically verified before broad rollout.
> **Live deployment**: Managing SMK + SMP lab PCs in Medan, Indonesia. Server runs on **Cloudflare Workers + D1** (free tier, $0/month) — no homeserver required, no tunnel to maintain, no VM to babysit.
>
> **This is a self-hosted project.** Deploy your own Workers URL (see [Server setup](#server-setup-cloudflare-workers)) — the URLs in this README are examples from the author's own deployment. **Never ship someone else's Workers URL with your token.**
## Features
- 🛡️ **Triple-layer blocking** — hosts file, browser policy registry, Windows IFEO. DoH-proof (disables Secure DNS via policy). Incognito-proof (disables Private mode).
- 📷🔊 **Camera & audio control** — kill switches per-PC or globally, reversible, delivered via heartbeat.
- 🆔 **MAC-based client ID** — one device = one record, even after agent reinstall or PC rename. No duplicates.
- 📦 **1-click installers** — `install.bat` (agent) + `labsch_startup.bat` (4-layer auto-start that survives reboots) + `labsch_full_uninstall.bat` (clean removal of everything).
- 🔁 **Self-protection** — Scheduled tasks + Run key + (optional) Task Manager lockdown. The student cannot kill or uninstall the agent through normal means.
- 💾 **Named rule profiles** — Save config as "Rules Lab", "Ujian", "Bebas Akses", activate with one command.
- 🪟 **Windows 10/11 LTSC** — Tested on MSI Thin 15 (LTSC), should work on Pro/Home/Enterprise.
- ⚡ **Serverless edge** — Cloudflare Workers global anycast, <50ms latency from Indonesia, 100k requests/day free.
- 🛠️ **Hermes skill** — Bundled admin CLI integrates with the Hermes Agent skill system. Just `labschctl` from any terminal.
- 🌐 **Cloudflare Pages Web UI** — Dashboard, clients, config, profiles, commands, downloads, device flags, and events from a responsive browser interface.
- ⬇️ **Durable download tasks** — Explicit per-client HTTPS downloads with SHA-256 verification, size/TTL limits, separate download/execution status, cancellation, and optional user-context autorun.
- ✅ **Confirmed remote commands** — Commands remain queued until the matching agent reports success; failed executions are retained for retry.
- 🧑💻 **Session refresh API** — Idempotent session refresh using stable device IDs and daily retention cleanup.
## Web UI
The static admin interface lives in [`web/`](web/) and talks directly to the Workers REST API. Deploy it to Cloudflare Pages:
```bash
node --check web/app.js
npx wrangler pages deploy web --project-name labsch-manager --branch main
```
Open the Pages URL, enter your own Worker URL and admin token, then manage the fleet. The token is never committed or baked into the static files; it is supplied at runtime and stored in that browser's `localStorage`. Use only a trusted admin device and log out afterward.
The UI considers a PC **online only when `last_seen` is no more than 60 seconds old**, rather than trusting a potentially stale database status field.
## Secure download tasks (v0.4.0 test)
```bash
# Autorun is the default and requires the expected SHA-256.
labschctl download https://example.org/worksheet.pdf \
--name worksheet.pdf \
--sha256 <64-lowercase-hex> \
--clients PC-LAB-01
# Download without opening the file.
labschctl download https://example.org/worksheet.pdf \
--no-run --clients PC-LAB-01
labschctl downloads
labschctl download-cancel <task-uuid>
```
Only explicit clients are supported. There is no implicit broadcast. Sources must use public HTTPS; script/installer autorun, SYSTEM autorun, custom arguments, and group targeting are intentionally unsupported in this test line. See [`docs/DOWNLOAD_TEST_RELEASE.md`](docs/DOWNLOAD_TEST_RELEASE.md).
## 🔐 API token management (v0.3.2+)
Rotate your `X-Agent-Token` without redeploying secrets manually:
```bash
# Generate a new token (returned ONCE, save it!)
labschctl token generate
# Check current token fingerprint (12-char SHA-256 prefix)
labschctl token info
# Revoke in-process / clear KV metadata
labschctl token revoke
```
**Under the hood:** `POST /api/admin/token/generate`, `GET /api/admin/token/info`, `DELETE /api/admin/token` — Workers KV namespace `TOKEN_META` (provision once: `wrangler kv:namespace create "TOKEN_META"`). For local FastAPI, tokens auto-persist to `~/.hermes/.env`. **The full token is never stored server-side** — only its SHA-256 fingerprint, so a KV / `.env` leak does not leak usable credentials.
## 🛠 API token CLI
`labschctl token <action>` supports `generate`, `info`, `revoke`. The CLI prompts for confirmation unless you pass `--yes`. See `docs/API.md` for full HTTP contract.
## Architecture
```
┌──────────────────────────────────────────┐
│ Cloudflare Edge (serverless) │
│ │
│ Workers: labsch-api (Hono, TS) │
│ ├─ 29 REST endpoints │
│ ├─ X-Agent-Token auth middleware │
│ └─ Cron: mark-stale (every 5 min) │
│ │
│ D1 Database: labsch-db (SQLite) │
│ ├─ clients / config / events │
│ └─ profiles / client_overrides │
└────────────┬─────────────────────────────┘
│ HTTPS polling
│ 30s heartbeat (configurable)
│ Auth: X-Agent-Token (UUID)
▼
┌──────────────────────────────────────────┐
│ Each Windows PC │
│ (agent/labsch_agent.py) │
│ Python → PyInstaller .exe │
│ 4-layer auto-start, self-protect │
│ Pulls config → applies policy │
│ Hosts + browser policy + IFEO + devices │
└──────────────────────────────────────────┘
▲
│ HTTPS (X-Agent-Token)
│
┌────────────┴─────────────────────────────┐
│ Admin (anywhere) │
│ labschctl CLI or Hermes Agent │
└──────────────────────────────────────────┘
```
The agent is **pull-based**: each client polls the server. No firewall ports to open on the school network, no NAT-traversal, no WebSocket. Plain HTTPS polling — simple and reliable at 20-PC scale.
**Why serverless?** The old design (FastAPI on a homeserver + Cloudflare Tunnel) worked, but meant babysitting a VM: systemd restarts, tunnel URL rotations, disk space. Workers + D1 removes all of that — $0/month, scales automatically, and the deployment URL never changes.
## Quick start (admin)
First, deploy your own server (see [Server setup](#server-setup-cloudflare-workers) below), then point the CLI at it:
```bash
# Set environment (or in ~/.hermes/.env):
export SCHOOL_SERVER_URL="https://labsch-api.<your-subdomain>.workers.dev"
export SCHOOL_API_TOKEN="<uuid>"
# List all clients
labschctl clients
# Block / allow
labschctl config block-site tiktok.com
labschctl config block-app RobloxPlayerLauncher.exe
labschctl config allow-site wikipedia.org
# Camera / audio control (v0.3.1+)
# (via API: POST /api/admin/device {"disable_camera": true})
# Bulk clear
labschctl unblock-all
# Named profiles
labschctl profile save "Rules Lab"
labschctl profile activate "Rules Lab"
# Rename a PC, view events
labschctl rename <client_id> "PC-LAB-01"
labschctl events --hours 1
```
## Deploy to a new PC
1. **Download** `labsch-agent-v0.3.2.zip` from the [Releases](../../releases) page.
2. **Copy** to a USB drive or directly to the target PC.
3. **Extract** the zip.
4. **Right-click `install.bat` → "Run as administrator"**.
5. **Answer two prompts**:
- Display name (e.g. `PC-LAB-01`)
- Is this a test PC? (Y/N — test PCs are excluded from profile rules)
6. **Run `labsch_startup.bat`** (also as admin) — installs 4-layer auto-start so the agent survives reboots reliably.
7. **Done.** The agent registers and starts applying the active config within 60 seconds.
### Auto-start layers (v0.3.1+)
`labsch_startup.bat` installs all of these:
| Layer | Trigger | Purpose |
|---|---|---|
| Startup folder (user) | User login | First chance |
| Startup folder (all users) | Any user login | Coverage for shared PCs |
| Run key HKLM | Boot | Classic auto-start |
| Scheduled task ONSTART (SYSTEM) | **Before login** | Runs even at the login screen |
Plus a **starter script with retry**: waits until Python is available on PATH (up to 5 min), then launches the agent, then respawns it every 10 seconds if it dies. This fixes the "sometimes doesn't load after reboot" issue where Windows fires the Run key before Python/PATH is ready.
## Per-PC overrides
The global config applies to every client by default. A per-PC override replaces it for one client only.
```bash
# Block YouTube + Roblox only on PC-LAB-01; allow Google there
labschctl client-config set desktop-abc123 \
--blocked-site youtube.com \
--allowed-site google.com \
--blocked-app RobloxPlayerBeta.exe
# Inspect the override
labschctl client-config show desktop-abc123
# Remove the override; it inherits global rules again
labschctl client-config clear desktop-abc123
```
To keep one PC completely free while the global profile remains active, save an explicit empty override:
```bash
labschctl client-config set desktop-abc123
```
`set` with no lists means "free access for this PC". `clear` means "inherit global rules".
## Camera & audio control (v0.3.1+)
Two new kill switches, set globally or per-PC, delivered through the same heartbeat as blocking rules:
| Flag | Effect | Mechanism |
|---|---|---|
| `disable_camera` | No app can access the webcam | Registry policy `AllowCamera=0` + device-install restriction on the camera class GUID |
| `disable_audio` | System-wide silence | Stop + disable `audiosrv` and `AudioEndpointBuilder` services |
Both are **reversible** — flipping the flag off immediately restores the device. Set via the admin API:
```bash
# Global: kill both
curl -X POST -H "X-Agent-Token: $TOKEN" -H "Content-Type: application/json" \
-d '{"disable_camera": true, "disable_audio": true}' \
$SCHOOL_SERVER_URL/api/admin/device
# Per-PC only
curl -X POST ... $SCHOOL_SERVER_URL/api/admin/device/<client_id>
# Clear per-PC override (inherit global)
curl -X DELETE ... $SCHOOL_SERVER_URL/api/admin/device/<client_id>
```
## Uninstall (per PC)
```powershell
labsch_full_uninstall.bat # Right-click → Run as administrator
```
Removes **everything**: agent processes, all 4 auto-start layers, hosts-file entries, browser policies, IFEO keys, re-enables Task Manager, and deletes `C:\ProgramData\LabSCHAgent`. The PC returns to a clean state.
For the agent files only (keep auto-start), use `uninstall.bat` instead.
## How blocking works (defense in depth)
The agent applies three layers on every config change:
### 1. Hosts file
Appends `127.0.0.1 <domain>` lines with marker comments. Catches all DNS-aware software.
### 2. Browser policy (registry)
Sets `URLBlocklist` and `URLAllowlist` for Edge/Chrome/Brave. Disables DoH and Incognito so the browser can't bypass.
### 3. IFEO (Image File Execution Options)
Sets `Debugger=cmd.exe /c exit` for each blocked `.exe`. The OS itself blocks execution *before* the process starts — renaming or moving the binary doesn't help.
If config is cleared, the agent **deletes** the registry keys (not sets to empty) to avoid stale policy caching in Chromium browsers.
## Rule profiles
A profile is a named snapshot of `(blocked_apps, blocked_websites, allowed_websites)`. Workflow:
1. Set up the live config:
```bash
labschctl config block-site tiktok.com
labschctl config allow-site wikipedia.org
labschctl config block-app RobloxPlayerLauncher.exe
```
2. Save it: `labschctl profile save "Rules Lab"`
3. Restore it later: `labschctl profile activate "Rules Lab"`
Multiple profiles coexist. Activation bumps `config_version`, which agents pull on their next cycle.
## Requirements
### Server (Cloudflare)
- Cloudflare free account
- Node.js 18+ with `wrangler` (deploy-time only, not runtime)
- D1 database (free tier: 5 GB storage, 5M reads/day)
### Agent (per PC)
- Windows 10 or 11 (LTSC, Pro, Home all tested)
- Python 3.10+ (or pre-built `.exe` via `build.bat`)
- Administrator privileges for install + first run
- Internet access to `*.workers.dev` (or your custom domain)
### Quota math (free tier)
| Item | Usage at 20 PCs | Free limit |
|---|---|---|
| Workers requests | ~86,500/day | 100,000/day ✅ |
| D1 reads | ~50,000/day | 5M/day ✅ |
| D1 writes | ~5,000/day | 100k/day ✅ |
Heartbeat interval is configurable (`heartbeat_interval` in config.ini, default 30s) if you need to trade latency for quota headroom.
## Server setup (Cloudflare Workers)
From scratch (replacing the old homeserver + tunnel setup):
```bash
# 1. Install wrangler + login
npm install -g wrangler
wrangler login # browser OAuth (or --device flow on headless)
# 2. Create the D1 database
wrangler d1 create labsch-db
# Note the database_id → put in workers/wrangler.toml
# 3. Apply schema
cd workers
wrangler d1 execute labsch-db --remote --file=schema.sql -y
# 4. Set the auth token secret
wrangler secret put SCHOOL_API_TOKEN # paste a UUID
# 5. Deploy
wrangler deploy
# → https://labsch-api.<your-subdomain>.workers.dev
```
Set the URL as `SCHOOL_SERVER_URL` for `labschctl`, and bake it into the agent's `install.bat` before distribution. Cron trigger (mark-stale every 5 min) is already in `wrangler.toml`.
## ⚠️ Configure your own URL (REQUIRED before deploying agents)
After deploying your own Worker, you must replace the server URL in **every place the agents and CLI read it**. There is no central config — each location is baked at install/build time:
| # | File | What to change |
|---|---|---|
| 1 | **`agent/install.bat`** (line ~34) | `set "SERVER_URL=https://labsch-api.<your-subdomain>.workers.dev"` |
| 2 | **`agent/build.bat`** args | `build.bat --server https://labsch-api.<your-subdomain>.workers.dev --token <UUID>` |
| 3 | **`skill/labschctl`** (or `~/.hermes/.env`) | `SCHOOL_SERVER_URL` env var |
| 4 | **Existing PCs** | Edit `C:\ProgramData\LabSCHAgent\config.ini` → `"server_url"` key, then restart the agent |
Also replace `API_TOKEN` in `install.bat` with **your own** generated UUID:
```bash
python3 -c "import uuid; print(uuid.uuid4())"
```
> **Security note**: the API token grants full control of every registered PC (including remote shutdown). Never commit your real token to a public repo, and never point your agents at a Workers URL you don't control — the operator of that URL could shut down your lab.
>
> **Already-installed PCs without a reinstall?** Fastest path: edit `config.ini` on each PC (option 4 above). The MAC-based client ID means no re-registration is needed — the same PC keeps its record.
## Build (Windows .exe)
To distribute as a single `.exe` instead of Python scripts:
```cmd
cd agent
build.bat --server https://labsch-api.<your-subdomain>.workers.dev --token <UUID>
```
Output: `dist\LabSCHAgent.exe` (~15 MB, self-contained).
For 1-click install across 20 PCs, use the Python version + `install.bat` — it's smaller and updates the agent in one place.
## API
All admin endpoints require `X-Agent-Token: <token>` header. See [`docs/API.md`](docs/API.md) for the full list. Quick reference:
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/api/health` | Health check (open) |
| `POST` | `/api/heartbeat` | Agent heartbeat — returns config + device flags + pending command |
| `GET` | `/api/clients` | List registered clients |
| `GET` | `/api/admin/config` | Live blocklist config |
| `POST` | `/api/admin/profiles` | Create or update named profile |
| `POST` | `/api/admin/profiles/{name}/activate` | Apply profile to all clients |
| `POST` | `/api/admin/device` | Set camera/audio flags (global) |
| `POST` | `/api/admin/device/{client_id}` | Set camera/audio flags (per-PC) |
| `POST` | `/api/admin/command/{client_id}` | Queue shutdown/restart/lock/notify |
## File layout
```
labsch/
├── workers/ Cloudflare Workers server (TypeScript, Hono)
│ ├── src/index.ts Entry + routing
│ ├── src/auth.ts Token middleware
│ ├── src/handlers/ API handlers + Vitest coverage
│ ├── migrations/ Additive D1 migrations
│ ├── schema.sql Complete D1 schema
│ ├── wrangler.toml Deploy config (D1/KV bindings, crons)
│ └── package.json
├── web/ Static Cloudflare Pages admin UI
│ ├── index.html
│ ├── app.js
│ └── style.css
├── agent/ Windows client
│ ├── labsch_agent.py Main loop
│ ├── config_sync.py HTTP client (custom UA, CF bot-fight safe)
│ ├── device_blocker.py Camera/audio kill switches (v0.3.1)
│ ├── browser_policy.py Edge/Chrome/Brave registry policy
│ ├── ifeo_blocker.py App execution block
│ ├── website_blocker.py Hosts file editor
│ ├── self_protect.py Scheduled task + Run key
│ ├── device_id.py MAC-based stable ID
│ ├── install.bat 1-click agent installer
│ ├── labsch_startup.bat 4-layer auto-start installer
│ ├── labsch_full_uninstall.bat Full clean uninstall
│ ├── uninstall.bat Minimal uninstall
│ └── build.bat PyInstaller .exe builder
├── skill/ Hermes skill
│ ├── SKILL.md
│ └── labschctl Admin CLI
└── docs/
├── ARCHITECTURE.md
├── API.md
├── ALLOWLIST_BAT.md
└── plans/ Migration plans
```
## Roadmap
- [x] Per-client config overrides (block site X only on PC-LAB-01)
- [x] Serverless migration (Cloudflare Workers + D1)
- [x] Camera & audio control
- [x] Reliable auto-start across reboots (4-layer)
- [ ] Group profiles (apply Rules Lab to group "lab", not test PCs)
- [x] Cloudflare Pages Web UI (dashboard, clients, config, profiles, commands, downloads, devices, events)
- [ ] Real-time events stream (WebSocket/SSE for admin UI)
- [ ] Per-PC schedule (different rules for class time vs break time)
- [ ] Native Windows installer (MSI) for Group Policy deployment
## License
[MIT](LICENSE) — use freely for any school / lab / office environment.
## Credits
- Inspired by the [FULL ALLOWLIST WEB v4](docs/ALLOWLIST_BAT.md) BAT script (registry-based browser policy)
- Built for SMK/SMP schools in Medan, Indonesia
- Runs on [Cloudflare Workers](https://workers.cloudflare.com/) + D1 (free tier)