Deploy achievement-safe Factorio 2.0.77 with strict placement, monitoring and limited CD
This commit is contained in:
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Only ordinary console commands: no /c, /sc, Lua, mods, or achievement changes.
|
||||
set -eu
|
||||
metrics_dir=${METRICS_DIR:-/metrics}
|
||||
mkdir -p "$metrics_dir"
|
||||
while :; do
|
||||
now=$(date +%s)
|
||||
up=0
|
||||
players=''
|
||||
if response=$(timeout 8 rcon /players online 2>/dev/null); then
|
||||
players=$(printf '%s\n' "$response" | sed -n 's/^Online players (\([0-9][0-9]*\)):.*/\1/p' | head -1)
|
||||
[ -z "$players" ] || up=1
|
||||
fi
|
||||
{
|
||||
echo '# HELP factorio_server_up Authenticated non-Lua RCON player query succeeded.'
|
||||
echo '# TYPE factorio_server_up gauge'
|
||||
echo "factorio_server_up $up"
|
||||
echo '# HELP factorio_players_online Connected players; omitted if the query fails.'
|
||||
echo '# TYPE factorio_players_online gauge'
|
||||
[ -z "$players" ] || echo "factorio_players_online $players"
|
||||
echo '# HELP factorio_collector_last_poll_timestamp_seconds Last completed polling attempt.'
|
||||
echo '# TYPE factorio_collector_last_poll_timestamp_seconds gauge'
|
||||
echo "factorio_collector_last_poll_timestamp_seconds $now"
|
||||
} > "$metrics_dir/factorio.prom.tmp"
|
||||
mv "$metrics_dir/factorio.prom.tmp" "$metrics_dir/factorio.prom"
|
||||
sleep 15
|
||||
done
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
test -s /metrics/factorio.prom
|
||||
[ "$(( $(date +%s) - $(stat -c %Y /metrics/factorio.prom) ))" -lt 60 ]
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
# Exercise the game loop over authenticated RCON, and check that its data is writable.
|
||||
test -s /factorio/config/rconpw
|
||||
test -d /factorio/saves
|
||||
test -w /factorio/saves
|
||||
response=$(rcon /players online)
|
||||
printf '%s\n' "$response" | grep -q 'Online players'
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# Read-only node health; patch only the named Factorio Deployment.
|
||||
set -eu
|
||||
node=$(kubectl get node arschrock --ignore-not-found -o json)
|
||||
deployment=$(kubectl -n factorio get deployment factorio -o json)
|
||||
current=$(printf '%s' "$deployment" | jq -c '.spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms[0].matchExpressions[0].values')
|
||||
if [ -z "$node" ]; then
|
||||
mode=odroids
|
||||
else
|
||||
mode=$(printf '%s' "$node" | jq -r '
|
||||
if .metadata.labels["factorio.brunner.ninja/unavailable"] == "true" or .spec.unschedulable == true then "odroids"
|
||||
else ([.status.conditions[] | select(.type == "Ready")][0]) as $ready |
|
||||
if $ready == null then "hold"
|
||||
elif (now - ($ready.lastTransitionTime | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601)) < 300 then "hold"
|
||||
elif $ready.status == "True" then "primary" else "odroids" end
|
||||
end')
|
||||
fi
|
||||
case "$mode" in
|
||||
primary) desired='["arschrock"]' ;;
|
||||
odroids) desired='["odroid-h3-00","odroid-h3-01","odroid-h3-02","odroid-h3-03","odroid-h3-04","odroid-h3-05"]' ;;
|
||||
hold) echo 'Node health transition less than five minutes ago; holding placement'; exit 0 ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
if [ "$current" = "$desired" ]; then
|
||||
echo "Placement unchanged: $mode"
|
||||
exit 0
|
||||
fi
|
||||
# Optimistic concurrency: never overwrite a concurrent deployment or maintenance action.
|
||||
patch=$(printf '%s' "$deployment" | jq -c --argjson nodes "$desired" '[
|
||||
{op:"test",path:"/metadata/resourceVersion",value:.metadata.resourceVersion},
|
||||
{op:"replace",path:"/spec/template/spec/affinity/nodeAffinity/requiredDuringSchedulingIgnoredDuringExecution/nodeSelectorTerms/0/matchExpressions/0/values",value:$nodes}
|
||||
]')
|
||||
kubectl -n factorio patch deployment factorio --type=json -p "$patch"
|
||||
echo "Placement changed to $mode"
|
||||
@@ -0,0 +1,23 @@
|
||||
"""Preserve reconciler-owned placement; roll when mounted configuration changes."""
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
|
||||
docs = list(yaml.safe_load_all(Path(sys.argv[1]).read_text()))
|
||||
live_text = Path(sys.argv[2]).read_text().strip()
|
||||
live = json.loads(live_text) if live_text else None
|
||||
config = {d['metadata']['name']: d.get('data', {}) for d in docs if d['kind'] == 'ConfigMap' and d['metadata']['namespace'] == 'factorio'}
|
||||
checksum = hashlib.sha256(json.dumps(config, sort_keys=True).encode()).hexdigest()
|
||||
for doc in docs:
|
||||
if doc['kind'] == 'Deployment' and doc['metadata']['name'] == 'factorio':
|
||||
template = doc['spec']['template']
|
||||
template['metadata'].setdefault('annotations', {})['factorio.brunner.ninja/config-revision'] = checksum
|
||||
if live:
|
||||
template['spec']['affinity'] = live['spec']['template']['spec']['affinity']
|
||||
# The fresh PVC is an administrator bootstrap resource. CI never owns storage.
|
||||
if doc['kind'] == 'PersistentVolumeClaim':
|
||||
continue
|
||||
print('---')
|
||||
print(yaml.safe_dump(doc, sort_keys=False), end='')
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
mkdir -p /factorio/config /factorio/upgrade-backups
|
||||
# A startup snapshot preserves the pre-upgrade saves. Keep the last five starts.
|
||||
if compgen -G '/factorio/saves/*.zip' >/dev/null; then
|
||||
backup="/factorio/upgrade-backups/$(date -u +%Y%m%dT%H%M%S)"
|
||||
mkdir -p "$backup"
|
||||
cp -a /factorio/saves/*.zip "$backup/"
|
||||
mapfile -t old < <(find /factorio/upgrade-backups -mindepth 1 -maxdepth 1 -type d | sort -r | tail -n +6)
|
||||
for path in "${old[@]}"; do rm -rf -- "$path"; done
|
||||
fi
|
||||
cp /settings/server-settings.json /factorio/config/server-settings.json
|
||||
cp /settings/server-adminlist.json /factorio/config/server-adminlist.json
|
||||
cp /credentials/rcon-password /factorio/config/rconpw
|
||||
chmod 600 /factorio/config/rconpw
|
||||
# Upstream uses bash xtrace, which would otherwise log the RCON password.
|
||||
exec 3>/dev/null
|
||||
export BASH_XTRACEFD=3
|
||||
exec /docker-entrypoint.sh
|
||||
Reference in New Issue
Block a user