homelab infrastructure services
1#!/bin/bash
2# Shared library functions for tinsnip machine setup
3
4# Color constants
5RED='\033[0;31m'
6GREEN='\033[0;32m'
7YELLOW='\033[1;33m'
8NC='\033[0m'
9
10# Shared logging functions with customizable prefix
11log_with_prefix() {
12 local prefix="$1"
13 shift
14 echo -e "${GREEN}[$prefix]${NC} $*"
15}
16
17error_with_prefix() {
18 local prefix="$1"
19 shift
20 echo -e "${RED}[ERROR]${NC} $*" >&2
21 exit 1
22}
23
24warn_with_prefix() {
25 local prefix="$1"
26 shift
27 echo -e "${YELLOW}[WARNING]${NC} $*"
28}
29
30# Get namespace number from namespace name (1-9 range)
31# Uses deterministic hash that maps "dynamicalsystem" to 1 for backward compatibility
32get_namespace_number() {
33 local namespace="${1:-dynamicalsystem}"
34
35 # Hash namespace to 1-9 range
36 echo "$namespace" | md5sum | cut -c1-1 | {
37 read hex
38 printf "%d\n" "0x$hex" | awk '{n=($1 % 9) + 1; print n}'
39 }
40}
41
42# Find the next available service number from a registry file
43find_next_service_number() {
44 local registry_path="$1"
45
46 if [[ ! -f "$registry_path" ]]; then
47 echo "1"
48 return
49 fi
50
51 # Extract all service numbers from registry, find the highest
52 local max_num=0
53 while IFS='=' read -r service_name service_num; do
54 # Skip comments and empty lines
55 [[ "$service_name" =~ ^#.*$ ]] || [[ -z "$service_name" ]] && continue
56
57 if [[ "$service_num" =~ ^[0-9]+$ ]] && [[ "$service_num" -gt "$max_num" ]]; then
58 max_num="$service_num"
59 fi
60 done < "$registry_path"
61
62 # Return next available number (max + 1)
63 echo $((max_num + 1))
64}
65
66# Calculate service UID using TNSEP convention
67# T=1 (Docker), N=namespace number, S=service, E=environment, P=0
68calculate_service_uid() {
69 local service_name="$1"
70 local service_env="$2"
71
72 # Get namespace from environment or file
73 local namespace="${TIN_NAMESPACE:-}"
74 if [[ -z "$namespace" ]] && [[ -f "/etc/tinsnip-namespace" ]]; then
75 namespace=$(cat /etc/tinsnip-namespace)
76 fi
77 namespace="${namespace:-dynamicalsystem}"
78
79 # Get namespace number (1-9)
80 local namespace_num=$(get_namespace_number "$namespace")
81
82 # Service number mapping
83 local service_num
84
85 # Special case for station (namespace infrastructure)
86 if [[ "$service_name" == "station" ]]; then
87 service_num=0
88 else
89 # Try to read from service registry
90 local registry_path="/volume1/${namespace}/station/prod/service-registry"
91 if [[ -f "$registry_path" ]]; then
92 service_num=$(grep "^${service_name}=" "$registry_path" | cut -d= -f2)
93 if [[ -z "$service_num" ]]; then
94 # Service not in registry - auto-assign next available number
95 service_num=$(find_next_service_number "$registry_path")
96 warn_with_prefix "UID Calculation" "Service '$service_name' not in registry, auto-assigning service number $service_num"
97 fi
98 else
99 # Registry doesn't exist - use fallback mapping or auto-assign
100 case "$service_name" in
101 gazette) service_num=1 ;;
102 lldap) service_num=2 ;;
103 gateway) service_num=3 ;;
104 *)
105 # Auto-assign starting from service number 4
106 service_num=4
107 warn_with_prefix "UID Calculation" "No registry found, auto-assigning service number $service_num for '$service_name'"
108 ;;
109 esac
110 fi
111 fi
112
113 # Environment number mapping
114 local env_num
115 case "$service_env" in
116 prod) env_num=0 ;;
117 test) env_num=1 ;;
118 *)
119 echo "ERROR: Unknown environment: $service_env" >&2
120 return 1
121 ;;
122 esac
123
124 # Return TNSEP UID
125 echo "1${namespace_num}${service_num}${env_num}0"
126}
127
128# Calculate service ports based on UID
129calculate_service_ports() {
130 local service_uid="$1"
131 local port_count="${2:-3}"
132
133 local base_port=$service_uid
134 for ((i=0; i<port_count; i++)); do
135 echo $((base_port + i))
136 done
137}
138
139# Update shell configuration files (.bashrc or .profile) with environment variables
140# Usage: update_shell_config username config_file "VAR1=value1" "VAR2=value2" ...
141update_shell_config() {
142 local username="$1"
143 local config_file="$2"
144 shift 2
145 local env_vars=("$@")
146
147 sudo -u "$username" -i bash << EOF
148# Remove any existing environment variables to avoid duplicates
149grep -v "$(printf "%s\\\\|" "${env_vars[@]}" | sed 's/=.*/=/g' | sed 's/\\\\|$//')" ~/$config_file > ~/${config_file}.tmp 2>/dev/null && mv ~/${config_file}.tmp ~/$config_file || touch ~/$config_file
150
151# Add environment variables
152cat >> ~/$config_file << 'CONFIG_EOF'
153
154# Environment variables managed by tinsnip
155$(printf "export %s\n" "${env_vars[@]}")
156CONFIG_EOF
157EOF
158}
159
160# Create service environment file on NFS mount with all variables needed by service
161create_service_env_file() {
162 local service_name="$1"
163 local service_env="$2"
164 local service_uid="$3"
165 local mount_point="/mnt/${service_name}-${service_env}"
166 local namespace="${TIN_NAMESPACE:-dynamicalsystem}"
167
168 local env_file="$mount_point/.env"
169
170 log_with_prefix "Service Env" "Creating $env_file"
171
172 # Create the .env file with all service variables
173 sudo -u "${service_name}-${service_env}" tee "$env_file" > /dev/null << EOF
174# Tinsnip service environment variables
175# Generated automatically - do not edit manually
176TIN_SERVICE_NAME=$service_name
177TIN_SERVICE_ENVIRONMENT=$service_env
178TIN_SERVICE_UID=$service_uid
179TIN_NAMESPACE=$namespace
180
181# XDG Base Directory variables (NFS-backed)
182XDG_DATA_HOME=$mount_point/data
183XDG_CONFIG_HOME=$mount_point/config
184XDG_STATE_HOME=$mount_point/state
185XDG_CACHE_HOME=$mount_point/cache
186
187# Docker environment (set by install_docker.sh if applicable)
188# These will be added by Docker setup process
189EOF
190
191 log_with_prefix "Service Env" "Created service environment file"
192}
193
194# Generate shell environment loading code for .bashrc/.profile
195# This creates the code that loads service env with cache fallback
196generate_service_env_loader() {
197 cat << 'EOF'
198# Tinsnip service environment loading with NFS resilience
199# Set XDG_CACHE_HOME first (host-based, not NFS)
200export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
201
202# Load service environment variables
203if [[ "\$(whoami)" =~ ^[^-]+-[^-]+\$ ]]; then
204 # This is a service user (format: service-environment)
205 SERVICE_ENV_FILE="/mnt/\$(whoami)/.env"
206 TIN_SERVICE_ENV_CACHE="\$XDG_CACHE_HOME/tinsnip/\$(whoami).env"
207
208 if [[ -f "$SERVICE_ENV_FILE" ]]; then
209 # NFS available - source primary file and update cache
210 source "$SERVICE_ENV_FILE"
211 mkdir -p "$(dirname "$TIN_SERVICE_ENV_CACHE")"
212 cp "$SERVICE_ENV_FILE" "$TIN_SERVICE_ENV_CACHE" 2>/dev/null || true
213 elif [[ -f "$TIN_SERVICE_ENV_CACHE" ]]; then
214 # NFS unavailable - use cached version
215 source "$TIN_SERVICE_ENV_CACHE"
216 fi
217fi
218EOF
219}