@@ -291,8 +291,6 @@ func (r *AppRuntimeReconciler) createHeadlessService(ctx context.Context, ar *en
291291}
292292
293293func (r * AppRuntimeReconciler ) createPod (ctx context.Context , appRuntime * enterpriseApi.AppRuntime , nn types.NamespacedName , splunkStsName string , ordinal int32 ) error {
294- // Construct Unison server address: the Unison server runs as a sidecar in the
295- // corresponding Splunk pod, reachable via the Splunk headless service DNS.
296294 splunkPodName := fmt .Sprintf ("%s-%d" , splunkStsName , ordinal )
297295 parentName := getParentName (appRuntime .Name )
298296 parentKind := getParentKind (appRuntime .Name )
@@ -308,37 +306,131 @@ func (r *AppRuntimeReconciler) createPod(ctx context.Context, appRuntime *enterp
308306 instType = enterprise .SplunkStandalone
309307 }
310308 splunkHeadlessSvc := enterprise .GetSplunkServiceName (instType , parentName , true )
311- unisonAddr := fmt .Sprintf ("%s.%s.%s.svc.cluster.local" , splunkPodName , splunkHeadlessSvc , nn .Namespace )
309+ splunkAddr := fmt .Sprintf ("%s.%s.%s.svc.cluster.local" , splunkPodName , splunkHeadlessSvc , nn .Namespace )
312310
313- // Unison command components shared between init container and sidecar
314- unisonRemoteRoot := fmt .Sprintf ("socket://%s:5000//opt/splunk" , unisonAddr )
315- unisonInitCmd := fmt .Sprintf (`# Create var directory structure needed by the worker process
311+ syncthingAPIKey := "syncthing-appruntime"
312+
313+ // Init script: generate certs, discover server device ID via REST API,
314+ // configure mutual peering, wait for initial sync to complete.
315+ // Uses Syncthing v1.18 CLI syntax (-flag= instead of --flag or subcommands).
316+ // Uses /rest/system/config endpoint (works across all Syncthing versions).
317+ syncthingInitCmd := fmt .Sprintf (`set -e
318+
319+ # Create var directory structure needed by the worker process
316320mkdir -p /opt/splunk/var/log/splunk/ep \
317321 /opt/splunk/var/lib/splunk \
318322 /opt/splunk/var/run/splunk \
319323 /opt/splunk/var/spool
320324
321- for i in $(seq 1 30); do
322- unison %s /opt/splunk \
323- -path etc \
324- -batch -auto -confirmbigdel=false
325- rc=$?
326- if [ $rc -eq 0 ] || [ $rc -eq 1 ]; then
327- echo "Unison initial sync completed (exit code $rc)"
325+ ST_HOME=/var/syncthing
326+ SERVER_ADDR="%s"
327+ SERVER_API="http://${SERVER_ADDR}:8384"
328+ API_KEY="%s"
329+
330+ mkdir -p "$ST_HOME"
331+
332+ # Generate local certs (v1.18 syntax)
333+ syncthing -generate="$ST_HOME"
334+ MY_DEVICE_ID=$(syncthing -home="$ST_HOME" -device-id)
335+ echo "Client device ID: $MY_DEVICE_ID"
336+
337+ # Wait for server API to be reachable and get its device ID
338+ echo "Waiting for Syncthing server at $SERVER_API..."
339+ SERVER_DEVICE_ID=""
340+ for i in $(seq 1 60); do
341+ SERVER_DEVICE_ID=$(curl -sf -H "X-API-Key: $API_KEY" "$SERVER_API/rest/system/status" 2>/dev/null | \
342+ python3 -c "import sys,json; print(json.load(sys.stdin)['myID'])" 2>/dev/null) && break
343+ SERVER_DEVICE_ID=""
344+ echo " attempt $i: server not ready, retrying in 3s..."
345+ sleep 3
346+ done
347+ if [ -z "$SERVER_DEVICE_ID" ]; then
348+ echo "ERROR: could not reach Syncthing server after 60 attempts"
349+ exit 1
350+ fi
351+ echo "Server device ID: $SERVER_DEVICE_ID"
352+
353+ # Write client config with server as peer
354+ cat > "$ST_HOME/config.xml" <<XMLEOF
355+ <configuration version="28">
356+ <folder id="splunk-etc" label="splunk-etc" path="/opt/splunk/etc" type="sendreceive" rescanIntervalS="3600" fsWatcherEnabled="true" fsWatcherDelayS="1">
357+ <filesystemType>basic</filesystemType>
358+ <device id="$MY_DEVICE_ID" introducedBy=""></device>
359+ <device id="$SERVER_DEVICE_ID" introducedBy=""></device>
360+ <minDiskFree unit="%%">0</minDiskFree>
361+ </folder>
362+ <device id="$MY_DEVICE_ID" name="client" compression="metadata">
363+ <address>dynamic</address>
364+ </device>
365+ <device id="$SERVER_DEVICE_ID" name="server" compression="metadata">
366+ <address>tcp://${SERVER_ADDR}:22000</address>
367+ </device>
368+ <gui enabled="true" tls="false" debugging="false">
369+ <address>0.0.0.0:8385</address>
370+ <apikey>$API_KEY</apikey>
371+ </gui>
372+ <options>
373+ <listenAddress>tcp://0.0.0.0:22001</listenAddress>
374+ <globalAnnounceEnabled>false</globalAnnounceEnabled>
375+ <localAnnounceEnabled>false</localAnnounceEnabled>
376+ <relaysEnabled>false</relaysEnabled>
377+ <natEnabled>false</natEnabled>
378+ <urAccepted>-1</urAccepted>
379+ <crashReportingEnabled>false</crashReportingEnabled>
380+ </options>
381+ </configuration>
382+ XMLEOF
383+
384+ # Add this client as a device on the server and share the folder with it.
385+ # Uses /rest/system/config (GET + POST entire config) which works on all versions.
386+ echo "Adding client device to server config..."
387+ SERVER_CFG=$(curl -sf -H "X-API-Key: $API_KEY" "$SERVER_API/rest/system/config")
388+ UPDATED_CFG=$(echo "$SERVER_CFG" | python3 -c "
389+ import sys, json
390+ cfg = json.load(sys.stdin)
391+ my_id = '$MY_DEVICE_ID'
392+ # Add client device if not present
393+ devs = cfg.get('devices', [])
394+ if not any(d['deviceID'] == my_id for d in devs):
395+ devs.append({'deviceID': my_id, 'name': 'client-$HOSTNAME', 'addresses': ['dynamic'], 'compression': 'metadata'})
396+ cfg['devices'] = devs
397+ # Add client device to the folder if not present
398+ for f in cfg.get('folders', []):
399+ if f['id'] == 'splunk-etc':
400+ fdevs = f.get('devices', [])
401+ if not any(d['deviceID'] == my_id for d in fdevs):
402+ fdevs.append({'deviceID': my_id, 'introducedBy': ''})
403+ f['devices'] = fdevs
404+ json.dump(cfg, sys.stdout)
405+ ")
406+ curl -sf -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
407+ "$SERVER_API/rest/system/config" \
408+ -d "$UPDATED_CFG"
409+ echo "Server config updated."
410+
411+ # Start syncthing in the background to perform initial sync (v1.18 syntax)
412+ syncthing -home="$ST_HOME" -no-browser -no-restart -verbose &
413+ ST_PID=$!
414+
415+ # Wait for initial sync to complete
416+ echo "Waiting for initial sync to complete..."
417+ for i in $(seq 1 120); do
418+ sleep 3
419+ RESULT=$(curl -sf -H "X-API-Key: $API_KEY" "http://127.0.0.1:8385/rest/db/completion?folder=splunk-etc&device=$SERVER_DEVICE_ID" 2>/dev/null) || continue
420+ COMPLETION=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(f\"{d['completion']:.0f}\")" 2>/dev/null) || COMPLETION="0"
421+ NEED=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['needBytes'])" 2>/dev/null) || NEED="-1"
422+ echo " sync progress: ${COMPLETION}%% (needBytes: $NEED)"
423+ if [ "$COMPLETION" = "100" ] && [ "$NEED" = "0" ]; then
424+ echo "Initial sync complete!"
425+ kill $ST_PID 2>/dev/null || true
426+ wait $ST_PID 2>/dev/null || true
328427 exit 0
329428 fi
330- echo "Unison sync attempt $i failed (exit code $rc), retrying in 5s..."
331- sleep 5
332429done
333- echo "Unison initial sync failed after 30 attempts"
334- exit 1` , unisonRemoteRoot )
335-
336- unisonSyncCmd := fmt .Sprintf (`unison %s /opt/splunk \
337- -path etc \
338- -batch -auto -confirmbigdel=false \
339- -repeat 30` , unisonRemoteRoot )
430+ echo "ERROR: initial sync did not complete in time"
431+ kill $ST_PID 2>/dev/null || true
432+ exit 1` , splunkAddr , syncthingAPIKey )
340433
341- // Volume mounts shared by the appruntime container and unison containers
342434 splunkVolumeMounts := []corev1.VolumeMount {
343435 {Name : "splunk-etc" , MountPath : "/opt/splunk/etc" },
344436 {Name : "splunk-var" , MountPath : "/opt/splunk/var" },
@@ -354,10 +446,9 @@ exit 1`, unisonRemoteRoot)
354446 Spec : corev1.PodSpec {
355447 Hostname : nn .Name ,
356448 Subdomain : getHeadlessName (appRuntime .Name ),
357- // No Affinity — Unison sync replaces shared PVCs, pod can schedule on any node
358449 InitContainers : []corev1.Container {
359450 {
360- Name : "copy-splunk-dirs" , // populate lib and bin from Splunk image - most apps need it
451+ Name : "copy-splunk-dirs" ,
361452 Image : appRuntime .Spec .SplunkImage ,
362453 ImagePullPolicy : corev1 .PullIfNotPresent ,
363454 Command : []string {"sh" , "-c" , "cp -rp /opt/splunk/lib/. /mnt/splunk-lib/ && cp -rp /opt/splunk/bin/. /mnt/splunk-bin/" },
@@ -368,13 +459,12 @@ exit 1`, unisonRemoteRoot)
368459 },
369460 },
370461 {
371- Name : "unison -init-sync" , // one-shot sync before supervisor starts
462+ Name : "syncthing -init" ,
372463 Image : appRuntime .Spec .Image ,
373464 ImagePullPolicy : corev1 .PullIfNotPresent ,
374- Command : []string {"sh" , "-c" , unisonInitCmd },
375- Env : []corev1.EnvVar {{Name : "UNISON" , Value : "/unison-archive" }},
465+ Command : []string {"sh" , "-c" , syncthingInitCmd },
376466 VolumeMounts : append (splunkVolumeMounts ,
377- corev1.VolumeMount {Name : "unison-archive " , MountPath : "/unison-archive " },
467+ corev1.VolumeMount {Name : "syncthing-config " , MountPath : "/var/syncthing " },
378468 ),
379469 },
380470 },
@@ -407,13 +497,12 @@ exit 1`, unisonRemoteRoot)
407497 SecurityContext : & corev1.SecurityContext {Privileged : & privileged },
408498 },
409499 {
410- Name : "unison -client" , // continuous bidirectional sync
500+ Name : "syncthing -client" ,
411501 Image : appRuntime .Spec .Image ,
412502 ImagePullPolicy : corev1 .PullIfNotPresent ,
413- Command : []string {"sh" , "-c" , unisonSyncCmd },
414- Env : []corev1.EnvVar {{Name : "UNISON" , Value : "/unison-archive" }},
503+ Command : []string {"syncthing" , "-home=/var/syncthing" , "-no-browser" , "-no-restart" , "-verbose" },
415504 VolumeMounts : append (splunkVolumeMounts ,
416- corev1.VolumeMount {Name : "unison-archive " , MountPath : "/unison-archive " },
505+ corev1.VolumeMount {Name : "syncthing-config " , MountPath : "/var/syncthing " },
417506 ),
418507 },
419508 },
@@ -443,7 +532,7 @@ exit 1`, unisonRemoteRoot)
443532 VolumeSource : corev1.VolumeSource {EmptyDir : & corev1.EmptyDirVolumeSource {}},
444533 },
445534 {
446- Name : "unison-archive " ,
535+ Name : "syncthing-config " ,
447536 VolumeSource : corev1.VolumeSource {EmptyDir : & corev1.EmptyDirVolumeSource {}},
448537 },
449538 },
0 commit comments