homelab infrastructure services
at main 499 lines 16 kB view raw view rendered
1# Service Setup Script Guide 2 3This guide covers creating `setup.sh` scripts for tinsnip services. Setup scripts prepare the service environment after machine infrastructure is created but before the service containers start. 4 5## Overview 6 7Service setup scripts run once during service deployment to: 8- Create required directories and files 9- Set up configuration templates 10- Initialize service-specific data structures 11- Create convenience utilities for the service user 12 13## Script Structure 14 15### Basic Template 16 17```bash 18#!/bin/bash 19# [Service Name] Service Setup 20# Brief description of what this service does 21 22set -euo pipefail 23 24# Source service environment variables 25if [[ -f "/mnt/${USER}/.env" ]]; then 26 source "/mnt/${USER}/.env" 27elif [[ -f "../../../.env" ]]; then 28 source "../../../.env" 29else 30 echo "Error: Could not find service .env file" >&2 31 exit 1 32fi 33 34# Simple logging function (self-contained) 35log_with_prefix() { 36 local prefix="$1" 37 local message="$2" 38 echo "[$prefix] $message" 39} 40 41log_with_prefix "ServiceName" "Setting up service environment" 42 43# Your setup logic here... 44 45log_with_prefix "ServiceName" "Setup complete!" 46``` 47 48## Available Environment Variables 49 50The service `.env` file provides these standard variables: 51 52```bash 53TIN_SERVICE_NAME # Service name (e.g., "nrfconnect") 54TIN_SERVICE_ENVIRONMENT # Environment (e.g., "prod", "test") 55TIN_SERVICE_UID # Service user UID (e.g., 50300) 56TIN_SHEET # Sheet name (e.g., "topsheet") 57 58# Ports (if service uses them) 59TIN_PORT_0 # Base port (usually same as UID) 60TIN_PORT_1 # Additional ports as needed 61TIN_PORT_2 # ... 62 63# XDG directories (NFS-backed) 64XDG_DATA_HOME # /mnt/service-env/data 65XDG_CONFIG_HOME # /mnt/service-env/config 66XDG_STATE_HOME # /mnt/service-env/state 67 68# Docker environment 69DOCKER_HOST # unix:///run/user/UID/docker.sock 70XDG_RUNTIME_DIR # /run/user/UID 71``` 72 73## Directory Structure 74 75Services follow the XDG Base Directory specification with NFS backing: 76 77``` 78/mnt/service-environment/ 79├── data/ # Persistent application data (XDG_DATA_HOME) 80├── config/ # Configuration files (XDG_CONFIG_HOME) 81├── state/ # Logs, runtime state (XDG_STATE_HOME) 82└── service/ # Service definitions (docker-compose.yml, etc.) 83``` 84 85### Directory Usage Patterns 86 87- **data/** - Database files, user content, application-specific data 88- **config/** - Configuration templates, settings files 89- **state/** - Log files, session data 90 91## Common Setup Tasks 92 93### 1. Create Required Directories 94 95```bash 96# Define service-specific directory structure 97MOUNT_BASE="/mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" 98REQUIRED_DIRS=( 99 "$MOUNT_BASE/data/app-specific" 100 "$MOUNT_BASE/config/templates" 101 "$MOUNT_BASE/state/logs" 102) 103 104log_with_prefix "ServiceName" "Creating required directories" 105for dir in "${REQUIRED_DIRS[@]}"; do 106 if [[ ! -d "$dir" ]]; then 107 mkdir -p "$dir" 108 log_with_prefix "ServiceName" "Created: $dir" 109 fi 110done 111``` 112 113### 2. Create Configuration Templates 114 115```bash 116# Create default configuration if it doesn't exist 117CONFIG_DIR="$MOUNT_BASE/config" 118if [[ ! -f "$CONFIG_DIR/app.conf" ]]; then 119 log_with_prefix "ServiceName" "Creating default configuration" 120 cat > "$CONFIG_DIR/app.conf" << 'EOF' 121# Default configuration for ServiceName 122port=${TIN_PORT_0} 123data_dir=/workdir/data 124log_level=info 125EOF 126fi 127``` 128 129### 3. Create User Convenience Tools 130 131```bash 132# Create aliases for easy service management 133SERVICE_USER="${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" 134SERVICE_HOME="/home/$SERVICE_USER" 135 136if [[ -d "$SERVICE_HOME" ]]; then 137 log_with_prefix "ServiceName" "Creating convenience aliases" 138 cat > "$SERVICE_HOME/.service_aliases" << EOF 139# ServiceName convenience aliases 140# IMPORTANT: Always include --env-file flag for tinsnip services 141alias service-logs='docker compose --env-file /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/.env logs -f servicename' 142alias service-shell='docker compose --env-file /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/.env exec servicename bash' 143alias service-restart='docker compose --env-file /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/.env restart servicename' 144 145echo "ServiceName aliases loaded. Available commands:" 146echo " service-logs - View service logs" 147echo " service-shell - Interactive shell" 148echo " service-restart - Restart service" 149EOF 150 151 # Add to .bashrc if not already there 152 if ! grep -q ".service_aliases" "$SERVICE_HOME/.bashrc" 2>/dev/null; then 153 echo "" >> "$SERVICE_HOME/.bashrc" 154 echo "# ServiceName aliases" >> "$SERVICE_HOME/.bashrc" 155 echo "source ~/.service_aliases" >> "$SERVICE_HOME/.bashrc" 156 log_with_prefix "ServiceName" "Added aliases to .bashrc" 157 fi 158fi 159``` 160 161## Best Practices 162 163### Do: 164-**Source .env file first** - Before using any TIN_* variables 165-**Use self-contained functions** - Don't depend on tinsnip lib.sh 166-**Check if files/dirs exist** - Make script idempotent 167-**Use absolute paths** - Relative paths can be unreliable 168-**Log all actions** - Help with debugging 169-**Use heredocs for templates** - Cleaner than echo statements 170-**Follow XDG structure** - data/, config/, state/ organization 171-**Let containers run as root** - Most images expect root for initialization 172 173### Don't: 174-**Don't use sudo** - Service user doesn't have sudo access 175-**Don't use chown** - NFS all_squash handles ownership 176-**Don't depend on external tools** - Keep scripts portable 177-**Don't hardcode usernames/paths** - Use environment variables 178-**Don't assume directories exist** - Always check first 179-**Don't modify system files** - Stay within service boundaries 180 181### Script Execution Context 182 183Setup scripts run as the service user in this context: 184```bash 185# Working directory: /mnt/service-env/service/servicename 186# User: service-env (e.g., nrfconnect-prod) 187# UID: Service-specific UID (e.g., 50300) 188# Environment: Variables from /mnt/service-env/.env 189``` 190 191## Testing Setup Scripts 192 193Test your setup script manually: 194 195```bash 196# Switch to service user 197sudo -u service-environment -i 198 199# Navigate to service directory 200cd /mnt/service-environment/service/servicename 201 202# Run setup script 203./setup.sh 204 205# Verify results 206ls -la /mnt/service-environment/ 207cat ~/.bashrc 208source ~/.service_aliases # Test aliases 209``` 210 211## Examples 212 213### Simple Service (No Special Setup) 214```bash 215#!/bin/bash 216set -euo pipefail 217source "/mnt/${USER}/.env" 218log_with_prefix() { echo "[$1] $2"; } 219log_with_prefix "SimpleService" "No additional setup required" 220``` 221 222### Database Service 223```bash 224#!/bin/bash 225set -euo pipefail 226source "/mnt/${USER}/.env" 227log_with_prefix() { echo "[$1] $2"; } 228 229MOUNT_BASE="/mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" 230 231# Create database directory 232mkdir -p "$MOUNT_BASE/data/database" 233mkdir -p "$MOUNT_BASE/config" 234 235# Create default database config 236if [[ ! -f "$MOUNT_BASE/config/db.conf" ]]; then 237 cat > "$MOUNT_BASE/config/db.conf" << EOF 238port=${TIN_PORT_0} 239data_directory=/workdir/data/database 240log_directory=/workdir/state/logs 241EOF 242fi 243 244log_with_prefix "Database" "Setup complete" 245``` 246 247### Development Environment 248```bash 249#!/bin/bash 250set -euo pipefail 251source "/mnt/${USER}/.env" 252log_with_prefix() { echo "[$1] $2"; } 253 254MOUNT_BASE="/mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" 255 256# Create development workspace 257mkdir -p "$MOUNT_BASE/data/projects" 258mkdir -p "$MOUNT_BASE/data/builds" 259mkdir -p "$MOUNT_BASE/state/.config" 260 261# Create development aliases 262SERVICE_HOME="/home/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}" 263if [[ -d "$SERVICE_HOME" ]]; then 264 cat > "$SERVICE_HOME/.dev_aliases" << 'EOF' 265alias dev-build='docker compose exec dev make build' 266alias dev-test='docker compose exec dev make test' 267alias dev-shell='docker compose exec dev bash' 268EOF 269 270 if ! grep -q ".dev_aliases" "$SERVICE_HOME/.bashrc" 2>/dev/null; then 271 echo "source ~/.dev_aliases" >> "$SERVICE_HOME/.bashrc" 272 fi 273fi 274 275log_with_prefix "DevEnv" "Development environment ready" 276``` 277 278## Integration with Docker Compose 279 280Setup scripts run before `docker compose up`, so they can create files that containers will mount: 281 282```yaml 283# docker-compose.yml 284volumes: 285 - /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/config/app.conf:/app/config.conf:ro 286 - /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/data:/app/data 287``` 288 289The setup script ensures `config/app.conf` exists before the container starts. 290 291### Volume Mount Alignment 292 293**Critical**: Ensure volume mounts in docker-compose.yml match directories created by setup.sh: 294 295```bash 296# In setup.sh 297mkdir -p "$MOUNT_BASE/data/west-workspace" 298 299# In docker-compose.yml 300volumes: 301 - /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/data/west-workspace:/workdir/west 302``` 303 304Mismatched directory names will cause mount failures. 305 306### Container User Management 307 308**Recommended Approach**: Let containers run as root initially, then drop to service user. 309 310Most Docker images are designed to: 3111. Start as root to handle initialization (create dirs, set permissions, install deps) 3122. Drop to non-root user for normal operation 313 314```yaml 315# Recommended: Let container handle user management 316# user: "${TIN_SERVICE_UID}:${TIN_SERVICE_UID}" # Comment out 317 318# Alternative: Force specific UID (may cause issues with some images) 319user: "${TIN_SERVICE_UID}:${TIN_SERVICE_UID}" 320``` 321 322**Why this works with tinsnip:** 323- NFS `all_squash` handles file ownership on the host side 324- Container can start as root, do setup, then switch users internally 325- More compatible with existing Docker ecosystem 326 327**Home Directory:** 328```yaml 329# Container expects writable home directory 330environment: 331 - HOME=/workdir/home 332volumes: 333 - /mnt/${TIN_SERVICE_NAME}-${TIN_SERVICE_ENVIRONMENT}/state:/workdir/home 334``` 335 336**Environment File Requirement:** 337 338Tinsnip places the service `.env` file at `/mnt/service-environment/.env`. When running `docker compose` manually, always specify: 339 340```bash 341# Required pattern for manual docker compose commands 342docker compose --env-file /mnt/service-environment/.env [command] 343 344# Examples 345docker compose --env-file /mnt/nrfconnect-prod/.env ps 346docker compose --env-file /mnt/nrfconnect-prod/.env exec service bash 347docker compose --env-file /mnt/nrfconnect-prod/.env logs -f 348``` 349 350**Common Issues:** 351- Permission denied errors → Remove `user:` constraint, let container run as root initially 352- Logger/config failures → Ensure HOME directory is mounted and writable 353- Missing directories → Check setup.sh creates all directories referenced in docker-compose.yml 354- Image won't start → Many images expect root access for initialization 355- "Variable not set" warnings → Always use `--env-file` flag when running docker compose manually 356 357## Error Handling 358 359```bash 360# Good error handling pattern 361if ! mkdir -p "$REQUIRED_DIR" 2>/dev/null; then 362 log_with_prefix "ServiceName" "ERROR: Could not create $REQUIRED_DIR" 363 exit 1 364fi 365 366# Check for required tools 367if ! command -v some-tool >/dev/null 2>&1; then 368 log_with_prefix "ServiceName" "WARNING: some-tool not found, skipping optional setup" 369fi 370``` 371 372## Working with Third-Party Containers 373 374Many third-party containers require special handling beyond basic Docker Compose configuration. 375 376### Container Initialization Requirements 377 378**Development toolchain containers** (like Nordic nRF Connect SDK, Xilinx Vivado, etc.) often need initialization: 379 380```bash 381# Common pattern: containers expect toolchains/SDKs to be installed on first run 382# Inside container (during debugging): 383container-tool install --version latest 384container-tool setup --workspace /workdir 385 386# This may take 30-60 minutes for large toolchain downloads 387``` 388 389**Key strategies:** 390- **Use persistent storage** - Install to NFS-mounted directories so installation survives restarts 391- **Expect long initialization** - Large toolchain downloads can take hours 392- **Test interactively first** - Use `docker compose run --rm --entrypoint="" service bash` to debug 393 394### Container Command Patterns 395 396**Don't override entrypoints unless necessary:** 397```yaml 398# Bad: Overrides container's initialization logic 399command: ["tail", "-f", "/dev/null"] 400 401# Good: Let container handle its own setup 402# command: # commented out - use container default 403 404# Better: Use container's recommended shell launcher 405command: ["container-tool", "launch", "--shell"] 406``` 407 408### Debugging Container Issues 409 410**Step 1: Get a working shell** 411```bash 412# Override command to get basic shell access 413docker compose run --rm --entrypoint="" service bash 414 415# Inside container, explore what's available: 416ls /usr/local/bin/ # Check installed tools 417env # Check environment variables 418service-tool --help # Test main application 419``` 420 421**Step 2: Understand initialization requirements** 422```bash 423# Common checks inside container: 424which main-tool # Is primary tool available? 425main-tool list-installed # What components are installed? 426main-tool search-available # What needs to be installed? 427ls -la /opt/ /usr/local/share/ # Check installation directories 428``` 429 430**Step 3: Install missing components** 431```bash 432# Install to persistent storage (NFS-mounted) 433main-tool install --install-dir /workdir/home/tools 434main-tool setup --workspace /workdir 435``` 436 437**Step 4: Update service configuration** 438```yaml 439# Once you understand requirements, update docker-compose.yml: 440volumes: 441 # Persist large installations 442 - /mnt/service-env/state:/workdir/home 443environment: 444 # Set tool-specific environment variables 445 - TOOL_HOME=/workdir/home/tools 446command: ["main-tool", "launch", "--shell"] # Use proper launcher 447``` 448 449### Common Third-Party Container Patterns 450 451**SDK/Toolchain containers** (nRF Connect SDK, ESP-IDF, etc.): 452- Expect user to install specific SDK versions 453- May need 1-10GB of downloads and storage 454- Usually provide launcher commands for proper environment 455 456**Database containers** (PostgreSQL, MongoDB, etc.): 457- May need initialization scripts run on first start 458- Often create users/databases from environment variables 459- Check logs for initialization completion 460 461**IDE/Development containers** (VSCode, Jupyter, etc.): 462- May need extensions or plugins installed 463- Often expose web interfaces on specific ports 464- May need authentication setup 465 466### Performance Considerations 467 468**Large downloads:** 469- SDK installations can take 30-60+ minutes 470- Use persistent storage to avoid re-downloading 471- Consider running installation during setup.sh instead of container startup 472 473**Resource usage:** 474- Development containers often need significant RAM/CPU 475- Monitor with `docker stats` during heavy operations 476- Consider resource limits in docker-compose.yml 477 478## Debugging 479 480If setup scripts fail: 481 4821. **Check the logs** - Setup output is shown during deployment 4832. **Run manually** - Switch to service user and run `./setup.sh` 4843. **Check permissions** - Ensure service user can write to directories 4854. **Verify .env file** - `cat /mnt/service-env/.env` 4865. **Test environment loading** - `source /mnt/service-env/.env; env | grep TIN_` 487 488**For container issues:** 4896. **Get interactive shell** - `docker compose --env-file /mnt/service-env/.env run --rm --entrypoint="" service bash` 4907. **Check container tools** - Verify expected applications are installed and working 4918. **Review container logs** - `docker compose --env-file /mnt/service-env/.env logs service` 4929. **Test default command** - Remove custom `command:` to see container's normal behavior 493 494## See Also 495 496- [CLAUDE.md](CLAUDE.md) - Project architecture overview 497- [DEPLOYMENT_STRATEGY.md](DEPLOYMENT_STRATEGY.md) - Complete deployment workflow 498- [SHEET_CONFIGURATION.md](SHEET_CONFIGURATION.md) - Multi-tenant configuration 499- Service examples in `./service/` directory