Skip to content

Commit a8ef472

Browse files
committed
Add real terminal backend with Docker sandboxing
1 parent 3b4f185 commit a8ef472

57 files changed

Lines changed: 11556 additions & 1188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ yarn-debug.log*
3030
yarn-error.log*
3131
.pnpm-debug.log*
3232

33-
# env files (can opt-in for committing if needed)
34-
.env*
33+
# env files
34+
.env
35+
.env.local
36+
.env.production
37+
!.env.example
3538

3639
# vercel
3740
.vercel

DEPLOY.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Deployment Guide
2+
3+
## Quick Start (Hetzner + Vercel)
4+
5+
### Step 1: Server Setup (5 min)
6+
7+
SSH into your Hetzner server:
8+
```bash
9+
ssh root@159.69.189.116
10+
```
11+
12+
Copy and run:
13+
```bash
14+
# Update system
15+
apt update && apt upgrade -y
16+
17+
# Install essentials
18+
apt install -y curl git ufw fail2ban docker.io nginx certbot python3-certbot-nginx
19+
20+
# Enable Docker
21+
systemctl enable docker
22+
systemctl start docker
23+
24+
# Create user
25+
useradd -m -s /bin/bash -G docker,sudo termuser
26+
passwd termuser
27+
28+
# Setup firewall
29+
ufw default deny incoming
30+
ufw default allow outgoing
31+
ufw allow 22/tcp
32+
ufw allow 80/tcp
33+
ufw allow 443/tcp
34+
ufw --force enable
35+
36+
# Install Node.js
37+
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
38+
apt install -y nodejs
39+
40+
# Create app directory
41+
mkdir -p /opt/terminal-learning
42+
chown termuser:termuser /opt/terminal-learning
43+
```
44+
45+
### Step 2: Add SSH Key (IMPORTANT!)
46+
47+
On your **local machine**:
48+
```bash
49+
cat ~/.ssh/id_ed25519.pub
50+
```
51+
52+
On the **server** (still as root):
53+
```bash
54+
mkdir -p /home/termuser/.ssh
55+
echo "YOUR_KEY_HERE" > /home/termuser/.ssh/authorized_keys
56+
chmod 700 /home/termuser/.ssh
57+
chmod 600 /home/termuser/.ssh/authorized_keys
58+
chown -R termuser:termuser /home/termuser/.ssh
59+
```
60+
61+
Test in **new terminal**:
62+
```bash
63+
ssh termuser@159.69.189.116
64+
```
65+
66+
### Step 3: Deploy App
67+
68+
On server as termuser:
69+
```bash
70+
cd /opt/terminal-learning
71+
72+
# Clone your repo (or scp the files)
73+
git clone https://github.com/devatnull/learn-tmux-and-nvim.git .
74+
75+
# Go to backend
76+
cd backend
77+
78+
# Build Docker image
79+
docker build -t terminal-sandbox -f Dockerfile.sandbox .
80+
81+
# Test container
82+
docker run --rm -it terminal-sandbox
83+
84+
# Install Node deps
85+
npm install
86+
87+
# Build
88+
npm run build
89+
```
90+
91+
### Step 4: Setup Nginx + SSL
92+
93+
Replace `YOURDOMAIN.com` with your actual domain:
94+
95+
```bash
96+
# Create nginx config
97+
sudo tee /etc/nginx/sites-available/terminal << 'EOF'
98+
server {
99+
listen 80;
100+
server_name YOURDOMAIN.com;
101+
102+
location /ws {
103+
proxy_pass http://127.0.0.1:3001;
104+
proxy_http_version 1.1;
105+
proxy_set_header Upgrade $http_upgrade;
106+
proxy_set_header Connection "upgrade";
107+
proxy_set_header Host $host;
108+
proxy_read_timeout 3600s;
109+
}
110+
111+
location /health {
112+
proxy_pass http://127.0.0.1:3001;
113+
}
114+
}
115+
EOF
116+
117+
sudo ln -sf /etc/nginx/sites-available/terminal /etc/nginx/sites-enabled/
118+
sudo rm -f /etc/nginx/sites-enabled/default
119+
sudo nginx -t
120+
sudo systemctl reload nginx
121+
122+
# Get SSL cert
123+
sudo certbot --nginx -d YOURDOMAIN.com
124+
```
125+
126+
### Step 5: Create Service
127+
128+
```bash
129+
sudo tee /etc/systemd/system/terminal-learning.service << 'EOF'
130+
[Unit]
131+
Description=Terminal Learning Backend
132+
After=docker.service
133+
Requires=docker.service
134+
135+
[Service]
136+
Type=simple
137+
User=termuser
138+
WorkingDirectory=/opt/terminal-learning/backend
139+
ExecStart=/usr/bin/node dist/server.js
140+
Restart=always
141+
Environment=NODE_ENV=production
142+
Environment=PORT=3001
143+
Environment=ALLOWED_ORIGINS=https://learn-tmux-and-nvim.vercel.app,https://YOURDOMAIN.com
144+
145+
[Install]
146+
WantedBy=multi-user.target
147+
EOF
148+
149+
sudo systemctl daemon-reload
150+
sudo systemctl enable terminal-learning
151+
sudo systemctl start terminal-learning
152+
```
153+
154+
### Step 6: Vercel Config
155+
156+
Add to Vercel environment variables:
157+
```
158+
NEXT_PUBLIC_WS_URL=wss://YOURDOMAIN.com/ws
159+
```
160+
161+
## Verify
162+
163+
```bash
164+
# Check service
165+
sudo systemctl status terminal-learning
166+
167+
# Check logs
168+
journalctl -u terminal-learning -f
169+
170+
# Test WebSocket
171+
curl -i http://localhost:3001/health
172+
```
173+
174+
## Security Checklist
175+
176+
- [x] UFW firewall enabled
177+
- [x] SSH key auth only
178+
- [x] Non-root user for app
179+
- [x] Docker containers isolated
180+
- [x] Rate limiting in nginx
181+
- [x] SSL/HTTPS enabled
182+
- [ ] Disable root SSH (edit /etc/ssh/sshd_config: `PermitRootLogin no`)
183+
- [ ] Setup fail2ban for nginx

backend/.env.example

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Terminal Learning Backend Configuration
2+
# Copy to .env and adjust as needed
3+
4+
# Server port
5+
PORT=3001
6+
7+
# Max concurrent terminal sessions
8+
MAX_SESSIONS=15
9+
10+
# Session timeout (30 min in ms)
11+
SESSION_TIMEOUT=1800000
12+
13+
# Idle timeout (5 min in ms)
14+
IDLE_TIMEOUT=300000
15+
16+
# Allowed origins for CORS (comma-separated)
17+
ALLOWED_ORIGINS=https://vimux.dev,https://www.vimux.dev

backend/Dockerfile.sandbox

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# =============================================================================
2+
# Hardened Neovim + Tmux Sandbox Container
3+
# =============================================================================
4+
FROM ubuntu:24.04 AS base
5+
6+
# Prevent interactive prompts
7+
ENV DEBIAN_FRONTEND=noninteractive
8+
9+
# Install only what's needed
10+
RUN apt-get update && apt-get install -y --no-install-recommends \
11+
neovim \
12+
tmux \
13+
git \
14+
curl \
15+
ca-certificates \
16+
locales \
17+
&& rm -rf /var/lib/apt/lists/* \
18+
&& apt-get clean
19+
20+
# Set locale
21+
RUN locale-gen en_US.UTF-8
22+
ENV LANG=en_US.UTF-8
23+
ENV LC_ALL=en_US.UTF-8
24+
25+
# Create non-root user
26+
RUN useradd -m -s /bin/bash -u 1000 learner
27+
28+
# Setup practice directory with sample files
29+
RUN mkdir -p /home/learner/practice && \
30+
chown -R learner:learner /home/learner
31+
32+
# Copy practice files
33+
COPY --chown=learner:learner containers/practice/ /home/learner/practice/
34+
35+
# Basic neovim config for learning
36+
RUN mkdir -p /home/learner/.config/nvim && \
37+
chown -R learner:learner /home/learner/.config
38+
39+
COPY --chown=learner:learner containers/nvim-config/ /home/learner/.config/nvim/
40+
41+
# Basic tmux config for learning
42+
COPY --chown=learner:learner containers/tmux.conf /home/learner/.tmux.conf
43+
44+
# Set working directory
45+
WORKDIR /home/learner/practice
46+
47+
# Switch to non-root user
48+
USER learner
49+
50+
# Default command
51+
CMD ["tmux", "new-session", "-s", "learn"]
52+
53+
# =============================================================================
54+
# Security: This container runs with these restrictions (applied at runtime):
55+
# - --network none (no internet access)
56+
# - --read-only (read-only filesystem)
57+
# - --tmpfs /tmp (writable /tmp in memory)
58+
# - --memory 256m (memory limit)
59+
# - --cpus 0.5 (CPU limit)
60+
# - --cap-drop ALL (drop all capabilities)
61+
# - --security-opt no-new-privileges (prevent privilege escalation)
62+
# - --pids-limit 50 (limit number of processes)
63+
# =============================================================================

0 commit comments

Comments
 (0)