Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save motatoes/192624d19d147105d32b457c007b9cfa to your computer and use it in GitHub Desktop.

Select an option

Save motatoes/192624d19d147105d32b457c007b9cfa to your computer and use it in GitHub Desktop.
opencomputer recovery script
#!/usr/bin/env bash
# ----------------------------------------------------------------------------
# OpenComputer workspace recovery
#
# Restores a recovered /home/sandbox tarball into one of your sandboxes.
#
# Prerequisites (on the machine running this script):
# - oc CLI installed and authenticated (oc config set api-key <your-key>)
# - curl, jq, shasum (or sha256sum)
#
# Usage:
# ./recover-workspace.sh <path-to-tarball.tar.zst> [sandbox-id]
#
# If [sandbox-id] is omitted, a fresh 2 vCPU / 8 GB sandbox is created.
# If provided, recovery extracts INTO that existing sandbox's /home/sandbox.
#
# Optional env vars:
# EXPECTED_SHA256 If set, the script will refuse to upload unless the
# tarball's local SHA matches this value.
# API_URL Override (defaults to value in ~/.oc/config.json).
# API_KEY Override (defaults to value in ~/.oc/config.json).
# FORCE Set to 1 to extract even if /home/sandbox is non-empty.
#
# What it does:
# 1. Verifies the tarball locally (SHA256 if EXPECTED_SHA256 is set).
# 2. Creates a fresh sandbox (or uses the one you passed).
# 3. PUTs the tarball into the sandbox via the SDK files endpoint.
# 4. Re-verifies SHA256 inside the sandbox post-upload.
# 5. Installs zstd via apt (if missing), extracts with --numeric-owner
# preserved, ACLs/xattrs preserved, then chowns to the sandbox user.
# 6. Removes the temp tarball, prints a summary.
# ----------------------------------------------------------------------------
set -euo pipefail
# ---- args ----
TARBALL="${1:-}"
TARGET_SB="${2:-}"
if [ -z "$TARBALL" ]; then
echo "usage: $0 <tarball.tar.zst> [sandbox-id]" >&2
exit 64
fi
# ---- prereqs ----
for bin in oc curl jq; do
command -v "$bin" >/dev/null 2>&1 || { echo "ERROR: '$bin' not in PATH" >&2; exit 1; }
done
[ -f "$TARBALL" ] || { echo "ERROR: tarball not found: $TARBALL" >&2; exit 1; }
# ---- pick a sha256 tool ----
if command -v sha256sum >/dev/null 2>&1; then
sha256() { sha256sum "$1" | awk '{print $1}'; }
elif command -v shasum >/dev/null 2>&1; then
sha256() { shasum -a 256 "$1" | awk '{print $1}'; }
else
echo "ERROR: need sha256sum or shasum in PATH" >&2; exit 1
fi
# ---- resolve API URL + key ----
CONFIG="${HOME}/.oc/config.json"
API_URL="${API_URL:-}"
API_KEY="${API_KEY:-}"
if [ -z "$API_URL" ] && [ -f "$CONFIG" ]; then
API_URL=$(jq -r '.api_url // empty' "$CONFIG")
fi
if [ -z "$API_KEY" ] && [ -f "$CONFIG" ]; then
API_KEY=$(jq -r '.api_key // empty' "$CONFIG")
fi
API_URL="${API_URL:-https://app.opencomputer.dev}"
[ -n "$API_KEY" ] || { echo "ERROR: no api_key found. Run 'oc config set api-key <key>' or export API_KEY=..." >&2; exit 1; }
API_BASE="${API_URL%/}/api"
# ---- preflight: tarball SHA ----
echo "Tarball: $TARBALL ($(ls -la "$TARBALL" | awk '{print $5}') bytes)"
LOCAL_SHA=$(sha256 "$TARBALL")
echo "SHA256: $LOCAL_SHA"
if [ -n "${EXPECTED_SHA256:-}" ]; then
if [ "$LOCAL_SHA" != "$EXPECTED_SHA256" ]; then
echo "ERROR: SHA256 mismatch — refusing to upload." >&2
echo " expected: $EXPECTED_SHA256" >&2
echo " got: $LOCAL_SHA" >&2
exit 2
fi
echo " ✓ matches EXPECTED_SHA256"
fi
# ---- create or reuse sandbox ----
if [ -z "$TARGET_SB" ]; then
echo
echo "Creating fresh sandbox (2 vCPU / 8192 MB / timeout 7200s)..."
TARGET_SB=$(oc sandbox create --cpu 2 --memory 8192 --timeout 7200 \
--metadata purpose=workspace-recovery --json | jq -r '.sandboxID')
echo "Created: $TARGET_SB"
sleep 3
else
echo "Using existing sandbox: $TARGET_SB"
fi
# ---- safety: refuse to extract over non-empty workspace unless FORCE=1 ----
if [ "${FORCE:-0}" != "1" ]; then
EXISTING=$(oc exec "$TARGET_SB" --wait --timeout 30 -- bash -c \
'find /home/sandbox -mindepth 1 -maxdepth 1 ! -name lost+found 2>/dev/null | head -5' 2>/dev/null || true)
if [ -n "$EXISTING" ]; then
echo "ERROR: /home/sandbox in $TARGET_SB is not empty:" >&2
echo "$EXISTING" | sed 's/^/ /' >&2
echo "Re-run with FORCE=1 to extract on top of existing files." >&2
exit 3
fi
fi
# ---- upload via SDK files PUT endpoint ----
REMOTE_TARBALL="/home/sandbox/.recovery.tar.zst"
ENC_PATH=$(printf %s "$REMOTE_TARBALL" | jq -sRr @uri)
echo
echo "Uploading $(basename "$TARBALL") → $TARGET_SB:$REMOTE_TARBALL ..."
START=$(date +%s)
HTTP_STATUS=$(curl -fsS -X PUT \
"$API_BASE/sandboxes/$TARGET_SB/files?path=$ENC_PATH" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$TARBALL" \
-w '%{http_code}' \
-o /dev/null) || { echo "ERROR: curl failed (network?)" >&2; exit 4; }
ELAPSED=$(( $(date +%s) - START ))
echo " HTTP $HTTP_STATUS in ${ELAPSED}s"
[ "$HTTP_STATUS" = "204" ] || { echo "ERROR: upload failed (HTTP $HTTP_STATUS)" >&2; exit 4; }
# ---- verify SHA inside sandbox ----
echo "Verifying SHA256 inside the sandbox..."
REMOTE_SHA=$(oc exec "$TARGET_SB" --wait --timeout 120 -- \
sha256sum "$REMOTE_TARBALL" 2>/dev/null | awk '{print $1}')
echo " remote: $REMOTE_SHA"
if [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then
echo "ERROR: SHA mismatch after upload (transport corruption?)" >&2
exit 5
fi
echo " ✓ matches"
# ---- extract ----
echo
echo "Extracting in sandbox..."
oc exec "$TARGET_SB" --wait --timeout 1800 -- bash -c '
set -eu
echo " installing zstd if missing..."
if ! command -v zstd >/dev/null 2>&1; then
sudo apt-get update -qq >/dev/null 2>&1 || sudo apt-get update -qq
sudo apt-get install -y -qq zstd >/dev/null
fi
cd /home/sandbox
echo " extracting (this can take a minute for large tarballs)..."
START=$(date +%s)
sudo tar --numeric-owner --acls --xattrs --zstd -xf .recovery.tar.zst
echo " extracted in $(( $(date +%s) - START ))s"
sudo rm -f .recovery.tar.zst
SBUSER=sandbox
if id "$SBUSER" >/dev/null 2>&1; then
SBUID=$(id -u "$SBUSER"); SBGID=$(id -g "$SBUSER")
else
SBUID=1000; SBGID=1000
fi
echo " chowning to $SBUSER ($SBUID:$SBGID)..."
sudo chown -R "$SBUID:$SBGID" /home/sandbox
echo
echo " ─── final state ───"
ls -la /home/sandbox | head -25
echo " files: $(find /home/sandbox -type f 2>/dev/null | wc -l)"
echo " symlinks: $(find /home/sandbox -type l 2>/dev/null | wc -l)"
echo " size: $(du -sh /home/sandbox 2>/dev/null | cut -f1)"
'
echo
echo "✓ Recovery complete in sandbox $TARGET_SB"
echo
echo "Inspect with:"
echo " oc shell $TARGET_SB"
echo " # then inside: ls -la /home/sandbox"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment