homelab infrastructure services
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