homelab infrastructure services
1#!/bin/bash
2
3set -euo pipefail
4
5# tinsnip installer - Downloads and sets up tinsnip
6
7REPO_URL="${REPO_URL:-https://tangled.sh/dynamicalsystem.com/tinsnip}"
8BRANCH="main"
9INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/opt/dynamicalsystem.tinsnip}"
10INSTALLER_VERSION="e3981a2" # Updated automatically on commit via hooks
11
12# Service repository options
13SERVICE_REPO_URL="${SERVICE_REPO_URL:-https://tangled.org/@dynamicalsystem.com/service}"
14SERVICE_INSTALL_DIR="${SERVICE_INSTALL_DIR:-$HOME/.local/opt/dynamicalsystem.service}"
15
16log() {
17 echo "[Installer] $*"
18}
19
20error() {
21 log "ERROR: $*" >&2
22 exit 1
23}
24
25download_file() {
26 local file_path="$1"
27 local dest_path="$2"
28
29 # Support both custom REPO_URL and default git server
30 if [[ -n "${REPO_URL_OVERRIDE:-}" ]] || [[ "$REPO_URL" != "https://tangled.sh/dynamicalsystem.com/tinsnip" ]]; then
31 local url="${REPO_URL}/${file_path}"
32 else
33 local url="${REPO_URL}/raw/${BRANCH}/${file_path}"
34 fi
35
36 local filename=$(basename "$file_path")
37
38 log " $filename (from $url)"
39
40 if command -v curl &> /dev/null; then
41 if curl -fsSL "$url" -o "$dest_path" 2>/dev/null; then
42 chmod +x "$dest_path" 2>/dev/null || true
43 return 0
44 fi
45 fi
46
47 error "Failed to download $file_path"
48}
49
50clone_with_git() {
51 log "Cloning repository with git..."
52 if ! command -v git &> /dev/null; then
53 log "Git not found, installing..."
54 sudo apt-get update -qq && sudo apt-get install -y git
55 fi
56
57 if ! git clone "git@tangled.sh:dynamicalsystem.com/tinsnip" "$INSTALL_DIR"; then
58 error "Failed to clone repository. Make sure you have SSH access to git@tangled.sh"
59 fi
60}
61
62# Clone or download service repository
63install_service_catalog() {
64 local service_repo="$SERVICE_REPO_URL"
65
66 log "Installing service catalog..."
67 log " Repository: $service_repo"
68
69 if [[ -d "$SERVICE_INSTALL_DIR" ]]; then
70 log " Removing: $SERVICE_INSTALL_DIR"
71 rm -rf "$SERVICE_INSTALL_DIR"
72 fi
73
74 mkdir -p "$(dirname "$SERVICE_INSTALL_DIR")"
75
76 # Try git clone first
77 if command -v git &> /dev/null; then
78 # Convert HTTPS URL to git URL for cloning
79 local git_url="${service_repo/https:\/\/tangled.sh\//git@tangled.sh:}"
80
81 if git clone "$git_url" "$SERVICE_INSTALL_DIR" 2>/dev/null; then
82 log " Service catalog installed: $SERVICE_INSTALL_DIR"
83 return 0
84 fi
85 fi
86
87 # Fallback to downloading files if git fails
88 log "Git clone failed, downloading service files..."
89 # For now, skip download fallback as service repo structure may vary
90 log "WARNING: Could not clone service repository. Services will not be available."
91 log "You can manually clone the service repository later:"
92 log " git clone $service_repo $SERVICE_INSTALL_DIR"
93}
94
95# Download all local services from ./service/ directory
96download_local_services() {
97 log "Discovering and downloading local services..."
98
99 # For HTTP server, try to get directory listing of /service/
100 if [[ "$REPO_URL" == http://* ]]; then
101 local service_list_url="${REPO_URL}/service/"
102 log "Checking for services at: $service_list_url"
103
104 # Try to get directory listing (works with simple HTTP servers)
105 local services=()
106 if command -v curl &> /dev/null; then
107 # Extract directory names from HTTP directory listing
108 local listing
109 if listing=$(curl -fsSL "$service_list_url" 2>/dev/null); then
110 log "Directory listing received, parsing..."
111 log "Raw listing preview: $(echo "$listing" | head -10 | tr '\n' ' ')"
112
113 # Try multiple parsing approaches for different HTTP server formats
114
115 # Approach 1: Standard href links for directories
116 local href_services=($(echo "$listing" | grep -oE 'href="[^/"]+/"' | sed 's/href="//g' | sed 's/\/"//g' | grep -v '^\.$\|^\.\.$' | sort))
117 log "Parsed href services: ${href_services[*]}"
118
119 # Approach 2: Simple directory names (for plain listings)
120 local plain_services=($(echo "$listing" | grep -oE '^[a-zA-Z][a-zA-Z0-9_-]*/$' | sed 's/\/$//g' | sort))
121 log "Parsed plain services: ${plain_services[*]}"
122
123 # Combine and deduplicate results
124 services=()
125 if [[ ${#href_services[@]} -gt 0 ]] || [[ ${#plain_services[@]} -gt 0 ]]; then
126 services=($(printf '%s\n' "${href_services[@]}" "${plain_services[@]}" 2>/dev/null | sort -u))
127 fi
128
129 log "Discovered services: ${services[*]}"
130 else
131 log "Failed to get directory listing from $service_list_url"
132 fi
133 fi
134
135 # If discovery failed, warn but don't fallback to hardcoded list
136 if [[ ${#services[@]} -eq 0 ]]; then
137 log "WARNING: Could not discover any local services"
138 log "This may be normal if no services are in development"
139 else
140 log "Final service list: ${services[*]}"
141
142 # Download each discovered service
143 for service in "${services[@]}"; do
144 download_service "$service"
145 done
146 fi
147 else
148 # For git repositories, we can't discover dynamically, so skip
149 log "Git repository detected - local services not available via git clone"
150 log "Local services are only available when using HTTP server during development"
151 fi
152}
153
154# Download a single service and all its files
155download_service() {
156 local service="$1"
157 log " Downloading $service service..."
158
159 # Create service directory in SERVICE_INSTALL_DIR (not INSTALL_DIR/service/)
160 mkdir -p "$SERVICE_INSTALL_DIR/$service"
161
162 # Try to download common service files
163 local service_files=(
164 "docker-compose.yml"
165 "setup.sh"
166 "README.md"
167 "Dockerfile"
168 "requirements.txt"
169 )
170
171 local downloaded_count=0
172 for file in "${service_files[@]}"; do
173 local file_path="service/$service/$file"
174 local dest_path="$SERVICE_INSTALL_DIR/$service/$file"
175
176 # Try to download the file, but don't fail if it doesn't exist
177 if [[ "$REPO_URL" == http://* ]]; then
178 local url="${REPO_URL}/${file_path}"
179 else
180 local url="${REPO_URL}/raw/${BRANCH}/${file_path}"
181 fi
182
183 log " Downloading: $file from $url"
184 curl -v -L "$url" -o "$dest_path" --connect-timeout 10 --max-time 30
185 local curl_exit_code=$?
186
187 if [[ $curl_exit_code -eq 0 ]] && [[ -f "$dest_path" ]]; then
188 # Check if file is HTML (404 error page) and skip if so
189 if file "$dest_path" | grep -q "HTML" || head -1 "$dest_path" 2>/dev/null | grep -q "<!DOCTYPE"; then
190 log " ✗ Skipped: $file (got HTML error page, file doesn't exist)"
191 rm -f "$dest_path"
192 else
193 chmod +x "$dest_path" 2>/dev/null || true
194 downloaded_count=$((downloaded_count + 1))
195 log " ✓ Downloaded: $file"
196 fi
197 else
198 log " ✗ Failed: $file (exit code: $curl_exit_code)"
199 fi
200 done
201
202 # Try to download app/ directory (common for Python services)
203 log " Checking for app/ directory..."
204 mkdir -p "$SERVICE_INSTALL_DIR/$service/app"
205
206 local app_files=("main.py" "__init__.py" "config.py" "routes.py")
207 local app_downloaded=false
208 for app_file in "${app_files[@]}"; do
209 local file_path="service/$service/app/$app_file"
210 local dest_path="$SERVICE_INSTALL_DIR/$service/app/$app_file"
211
212 if [[ "$REPO_URL" == http://* ]]; then
213 local url="${REPO_URL}/${file_path}"
214 else
215 local url="${REPO_URL}/raw/${BRANCH}/${file_path}"
216 fi
217
218 if curl -fsSL "$url" -o "$dest_path" --connect-timeout 10 --max-time 30 2>/dev/null; then
219 log " ✓ Downloaded: app/$app_file"
220 downloaded_count=$((downloaded_count + 1))
221 app_downloaded=true
222 fi
223 done
224
225 # Clean up empty app directory if nothing was downloaded
226 if [[ "$app_downloaded" == false ]]; then
227 rmdir "$SERVICE_INSTALL_DIR/$service/app" 2>/dev/null || true
228 fi
229
230
231 if [[ $downloaded_count -gt 0 ]]; then
232 log " ✅ $service ($downloaded_count files)"
233 else
234 log " ❌ $service (no files found)"
235 # Remove empty directory
236 rmdir "$SERVICE_INSTALL_DIR/$service" 2>/dev/null || true
237 fi
238}
239
240# Bootstrap topsheet sheet infrastructure
241bootstrap_topsheet_sheet() {
242 log ""
243 log "Bootstrapping topsheet sheet infrastructure..."
244
245 # Check if topsheet.station-prod already exists
246 if [[ -d "/mnt/topsheet-station-prod" ]] || getent passwd topsheet-station-prod &>/dev/null; then
247 log "topsheet.station-prod already exists - skipping bootstrap"
248 return
249 fi
250
251 # First-time setup - bootstrap is required
252 log ""
253 log "REQUIRED: Setting up the topsheet (topsheet.station-prod)"
254 log "This creates essential infrastructure:"
255 log " - Global sheet registry (prevents UID collisions)"
256 log " - NAS credential storage"
257 log " - Service catalog management"
258 log ""
259 log "This is required for tinsnip to function properly."
260 log ""
261
262 # Check if we can read from terminal (not piped)
263 if [[ ! -t 0 ]]; then
264 log "ERROR: Interactive setup required but stdin is not a terminal (piped input detected)"
265 log ""
266 log "For first-time setup, run the installer interactively:"
267 log " curl -fsSL \"${REPO_URL}/raw/${BRANCH}/install.sh?\$(date +%s)\" -o /tmp/tinsnip-install.sh"
268 log " INSTALL_SERVICES=true bash /tmp/tinsnip-install.sh"
269 log ""
270 log "Or install CLI only and run bootstrap manually:"
271 log " export BOOTSTRAP_TOPSHEET=false"
272 log " curl -fsSL \"${REPO_URL}/raw/${BRANCH}/install.sh?\$(date +%s)\" | BOOTSTRAP_TOPSHEET=false INSTALL_SERVICES=true bash"
273 log " ~/.local/opt/dynamicalsystem.tinsnip/bin/tin machine station prod <your-nas-server>"
274 return 1
275 fi
276
277 # Check if topsheet NAS server is already registered
278 local nas_registry="/mnt/station-prod/data/nas-credentials/nas-servers"
279 if [[ -f "$nas_registry" ]] && grep -q "^topsheet=" "$nas_registry" 2>/dev/null; then
280 nas_server=$(grep "^topsheet=" "$nas_registry" | cut -d= -f2)
281 log "Using existing NAS server for topsheet sheet: $nas_server"
282 else
283 # Get NAS server for topsheet sheet
284 echo -e "\033[1;36mEnter NAS server hostname or IP for topsheet sheet: \033[0m\c"
285 read nas_server
286 if [[ -z "$nas_server" ]]; then
287 log "ERROR: NAS server is required to setop the topsheet"
288 log "You can complete setup later with:"
289 log " cd $INSTALL_DIR && TIN_SHEET=topsheet ./bin/tin machine create station prod <nas-server>"
290 return 1
291 fi
292 fi
293
294 setup_tinsnip="y"
295 case "$setup_tinsnip" in
296 [Yy]*)
297 log "Setting up topsheet sheet infrastructure..."
298
299 # Set up topsheet.station-prod
300 log "Creating topsheet.station-prod infrastructure..."
301 cd "$INSTALL_DIR"
302
303 if TIN_SHEET=topsheet ./bin/tin machine create station prod "$nas_server"; then
304 log "✅ topsheet.station-prod created successfully"
305 log ""
306
307 # Only offer SSH key generation if we don't already have working SSH
308 if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$nas_server" exit 2>/dev/null; then
309 log ""
310 log "Would you like to generate an SSH key for passwordless NAS access?"
311 log "This will enable automated operations without password prompts."
312 echo -e "\033[1;36mGenerate SSH key for $nas_server? [Y/n]: \033[0m\c"
313 read generate_key
314 else
315 log "✅ SSH access already configured for $nas_server"
316 generate_key="n" # Skip key generation
317 fi
318 case "${generate_key:-y}" in
319 [Nn]*)
320 log "Skipping SSH key generation"
321 log "You can generate it later with: tin key generate $nas_server"
322 ;;
323 *)
324 log "Generating SSH key for $nas_server..."
325
326 # Debug: Check if topsheet.station-prod infrastructure is ready
327 if [[ -d "/mnt/station-prod/data" ]]; then
328 log "✅ topsheet.station-prod infrastructure detected"
329 log " Mount point: /mnt/station-prod"
330 log " Data directory: /mnt/station-prod/data"
331 else
332 log "❌ topsheet.station-prod infrastructure not found"
333 log " Expected: /mnt/station-prod/data"
334 log " Mount check:"
335 mount | grep station-prod || echo " No station-prod mount found"
336 log " Directory check:"
337 ls -la /mnt/ | grep station || echo " No station directory in /mnt/"
338 fi
339
340 if "$INSTALL_DIR/bin/tin" key generate "$nas_server"; then
341 log "✅ SSH key generated and installed"
342 else
343 log "⚠️ SSH key generation failed - you can retry later"
344 log "Manual command: tin key generate $nas_server"
345 fi
346 ;;
347 esac
348
349 log ""
350 log "Platform infrastructure is ready:"
351 log " - Sheet registry: /mnt/station-prod/data/sheets"
352 log " - Service registry: /mnt/station-prod/data/services"
353 log " - NAS credentials: /mnt/station-prod/data/nas-credentials/"
354 log ""
355 log "You can now create other sheets:"
356 log " ./bin/tin machine create station prod $nas_server # (uses default sheet)"
357 log " TIN_SHEET=infrastructure ./bin/tin machine create station prod $nas_server"
358 else
359 log "WARNING: Failed to create topsheet.station-prod"
360 log "You can retry later with:"
361 log " cd $INSTALL_DIR && TIN_SHEET=topsheet ./bin/tin machine create station prod $nas_server"
362 fi
363 ;;
364 *)
365 log "Skipping topsheet setup"
366 log "You can set this up later with:"
367 log " cd $INSTALL_DIR && TIN_SHEET=topsheet ./bin/tin machine create station prod <nas-server>"
368 ;;
369 esac
370}
371
372main() {
373 log "tinsnip Infrastructure Installer (version: $INSTALLER_VERSION)"
374 log "=============================================================="
375
376 cd ~ || error "Failed to change to home directory"
377
378 if [[ ! -f /etc/os-release ]] || ! grep -q "Ubuntu" /etc/os-release; then
379 error "This installer requires Ubuntu"
380 fi
381
382 if [[ -d "$INSTALL_DIR" ]]; then
383 log "Tinsnip directory already exists. Removing for fresh installation..."
384 log " Removing: $INSTALL_DIR"
385 rm -rf "$INSTALL_DIR"
386 fi
387
388 log " Creating: $INSTALL_DIR"
389 mkdir -p "$INSTALL_DIR"/{scripts,machine/scripts,config}
390
391 log "Downloading setup files..."
392
393 # Download main files
394 download_file "setup.sh" "$INSTALL_DIR/setup.sh"
395 download_file "README.md" "$INSTALL_DIR/README.md"
396
397 # Download CLI commands
398 mkdir -p "$INSTALL_DIR/bin"
399 download_file "bin/tin" "$INSTALL_DIR/bin/tin"
400 # All CLI functionality now migrated to cmd/ structure
401
402 # Legacy deployment scripts removed - functionality moved to CLI
403
404 # Download machine infrastructure (the important stuff!)
405 download_file "machine/teardown.sh" "$INSTALL_DIR/machine/teardown.sh"
406
407 # Download new library structure
408 mkdir -p "$INSTALL_DIR/lib"
409 download_file "lib/core.sh" "$INSTALL_DIR/lib/core.sh"
410 download_file "lib/uid.sh" "$INSTALL_DIR/lib/uid.sh"
411 download_file "lib/registry.sh" "$INSTALL_DIR/lib/registry.sh"
412 download_file "lib/nas.sh" "$INSTALL_DIR/lib/nas.sh"
413 download_file "lib/nfs.sh" "$INSTALL_DIR/lib/nfs.sh"
414 download_file "lib/docker.sh" "$INSTALL_DIR/lib/docker.sh"
415 download_file "lib/metadata.sh" "$INSTALL_DIR/lib/metadata.sh"
416
417 # Download validation scripts
418 mkdir -p "$INSTALL_DIR/validation"
419 download_file "validation/validate.sh" "$INSTALL_DIR/validation/validate.sh"
420 download_file "validation/validate_functions.sh" "$INSTALL_DIR/validation/validate_functions.sh"
421 download_file "validation/validate_dry_run.sh" "$INSTALL_DIR/validation/validate_dry_run.sh"
422 download_file "validation/validate_port_edge_cases.sh" "$INSTALL_DIR/validation/validate_port_edge_cases.sh"
423 download_file "validation/validate_docker.sh" "$INSTALL_DIR/validation/validate_docker.sh"
424
425 # Download remaining machine scripts (still needed for now)
426 download_file "machine/setup_service.sh" "$INSTALL_DIR/machine/setup_service.sh"
427 download_file "machine/setup_station.sh" "$INSTALL_DIR/machine/setup_station.sh"
428 download_file "machine/install_docker.sh" "$INSTALL_DIR/machine/install_docker.sh"
429
430 # Legacy lib.sh removed - functionality consolidated into ./lib/ modules
431
432 # Note: Service catalog is maintained in separate repository
433 # Installed via install_service_catalog() to ~/.local/opt/{sheet}.service/
434
435 # Download new CMD structure
436 mkdir -p "$INSTALL_DIR/cmd/"{sheet,key,machine,service,nas,registry,status}
437
438 # Sheet commands
439 download_file "cmd/sheet/create.sh" "$INSTALL_DIR/cmd/sheet/create.sh"
440 download_file "cmd/sheet/list.sh" "$INSTALL_DIR/cmd/sheet/list.sh"
441 download_file "cmd/sheet/show.sh" "$INSTALL_DIR/cmd/sheet/show.sh"
442 download_file "cmd/sheet/rm.sh" "$INSTALL_DIR/cmd/sheet/rm.sh"
443
444 # Key commands
445 download_file "cmd/key/generate.sh" "$INSTALL_DIR/cmd/key/generate.sh"
446 download_file "cmd/key/install.sh" "$INSTALL_DIR/cmd/key/install.sh"
447 download_file "cmd/key/list.sh" "$INSTALL_DIR/cmd/key/list.sh"
448 download_file "cmd/key/remove.sh" "$INSTALL_DIR/cmd/key/remove.sh"
449 download_file "cmd/key/status.sh" "$INSTALL_DIR/cmd/key/status.sh"
450 download_file "cmd/key/show.sh" "$INSTALL_DIR/cmd/key/show.sh"
451 download_file "cmd/key/test.sh" "$INSTALL_DIR/cmd/key/test.sh"
452
453 # Machine commands
454 download_file "cmd/machine/create.sh" "$INSTALL_DIR/cmd/machine/create.sh"
455 download_file "cmd/machine/list.sh" "$INSTALL_DIR/cmd/machine/list.sh"
456 download_file "cmd/machine/status.sh" "$INSTALL_DIR/cmd/machine/status.sh"
457 download_file "cmd/machine/rm.sh" "$INSTALL_DIR/cmd/machine/rm.sh"
458
459 # NAS commands
460 download_file "cmd/nas/list.sh" "$INSTALL_DIR/cmd/nas/list.sh"
461 download_file "cmd/nas/show.sh" "$INSTALL_DIR/cmd/nas/show.sh"
462 download_file "cmd/nas/add.sh" "$INSTALL_DIR/cmd/nas/add.sh"
463 download_file "cmd/nas/discover.sh" "$INSTALL_DIR/cmd/nas/discover.sh"
464
465 # Service commands
466 download_file "cmd/service/deploy.sh" "$INSTALL_DIR/cmd/service/deploy.sh"
467 download_file "cmd/service/list.sh" "$INSTALL_DIR/cmd/service/list.sh"
468 download_file "cmd/service/status.sh" "$INSTALL_DIR/cmd/service/status.sh"
469 download_file "cmd/service/logs.sh" "$INSTALL_DIR/cmd/service/logs.sh"
470 download_file "cmd/service/stop.sh" "$INSTALL_DIR/cmd/service/stop.sh"
471 download_file "cmd/service/rm.sh" "$INSTALL_DIR/cmd/service/rm.sh"
472
473 # Registry and Status commands
474 download_file "cmd/registry/show.sh" "$INSTALL_DIR/cmd/registry/show.sh"
475 download_file "cmd/status/show.sh" "$INSTALL_DIR/cmd/status/show.sh"
476
477 # Station service scripts removed - functionality migrated to tin CLI
478
479 # Always install service catalog first (git clone if available)
480 install_service_catalog
481
482 # Download all local services from ./service/ directory (adds to catalog)
483 download_local_services
484
485 # Copy all local services to service catalog
486 if [[ -d "$INSTALL_DIR/service" ]]; then
487 log "Installing local services to catalog..."
488 mkdir -p "$SERVICE_INSTALL_DIR"
489
490 # Copy all services found in INSTALL_DIR/service/
491 local service_count=0
492 for service_dir in "$INSTALL_DIR/service"/*; do
493 if [[ -d "$service_dir" ]]; then
494 local service_name=$(basename "$service_dir")
495 # Skip station service (it's infrastructure, not a containerised service)
496 if [[ "$service_name" != "station" ]]; then
497 cp -r "$service_dir" "$SERVICE_INSTALL_DIR/"
498 chmod +x "$SERVICE_INSTALL_DIR/$service_name/setup.sh" 2>/dev/null || true
499 log " ✅ $service_name"
500 service_count=$((service_count + 1))
501 fi
502 fi
503 done
504
505 if [[ $service_count -eq 0 ]]; then
506 log " No local services found to install"
507 else
508 log " Installed $service_count local service(s)"
509 fi
510 fi
511
512 log "Installation complete!"
513 log ""
514 log "Adding tinsnip CLI to your PATH..."
515
516 # Add to PATH in shell profiles if not already present
517 local path_export="export PATH=\"$INSTALL_DIR/bin:\$PATH\""
518 local added_to_profile=false
519
520 # Add to ~/.bashrc if it exists and path not already there
521 if [[ -f ~/.bashrc ]] && ! grep -q "$INSTALL_DIR/bin" ~/.bashrc 2>/dev/null; then
522 echo "" >> ~/.bashrc
523 echo "# tinsnip CLI" >> ~/.bashrc
524 echo "$path_export" >> ~/.bashrc
525 log " Added to ~/.bashrc"
526 added_to_profile=true
527 fi
528
529 # Add to ~/.bash_profile if it exists and path not already there
530 if [[ -f ~/.bash_profile ]] && ! grep -q "$INSTALL_DIR/bin" ~/.bash_profile 2>/dev/null; then
531 echo "" >> ~/.bash_profile
532 echo "# tinsnip CLI" >> ~/.bash_profile
533 echo "$path_export" >> ~/.bash_profile
534 log " Added to ~/.bash_profile"
535 added_to_profile=true
536 fi
537
538 # Add to ~/.profile as fallback if no other profile was updated
539 if [[ "$added_to_profile" == false ]]; then
540 if [[ ! -f ~/.profile ]] || ! grep -q "$INSTALL_DIR/bin" ~/.profile 2>/dev/null; then
541 echo "" >> ~/.profile
542 echo "# tinsnip CLI" >> ~/.profile
543 echo "$path_export" >> ~/.profile
544 log " Added to ~/.profile"
545 added_to_profile=true
546 fi
547 fi
548
549 if [[ "$added_to_profile" == true ]]; then
550 log " ✅ PATH updated in shell profile(s)"
551 log " ⚠️ Start a new shell session or run: source ~/.bashrc"
552 else
553 log " ⚠️ PATH already configured or no shell profile found"
554 fi
555
556 log ""
557 log "Verify installation:"
558 log " $INSTALL_DIR/bin/tin --version"
559 log " $INSTALL_DIR/bin/tin help"
560 log ""
561
562 # Bootstrap topsheet if requested (after CLI is installed and in PATH)
563 if [[ "${BOOTSTRAP_TOPSHEET:-true}" == "true" ]]; then
564 bootstrap_topsheet_sheet
565 fi
566
567 log ""
568 log "Next steps:"
569 log "1. Set up topsheet (one-time):"
570 log " TIN_SHEET=topsheet $INSTALL_DIR/bin/tin machine station prod <nas-server>"
571 log ""
572 log "2. Create a sheet and deploy services:"
573 log " $INSTALL_DIR/bin/tin sheet infrastructure"
574 log " $INSTALL_DIR/bin/tin machine gateway prod"
575 log " $INSTALL_DIR/bin/tin service gateway-prod lldap"
576 log ""
577 log "3. Deploy services:"
578 log " $INSTALL_DIR/bin/tin machine marshal prod # External route coordinator"
579 log " $INSTALL_DIR/bin/tin service marshal-prod marshal"
580 log " $INSTALL_DIR/bin/tin machine pds prod # atproto Personal Data Server"
581 log " $INSTALL_DIR/bin/tin service pds-prod pds"
582 log ""
583 log "Or use the legacy script interface:"
584 if [[ -d "$SERVICE_INSTALL_DIR" ]]; then
585 # Get list of available services from the catalog
586 for service in $(ls -1 "$SERVICE_INSTALL_DIR" 2>/dev/null | grep -v README | head -3); do
587 log " cd $INSTALL_DIR && ./bin/tin machine create $service prod DS412plus.local"
588 done
589 else
590 log " cd $INSTALL_DIR && ./bin/tin machine create gateway prod DS412plus.local"
591 fi
592}
593
594main "$@"