homelab infrastructure services
1#!/bin/bash
2# tin service deploy - Deploy service from catalog
3
4set -euo pipefail
5
6# Get tinsnip root and source libraries
7SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8TINSNIP_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
9source "$TINSNIP_ROOT/lib/core.sh"
10source "$TINSNIP_ROOT/lib/uid.sh"
11source "$TINSNIP_ROOT/lib/metadata.sh"
12
13# Helper function to calculate port numbers
14get_service_port() {
15 local service_name="$1"
16 local service_env="$2"
17 local port_index="${3:-0}"
18
19 local service_uid=$(calculate_service_uid "$service_name" "$service_env")
20 echo $((service_uid + port_index))
21}
22
23# Deploy service from catalog
24deploy_service() {
25 local service_env="$1"
26 local catalog_service="$2"
27
28 # Parse service-environment
29 local parsed_output
30 if ! parsed_output=$(parse_machine_name "$service_env" 2>/dev/null); then
31 error_with_prefix "Service Deploy" "Invalid machine environment format: '$service_env'"
32 echo "Expected: <service>-<environment> (e.g., gazette-prod, bsky-pds-dev)" >&2
33 exit 1
34 fi
35
36 local machine_service=$(echo "$parsed_output" | sed -n '1p')
37 local environment=$(echo "$parsed_output" | sed -n '2p')
38 local service_user="$service_env"
39
40 log_with_prefix "Service Deploy" "Deploying service: $catalog_service → $service_env"
41 echo
42
43 # Debug: Show current TIN_SHEET
44 log_with_prefix "Service Deploy" "Using sheet: ${TIN_SHEET:-topsheet}"
45
46 # Verify machine environment exists
47 local service_uid
48 if ! service_uid=$(calculate_service_uid "$machine_service" "$environment" 2>/dev/null); then
49 error_with_prefix "Service Deploy" "Cannot calculate UID for machine environment"
50 exit 1
51 fi
52
53 log_with_prefix "Service Deploy" "Calculated UID: $service_uid"
54
55 if ! getent passwd "$service_uid" >/dev/null 2>&1; then
56 error_with_prefix "Service Deploy" "Machine environment '$service_env' not found"
57 echo "Create with: tin machine create $machine_service $environment" >&2
58 exit 1
59 fi
60
61 # Check if service catalog exists
62 local service_catalog_path="$HOME/.local/opt/dynamicalsystem.service"
63 if [[ ! -d "$service_catalog_path/$catalog_service" ]]; then
64 error_with_prefix "Service Deploy" "Service '$catalog_service' not found in catalog"
65 echo "Available services:" >&2
66 if [[ -d "$service_catalog_path" ]]; then
67 ls -1 "$service_catalog_path" | grep -v README | head -10 >&2
68 fi
69 exit 1
70 fi
71
72 log_with_prefix "Service Deploy" "Machine environment verified: $service_env"
73 log_with_prefix "Service Deploy" "Service catalog found: $catalog_service"
74
75 # Show deployment plan
76 echo "Deployment Plan:"
77 echo " Machine: $service_env"
78 echo " Service: $catalog_service"
79 echo " User: $service_user"
80 echo " UID: $service_uid"
81 echo " Source: $service_catalog_path/$catalog_service"
82 echo " Target: /mnt/$service_env/service/$catalog_service"
83 echo
84
85 # Confirm deployment
86 read -p "Deploy $catalog_service to $service_env? [Y/n]: " confirm
87 case "${confirm:-y}" in
88 [Yy]*|"")
89 log_with_prefix "Service Deploy" "Starting deployment..."
90 ;;
91 [Nn]*)
92 log_with_prefix "Service Deploy" "Deployment cancelled"
93 exit 1
94 ;;
95 *)
96 error_with_prefix "Service Deploy" "Invalid input. Deployment cancelled"
97 exit 1
98 ;;
99 esac
100
101 # Copy service files
102 log_with_prefix "Service Deploy" "Copying service files..."
103 sudo -u "$service_user" mkdir -p "/mnt/$service_env/service"
104
105 # Copy as current user (to avoid permission issues reading from home directory)
106 # then fix ownership for the service user
107 if cp -r "$service_catalog_path/$catalog_service" "/mnt/$service_env/service/"; then
108 # Fix ownership to service user (NFS all_squash will handle actual ownership)
109 sudo chown -R "$service_uid:$service_uid" "/mnt/$service_env/service/$catalog_service" 2>/dev/null || true
110 log_with_prefix "Service Deploy" "Service files copied successfully"
111 else
112 error_with_prefix "Service Deploy" "Failed to copy service files"
113 exit 1
114 fi
115
116 # Allocate ports and generate service .env
117 log_with_prefix "Service Deploy" "Allocating ports..."
118 local compose_file="/mnt/$service_env/service/$catalog_service/docker-compose.yml"
119 local port_count
120 port_count=$(get_service_port_count "$compose_file")
121
122 local start_port
123 if ! start_port=$(allocate_service_ports "$machine_service" "$environment" "$catalog_service" "$port_count"); then
124 error_with_prefix "Service Deploy" "Port allocation failed"
125 # Clean up copied files
126 sudo rm -rf "/mnt/$service_env/service/$catalog_service"
127 exit 1
128 fi
129
130 if ! generate_service_env "$service_env" "$catalog_service" "$start_port" "$port_count"; then
131 error_with_prefix "Service Deploy" "Failed to generate service .env"
132 # Clean up
133 sudo rm -rf "/mnt/$service_env/service/$catalog_service"
134 exit 1
135 fi
136
137 local end_port=$((start_port + port_count - 1))
138 log_with_prefix "Service Deploy" "✓ Ports allocated: $start_port-$end_port ($port_count ports)"
139
140 # Run service setup if it exists
141 local setup_script="/mnt/$service_env/service/$catalog_service/setup.sh"
142 if [[ -f "$setup_script" ]]; then
143 log_with_prefix "Service Deploy" "Running service setup script..."
144 # Ensure script is executable
145 chmod +x "$setup_script"
146 # Run as service user with machine and service env sourced
147 if sudo -u "$service_user" bash -c "source /mnt/$service_env/.machine/machine.env && source /mnt/$service_env/service/$catalog_service/.env && bash $setup_script"; then
148 log_with_prefix "Service Deploy" "Service setup completed"
149 else
150 warn_with_prefix "Service Deploy" "Service setup script failed"
151 fi
152 fi
153
154 # Start services
155 local compose_file="/mnt/$service_env/service/$catalog_service/docker-compose.yml"
156 if [[ -f "$compose_file" ]]; then
157 log_with_prefix "Service Deploy" "Starting services..."
158
159 # Change to service directory and start
160 # Source env files with auto-export for Docker Compose YAML interpolation, but unset XDG vars that break rootless Docker
161 # Containers get all vars via env_file directive in docker-compose.yml
162 if sudo -u "$service_user" bash -c "set -a && source /mnt/$service_env/.machine/machine.env && source /mnt/$service_env/service/$catalog_service/.env && set +a && unset XDG_DATA_HOME XDG_CONFIG_HOME XDG_STATE_HOME && cd /mnt/$service_env/service/$catalog_service && docker compose up -d"; then
163 log_with_prefix "Service Deploy" "✅ Service '$catalog_service' deployed successfully to '$service_env'"
164 else
165 error_with_prefix "Service Deploy" "Failed to start services"
166 exit 1
167 fi
168 else
169 warn_with_prefix "Service Deploy" "No docker-compose.yml found - service copied but not started"
170 fi
171
172 echo
173 log_with_prefix "Service Deploy" "Deployment complete!"
174 echo " Service: $catalog_service"
175 echo " Ports: $start_port-$end_port"
176 echo ""
177 echo "Check status with: tin service status $service_env"
178 echo "View logs with: tin service logs $service_env $catalog_service"
179}
180
181show_help() {
182 cat << EOF
183tin service deploy - Deploy service from catalog
184
185USAGE:
186 tin service deploy <service-env> <catalog-service>
187 tin service <service-env> <catalog-service> # Shorthand
188
189DESCRIPTION:
190 Deploy a service from the catalog to a prepared machine environment.
191 The service is copied from the catalog and configured for the target
192 environment with proper UID mapping and port allocation.
193
194ARGUMENTS:
195 <service-env> Target machine environment (e.g., gazette-prod)
196 <catalog-service> Service name from catalog to deploy
197
198EXAMPLES:
199 tin service deploy gazette-prod lldap # Deploy LLDAP to gazette-prod
200 tin service lldap-test redis # Deploy Redis to lldap-test
201 tin service station-prod prometheus # Deploy Prometheus to station
202
203NOTES:
204 - The target machine environment must exist (created with tin machine)
205 - The catalog service must be available in the service catalog
206 - Services are deployed as the service user with proper UID/port mapping
207
208EOF
209}
210
211# Handle help flags
212case "${1:-}" in
213 --help|-h|help)
214 show_help
215 exit 0
216 ;;
217esac
218
219# Main execution
220if [[ $# -lt 2 ]]; then
221 error_with_prefix "Service Deploy" "Service environment and catalog service required"
222 echo "Usage: tin service deploy <service-env> <catalog-service>" >&2
223 exit 1
224fi
225
226service_env="$1"
227catalog_service="$2"
228
229deploy_service "$service_env" "$catalog_service"