Skip to content

Commit 267b73a

Browse files
committed
syncthing
1 parent 4e1d1d6 commit 267b73a

2 files changed

Lines changed: 138 additions & 52 deletions

File tree

internal/controller/appruntime_controller.go

Lines changed: 123 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,6 @@ func (r *AppRuntimeReconciler) createHeadlessService(ctx context.Context, ar *en
291291
}
292292

293293
func (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
316320
mkdir -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
332429
done
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
},

pkg/splunk/enterprise/configuration.go

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ func getSplunkService(ctx context.Context, cr splcommon.MetaObject, spec *enterp
244244
splcommon.AppendParentMeta(service.ObjectMeta.GetObjectMeta(), cr.GetObjectMeta())
245245

246246
if instanceType == SplunkDeployer || isHeadless {
247-
// required for SHC bootstrap process and for App Runtime Unison sync (which needs to
248-
// reach the unison-server sidecar before splunkd is fully ready)
247+
// required for SHC bootstrap process and for App Runtime Syncthing sync (which needs to
248+
// reach the syncthing-server sidecar before splunkd is fully ready)
249249
service.Spec.PublishNotReadyAddresses = true
250250
}
251251

@@ -1173,24 +1173,25 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con
11731173
}
11741174
}
11751175

1176-
// Add Unison server sidecar for filesystem sync with App Runtime pods.
1176+
// Add Syncthing server sidecar for filesystem sync with App Runtime pods.
11771177
// Appended AFTER the container loop above so its Env/Resources/SecurityContext are not overwritten.
1178-
unisonServerImage := os.Getenv("RELATED_IMAGE_UNISON_SERVER")
1179-
if unisonServerImage == "" {
1180-
unisonServerImage = "493245399694.dkr.ecr.us-west-2.amazonaws.com/appruntime/ecr-repo/unison-server:latest"
1178+
syncthingServerImage := os.Getenv("RELATED_IMAGE_SYNCTHING_SERVER")
1179+
if syncthingServerImage == "" {
1180+
syncthingServerImage = "493245399694.dkr.ecr.us-west-2.amazonaws.com/appruntime/ecr-repo/syncthing-server:latest"
11811181
}
11821182
podTemplateSpec.Spec.Containers = append(podTemplateSpec.Spec.Containers, corev1.Container{
1183-
Name: "unison-server",
1184-
Image: unisonServerImage,
1183+
Name: "syncthing-server",
1184+
Image: syncthingServerImage,
11851185
ImagePullPolicy: corev1.PullAlways,
1186-
Command: []string{"unison", "-socket", "5000"},
1187-
Env: []corev1.EnvVar{
1188-
{Name: "UNISON", Value: "/tmp/.unison"},
1189-
},
11901186
Ports: []corev1.ContainerPort{
11911187
{
1192-
Name: "unison",
1193-
ContainerPort: 5000,
1188+
Name: "st-sync",
1189+
ContainerPort: 22000,
1190+
Protocol: corev1.ProtocolTCP,
1191+
},
1192+
{
1193+
Name: "st-api",
1194+
ContainerPort: 8384,
11941195
Protocol: corev1.ProtocolTCP,
11951196
},
11961197
},
@@ -1199,10 +1200,6 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con
11991200
Name: fmt.Sprintf(splcommon.PvcNamePrefix, splcommon.EtcVolumeStorage),
12001201
MountPath: fmt.Sprintf(splcommon.SplunkMountDirecPrefix, splcommon.EtcVolumeStorage),
12011202
},
1202-
{
1203-
Name: fmt.Sprintf(splcommon.PvcNamePrefix, splcommon.VarVolumeStorage),
1204-
MountPath: fmt.Sprintf(splcommon.SplunkMountDirecPrefix, splcommon.VarVolumeStorage),
1205-
},
12061203
},
12071204
Resources: corev1.ResourceRequirements{
12081205
Requests: corev1.ResourceList{

0 commit comments

Comments
 (0)