···1+// Change admin password
2+import { adminAuth } from './src/lib/admin-auth'
3+import { db } from './src/lib/db'
4+import { randomBytes, createHash } from 'crypto'
5+6+// Get username and new password from command line
7+const username = process.argv[2]
8+const newPassword = process.argv[3]
9+10+if (!username || !newPassword) {
11+ console.error('Usage: bun run change-admin-password.ts <username> <new-password>')
12+ process.exit(1)
13+}
14+15+if (newPassword.length < 8) {
16+ console.error('Password must be at least 8 characters')
17+ process.exit(1)
18+}
19+20+// Hash password
21+function hashPassword(password: string, salt: string): string {
22+ return createHash('sha256').update(password + salt).digest('hex')
23+}
24+25+function generateSalt(): string {
26+ return randomBytes(32).toString('hex')
27+}
28+29+// Initialize
30+await adminAuth.init()
31+32+// Check if user exists
33+const result = await db`SELECT username FROM admin_users WHERE username = ${username}`
34+if (result.length === 0) {
35+ console.error(`Admin user '${username}' not found`)
36+ process.exit(1)
37+}
38+39+// Update password
40+const salt = generateSalt()
41+const passwordHash = hashPassword(newPassword, salt)
42+43+await db`UPDATE admin_users SET password_hash = ${passwordHash}, salt = ${salt} WHERE username = ${username}`
44+45+console.log(`✓ Password updated for admin user '${username}'`)
46+process.exit(0)
+33
claude.md
···000000000000000000000000000000000
···1+ Wisp.place - Decentralized Static Site Hosting
2+3+ Architecture Overview
4+5+ Wisp.Place a two-service application that provides static site hosting on the AT
6+ Protocol. Wisp aims to be a CDN for static sites where the content is ultimately owned by the user at their repo. The microservice is responsbile for injesting firehose events and serving a on-disk cache of the latest site files.
7+8+ Service 1: Main App (Port 8000, Bun runtime, elysia.js)
9+ - User-facing editor and API
10+ - OAuth authentication (AT Protocol)
11+ - File upload processing (gzip + base64 encoding)
12+ - Domain management (subdomains + custom domains)
13+ - DNS verification worker
14+ - React frontend
15+16+ Service 2: Hosting Service (Port 3001, Node.js runtime, hono.js)
17+ - AT Protocol Firehose listener for real-time updates
18+ - Serves hosted websites from local cache
19+ - Multi-domain routing (custom domains, wisp.place subdomains, sites subdomain)
20+ - Distributed locking for multi-instance coordination
21+22+ Tech Stack
23+24+ - Backend: Bun/Node.js, Elysia.js, PostgreSQL, AT Protocol SDK
25+ - Frontend: React 19, Tailwind CSS v4, Shadcn UI
26+27+ Key Features
28+29+ - AT Protocol Integration: Sites stored as place.wisp.fs records in user repos
30+ - File Processing: Validates, compresses (gzip), encodes (base64), uploads to user's PDS
31+ - Domain Management: wisp.place subdomains + custom BYOD domains with DNS verification
32+ - Real-time Sync: Firehose worker listens for site updates and caches files locally
33+ - Atomic Updates: Safe cache swapping without downtime
+31
create-admin.ts
···0000000000000000000000000000000
···1+// Quick script to create admin user with randomly generated password
2+import { adminAuth } from './src/lib/admin-auth'
3+import { randomBytes } from 'crypto'
4+5+// Generate a secure random password
6+function generatePassword(length: number = 20): string {
7+ const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*'
8+ const bytes = randomBytes(length)
9+ let password = ''
10+ for (let i = 0; i < length; i++) {
11+ password += chars[bytes[i] % chars.length]
12+ }
13+ return password
14+}
15+16+const username = 'admin'
17+const password = generatePassword(20)
18+19+await adminAuth.init()
20+await adminAuth.createAdmin(username, password)
21+22+console.log('\n╔════════════════════════════════════════════════════════════════╗')
23+console.log('║ ADMIN USER CREATED SUCCESSFULLY ║')
24+console.log('╚════════════════════════════════════════════════════════════════╝\n')
25+console.log(`Username: ${username}`)
26+console.log(`Password: ${password}`)
27+console.log('\n⚠️ IMPORTANT: Save this password securely!')
28+console.log('This password will not be shown again.\n')
29+console.log('Change it with: bun run change-admin-password.ts admin NEW_PASSWORD\n')
30+31+process.exit(0)
+3-2
hosting-service/src/index.ts
···1import app from './server';
2import { FirehoseWorker } from './lib/firehose';
03import { mkdirSync, existsSync } from 'fs';
45const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
···11 console.log('Created cache directory:', CACHE_DIR);
12}
1314-// Start firehose worker
15const firehose = new FirehoseWorker((msg, data) => {
16- console.log(msg, data);
17});
1819firehose.start();
···1import app from './server';
2import { FirehoseWorker } from './lib/firehose';
3+import { logger } from './lib/observability';
4import { mkdirSync, existsSync } from 'fs';
56const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
···12 console.log('Created cache directory:', CACHE_DIR);
13}
1415+// Start firehose worker with observability logger
16const firehose = new FirehoseWorker((msg, data) => {
17+ logger.info(msg, data);
18});
1920firehose.start();