homelab infrastructure services
1#!/bin/bash
2# UID and port calculation functions for tinsnip SMEP scheme
3
4# Source core functions
5LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6source "$LIB_DIR/core.sh"
7source "$LIB_DIR/registry.sh"
8
9# Parse machine environment name into service and environment components
10# Handles service names with hyphens (e.g., bsky-pds-dev → service=bsky-pds, env=dev)
11# Outputs two lines: service name, then environment name
12parse_machine_name() {
13 local machine_name="$1"
14
15 # Known environments (must match calculate_machine_uid)
16 local environments="prod|test|dev|staging|demo|qa|uat|preview|canary|local"
17
18 # Match the last occurrence of -<environment>
19 if [[ "$machine_name" =~ ^(.+)-(${environments})$ ]]; then
20 echo "${BASH_REMATCH[1]}" # service name
21 echo "${BASH_REMATCH[2]}" # environment
22 return 0
23 else
24 return 1
25 fi
26}
27
28# Get sheet number from registry
29get_sheet_number() {
30 local sheet="${1:-${TIN_SHEET:-topsheet}}"
31
32 # Reserve sheet 5 for the tinsnip topsheet
33 if [[ "$sheet" == "topsheet" ]]; then
34 echo "5"
35 return
36 fi
37
38 # Look up sheet in registry
39 local registry_path=$(get_sheet_registry_path)
40 if [[ -f "$registry_path" ]]; then
41 local number=$(grep "^${sheet}=" "$registry_path" | cut -d= -f2)
42 if [[ -n "$number" ]]; then
43 echo "$number"
44 return
45 fi
46 fi
47
48 # Sheet not in registry - error
49 error_with_prefix "Sheet" "Sheet '$sheet' not registered"
50 error_with_prefix "Sheet" "Register with: tin sheet add $sheet <number>"
51 return 1
52}
53
54# Get sheet name from UID (reverse lookup)
55get_sheet_from_uid() {
56 local uid="$1"
57
58 # Extract sheet number from UID (first digit of 5-digit UID)
59 local sheet_num="${uid:0:1}"
60
61 # Special case for topsheet (sheet number 5)
62 if [[ "$sheet_num" == "5" ]]; then
63 echo "topsheet"
64 return 0
65 fi
66
67 # Look up sheet name in registry
68 local registry_path=$(get_sheet_registry_path)
69 if [[ -f "$registry_path" ]]; then
70 local sheet_name=$(grep "=${sheet_num}$" "$registry_path" | cut -d= -f1)
71 if [[ -n "$sheet_name" ]]; then
72 echo "$sheet_name"
73 return 0
74 fi
75 fi
76
77 # Sheet not found - default to topsheet as fallback
78 warn_with_prefix "UID" "Could not find sheet for UID $uid (sheet number $sheet_num)"
79 echo "topsheet"
80 return 1
81}
82
83# Get path to global sheet registry
84get_sheet_registry_path() {
85 echo "/mnt/station-prod/data/sheets"
86}
87
88# Find the next available sheet number from the registry (1-4 range, 5 reserved)
89find_next_sheet_number() {
90 local registry_path=$(get_sheet_registry_path)
91
92 if [[ ! -f "$registry_path" ]]; then
93 echo "1" # First available if no registry
94 return
95 fi
96
97 # Find highest assigned number
98 local max_num=0
99 while IFS='=' read -r sheet_name sheet_num; do
100 # Skip comments and empty lines
101 [[ "$sheet_name" =~ ^#.*$ ]] || [[ -z "$sheet_name" ]] && continue
102
103 # Skip topsheet (reserved at 5)
104 [[ "$sheet_name" == "topsheet" ]] && continue
105
106 # Convert to integer and track max (handle invalid numbers)
107 if [[ "$sheet_num" =~ ^[0-9]+$ ]]; then
108 local num_int=$((10#$sheet_num))
109 if [[ "$num_int" -gt "$max_num" && "$num_int" -le 4 ]]; then
110 max_num="$num_int"
111 fi
112 fi
113 done < "$registry_path"
114
115 # Return next available number (max + 1), capped at 4
116 local next_num=$((max_num + 1))
117 if [[ "$next_num" -gt 4 ]]; then
118 echo "ERROR: Sheet registry full (range 1-4, 5 reserved)" >&2
119 return 1
120 fi
121
122 echo "$next_num"
123}
124
125# Find the next available machine number from a registry file (now supports 2-digit format)
126find_next_machine_number() {
127 local registry_path="$1"
128
129 if [[ ! -f "$registry_path" ]]; then
130 echo "1" # Will be formatted as "01" by caller
131 return
132 fi
133
134 # Extract all machine numbers from registry, find the highest
135 local max_num=0
136 while IFS='=' read -r machine_name machine_num; do
137 # Skip comments and empty lines
138 [[ "$machine_name" =~ ^#.*$ ]] || [[ -z "$machine_name" ]] && continue
139
140 # Convert machine_num to integer (handles both "1" and "01" formats)
141 local num_int
142 if [[ -n "$machine_num" ]] && num_int=$((10#$machine_num)) 2>/dev/null; then
143 : # Success
144 else
145 continue
146 fi
147 if [[ "$num_int" -gt "$max_num" ]]; then
148 max_num="$num_int"
149 fi
150 done < "$registry_path"
151
152 # Return next available number (max + 1), capped at 99
153 local next_num=$((max_num + 1))
154
155 if [[ "$next_num" -gt 99 ]]; then
156 return 1
157 fi
158
159 echo "$next_num"
160}
161
162# Calculate machine UID using SMEP convention
163# S=sheet number, M=machine (2-digit), E=environment, P=port index
164calculate_machine_uid() {
165 local machine_name="$1"
166 local machine_env="$2"
167 local port_index="${3:-0}" # Optional port index, defaults to 0
168
169 # Get sheet from environment or file
170 local sheet="${TIN_SHEET:-}"
171 if [[ -z "$sheet" ]] && [[ -f "/etc/tinsnip-sheet" ]]; then
172 sheet=$(cat /etc/tinsnip-sheet)
173 fi
174 sheet="${sheet:-${TIN_SHEET:-topsheet}}"
175
176 # Get sheet number (1-9)
177 local sheet_num=$(get_sheet_number "$sheet")
178
179 # Machine number mapping (now 2-digit)
180 local machine_num
181
182 # Special case for station (sheet infrastructure)
183 if [[ "$machine_name" == "station" ]]; then
184 machine_num=0
185 else
186 # Try to read from sheet-specific machine registry
187 local registry_path="/mnt/station-prod/data/machines/$sheet/registry"
188 if [[ -f "$registry_path" ]]; then
189 machine_num=$(grep "^${machine_name}=" "$registry_path" | head -1 | cut -d= -f2)
190 if [[ -n "$machine_num" ]]; then
191 # Convert to decimal to avoid octal interpretation issues
192 machine_num=$((10#$machine_num))
193 else
194 # Machine not in registry - auto-assign next available number
195 local next_num=$(find_next_machine_number "$registry_path")
196 machine_num=$next_num
197 warn_with_prefix "UID Calculation" "Machine '$machine_name' not in registry, auto-assigning machine number $machine_num"
198
199 # Auto-register the machine
200 if register_machine "$machine_name" "$(printf "%02d" "$machine_num")" "$sheet" 2>/dev/null; then
201 log_with_prefix "UID Calculation" "Auto-registered $machine_name=$machine_num in sheet '$sheet'"
202 fi
203 fi
204 else
205 # Registry doesn't exist - sheet not registered
206 error_with_prefix "UID Calculation" "Machine registry not found: $registry_path"
207 error_with_prefix "UID Calculation" "Register sheet first: tin sheet create $sheet"
208 return 1
209 fi
210 fi
211
212 # Environment number mapping (expanded to 0-9)
213 local env_num
214 case "$machine_env" in
215 prod) env_num=0 ;;
216 test) env_num=1 ;;
217 dev) env_num=2 ;;
218 staging) env_num=3 ;;
219 demo) env_num=4 ;;
220 qa) env_num=5 ;;
221 uat) env_num=6 ;;
222 preview) env_num=7 ;;
223 canary) env_num=8 ;;
224 local) env_num=9 ;;
225 *)
226 echo "ERROR: Unknown environment: $machine_env (supported: prod,test,dev,staging,demo,qa,uat,preview,canary,local)" >&2
227 return 1
228 ;;
229 esac
230
231 # Validate port index (0-9)
232 if [[ ! "$port_index" =~ ^[0-9]$ ]]; then
233 echo "ERROR: Port index must be 0-9, got: $port_index" >&2
234 return 1
235 fi
236
237 # Return SMEP UID: S-M-E-P format
238 printf "%d%02d%d%d\n" "$sheet_num" "$machine_num" "$env_num" "$port_index"
239}
240
241# Calculate machine ports based on UID
242calculate_machine_ports() {
243 local machine_uid="$1"
244 local port_count="${2:-3}"
245
246 local base_port=$machine_uid
247 for ((i=0; i<port_count; i++)); do
248 echo $((base_port + i))
249 done
250}
251
252# Backward compatibility wrappers (deprecated)
253calculate_service_uid() {
254 warn_with_prefix "Deprecated" "calculate_service_uid() is deprecated, use calculate_machine_uid()"
255 calculate_machine_uid "$@"
256}
257
258find_next_service_number() {
259 warn_with_prefix "Deprecated" "find_next_service_number() is deprecated, use find_next_machine_number()"
260 find_next_machine_number "$@"
261}
262
263calculate_service_ports() {
264 warn_with_prefix "Deprecated" "calculate_service_ports() is deprecated, use calculate_machine_ports()"
265 calculate_machine_ports "$@"
266}