Skip to content

digitalocean/ShellPort

Repository files navigation

ShellPort

Ephemeral coding interview workstation. One command to install, a local browser dashboard to run it, one click to remove.


Install

Two entry points, same installer — admins set up company-owned machines, candidates set up their own.

Admin (company-owned machine):

# macOS / Linux
curl -fsSL https://do.co/shellport-admin-mac | bash
# Windows (PowerShell)
irm https://do.co/shellport-admin-win | iex

Candidate (own machine):

# macOS / Linux
curl -fsSL https://do.co/shellport-macos | bash
# Windows (PowerShell)
irm https://do.co/shellport-windows | iex

Prerequisites: Docker (running) and Node.js. Your browser opens to http://localhost:3000 — everything runs locally, no cloud. The dashboard shows build progress, then "Start in" buttons for each detected editor (VS Code, Cursor, Windsurf, VSCodium…), the web editors vscode.dev and github.dev, and the container terminal.

Deploy via MDM (Jamf / Intune)

The same one-liner is the MDM payload — no wrapper needed. ShellPort is per-user (it installs to ~/shellport, uses the logged-in user's Docker, and opens their browser), and the installer detects when an MDM runs it as root (Jamf) or SYSTEM (Intune) and automatically re-runs in the logged-in user's session, forwarding any SHELLPORT_* config.

  • Jamf (macOS): paste curl -fsSL https://do.co/shellport-admin-mac | bash into a script and run it from a policy. Use a login or Self Service trigger so a user is present; if none is, the run exits cleanly so the next login retries. Set per-event config (questions, Slack token/channel, label) as policy parameters exported as SHELLPORT_*, or bake them into the release.
  • Intune (Windows): deploy irm https://do.co/shellport-admin-win | iex as a platform script. Either set "Run this script using the logged-on credentials = Yes" (cleanest — runs as the user directly), or leave it as SYSTEM and the installer re-launches into the user's session via a one-shot scheduled task.

Prerequisites still apply per user: Docker (set to start at login) and Node.js must be installed for the logged-in user, or the installer stops with a clear message.


The Container

The IDE opens inside an isolated container with the latest Go, Python, Java, Node.js, C/C++, TypeScript, GitHub Copilot, Claude Code, GitHub CLI, doctl, Homebrew, neovim, jq, and yq. All candidate work must be saved in /workspaces.


Cleanup

Click "End Interview" in the dashboard, or run the script directly:

  • macOS / Linux: ~/shellport/done.sh
  • Windows: ~\shellport\Done.ps1

This stops the server, kills IDE processes, tears down ShellPort's own container and volume, and deletes the project directory. It is scoped to ShellPort — never a host-wide Docker prune, and it leaves browsers and credentials untouched.

Recycle vs. End Event (operator)

Two operator actions, both gated behind OS-level admin auth (macOS/Linux sudo/pkexec, Windows UAC) so a candidate session can't reach them:

  • Recycle (DO station): scrub the host and load a fresh question for the next candidate without rebuilding the machine.
  • End Event: tear down the workstation — power off a DO station, or return the uninstall command on a BYOD machine.

ShellPort detects its machine type. On a candidate's own (BYOD) machine it only ever removes its own footprint — never the aggressive host scrub reserved for dedicated DO stations.


Security

Credential isolation is enforced via devcontainer.json: IDE settings block GitHub token injection, Git credential forwarding, SSH agent forwarding, and port forwarding; every spawned shell force-clears host tokens; the remote environment nullifies host variables and suppresses history. The container mounts only a named Docker volume — no host filesystem, Docker socket, or SSH agent socket is exposed.

Interviewer surfaces — Settings, telemetry, Recycle, End Event — are gated behind an admin unlock (the machine's OS-user password; no separate admin password) and never appear on a BYOD machine. Before each interview ShellPort confirms the host is clean and holds setup until any residue is cleared.

The troubleshooting panel (common fixes plus the manual fallback) is always available to a candidate on their own BYOD machine so they can recover unaided — destructive commands carry a warning and a confirm. On a managed DO station it is gated behind the same admin unlock, so a candidate session can't reach teardown/remove commands.


Optional Features

Off by default. Enable via .env or a config URL.

Feature .env variable What it does
Timer ENABLE_TIMER=true Live active/idle countdown. On expiry: NOTIFY, LOCK, or WIPE. Idle defined by INACTIVITY_TIMEOUT_MINUTES.
Telemetry ENABLE_TELEMETRY=true Exports shell history, AI usage, and Git activity on cleanup.
Questions QUESTIONS_URL=… Loads the assigned question during setup. Source is a Google Sheet of [title, docId] rows (QUESTION_ROW pins the row) or a Google Doc with one question per tab — all tabs are detected automatically and one is assigned at random (set QUESTION_TAB=t.<id> to pin a specific tab). The source is never sent to the candidate view — only the rendered question.
Question delivery QUESTION_DELIVERY=auto auto renders inline, falls back to PDF; inline; pdf; none.
Notifications SLACK_BOT_TOKEN=… Posts who/what/where to Slack when a question is assigned — candidate name, machine label, and the question. Preferred: a Slack App bot token (xoxb-…, scope chat:write) plus SLACK_CHANNEL — one token works across every blitz, just change the channel per event. Falls back to a single-channel incoming QUESTION_WEBHOOK if no token is set. Candidate name is set per-session in the dashboard.
Editors ENABLED_EDITORS=… Comma-separated allow-list of local editors (e.g. VS Code,Cursor). Empty offers every detected editor.
Terminal TERMINAL_ACCESS=false Hides the "Start in terminal" button. Default true.

These settings, plus appearance (theme/accent/font), are also editable live in the dashboard under Admin → Settings.


Repository Layout

.
├── app/
│   ├── server.js            # Local orchestration server
│   ├── index.html           # Dashboard frontend
│   └── package.json         # Server dependencies (ws only)
├── .devcontainer/
│   ├── devcontainer.json    # Container definition, tools, isolation
│   └── Dockerfile           # System packages (always latest)
├── docker-compose.yml
├── install.sh / Install.ps1               # Candidate / universal installers
├── admin-install.sh / admin-install.ps1   # Managed DO station installers
├── done.sh / Done.ps1                     # Cleanup
├── .env.defaults            # Baked defaults copied into .env at install
├── .env.example             # Documented reference for all options
├── .github/workflows/release.yml
├── admin/                   # Optional MDM overlay (host sanitization)
│   ├── macos/reset.sh
│   └── windows/Reset.ps1
└── README.md

Third-Party Environments

devcontainer.json works natively with GitHub Codespaces, DevPod, Coder, Daytona, and Gitpod — open the repo in any of them; no ShellPort install or cleanup scripts needed.


Admin Overlay (IT/MDM)

For company-owned machines cycled between candidates. The admin/ overlay adds host-level sanitization (browser/keychain/IDE wipe, history clear, post-wipe verification, container rebuild) and marks the machine as a managed DO station. It ships only in the admin release package.

sudo ./admin/macos/reset.sh                  # local, from inside the repo
sudo ./admin/macos/reset.sh /path/shellport  # explicit path
# MDM push (Jamf/Intune) auto-discovers ~/shellport

Releases

Push a tag; GitHub Actions builds the packages.

git tag -a v1.0.0 -m "Release" && git push origin v1.0.0
Package Audience Contains
shellport-VERSION.tar.gz / .zip Everyone Container + app + install + cleanup
shellport-VERSION-admin.tar.gz / .zip IT only Above + admin/ overlay
install.sh / Install.ps1 Standalone Candidate entry points
admin-install.sh / admin-install.ps1 Standalone Managed DO station entry points

Manual Fallback

If the dashboard fails:

cd ~/shellport
docker compose up -d --build
docker compose exec interview-env bash

Clean up manually:

docker compose down -v --remove-orphans
rm -rf ~/shellport

docker compose down -v removes only ShellPort's own container and volume.


Maintainer

DigitalOcean IT — it@digitalocean.com

About

Ephemeral Coding Interview Workstation

Resources

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors