adithya-interview / scripts /sync-workspace-spaces.sh
calmodovar23's picture
Upload 13 files
6164163 verified
Raw
History Blame Contribute Delete
5.65 kB
#!/bin/sh
set -eu
log() {
printf '%s\n' "[workspace-spaces-sync] $*"
}
fail() {
log "ERROR: $*" >&2
exit 1
}
DATA_ROOT="${DATA_ROOT:-/data}"
S3CMD_BIN="${S3CMD_BIN:-s3cmd}"
SPACES_BUCKET="${SPACES_BUCKET-}"
SPACES_ENDPOINT="${SPACES_ENDPOINT-}"
SPACES_SYNC_INTERVAL_SECONDS="${SPACES_SYNC_INTERVAL_SECONDS:-60}"
# An unset bucket disables synchronization on platforms that provide their own
# persistent volume, including HF Spaces and suitably configured Cloud Run.
if [ -z "${SPACES_BUCKET}" ]; then
exit 0
fi
[ -n "${SPACES_ENDPOINT}" ] || fail "SPACES_ENDPOINT is required"
[ -n "${AWS_ACCESS_KEY_ID-}" ] || fail "AWS_ACCESS_KEY_ID is required"
[ -n "${AWS_SECRET_ACCESS_KEY-}" ] || fail "AWS_SECRET_ACCESS_KEY is required"
command -v "${S3CMD_BIN}" >/dev/null 2>&1 || fail "s3cmd is not installed"
endpoint="${SPACES_ENDPOINT#https://}"
endpoint="${endpoint#http://}"
endpoint="${endpoint%/}"
case "${endpoint}" in
''|*[!A-Za-z0-9.-]*) fail "SPACES_ENDPOINT must be a regional hostname" ;;
esac
case "${SPACES_BUCKET}" in
*[!a-z0-9.-]*|'') fail "SPACES_BUCKET contains unsupported characters" ;;
esac
case "${SPACES_SYNC_INTERVAL_SECONDS}" in
''|*[!0-9]*) fail "SPACES_SYNC_INTERVAL_SECONDS must be an integer" ;;
esac
if [ "${SPACES_SYNC_INTERVAL_SECONDS}" -lt 30 ]; then
fail "SPACES_SYNC_INTERVAL_SECONDS must be at least 30"
fi
umask 077
config_file="$(mktemp /tmp/workspace-spaces-config.XXXXXX)"
temporary_file=''
cleanup() {
rm -f "${config_file}"
if [ -n "${temporary_file}" ]; then
rm -f "${temporary_file}"
fi
}
trap cleanup EXIT
{
printf '%s\n' '[default]'
printf '%s\n' 'access_key ='
printf '%s\n' 'secret_key ='
printf '%s\n' 'bucket_location = US'
printf '%s\n' "host_base = ${endpoint}"
printf '%s\n' "host_bucket = %(bucket)s.${endpoint}"
printf '%s\n' 'use_https = True'
printf '%s\n' 'check_ssl_certificate = True'
printf '%s\n' 'check_ssl_hostname = True'
} > "${config_file}"
s3cmd_run() {
"${S3CMD_BIN}" --config="${config_file}" --no-progress "$@"
}
workspace_dir="${DATA_ROOT}/workspace"
claude_dir="${DATA_ROOT}/claude"
remote_uri="s3://${SPACES_BUCKET}/latest.tar.gz"
restore_snapshot() {
listing="$(s3cmd_run ls "${remote_uri}")" \
|| fail "Unable to check ${remote_uri}"
if [ -z "${listing}" ]; then
log "No existing snapshot found in ${SPACES_BUCKET}"
return 0
fi
temporary_file="$(mktemp /tmp/workspace-restore.XXXXXX)"
s3cmd_run get --force "${remote_uri}" "${temporary_file}" \
|| fail "Unable to download ${remote_uri}"
tar -tzf "${temporary_file}" >/dev/null \
|| fail "Downloaded workspace snapshot is not a valid gzip archive"
invalid_member="$(
tar -tzf "${temporary_file}" \
| awk '
/(^|\/)\.\.($|\/)/ {
print
exit
}
$0 == "workspace" || $0 ~ /^workspace\// {
next
}
$0 == "claude/projects" || $0 ~ /^claude\/projects\// {
next
}
$0 == "claude/history.jsonl" {
next
}
{
print
exit
}
'
)"
[ -z "${invalid_member}" ] \
|| fail "Workspace snapshot contains an invalid path: ${invalid_member}"
tar --touch -C "${DATA_ROOT}" -xzf "${temporary_file}"
rm -f "${temporary_file}"
temporary_file=''
log "Restored candidate snapshot from ${SPACES_BUCKET}"
}
upload_snapshot() {
[ -d "${workspace_dir}" ] || return 0
temporary_file="$(mktemp /tmp/workspace-snapshot.XXXXXX)"
# Preserve the candidate's code and the minimum Claude Code state needed to
# inspect/resume conversations. Do not archive Claude credentials, settings,
# plugins, caches, or other reusable user configuration.
set -- workspace
if [ -d "${claude_dir}/projects" ]; then
set -- "$@" claude/projects
fi
if [ -f "${claude_dir}/history.jsonl" ]; then
set -- "$@" claude/history.jsonl
fi
if ! tar -C "${DATA_ROOT}" -czf "${temporary_file}" \
--exclude='workspace/.venv' \
--exclude='workspace/__pycache__' \
--exclude='workspace/.pytest_cache' \
--exclude='workspace/.mypy_cache' \
--exclude='workspace/.ruff_cache' \
--exclude='workspace/.env' \
--exclude='workspace/.env.*' \
--exclude='workspace/.claude' \
--exclude='workspace/.claude.json' \
--exclude='workspace/.openrouter' \
--exclude='workspace/openrouter.env' \
--exclude='workspace/.git/index.lock' \
"$@"
then
log "WARNING: Workspace changed during snapshot; retrying later" >&2
rm -f "${temporary_file}"
temporary_file=''
return 1
fi
if ! s3cmd_run put "${temporary_file}" "${remote_uri}"; then
log "WARNING: Unable to upload workspace snapshot; retrying later" >&2
rm -f "${temporary_file}"
temporary_file=''
return 1
fi
rm -f "${temporary_file}"
temporary_file=''
log "Uploaded candidate workspace and Claude transcript snapshot to ${SPACES_BUCKET}"
}
watch_snapshots() {
stop_requested=0
trap 'stop_requested=1' INT TERM HUP
while [ "${stop_requested}" -eq 0 ]; do
upload_snapshot || true
if [ "${stop_requested}" -eq 0 ]; then
sleep "${SPACES_SYNC_INTERVAL_SECONDS}" &
wait "$!" || true
fi
done
log "Shutdown requested; uploading final workspace snapshot"
upload_snapshot || true
}
case "${1:-}" in
validate)
log "Spaces synchronization configured for candidate bucket ${SPACES_BUCKET}"
;;
restore)
restore_snapshot
;;
snapshot)
upload_snapshot
;;
watch)
watch_snapshots
;;
*)
fail "Usage: $0 {validate|restore|snapshot|watch}"
;;
esac