hackerrun 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:uncloud.run)",
5
+ "WebFetch(domain:www.ubicloud.com)",
6
+ "Bash(grep:*)",
7
+ "Bash(ssh root@46.4.244.246 \"jool -i hackerrun pool4 add 46.4.244.246 0-65535 --icmp\")",
8
+ "Bash(ssh root@46.4.244.246 \"jool -i hackerrun stats display\")",
9
+ "Bash(ssh root@46.4.244.246 \"ip -6 route show | grep 64:ff9b\")",
10
+ "Bash(ssh root@46.4.244.246 \"ip6tables -L -v -n\")",
11
+ "Bash(ssh:*)",
12
+ "Bash(find:*)",
13
+ "Bash(npm install:*)",
14
+ "Bash(npm run build:*)",
15
+ "Bash(node dist/index.js:*)",
16
+ "Bash(npx prisma migrate:*)",
17
+ "Bash(ls:*)",
18
+ "Bash(npm ls:*)",
19
+ "Bash(npx prisma generate:*)"
20
+ ]
21
+ }
22
+ }
package/.env.example ADDED
@@ -0,0 +1,9 @@
1
+ # Ubicloud API Configuration
2
+ UBICLOUD_API_TOKEN=your_api_token_here
3
+ UBICLOUD_PROJECT_ID=your_project_id_here
4
+
5
+ # Optional: Default values
6
+ # Available locations: eu-central-h1, eu-north-h1, us-east-a2
7
+ UBICLOUD_DEFAULT_LOCATION=us-east-a2
8
+ UBICLOUD_DEFAULT_SIZE=standard-2
9
+ UBICLOUD_DEFAULT_IMAGE=ubuntu-noble
package/CLAUDE.md ADDED
@@ -0,0 +1,532 @@
1
+ # Hackerrun CLI
2
+
3
+ CLI tool for deploying apps using Ubicloud VMs + Uncloud container orchestration.
4
+
5
+ ## Related Projects
6
+
7
+ - `../hackerrun-platform` - Platform API (Next.js)
8
+ - `../uncloud` - Container orchestration (not our code)
9
+
10
+ ## Domains
11
+
12
+ | Domain | Purpose |
13
+ |--------|---------|
14
+ | `hackerrun.com` | Platform website, docs, dashboard |
15
+ | `hackerrun.app` | User deployed applications |
16
+
17
+ ## User Experience
18
+
19
+ ```bash
20
+ $ hackerrun login # One-time GitHub OAuth
21
+ $ cd myapp
22
+ $ hackerrun deploy # App live at https://laughing-buddha.hackerrun.app
23
+ ```
24
+
25
+ ### App Naming
26
+
27
+ | Identifier | Scope | Example |
28
+ |------------|-------|---------|
29
+ | **App Name** | Per user | `myapp` |
30
+ | **Domain Name** | Global | `laughing-buddha` (Railway-style random) |
31
+
32
+ ### Domain Format
33
+
34
+ - Single service: `{domain}.hackerrun.app`
35
+ - Multi-service: `{domain}-{service}.hackerrun.app` (flattened for Cloudflare wildcard SSL)
36
+
37
+ ### Config Files
38
+
39
+ - **`.hackerrun`** - Links folder to app (contains app name)
40
+ - **`hackerrun.yml`** - Production compose with `x-ports` for public exposure
41
+ - **`docker-compose.yml`** - Local dev (ignored by hackerrun)
42
+
43
+ ## Commands
44
+
45
+ | Command | Description |
46
+ |---------|-------------|
47
+ | `hackerrun login` | Auth via GitHub |
48
+ | `hackerrun deploy` | Deploy app |
49
+ | `hackerrun apps` | List apps |
50
+ | `hackerrun logs <app>` | View logs |
51
+ | `hackerrun ssh <app>` | SSH into VM |
52
+ | `hackerrun destroy <app>` | Delete app |
53
+ | `hackerrun link <app>` | Link folder to existing app |
54
+ | `hackerrun unlink` | Remove `.hackerrun` |
55
+ | `hackerrun rename --app <old> <new>` | Rename app |
56
+ | `hackerrun domain --app <app> <new>` | Change domain |
57
+
58
+ ## Architecture: IPv4 Cost Optimization
59
+
60
+ **Problem:** Ubicloud charges $3/mo per public IPv4.
61
+
62
+ **Solution:** Single Gateway ($3/mo fixed) handles all traffic:
63
+
64
+ ```
65
+ INBOUND: User → Cloudflare → Tunnel → Gateway (Caddy) → App VMs (IPv6-only)
66
+ OUTBOUND: Container → DNS64 → NAT64 → IPv4 Internet
67
+ ```
68
+
69
+ ### Gateway Components
70
+
71
+ | Component | Purpose |
72
+ |-----------|---------|
73
+ | cloudflared | Tunnel endpoint (no inbound ports exposed) |
74
+ | Caddy | Routes by hostname to App VMs |
75
+ | BIND9 | DNS64 (synthesizes AAAA for IPv4 hosts) |
76
+ | Jool | NAT64 (translates 64:ff9b::/96 to IPv4) |
77
+ | WireGuard | NAT64 tunnel from App VMs |
78
+
79
+ ### Gateway Details
80
+
81
+ ```
82
+ IPv4: 46.4.244.246 | IPv6: 2a01:4f8:2b01:1317:e830::2
83
+ Location: eu-central-h1 | SSH: ssh root@46.4.244.246
84
+ Tunnel: 29a25987-06a5-425e-bbed-b090492a4e71
85
+ ```
86
+
87
+ ### x-ports Protocol
88
+
89
+ Use `/http` (Cloudflare terminates external HTTPS):
90
+
91
+ ```yaml
92
+ services:
93
+ app:
94
+ x-ports:
95
+ - "3000/http" # Single service
96
+ - "laughing-buddha-api.hackerrun.app:8080/http" # Multi-service
97
+ ```
98
+
99
+ ## SSH Authentication: Temporary Certificates
100
+
101
+ Users don't manage SSH keys. Instead, CLI gets short-lived certificates signed by platform CA.
102
+
103
+ ### Architecture
104
+
105
+ ```
106
+ ┌─────────────────────────────────────────────────────────────────┐
107
+ │ PLATFORM API │
108
+ │ │
109
+ │ SSH CA Private Key (signs certificates) │
110
+ │ Platform SSH Key (for VM creation + admin access) │
111
+ │ │
112
+ │ POST /api/apps/:appName/ssh-certificate │
113
+ │ → Signs short-lived cert (5 min TTL) │
114
+ │ → Returns certificate to CLI │
115
+ └─────────────────────────────────────────────────────────────────┘
116
+
117
+
118
+ ┌─────────────────────────────────────────────────────────────────┐
119
+ │ CLI │
120
+ │ │
121
+ │ 1. Generate ephemeral keypair │
122
+ │ 2. Request cert from platform │
123
+ │ 3. Load key+cert into temp SSH agent │
124
+ │ 4. Run uc --connect ssh+cli://root@[vm-ip] <command> │
125
+ │ 5. Cleanup (kill agent, delete temp files) │
126
+ └─────────────────────────────────────────────────────────────────┘
127
+ │ │
128
+ │ IPv6 (direct) │ IPv4 (tunnel via gateway)
129
+ ▼ ▼
130
+ ┌─────────────────┐ ┌─────────────────┐
131
+ │ App VM │ │ Gateway │
132
+ │ Trusts CA │ │ Trusts CA │
133
+ │ │ │ │ │
134
+ │ TrustedUserCA │ │ ▼ │
135
+ │ Keys = ca.pub │ │ App VM │
136
+ └─────────────────┘ └─────────────────┘
137
+ ```
138
+
139
+ ### VM Authentication Setup
140
+
141
+ When creating app VMs via Ubicloud:
142
+
143
+ | Key | Location on VM | Purpose |
144
+ |-----|----------------|---------|
145
+ | Platform SSH public key | `/root/.ssh/authorized_keys` | Ubicloud requirement + admin access |
146
+ | CA public key | `/etc/ssh/hackerrun-ca.pub` | User certificate validation |
147
+
148
+ ```bash
149
+ # /etc/ssh/sshd_config.d/hackerrun-ca.conf
150
+ TrustedUserCAKeys /etc/ssh/hackerrun-ca.pub
151
+ ```
152
+
153
+ ### IPv6 vs IPv4 Connectivity
154
+
155
+ CLI detects network and handles automatically (same logic as current `hackerrun ssh`):
156
+
157
+ ```typescript
158
+ // Test IPv6 connectivity (3s timeout)
159
+ let useProxy = false;
160
+ try {
161
+ execSync(`ssh -o ConnectTimeout=3 root@${vmIpv6} exit`, { timeout: 5000 });
162
+ } catch {
163
+ useProxy = true;
164
+ }
165
+
166
+ if (useProxy) {
167
+ // Tunnel through gateway, then connect
168
+ ssh -L localPort:[vm-ipv6]:22 root@gateway-ipv4 &
169
+ uc --connect "ssh+cli://root@localhost:localPort" deploy
170
+ } else {
171
+ // Direct connection
172
+ uc --connect "ssh+cli://root@[vm-ipv6]" deploy
173
+ }
174
+ ```
175
+
176
+ ### Why `ssh+cli://` (not `ssh://`)
177
+
178
+ | Connector | SSH Config | ProxyCommand | SSH Agent |
179
+ |-----------|------------|--------------|-----------|
180
+ | `ssh://` | No | No | Yes |
181
+ | `ssh+cli://` | Yes | Yes | Yes |
182
+
183
+ `ssh+cli://` uses system ssh binary which respects SSH agent where we load our temp cert.
184
+
185
+ ## Deploy Flow (No Local Config)
186
+
187
+ ### Current (Being Replaced)
188
+
189
+ ```
190
+ .hackerrun → app name → sync uncloud config → uc -c <context> deploy
191
+
192
+ └── Local ~/.config/uncloud/config.yaml
193
+ ```
194
+
195
+ ### New (Direct Connection)
196
+
197
+ ```
198
+ .hackerrun → app name → platform API → VM IP → uc --connect ssh+cli://root@[vm-ip] deploy
199
+
200
+ └── No local config, platform is source of truth
201
+ ```
202
+
203
+ ### Command Execution Flow
204
+
205
+ ```typescript
206
+ async function runUncloudCommand(command: string) {
207
+ // 1. Read app name from .hackerrun
208
+ const appName = (await readFile('.hackerrun', 'utf-8')).trim();
209
+
210
+ // 2. Get app details from platform API
211
+ const app = await platformClient.getApp(appName);
212
+ const vmIpv6 = app.nodes[0].ipv6;
213
+
214
+ // 3. Get temp SSH certificate
215
+ const { keyPath, certPath } = await getTempSSHCertificate(app);
216
+
217
+ // 4. Start temp SSH agent, add key+cert
218
+ const { authSock, agentPid } = await startTempAgent(keyPath);
219
+
220
+ // 5. Test IPv6, connect directly or via tunnel
221
+ // 6. Run: uc --connect "ssh+cli://root@[vm-ip]" <command>
222
+
223
+ // 7. Cleanup
224
+ process.kill(agentPid);
225
+ await rm(keyPath);
226
+ await rm(certPath);
227
+ }
228
+ ```
229
+
230
+ ### What Gets Removed
231
+
232
+ | File/Logic | Purpose | Status |
233
+ |------------|---------|--------|
234
+ | `src/lib/uncloud-sync.ts` | Syncs uncloud config locally | **Remove** |
235
+ | `src/lib/ssh.ts` (SSHKeyManager) | Manages user SSH keys | **Remove** |
236
+ | `~/.config/uncloud/` | Local uncloud contexts | **Not needed** |
237
+ | Context switching (`uc -c`) | Selects cluster | **Not needed** |
238
+
239
+ ## Platform DB Schema
240
+
241
+ ```prisma
242
+ model App {
243
+ id Int @id @default(autoincrement())
244
+ userId Int @map("user_id")
245
+ appName String @map("app_name")
246
+ domainName String @unique @map("domain_name")
247
+ location String
248
+ createdAt DateTime @default(now()) @map("created_at")
249
+ lastDeployedAt DateTime? @map("last_deployed_at")
250
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
251
+ nodes AppNode[]
252
+ routes AppRoute[]
253
+ @@unique([userId, appName])
254
+ @@map("apps")
255
+ }
256
+
257
+ model Gateway {
258
+ id Int @id @default(autoincrement())
259
+ vmId String @unique @map("vm_id")
260
+ ipv4 String
261
+ ipv6 String
262
+ tunnelId String @map("tunnel_id")
263
+ location String
264
+ @@map("gateways")
265
+ }
266
+
267
+ model AppRoute {
268
+ id Int @id @default(autoincrement())
269
+ appId Int @map("app_id")
270
+ hostname String @unique
271
+ backendIpv6 String @map("backend_ipv6")
272
+ backendPort Int @default(443)
273
+ @@map("app_routes")
274
+ }
275
+
276
+ # New models for SSH auth
277
+ model SSHCertificateAuthority {
278
+ id Int @id @default(autoincrement())
279
+ publicKey String @map("public_key") @db.Text
280
+ privateKeyEncrypted String @map("private_key_encrypted") @db.Text
281
+ createdAt DateTime @default(now()) @map("created_at")
282
+ @@map("ssh_ca")
283
+ }
284
+
285
+ model PlatformSSHKey {
286
+ id Int @id @default(autoincrement())
287
+ publicKey String @map("public_key") @db.Text
288
+ privateKeyEncrypted String @map("private_key_encrypted") @db.Text
289
+ createdAt DateTime @default(now()) @map("created_at")
290
+ @@map("platform_ssh_key")
291
+ }
292
+ ```
293
+
294
+ ## App VM Setup (cluster.ts)
295
+
296
+ 1. Create IPv6-only VM with **platform SSH key** (not user key)
297
+ 2. Configure CA for user certificates:
298
+ ```bash
299
+ echo "$CA_PUBLIC_KEY" > /etc/ssh/hackerrun-ca.pub
300
+ echo "TrustedUserCAKeys /etc/ssh/hackerrun-ca.pub" > /etc/ssh/sshd_config.d/hackerrun-ca.conf
301
+ systemctl reload sshd
302
+ ```
303
+ 3. Configure DNS64 via systemd-resolved
304
+ 4. Set up WireGuard for NAT64
305
+ 5. Run `uc machine init --no-dns`
306
+
307
+ ## Implementation Status
308
+
309
+ ### Completed
310
+
311
+ - [x] Gateway/AppRoute models, random name generator
312
+ - [x] IPv6-only VM creation, SSH with gateway fallback
313
+ - [x] Deploy with route registration, gateway sync
314
+ - [x] WireGuard/NAT64/Docker IPv6 config
315
+ - [x] Cloudflare Tunnel + wildcard DNS
316
+ - [x] SSH Certificates + Remove Local Config (temp certs, SSH agent, `uc --connect ssh+cli://`, removed uncloud-sync.ts and ssh.ts)
317
+
318
+ ## Docker Container NAT64
319
+
320
+ Containers need config to reach IPv4 APIs via NAT64:
321
+
322
+ 1. Enable IPv6 in Docker daemon
323
+ 2. Recreate `uncloud` network with IPv6
324
+ 3. Block IPv4 forwarding (iptables REJECT) so apps fall back to IPv6 quickly
325
+
326
+ ## Gateway Caddyfile
327
+
328
+ Auto-generated by `gateway-sync.ts`:
329
+
330
+ ```caddyfile
331
+ {
332
+ http_port 80
333
+ auto_https off
334
+ }
335
+
336
+ :80 {
337
+ @bright_ocean host bright-ocean.hackerrun.app *.bright-ocean.hackerrun.app
338
+ handle @bright_ocean {
339
+ reverse_proxy http://[2a01:4f8:2b01:131a:244c::2]:80
340
+ }
341
+ handle {
342
+ respond "App not found" 404
343
+ }
344
+ }
345
+ ```
346
+
347
+ ## Future: Scalability
348
+
349
+ ### Multi-Gateway HA
350
+ - Multiple cloudflared connect to same tunnel
351
+ - App VMs use multiple DNS64/NAT64 gateways
352
+
353
+ ### Scale-to-Zero
354
+ - Gateway detects idle → Platform stops VM → Traffic arrives → Gateway wakes VM
355
+ - Requires Ubicloud Stop/Resume feature
356
+
357
+ ## CI/CD: Integrated Build & Deploy
358
+
359
+ Railway/Render-style CI/CD without GitHub Actions. User connects their GitHub repo, pushes trigger automatic builds and deploys.
360
+
361
+ ### Architecture
362
+
363
+ ```
364
+ ┌─────────────────────────────────────────────────────────────────┐
365
+ │ 1. User pushes to GitHub │
366
+ └─────────────────────────────────────────────────────────────────┘
367
+
368
+
369
+ ┌─────────────────────────────────────────────────────────────────┐
370
+ │ 2. GitHub webhook (push event) → hackerrun-platform │
371
+ │ - Lookup app by repo URL │
372
+ │ - Generate build token (scoped to app, 15 min TTL) │
373
+ │ - Create ephemeral build VM via Ubicloud API │
374
+ └─────────────────────────────────────────────────────────────────┘
375
+
376
+
377
+ ┌─────────────────────────────────────────────────────────────────┐
378
+ │ 3. Ephemeral Build VM │
379
+ │ Pre-installed: git, docker, hackerrun CLI, uc CLI │
380
+ │ Receives: REPO_URL, COMMIT_SHA, BUILD_TOKEN, APP_NAME │
381
+ │ │
382
+ │ Build script: │
383
+ │ git clone $REPO_URL /build │
384
+ │ cd /build && git checkout $COMMIT_SHA │
385
+ │ hackerrun deploy --build-token $BUILD_TOKEN --app $APP │
386
+ │ │
387
+ │ hackerrun deploy internally: │
388
+ │ 1. Auth via build token (not user's CLI token) │
389
+ │ 2. docker build (on build VM, with cache) │
390
+ │ 3. SSH to app cluster VM │
391
+ │ 4. Transfer image via unregistry (direct, no registry) │
392
+ │ 5. Deploy containers │
393
+ └─────────────────────────────────────────────────────────────────┘
394
+
395
+
396
+ ┌─────────────────────────────────────────────────────────────────┐
397
+ │ 4. Build complete → VM destroyed │
398
+ │ - Build logs stored in platform DB │
399
+ │ - Status webhook to GitHub (optional) │
400
+ └─────────────────────────────────────────────────────────────────┘
401
+ ```
402
+
403
+ ### Key Design Decisions
404
+
405
+ | Decision | Rationale |
406
+ |----------|-----------|
407
+ | **Reuse `hackerrun deploy`** | Same deploy path for local and CI, no separate build system |
408
+ | **No container registry** | unregistry transfers images directly to cluster |
409
+ | **Ephemeral build VMs** | Clean environment, no multi-tenant security concerns |
410
+ | **Build tokens** | Scoped, short-lived auth for build VM to act on user's behalf |
411
+
412
+ ### CLI Changes
413
+
414
+ ```bash
415
+ # Local deploy (unchanged)
416
+ hackerrun deploy
417
+ └── Uses ~/.hackerrun/token
418
+ └── Reads app name from .hackerrun or hackerrun.yaml
419
+
420
+ # CI/CD deploy (new flags)
421
+ hackerrun deploy --build-token $TOKEN --app myapp
422
+ └── Uses provided token (no file lookup)
423
+ └── Uses provided app name (no file lookup)
424
+ └── Non-interactive, suitable for automation
425
+ ```
426
+
427
+ ### Docker Build Caching
428
+
429
+ Ephemeral VMs need external cache storage for fast rebuilds.
430
+
431
+ **Option 1: BuildKit with S3/R2 Remote Cache (Recommended)**
432
+
433
+ ```bash
434
+ docker buildx build \
435
+ --cache-from=type=s3,bucket=hackerrun-cache,region=auto,name=${userId}/${app} \
436
+ --cache-to=type=s3,bucket=hackerrun-cache,region=auto,name=${userId}/${app},mode=max \
437
+ -t image:tag .
438
+ ```
439
+
440
+ - Cache persists in Cloudflare R2 (no egress fees)
441
+ - Per-user/app cache isolation
442
+ - First build slow, subsequent builds fast
443
+
444
+ **Option 2: Pre-baked Build VM Image**
445
+
446
+ - Custom Ubicloud boot image with common base images pre-pulled
447
+ - `node:20`, `python:3.12`, `golang:1.22`, etc. already cached
448
+ - Reduces cold-start time for first builds
449
+
450
+ ### New Platform Models
451
+
452
+ ```prisma
453
+ model BuildToken {
454
+ id Int @id @default(autoincrement())
455
+ userId Int @map("user_id")
456
+ appName String @map("app_name")
457
+ token String @unique
458
+ expiresAt DateTime @map("expires_at")
459
+ createdAt DateTime @default(now()) @map("created_at")
460
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
461
+ @@map("build_tokens")
462
+ }
463
+
464
+ model Build {
465
+ id Int @id @default(autoincrement())
466
+ appId Int @map("app_id")
467
+ commitSha String @map("commit_sha")
468
+ commitMsg String? @map("commit_msg")
469
+ branch String
470
+ status String @default("pending") // pending, building, success, failed
471
+ logs String? @db.Text
472
+ vmId String? @map("vm_id") // Ephemeral build VM
473
+ startedAt DateTime? @map("started_at")
474
+ completedAt DateTime? @map("completed_at")
475
+ createdAt DateTime @default(now()) @map("created_at")
476
+ app App @relation(fields: [appId], references: [id], onDelete: Cascade)
477
+ @@map("builds")
478
+ }
479
+
480
+ model GithubRepo {
481
+ id Int @id @default(autoincrement())
482
+ appId Int @unique @map("app_id")
483
+ installationId BigInt @map("installation_id") // GitHub App installation
484
+ repoFullName String @map("repo_full_name") // owner/repo
485
+ branch String @default("main") // Branch to deploy
486
+ createdAt DateTime @default(now()) @map("created_at")
487
+ app App @relation(fields: [appId], references: [id], onDelete: Cascade)
488
+ @@map("github_repos")
489
+ }
490
+ ```
491
+
492
+ ### New Platform Endpoints
493
+
494
+ | Endpoint | Purpose |
495
+ |----------|---------|
496
+ | `POST /api/webhooks/github` | Receive push events, trigger builds |
497
+ | `POST /api/builds/token` | Generate short-lived build token |
498
+ | `GET /api/apps/:app/builds` | List builds for an app |
499
+ | `GET /api/apps/:app/builds/:id` | Get build details + logs |
500
+ | `POST /api/apps/:app/connect-repo` | Link GitHub repo to app |
501
+
502
+ ### GitHub App Requirements
503
+
504
+ Need a GitHub App (separate from OAuth) for:
505
+ - Receiving webhooks (push events)
506
+ - Cloning private repos on build VM
507
+ - Setting commit status (optional)
508
+
509
+ Permissions needed:
510
+ - Contents: read (clone repos)
511
+ - Webhooks: receive push events
512
+ - Commit statuses: write (optional, for status checks)
513
+
514
+ ### Implementation Status
515
+
516
+ - [ ] BuildToken model + API endpoint
517
+ - [ ] CLI `--build-token` and `--app` flags
518
+ - [ ] GitHub App setup + webhook handler
519
+ - [ ] Build model + status tracking
520
+ - [ ] Ephemeral build VM provisioning
521
+ - [ ] Build log streaming/storage
522
+ - [ ] S3/R2 cache bucket setup
523
+ - [ ] BuildKit cache integration
524
+ - [ ] Pre-baked build VM image (optional)
525
+
526
+ ## Development
527
+
528
+ ```bash
529
+ npm run build
530
+ npm run dev -- <command>
531
+ npm link # Global testing
532
+ ```
package/README.md ADDED
@@ -0,0 +1,94 @@
1
+ # Hackerrun CLI
2
+
3
+ Deploy apps with full control over your infrastructure. Hackerrun provides a Railway/Render-like experience on top of [Ubicloud](https://ubicloud.com) VMs with [Uncloud](https://github.com/psviderski/uncloud) container orchestration.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install
9
+ npm install -g hackerrun
10
+
11
+ # Login (one-time GitHub OAuth)
12
+ hackerrun login
13
+
14
+ # Deploy your app
15
+ cd myapp
16
+ hackerrun deploy
17
+ # => App live at https://laughing-buddha.hackerrun.app
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ | Command | Description |
23
+ |---------|-------------|
24
+ | `hackerrun login` | Authenticate via GitHub |
25
+ | `hackerrun deploy` | Deploy your application |
26
+ | `hackerrun apps` | List all your apps |
27
+ | `hackerrun logs <app>` | View application logs |
28
+ | `hackerrun ssh <app>` | SSH into app's VM |
29
+ | `hackerrun destroy <app>` | Delete app and infrastructure |
30
+ | `hackerrun link <app>` | Link current directory to an existing app |
31
+ | `hackerrun rename --app <old> <new>` | Rename an app |
32
+ | `hackerrun domain --app <app> <new>` | Change app's domain |
33
+
34
+ ## How It Works
35
+
36
+ 1. **First deploy** creates an IPv6-only VM on Ubicloud
37
+ 2. Your app is containerized and deployed via Uncloud
38
+ 3. Traffic flows through our gateway: `User → Cloudflare → Gateway → Your App`
39
+ 4. Apps get a random domain like `laughing-buddha.hackerrun.app`
40
+
41
+ ## App Naming
42
+
43
+ | Identifier | Scope | Example |
44
+ |------------|-------|---------|
45
+ | **App Name** | Per user | `myapp` (you choose) |
46
+ | **Domain** | Global | `laughing-buddha` (auto-generated) |
47
+
48
+ You can change the domain anytime with `hackerrun domain`.
49
+
50
+ ## Configuration Files
51
+
52
+ - **`hackerrun.yml`** - Docker Compose file for production with `x-ports` for public exposure
53
+ - **`docker-compose.yml`** - Local development (ignored by hackerrun)
54
+
55
+ ### Example hackerrun.yml
56
+
57
+ ```yaml
58
+ services:
59
+ web:
60
+ build: .
61
+ x-ports:
62
+ - "3000/http"
63
+ ```
64
+
65
+ The `x-ports` extension exposes your service publicly through the gateway.
66
+
67
+ ## Domains
68
+
69
+ | Domain | Purpose |
70
+ |--------|---------|
71
+ | `hackerrun.com` | Platform website & dashboard |
72
+ | `hackerrun.app` | Your deployed applications |
73
+
74
+ ### Domain Format
75
+
76
+ - Single service: `{domain}.hackerrun.app`
77
+ - Multi-service: `{domain}-{service}.hackerrun.app`
78
+
79
+ ## Development
80
+
81
+ ```bash
82
+ # Build
83
+ npm run build
84
+
85
+ # Run in development
86
+ npm run dev -- <command>
87
+
88
+ # Link globally for testing
89
+ npm link
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT