chore: remove GitHub Actions cloud runtime (workflow, scripts, docs) (#443)
Remove the 'OpenChamber for Actions' feature that ran OpenChamber on GitHub runners via Cloudflare/Ngrok tunnels. This was a separate deployment target with its own lifecycle scripts and documentation that added maintenance overhead without benefiting local usage. Deleted: - .github/workflows/opencode.yml (Actions workflow) - scripts/monitor.sh (service self-heal loop) - scripts/persistence-save.sh (artifact encryption/upload) - scripts/persistence-restore.sh (artifact decrypt/restore) - scripts/opencode-config.sh (Actions config bootstrap) - docs/OPENCHAMBER_FOR_ACTIONS.md (user guide) Updated: - README.md: removed 'GitHub Actions (Cloud Usage)' section Local Cloudflare Quick Tunnel support (--try-cf-tunnel) is unaffected.
This commit is contained in:
@@ -1,231 +0,0 @@
|
||||
name: OpenChamber for Actions
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tunnel_provider:
|
||||
description: 'Select Tunnel Provider'
|
||||
required: true
|
||||
default: 'cloudflare'
|
||||
type: choice
|
||||
options:
|
||||
- ngrok
|
||||
- cloudflare
|
||||
timeout_minutes:
|
||||
description: 'Auto-shutdown after (minutes)'
|
||||
required: true
|
||||
default: '300'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
serve:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OPENCODE_SERVER_PASSWORD: ${{ secrets.OPENCODE_SERVER_PASSWORD }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# ============================================================
|
||||
# PERSISTENCE: Restore previous session (OAuth, chats, config)
|
||||
# ============================================================
|
||||
- name: Restore Session Data (Artifact)
|
||||
if: ${{ env.OPENCODE_SERVER_PASSWORD != '' }}
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: opencode-session
|
||||
path: /tmp/opencode-restore
|
||||
workflow: opencode.yml
|
||||
workflow_conclusion: success
|
||||
if_no_artifact_found: ignore
|
||||
repo: ${{ github.repository }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Apply Restored Session Data
|
||||
if: ${{ env.OPENCODE_SERVER_PASSWORD != '' }}
|
||||
env:
|
||||
OPENCODE_SERVER_PASSWORD: ${{ env.OPENCODE_SERVER_PASSWORD }}
|
||||
run: |
|
||||
chmod +x scripts/persistence-restore.sh
|
||||
RESTORE_DIR=/tmp/opencode-restore ./scripts/persistence-restore.sh
|
||||
|
||||
# ============================================================
|
||||
# CACHE: Speed up npm installations
|
||||
# ============================================================
|
||||
- name: Cache npm modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
key: npm-cache-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-cache-${{ runner.os }}-
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install Tools
|
||||
run: |
|
||||
npm install -g opencode-ai @openchamber/web
|
||||
sudo apt update && sudo apt install jq lsof coreutils openssl -y
|
||||
|
||||
# Install ttyd for OpenCode TTY exposure
|
||||
wget -q -O ttyd https://github.com/tsl0922/ttyd/releases/download/1.7.3/ttyd.x86_64
|
||||
chmod +x ttyd
|
||||
sudo mv ttyd /usr/local/bin/
|
||||
|
||||
# ============================================================
|
||||
# CONFIGURATION: Setup OpenCode config (respects artifacts)
|
||||
# ============================================================
|
||||
- name: Configure OpenCode
|
||||
run: |
|
||||
chmod +x scripts/opencode-config.sh
|
||||
RESTORE_DIR=/tmp/opencode-restore ./scripts/opencode-config.sh
|
||||
|
||||
# ============================================================
|
||||
# STARTUP: Launch services and tunnel
|
||||
# ============================================================
|
||||
- name: Start Services
|
||||
env:
|
||||
OPENCODE_SERVER_PASSWORD: ${{ env.OPENCODE_SERVER_PASSWORD }}
|
||||
run: |
|
||||
# Setup Password Protection
|
||||
TTYD_ARGS=""
|
||||
if [[ -n "$OPENCODE_SERVER_PASSWORD" ]]; then
|
||||
echo "Password protection enabled."
|
||||
# Export for subsequent steps (Monitor script) and other services
|
||||
echo "OPENCHAMBER_UI_PASSWORD=$OPENCODE_SERVER_PASSWORD" >> $GITHUB_ENV
|
||||
echo "OPENCODE_UI_PASSWORD=$OPENCODE_SERVER_PASSWORD" >> $GITHUB_ENV
|
||||
|
||||
# Use for current step
|
||||
export OPENCHAMBER_UI_PASSWORD="$OPENCODE_SERVER_PASSWORD"
|
||||
export OPENCODE_UI_PASSWORD="$OPENCODE_SERVER_PASSWORD"
|
||||
|
||||
TTYD_ARGS="-c user:$OPENCODE_SERVER_PASSWORD"
|
||||
else
|
||||
echo "Password protection disabled (OPENCODE_SERVER_PASSWORD not set)."
|
||||
fi
|
||||
|
||||
# Start OpenCode TTY (via ttyd)
|
||||
echo "Starting OpenCode TTY..."
|
||||
nohup stdbuf -oL ttyd $TTYD_ARGS -p 7681 opencode > opencode_tty.log 2>&1 &
|
||||
|
||||
# Start OpenChamber
|
||||
echo "Starting OpenChamber..."
|
||||
if [[ -n "$OPENCODE_SERVER_PASSWORD" ]]; then
|
||||
nohup stdbuf -oL openchamber --port 9090 --ui-password "$OPENCODE_SERVER_PASSWORD" > openchamber.log 2>&1 &
|
||||
else
|
||||
nohup stdbuf -oL openchamber --port 9090 > openchamber.log 2>&1 &
|
||||
fi
|
||||
|
||||
# Start OpenCode Web
|
||||
echo "Starting OpenCode Web..."
|
||||
if [[ -n "$OPENCODE_SERVER_PASSWORD" ]]; then
|
||||
OPENCODE_SERVER_PASSWORD="$OPENCODE_SERVER_PASSWORD" nohup stdbuf -oL opencode web --port 8080 > opencode_web.log 2>&1 &
|
||||
else
|
||||
nohup stdbuf -oL opencode web --port 8080 > opencode_web.log 2>&1 &
|
||||
fi
|
||||
|
||||
# Wait for services to initialize
|
||||
sleep 20
|
||||
|
||||
echo "Services started. Checking status..."
|
||||
lsof -i :7681 || echo "Warning: OpenCode TTY may not be running"
|
||||
lsof -i :9090 || echo "Warning: OpenChamber may not be running"
|
||||
lsof -i :8080 || echo "Warning: OpenCode Web may not be running"
|
||||
|
||||
- name: Setup Tunnel
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.tunnel_provider }}" == "ngrok" ]]; then
|
||||
echo "Setting up ngrok tunnel..."
|
||||
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
|
||||
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list
|
||||
sudo apt update && sudo apt install ngrok -y
|
||||
ngrok config add-authtoken "${{ secrets.NGROK_AUTH_TOKEN }}"
|
||||
|
||||
# Note: Multiple ngrok tunnels on free plan might be limited or require config file
|
||||
# Attempting to start tunnels blindly
|
||||
nohup ngrok http 127.0.0.1:9090 --log=stdout > tunnel_chamber.log 2>&1 &
|
||||
nohup ngrok http 127.0.0.1:8080 --log=stdout > tunnel_web.log 2>&1 &
|
||||
nohup ngrok http 127.0.0.1:7681 --log=stdout > tunnel_tty.log 2>&1 &
|
||||
sleep 10
|
||||
|
||||
# Extract URLs
|
||||
URL_CHAMBER=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[] | select(.config.addr | contains("9090")) | .public_url' || true)
|
||||
URL_WEB=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[] | select(.config.addr | contains("8080")) | .public_url' || true)
|
||||
URL_TTY=$(curl -s http://localhost:4040/api/tunnels | jq -r '.tunnels[] | select(.config.addr | contains("7681")) | .public_url' || true)
|
||||
|
||||
echo "URL_CHAMBER=$URL_CHAMBER" >> $GITHUB_ENV
|
||||
echo "URL_WEB=$URL_WEB" >> $GITHUB_ENV
|
||||
echo "URL_TTY=$URL_TTY" >> $GITHUB_ENV
|
||||
|
||||
else
|
||||
echo "Setting up Cloudflare tunnel..."
|
||||
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
|
||||
sudo dpkg -i cloudflared-linux-amd64.deb
|
||||
|
||||
# Start 3 separate tunnels
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:7681 > tunnel_tty.log 2>&1 &
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:9090 > tunnel_chamber.log 2>&1 &
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:8080 > tunnel_web.log 2>&1 &
|
||||
|
||||
echo "Waiting for Cloudflare Tunnel URLs..."
|
||||
|
||||
# Loop to wait for URLs
|
||||
for i in {1..30}; do
|
||||
URL_TTY=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_tty.log 2>/dev/null | tail -n 1 || true)
|
||||
URL_CHAMBER=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_chamber.log 2>/dev/null | tail -n 1 || true)
|
||||
URL_WEB=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_web.log 2>/dev/null | tail -n 1 || true)
|
||||
|
||||
if [[ -n "$URL_TTY" && -n "$URL_CHAMBER" && -n "$URL_WEB" ]]; then
|
||||
echo "All tunnels established!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for tunnels... ($i/30)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ -z "$URL_TTY" ]]; then echo "Warning: TTY Tunnel URL missing"; fi
|
||||
if [[ -z "$URL_CHAMBER" ]]; then echo "Warning: Chamber Tunnel URL missing"; fi
|
||||
if [[ -z "$URL_WEB" ]]; then echo "Warning: Web Tunnel URL missing"; fi
|
||||
|
||||
echo "URL_TTY=$URL_TTY" >> $GITHUB_ENV
|
||||
echo "URL_CHAMBER=$URL_CHAMBER" >> $GITHUB_ENV
|
||||
echo "URL_WEB=$URL_WEB" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# MONITOR: Self-healing loop with status updates
|
||||
# ============================================================
|
||||
- name: Monitor & Self-Heal
|
||||
env:
|
||||
OPENCHAMBER_UI_PASSWORD: ${{ env.OPENCODE_SERVER_PASSWORD }}
|
||||
run: |
|
||||
chmod +x scripts/monitor.sh
|
||||
# Pass all 3 URLs to monitor script
|
||||
./scripts/monitor.sh "${{ github.event.inputs.tunnel_provider }}" "${{ github.event.inputs.timeout_minutes }}" "${{ env.URL_TTY }}" "${{ env.URL_CHAMBER }}" "${{ env.URL_WEB }}"
|
||||
|
||||
# ============================================================
|
||||
# PERSISTENCE: Save all session data (always runs)
|
||||
# ============================================================
|
||||
- name: Prepare Session Data for Save
|
||||
if: ${{ always() && env.OPENCODE_SERVER_PASSWORD != '' }}
|
||||
env:
|
||||
OPENCODE_SERVER_PASSWORD: ${{ env.OPENCODE_SERVER_PASSWORD }}
|
||||
run: |
|
||||
chmod +x scripts/persistence-save.sh
|
||||
SAVE_DIR=/tmp/opencode-save ./scripts/persistence-save.sh
|
||||
|
||||
- name: Upload Session Data (Artifact)
|
||||
if: ${{ always() && env.OPENCODE_SERVER_PASSWORD != '' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: opencode-session
|
||||
path: /tmp/opencode-save/
|
||||
retention-days: 90
|
||||
overwrite: true
|
||||
if-no-files-found: warn
|
||||
@@ -80,16 +80,6 @@ The whole project was built entirely with AI coding agents under my supervision.
|
||||
- In-extension settings, responsive layout, and theme mapping that matches your editor
|
||||
- Hardened runtime lifecycle and health checks for faster startup and fewer stuck reconnect states
|
||||
|
||||
### GitHub Actions (Cloud Usage)
|
||||
|
||||
Run OpenChamber remotely using GitHub Actions. No local computer required.
|
||||
|
||||
* **Zero Setup:** Runs on GitHub's infrastructure.
|
||||
* **Persistence:** Optional; enabled when `OPENCODE_SERVER_PASSWORD` is set (encrypted).
|
||||
* **Remote Access:** Access via secure tunnel (Cloudflare/Ngrok).
|
||||
|
||||
[**Read the Guide: OpenChamber for Actions**](docs/OPENCHAMBER_FOR_ACTIONS.md)
|
||||
|
||||
### Custom Themes
|
||||
|
||||
Create your own color schemes by dropping JSON files into `~/.config/openchamber/themes/`. Hot reload supported — no restart needed.
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
# OpenChamber for Actions
|
||||
|
||||
**Version:** `v0.1.0-preview`
|
||||
|
||||
OpenChamber for Actions allows you to run a full OpenChamber environment remotely using GitHub Actions infrastructure. It provides access to OpenCode, OpenChamber, and a web-based terminal without requiring any local hardware.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
OpenChamber for Actions is a "computer in the cloud" solution that leverages GitHub Runners to host a temporary development environment. It spins up three key services:
|
||||
* **OpenCode TTY:** A web-based terminal for command-line access.
|
||||
* **OpenChamber:** The core environment.
|
||||
* **OpenCode Web:** The web interface for coding and interaction.
|
||||
|
||||
These services are exposed via secure tunnels (Cloudflare or Ngrok), allowing you to access them from any browser. The session can persist data between runs using GitHub Artifacts.
|
||||
|
||||
---
|
||||
|
||||
## Key Features
|
||||
|
||||
* **No local hardware required:** Run OpenChamber on GitHub Actions infrastructure.
|
||||
* **OpenCode integration:** Access the OpenCode suite and "Antigravity" model selection (including Gemini 3 and Claude 4.5).
|
||||
* **Secure access (optional):** Protect OpenChamber, OpenCode Web, and the TTY with `OPENCODE_SERVER_PASSWORD`.
|
||||
* **Persistent sessions (optional):** Save login state, OAuth tokens, and configuration as GitHub Artifacts when a password is set (encrypted).
|
||||
* **Self-healing tunnels:** Background monitoring keeps the tunnel stable.
|
||||
|
||||
---
|
||||
|
||||
## New to this? (Beginners' Guide)
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand: What is this and how does it help me?</b></summary>
|
||||
|
||||
### 1. What exactly is this?
|
||||
Think of this as a "computer in the cloud." Instead of running a local AI environment, GitHub Actions provides the compute for you.
|
||||
|
||||
### 2. What do I need to start?
|
||||
* A GitHub account.
|
||||
* A web browser (Chrome, Edge, Safari, etc.).
|
||||
* No special hardware required.
|
||||
|
||||
### 3. Why is Cloudflare recommended?
|
||||
Cloudflare Quick Tunnels create a secure public URL automatically. You do not need extra accounts or API keys, so it is the simplest option.
|
||||
|
||||
### 4. What is persistence?
|
||||
When a password is set, the workflow saves your login info and settings as an encrypted GitHub Artifact. The next run restores the session so you can continue without logging in again.
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><b>Usage Limits & Quotas</b></summary>
|
||||
|
||||
To ensure stability and compliance with GitHub’s Terms of Service, be aware of the following limits:
|
||||
|
||||
| Limit Type | Constraint | Explanation |
|
||||
| :--- | :--- | :--- |
|
||||
| **Time Limit** | **6 Hours Max** | GitHub stops any job after 360 minutes. |
|
||||
| **Storage** | **2GB** | Session artifacts cannot exceed 2GB per repository. |
|
||||
| **Concurrency** | **1 Run Only** | Run a single OpenChamber instance at a time. |
|
||||
| **Hardware** | **2-Core CPU** | Standard Linux runners (~7GB RAM). |
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Installation Guide
|
||||
|
||||
### Option A: Easy Mode (Recommended)
|
||||
Uses Cloudflare tunnels. No configuration required unless you want password protection.
|
||||
|
||||
1. Fork this repository to your GitHub account.
|
||||
2. Open the Actions tab in your fork.
|
||||
3. Select the **OpenChamber for Actions** workflow.
|
||||
4. (Optional) **Set a Password (recommended):**
|
||||
* Go to **Settings** -> **Secrets and variables** -> **Actions**.
|
||||
* Click **New repository secret**.
|
||||
* **Name:** `OPENCODE_SERVER_PASSWORD`
|
||||
* **Secret:** Your desired password.
|
||||
* *Note: If this secret is set, it protects OpenCode TTY, OpenChamber, and OpenCode Web. It also enables encrypted persistence.*
|
||||
5. Click **Run workflow**.
|
||||
* **Tunnel Provider:** Choose `cloudflare` (default) or `ngrok`.
|
||||
* **Auto-shutdown after (minutes):** Set the duration (default `300`).
|
||||
6. Wait about 30 seconds for setup to finish.
|
||||
7. Open the run, then open the `serve` job.
|
||||
8. Expand the **Monitor & Self-Heal** step to find the URLs and open them.
|
||||
|
||||
> [!TIP]
|
||||
> Keep the repository visibility `Private` or use `OPENCODE_SERVER_PASSWORD` in Github Actions Secrets. Otherwise, your privacy can be violated.
|
||||
|
||||
---
|
||||
|
||||
### Option B: Pro Mode (Ngrok)
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand: Step-by-step guide for setting up Ngrok</b></summary>
|
||||
|
||||
If you prefer using Ngrok for a static domain, follow these steps to set up your API key.
|
||||
|
||||
#### Step 1: Get your authtoken
|
||||
1. Log in or sign up at dashboard.ngrok.com.
|
||||
2. In the sidebar, click "Your Authtoken."
|
||||
3. Copy the token string (for example, `21xYz...`).
|
||||
|
||||
#### Step 2: Add the secret to GitHub
|
||||
1. Open your forked repository on GitHub.
|
||||
2. Open Settings.
|
||||
3. In the sidebar, open Secrets and variables and click Actions.
|
||||
4. Click New repository secret.
|
||||
5. **Name:** `NGROK_AUTH_TOKEN`.
|
||||
6. **Secret:** Paste the token.
|
||||
7. (Optional) Add `OPENCODE_SERVER_PASSWORD` to enable password protection for all services and encrypted persistence.
|
||||
8. Click Add secret.
|
||||
|
||||
#### Step 3: Run with Ngrok
|
||||
1. Go to the Actions tab.
|
||||
2. Select **OpenChamber for Actions**.
|
||||
3. Click **Run workflow**.
|
||||
4. Select `ngrok` as the Tunnel Provider.
|
||||
5. Find the URL in the "Monitor & Self-Heal" step.
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Frequently Asked Questions (FAQ)
|
||||
|
||||
<details>
|
||||
<summary><b>Q: Is this completely free?</b></summary>
|
||||
GitHub provides 2,000 minutes for private repositories and unlimited minutes for public repositories. This workflow uses those minutes.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: Why did my session stop working after a few hours?</b></summary>
|
||||
GitHub Actions has a 6-hour job limit. The environment shuts down automatically. If persistence is enabled, your session data is restored on the next run.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: Can I use this on my phone or tablet?</b></summary>
|
||||
Yes. Once you have a public URL (Cloudflare or Ngrok), open it in any mobile browser.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: Is my data private?</b></summary>
|
||||
If you fork this as a public repository, your code may be visible depending on how you save it. For maximum privacy, use a private fork. If you want persistence, set `OPENCODE_SERVER_PASSWORD` so the artifact is encrypted.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Q: I see a "Connection Refused" error. What do I do?</b></summary>
|
||||
Wait about 30 seconds and refresh the page. If it persists, check the Monitor logs in the Actions run.
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
| Feature | Detail |
|
||||
| :--- | :--- |
|
||||
| **Version** | `v0.1.0-preview` |
|
||||
| **OS** | Ubuntu 22.04 LTS (GitHub Runner) |
|
||||
| **Node Version** | v20.x |
|
||||
| **Tunnel Protocols** | `cloudflared` (Argon) / `ngrok` (HTTP) |
|
||||
| **Artifact Retention** | 90 Days (Default GitHub Policy) |
|
||||
|
||||
> [!NOTE]
|
||||
> This project is for educational and development purposes. Do not use this workflow for mining crypto or other activities banned by the GitHub Acceptable Use Policy.
|
||||
@@ -1,292 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# OpenCode Monitor & Self-Heal Script
|
||||
# ==============================================================================
|
||||
# This script monitors OpenCode (TTY), OpenChamber, and OpenCode Web services,
|
||||
# automatically restarting them if they crash. It also manages the tunnel connections.
|
||||
#
|
||||
# Usage: ./monitor.sh <tunnel_provider> <timeout_minutes> <url_tty> <url_chamber> <url_web>
|
||||
#
|
||||
# Arguments:
|
||||
# tunnel_provider: "ngrok" or "cloudflare"
|
||||
# timeout_minutes: Auto-shutdown timeout in minutes
|
||||
# url_tty: Initial tunnel URL for OpenCode TTY
|
||||
# url_chamber: Initial tunnel URL for OpenChamber
|
||||
# url_web: Initial tunnel URL for OpenCode Web
|
||||
# ==============================================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
TUNNEL_PROVIDER="${1:-cloudflare}"
|
||||
TIMEOUT_MINUTES="${2:-300}"
|
||||
URL_TTY="${3:-}"
|
||||
URL_CHAMBER="${4:-}"
|
||||
URL_WEB="${5:-}"
|
||||
|
||||
echo "=============================================="
|
||||
echo "OpenCode Monitor & Self-Heal"
|
||||
echo "=============================================="
|
||||
echo "Tunnel Provider: $TUNNEL_PROVIDER"
|
||||
echo "Timeout: $TIMEOUT_MINUTES minute(s)"
|
||||
echo "----------------------------------------------"
|
||||
echo "OpenCode TTY URL: $URL_TTY"
|
||||
echo "OpenChamber URL: $URL_CHAMBER"
|
||||
echo "OpenCode Web URL: $URL_WEB"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Configuration
|
||||
# ------------------------------------------------------------------------------
|
||||
TTY_PORT=7681
|
||||
OPENCHAMBER_PORT=9090
|
||||
OPENCODE_WEB_PORT=8080
|
||||
|
||||
START_TIME=$(date +%s)
|
||||
TIMEOUT_SECONDS=$((TIMEOUT_MINUTES * 60))
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Helper Functions
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||
}
|
||||
|
||||
check_port() {
|
||||
local port=$1
|
||||
lsof -i :"$port" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
get_remaining_time() {
|
||||
local elapsed=$(($(date +%s) - START_TIME))
|
||||
local remaining=$((TIMEOUT_SECONDS - elapsed))
|
||||
echo $remaining
|
||||
}
|
||||
|
||||
format_time() {
|
||||
local seconds=$1
|
||||
local minutes=$((seconds / 60))
|
||||
local hours=$((minutes / 60))
|
||||
minutes=$((minutes % 60))
|
||||
|
||||
if [ $hours -gt 0 ]; then
|
||||
echo "${hours}h ${minutes}m"
|
||||
else
|
||||
echo "${minutes}m"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Service Management
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
restart_opencode_tty() {
|
||||
log "Restarting OpenCode TTY on port $TTY_PORT..."
|
||||
pkill -f "ttyd" 2>/dev/null || true
|
||||
sleep 2
|
||||
|
||||
if [ -n "${OPENCHAMBER_UI_PASSWORD:-}" ]; then
|
||||
log "Restarting TTY with password protection."
|
||||
nohup stdbuf -oL ttyd -c "user:${OPENCHAMBER_UI_PASSWORD}" -p $TTY_PORT bash -c "cd \$HOME && exec opencode" >> opencode_tty.log 2>&1 &
|
||||
else
|
||||
nohup stdbuf -oL ttyd -p $TTY_PORT bash -c "cd \$HOME && exec opencode" >> opencode_tty.log 2>&1 &
|
||||
fi
|
||||
sleep 5
|
||||
|
||||
if check_port $TTY_PORT; then
|
||||
log "OpenCode TTY restarted successfully"
|
||||
return 0
|
||||
else
|
||||
log "ERROR: Failed to restart OpenCode TTY"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart_openchamber() {
|
||||
log "Restarting OpenChamber on port $OPENCHAMBER_PORT..."
|
||||
pkill -f "openchamber" 2>/dev/null || true
|
||||
sleep 2
|
||||
if [ -n "${OPENCHAMBER_UI_PASSWORD:-}" ]; then
|
||||
nohup stdbuf -oL openchamber --port $OPENCHAMBER_PORT --ui-password "$OPENCHAMBER_UI_PASSWORD" >> openchamber.log 2>&1 &
|
||||
else
|
||||
nohup stdbuf -oL openchamber --port $OPENCHAMBER_PORT >> openchamber.log 2>&1 &
|
||||
fi
|
||||
sleep 5
|
||||
|
||||
if check_port $OPENCHAMBER_PORT; then
|
||||
log "OpenChamber restarted successfully"
|
||||
return 0
|
||||
else
|
||||
log "ERROR: Failed to restart OpenChamber"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart_opencode_web() {
|
||||
log "Restarting OpenCode Web on port $OPENCODE_WEB_PORT..."
|
||||
# Note: "opencode web" might be matched by "opencode" so we use full command in pkill if possible or handle order
|
||||
pkill -f "opencode web" 2>/dev/null || true
|
||||
sleep 2
|
||||
if [ -n "${OPENCHAMBER_UI_PASSWORD:-}" ]; then
|
||||
OPENCODE_SERVER_PASSWORD="$OPENCHAMBER_UI_PASSWORD" nohup stdbuf -oL opencode web --port $OPENCODE_WEB_PORT >> opencode_web.log 2>&1 &
|
||||
else
|
||||
nohup stdbuf -oL opencode web --port $OPENCODE_WEB_PORT >> opencode_web.log 2>&1 &
|
||||
fi
|
||||
sleep 5
|
||||
|
||||
if check_port $OPENCODE_WEB_PORT; then
|
||||
log "OpenCode Web restarted successfully"
|
||||
return 0
|
||||
else
|
||||
log "ERROR: Failed to restart OpenCode Web"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
restart_tunnels() {
|
||||
log "Restarting tunnels ($TUNNEL_PROVIDER)..."
|
||||
|
||||
if [ "$TUNNEL_PROVIDER" = "ngrok" ]; then
|
||||
pkill -f "ngrok" 2>/dev/null || true
|
||||
sleep 2
|
||||
# Complex to manage multiple ngrok tunnels without config file.
|
||||
# For now, attempting to restore single tunnel logic or just warn.
|
||||
log "WARNING: Multi-tunnel restart for ngrok not fully supported in this script version."
|
||||
# Attempt to start tunnels again (blindly)
|
||||
nohup ngrok http 127.0.0.1:$OPENCHAMBER_PORT --log=stdout > tunnel_chamber.log 2>&1 &
|
||||
nohup ngrok http 127.0.0.1:$OPENCODE_WEB_PORT --log=stdout > tunnel_web.log 2>&1 &
|
||||
nohup ngrok http 127.0.0.1:$TTY_PORT --log=stdout > tunnel_tty.log 2>&1 &
|
||||
sleep 10
|
||||
else
|
||||
pkill -f "cloudflared" 2>/dev/null || true
|
||||
sleep 2
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:$TTY_PORT > tunnel_tty.log 2>&1 &
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:$OPENCHAMBER_PORT > tunnel_chamber.log 2>&1 &
|
||||
nohup cloudflared tunnel --url http://127.0.0.1:$OPENCODE_WEB_PORT > tunnel_web.log 2>&1 &
|
||||
sleep 15
|
||||
|
||||
# Get new URLs
|
||||
URL_TTY=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_tty.log 2>/dev/null | tail -n 1)
|
||||
URL_CHAMBER=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_chamber.log 2>/dev/null | tail -n 1)
|
||||
URL_WEB=$(grep -o 'https://[-a-z0-9.]*trycloudflare.com' tunnel_web.log 2>/dev/null | tail -n 1)
|
||||
fi
|
||||
|
||||
log "Tunnels restarted."
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo "NEW ACCESS URLS:"
|
||||
echo "OpenCode TTY: $URL_TTY"
|
||||
echo "OpenChamber: $URL_CHAMBER"
|
||||
echo "OpenCode Web: $URL_WEB"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
}
|
||||
|
||||
check_tunnels() {
|
||||
if [ "$TUNNEL_PROVIDER" = "ngrok" ]; then
|
||||
pgrep -f "ngrok" > /dev/null 2>&1
|
||||
else
|
||||
pgrep -f "cloudflared" > /dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Graceful Shutdown
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
shutdown() {
|
||||
log "Initiating graceful shutdown..."
|
||||
|
||||
# Kill all services
|
||||
pkill -f "ttyd" 2>/dev/null || true
|
||||
pkill -f "opencode" 2>/dev/null || true
|
||||
pkill -f "openchamber" 2>/dev/null || true
|
||||
pkill -f "opencode web" 2>/dev/null || true
|
||||
pkill -f "ngrok" 2>/dev/null || true
|
||||
pkill -f "cloudflared" 2>/dev/null || true
|
||||
|
||||
log "All services stopped"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Trap signals for graceful shutdown
|
||||
trap shutdown SIGTERM SIGINT
|
||||
|
||||
# ==============================================================================
|
||||
# Main Monitoring Loop
|
||||
# ==============================================================================
|
||||
|
||||
log "Starting monitoring loop..."
|
||||
echo ""
|
||||
|
||||
# Write initial URLs to GitHub Step Summary (if running in GitHub Actions)
|
||||
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||
{
|
||||
echo "## OpenChamber for Actions"
|
||||
echo ""
|
||||
echo "| Service | URL |"
|
||||
echo "|---------|-----|"
|
||||
echo "| **OpenCode TTY** | $URL_TTY |"
|
||||
echo "| **OpenChamber** | $URL_CHAMBER |"
|
||||
echo "| **OpenCode Web** | $URL_WEB |"
|
||||
echo ""
|
||||
echo "_Timeout: ${TIMEOUT_MINUTES} minutes_"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
# Status display interval (every 5 minutes = 300 seconds)
|
||||
LAST_STATUS_TIME=0
|
||||
STATUS_INTERVAL=300
|
||||
|
||||
while true; do
|
||||
REMAINING=$(get_remaining_time)
|
||||
|
||||
# Check for timeout
|
||||
if [ "$REMAINING" -le 0 ]; then
|
||||
log "Timeout reached. Initiating graceful shutdown..."
|
||||
shutdown
|
||||
fi
|
||||
|
||||
# Periodic status update (every 5 minutes)
|
||||
CURRENT_TIME=$(date +%s)
|
||||
if [ $((CURRENT_TIME - LAST_STATUS_TIME)) -ge $STATUS_INTERVAL ]; then
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
log "Status Update"
|
||||
echo "Time remaining: $(format_time "$REMAINING")"
|
||||
echo "OpenCode TTY: $URL_TTY"
|
||||
echo "OpenChamber: $URL_CHAMBER"
|
||||
echo "OpenCode Web: $URL_WEB"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
LAST_STATUS_TIME=$CURRENT_TIME
|
||||
fi
|
||||
|
||||
# Check OpenCode TTY
|
||||
if ! check_port $TTY_PORT; then
|
||||
log "OpenCode TTY not responding on port $TTY_PORT"
|
||||
restart_opencode_tty
|
||||
fi
|
||||
|
||||
# Check OpenChamber
|
||||
if ! check_port $OPENCHAMBER_PORT; then
|
||||
log "OpenChamber not responding on port $OPENCHAMBER_PORT"
|
||||
restart_openchamber
|
||||
fi
|
||||
|
||||
# Check OpenCode Web
|
||||
if ! check_port $OPENCODE_WEB_PORT; then
|
||||
log "OpenCode Web not responding on port $OPENCODE_WEB_PORT"
|
||||
restart_opencode_web
|
||||
fi
|
||||
|
||||
# Check Tunnel Process (Basic check)
|
||||
if ! check_tunnels; then
|
||||
log "Tunnel processes not running"
|
||||
restart_tunnels
|
||||
fi
|
||||
|
||||
# Sleep before next check
|
||||
sleep 5
|
||||
done
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# OpenCode Configuration Script
|
||||
# ==============================================================================
|
||||
# This script manages OpenCode configuration with intelligent detection of
|
||||
# restored artifacts. If a config file exists from a previous session (restored
|
||||
# via artifact), it will be preserved. Otherwise, a default config is generated.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_DIR="$HOME/.config/opencode"
|
||||
RESTORE_DIR="${RESTORE_DIR:-/tmp/opencode-restore}"
|
||||
|
||||
echo "=== OpenCode Configuration Setup ==="
|
||||
echo "Config directory: $CONFIG_DIR"
|
||||
echo "Restore directory: $RESTORE_DIR"
|
||||
|
||||
# Create config directory if it doesn't exist
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Check for restored configuration (artifact preference)
|
||||
# ------------------------------------------------------------------------------
|
||||
check_restored_config() {
|
||||
local restored_config_json="$RESTORE_DIR/config/opencode.json"
|
||||
local restored_config_yml="$RESTORE_DIR/config/opencode.yml"
|
||||
local restored_config_yaml="$RESTORE_DIR/config/opencode.yaml"
|
||||
|
||||
# Priority: .yml > .yaml > .json from artifacts
|
||||
if [ -f "$restored_config_yml" ]; then
|
||||
echo "Found restored opencode.yml from artifact - using it"
|
||||
cp -v "$restored_config_yml" "$CONFIG_DIR/opencode.yml"
|
||||
return 0
|
||||
elif [ -f "$restored_config_yaml" ]; then
|
||||
echo "Found restored opencode.yaml from artifact - using it"
|
||||
cp -v "$restored_config_yaml" "$CONFIG_DIR/opencode.yaml"
|
||||
return 0
|
||||
elif [ -f "$restored_config_json" ]; then
|
||||
echo "Found restored opencode.json from artifact - using it"
|
||||
cp -v "$restored_config_json" "$CONFIG_DIR/opencode.json"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Check for existing configuration in config directory
|
||||
# ------------------------------------------------------------------------------
|
||||
check_existing_config() {
|
||||
if [ -f "$CONFIG_DIR/opencode.yml" ] || \
|
||||
[ -f "$CONFIG_DIR/opencode.yaml" ] || \
|
||||
[ -f "$CONFIG_DIR/opencode.json" ]; then
|
||||
echo "Existing configuration found in $CONFIG_DIR - keeping it"
|
||||
ls -la "$CONFIG_DIR"/opencode.* 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Generate default configuration
|
||||
# ------------------------------------------------------------------------------
|
||||
generate_default_config() {
|
||||
echo "Generating default OpenCode configuration..."
|
||||
|
||||
cat << 'EOF' > "$CONFIG_DIR/opencode.json"
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-antigravity-auth@beta"],
|
||||
"provider": {
|
||||
"google": {
|
||||
"models": {
|
||||
"antigravity-gemini-3-pro": {
|
||||
"name": "Gemini 3 Pro (Antigravity)",
|
||||
"limit": { "context": 1048576, "output": 65535 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingLevel": "low" },
|
||||
"high": { "thinkingLevel": "high" }
|
||||
}
|
||||
},
|
||||
"antigravity-gemini-3-flash": {
|
||||
"name": "Gemini 3 Flash (Antigravity)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"minimal": { "thinkingLevel": "minimal" },
|
||||
"low": { "thinkingLevel": "low" },
|
||||
"medium": { "thinkingLevel": "medium" },
|
||||
"high": { "thinkingLevel": "high" }
|
||||
}
|
||||
},
|
||||
"antigravity-claude-sonnet-4-5": {
|
||||
"name": "Claude Sonnet 4.5 (no thinking) (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"antigravity-claude-sonnet-4-5-thinking": {
|
||||
"name": "Claude Sonnet 4.5 Thinking (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingConfig": { "thinkingBudget": 8192 } },
|
||||
"max": { "thinkingConfig": { "thinkingBudget": 32768 } }
|
||||
}
|
||||
},
|
||||
"antigravity-claude-opus-4-5-thinking": {
|
||||
"name": "Claude Opus 4.5 Thinking (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingConfig": { "thinkingBudget": 8192 } },
|
||||
"max": { "thinkingConfig": { "thinkingBudget": 32768 } }
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash": {
|
||||
"name": "Gemini 2.5 Flash (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-2.5-pro": {
|
||||
"name": "Gemini 2.5 Pro (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-3-flash-preview": {
|
||||
"name": "Gemini 3 Flash Preview (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-3-pro-preview": {
|
||||
"name": "Gemini 3 Pro Preview (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65535 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Default configuration created at $CONFIG_DIR/opencode.json"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Main Logic
|
||||
# ==============================================================================
|
||||
|
||||
# Step 1: Check if config was restored from artifact (highest priority)
|
||||
if check_restored_config; then
|
||||
echo "Using restored configuration from artifact"
|
||||
# Step 2: Check if config already exists (from previous run or manual setup)
|
||||
elif check_existing_config; then
|
||||
echo "Using existing configuration"
|
||||
# Step 3: No config found - generate default
|
||||
else
|
||||
generate_default_config
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Configuration Summary ==="
|
||||
echo "Active configuration files:"
|
||||
ls -la "$CONFIG_DIR"/*.json "$CONFIG_DIR"/*.yml "$CONFIG_DIR"/*.yaml 2>/dev/null || echo "No config files found"
|
||||
echo ""
|
||||
echo "Configuration setup complete!"
|
||||
@@ -1,182 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# OpenCode Persistence Restore Script
|
||||
# ==============================================================================
|
||||
# This script restores session data from GitHub Actions artifacts.
|
||||
# It handles the restoration of:
|
||||
# - Configuration files (~/.config/opencode/)
|
||||
# - Session data, messages, chats (~/.local/share/opencode/storage/)
|
||||
# - Project snapshots (~/.local/share/opencode/snapshot/)
|
||||
# - Authentication tokens (~/.local/share/opencode/auth.json)
|
||||
#
|
||||
# If the artifact is encrypted (session.enc exists), it will be decrypted
|
||||
# using OPENCODE_SERVER_PASSWORD.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
RESTORE_DIR="${RESTORE_DIR:-/tmp/opencode-restore}"
|
||||
CONFIG_DIR="$HOME/.config/opencode"
|
||||
SHARE_DIR="$HOME/.local/share/opencode"
|
||||
ENCRYPTION_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
||||
|
||||
echo "=== OpenCode Session Restore ==="
|
||||
echo "Restore directory: $RESTORE_DIR"
|
||||
echo "Config directory: $CONFIG_DIR"
|
||||
echo "Share directory: $SHARE_DIR"
|
||||
echo ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Check for encrypted artifact and decrypt if needed
|
||||
# ------------------------------------------------------------------------------
|
||||
if [ -f "$RESTORE_DIR/session.enc" ]; then
|
||||
echo "=== Encrypted Artifact Detected ==="
|
||||
|
||||
if [ -z "$ENCRYPTION_PASSWORD" ]; then
|
||||
echo "ERROR: Encrypted artifact found but OPENCODE_SERVER_PASSWORD is not set."
|
||||
echo "Cannot decrypt session data. Starting fresh."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Decrypting session data..."
|
||||
TEMP_ARCHIVE="/tmp/opencode-session-data.tar.gz"
|
||||
|
||||
if openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 100000 \
|
||||
-in "$RESTORE_DIR/session.enc" \
|
||||
-out "$TEMP_ARCHIVE" \
|
||||
-pass pass:"$ENCRYPTION_PASSWORD" 2>/dev/null; then
|
||||
|
||||
rm -rf "$RESTORE_DIR"
|
||||
mkdir -p "$RESTORE_DIR"
|
||||
tar -xzf "$TEMP_ARCHIVE" -C "$RESTORE_DIR"
|
||||
rm -f "$TEMP_ARCHIVE"
|
||||
echo "Decryption successful."
|
||||
else
|
||||
echo "ERROR: Decryption failed. Password may be incorrect."
|
||||
echo "Starting fresh session."
|
||||
rm -rf "$RESTORE_DIR"
|
||||
rm -f "$TEMP_ARCHIVE"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Debug: Show what we're working with
|
||||
# ------------------------------------------------------------------------------
|
||||
echo "=== RESTORE DEBUG ==="
|
||||
echo "Contents of $RESTORE_DIR:"
|
||||
if [ -d "$RESTORE_DIR" ]; then
|
||||
ls -la "$RESTORE_DIR/" 2>/dev/null || echo "Directory exists but empty"
|
||||
echo ""
|
||||
echo "Full tree of restore directory:"
|
||||
find "$RESTORE_DIR" -type f 2>/dev/null | head -50 || echo "No files found"
|
||||
else
|
||||
echo "Restore directory does not exist - fresh start"
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Create target directories
|
||||
# ------------------------------------------------------------------------------
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
mkdir -p "$SHARE_DIR"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Restore configuration files
|
||||
# ------------------------------------------------------------------------------
|
||||
restore_config() {
|
||||
local src="$RESTORE_DIR/config"
|
||||
|
||||
if [ -d "$src" ] && [ "$(ls -A "$src" 2>/dev/null)" ]; then
|
||||
echo "Restoring configuration files..."
|
||||
|
||||
# Restore all config files except node_modules (will be reinstalled)
|
||||
find "$src" -maxdepth 1 -type f -exec cp -v {} "$CONFIG_DIR/" \; 2>/dev/null || true
|
||||
|
||||
# Count restored files
|
||||
local count=$(find "$src" -maxdepth 1 -type f 2>/dev/null | wc -l)
|
||||
echo "Restored $count configuration file(s)"
|
||||
else
|
||||
echo "No configuration files to restore"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Restore share data (sessions, messages, snapshots, etc.)
|
||||
# ------------------------------------------------------------------------------
|
||||
restore_share() {
|
||||
local src="$RESTORE_DIR/share"
|
||||
|
||||
if [ -d "$src" ] && [ "$(ls -A "$src" 2>/dev/null)" ]; then
|
||||
echo "Restoring share data..."
|
||||
|
||||
# Restore auth.json
|
||||
if [ -f "$src/auth.json" ]; then
|
||||
cp -v "$src/auth.json" "$SHARE_DIR/" 2>/dev/null || true
|
||||
echo "Authentication data restored"
|
||||
fi
|
||||
|
||||
# Restore storage directory (sessions, messages, parts, projects)
|
||||
if [ -d "$src/storage" ]; then
|
||||
mkdir -p "$SHARE_DIR/storage"
|
||||
cp -rv "$src/storage/"* "$SHARE_DIR/storage/" 2>/dev/null || true
|
||||
local storage_count=$(find "$SHARE_DIR/storage" -type f 2>/dev/null | wc -l)
|
||||
echo "Storage data restored: $storage_count file(s)"
|
||||
fi
|
||||
|
||||
# Restore snapshot directory (project snapshots)
|
||||
if [ -d "$src/snapshot" ]; then
|
||||
mkdir -p "$SHARE_DIR/snapshot"
|
||||
cp -rv "$src/snapshot/"* "$SHARE_DIR/snapshot/" 2>/dev/null || true
|
||||
local snapshot_count=$(find "$SHARE_DIR/snapshot" -type f 2>/dev/null | wc -l)
|
||||
echo "Snapshot data restored: $snapshot_count file(s)"
|
||||
fi
|
||||
|
||||
# Restore log directory
|
||||
if [ -d "$src/log" ]; then
|
||||
mkdir -p "$SHARE_DIR/log"
|
||||
cp -rv "$src/log/"* "$SHARE_DIR/log/" 2>/dev/null || true
|
||||
echo "Log data restored"
|
||||
fi
|
||||
else
|
||||
echo "No share data to restore"
|
||||
fi
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Main Restoration Process
|
||||
# ==============================================================================
|
||||
|
||||
echo "=== Starting Restoration ==="
|
||||
|
||||
restore_config
|
||||
echo ""
|
||||
|
||||
restore_share
|
||||
echo ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ------------------------------------------------------------------------------
|
||||
echo "=== Restoration Summary ==="
|
||||
|
||||
echo "Config directory contents:"
|
||||
ls -la "$CONFIG_DIR/" 2>/dev/null | head -10 || echo "Empty"
|
||||
echo ""
|
||||
|
||||
echo "Share directory structure:"
|
||||
if [ -d "$SHARE_DIR" ]; then
|
||||
du -sh "$SHARE_DIR"/* 2>/dev/null || echo "Empty"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Count total restored items
|
||||
config_files=$(find "$CONFIG_DIR" -maxdepth 1 -type f 2>/dev/null | wc -l)
|
||||
share_files=$(find "$SHARE_DIR" -type f 2>/dev/null | wc -l)
|
||||
|
||||
echo "Total restored: $config_files config files, $share_files share files"
|
||||
echo ""
|
||||
echo "Session restoration complete!"
|
||||
@@ -1,233 +0,0 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================
|
||||
# OpenCode Persistence Save Script
|
||||
# ==============================================================================
|
||||
# This script prepares all session data for artifact upload.
|
||||
# It saves:
|
||||
# - Configuration files (~/.config/opencode/)
|
||||
# - Session data, messages, chats (~/.local/share/opencode/storage/)
|
||||
# - Project snapshots (~/.local/share/opencode/snapshot/)
|
||||
# - Authentication tokens (~/.local/share/opencode/auth.json)
|
||||
# - Logs (~/.local/share/opencode/log/)
|
||||
#
|
||||
# Excludes (to keep artifact size manageable):
|
||||
# - node_modules/ directories
|
||||
# - bin/ directory (will be reinstalled)
|
||||
# - tool-output/ (temporary tool outputs)
|
||||
#
|
||||
# If OPENCODE_SERVER_PASSWORD is set, the artifact will be encrypted.
|
||||
# ==============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SAVE_DIR="${SAVE_DIR:-/tmp/opencode-save}"
|
||||
CONFIG_DIR="$HOME/.config/opencode"
|
||||
SHARE_DIR="$HOME/.local/share/opencode"
|
||||
ENCRYPTION_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}"
|
||||
|
||||
echo "=== OpenCode Session Save ==="
|
||||
echo "Save directory: $SAVE_DIR"
|
||||
echo "Config directory: $CONFIG_DIR"
|
||||
echo "Share directory: $SHARE_DIR"
|
||||
if [ -n "$ENCRYPTION_PASSWORD" ]; then
|
||||
echo "Encryption: ENABLED"
|
||||
else
|
||||
echo "Encryption: DISABLED (set OPENCODE_SERVER_PASSWORD to enable)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Clean up and create save directory
|
||||
# ------------------------------------------------------------------------------
|
||||
rm -rf "$SAVE_DIR"
|
||||
mkdir -p "$SAVE_DIR/config"
|
||||
mkdir -p "$SAVE_DIR/share"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Save configuration files
|
||||
# ------------------------------------------------------------------------------
|
||||
save_config() {
|
||||
echo "=== Saving Configuration Files ==="
|
||||
|
||||
if [ -d "$CONFIG_DIR" ] && [ "$(ls -A "$CONFIG_DIR" 2>/dev/null)" ]; then
|
||||
# Save all config files (excluding node_modules)
|
||||
for item in "$CONFIG_DIR"/*; do
|
||||
if [ -e "$item" ]; then
|
||||
local basename=$(basename "$item")
|
||||
|
||||
# Skip node_modules and bun.lock (will be reinstalled)
|
||||
if [ "$basename" = "node_modules" ] || [ "$basename" = "bun.lock" ]; then
|
||||
echo "Skipping: $basename (will be reinstalled)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Copy file or directory
|
||||
if [ -f "$item" ]; then
|
||||
cp -v "$item" "$SAVE_DIR/config/" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
local count=$(find "$SAVE_DIR/config" -type f 2>/dev/null | wc -l)
|
||||
echo "Config files saved: $count"
|
||||
else
|
||||
echo "No config data to save"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Save share data (sessions, messages, snapshots, auth)
|
||||
# ------------------------------------------------------------------------------
|
||||
save_share() {
|
||||
echo ""
|
||||
echo "=== Saving Share Data ==="
|
||||
|
||||
if [ ! -d "$SHARE_DIR" ]; then
|
||||
echo "No share directory found"
|
||||
return
|
||||
fi
|
||||
|
||||
# Save auth.json (authentication tokens - critical!)
|
||||
if [ -f "$SHARE_DIR/auth.json" ]; then
|
||||
cp -v "$SHARE_DIR/auth.json" "$SAVE_DIR/share/" 2>/dev/null || true
|
||||
echo "Auth data saved"
|
||||
fi
|
||||
|
||||
# Save storage directory (sessions, messages, parts, projects)
|
||||
if [ -d "$SHARE_DIR/storage" ] && [ "$(ls -A "$SHARE_DIR/storage" 2>/dev/null)" ]; then
|
||||
echo "Saving storage data (sessions, messages, chats)..."
|
||||
mkdir -p "$SAVE_DIR/share/storage"
|
||||
|
||||
# Copy all storage subdirectories
|
||||
for subdir in message migration part project session session_diff; do
|
||||
if [ -d "$SHARE_DIR/storage/$subdir" ]; then
|
||||
cp -r "$SHARE_DIR/storage/$subdir" "$SAVE_DIR/share/storage/" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
local storage_count=$(find "$SAVE_DIR/share/storage" -type f 2>/dev/null | wc -l)
|
||||
echo "Storage files saved: $storage_count"
|
||||
fi
|
||||
|
||||
# Save snapshot directory (project snapshots for undo/rollback)
|
||||
if [ -d "$SHARE_DIR/snapshot" ] && [ "$(ls -A "$SHARE_DIR/snapshot" 2>/dev/null)" ]; then
|
||||
echo "Saving snapshot data..."
|
||||
mkdir -p "$SAVE_DIR/share/snapshot"
|
||||
cp -r "$SHARE_DIR/snapshot/"* "$SAVE_DIR/share/snapshot/" 2>/dev/null || true
|
||||
|
||||
local snapshot_count=$(find "$SAVE_DIR/share/snapshot" -type f 2>/dev/null | wc -l)
|
||||
echo "Snapshot files saved: $snapshot_count"
|
||||
fi
|
||||
|
||||
# Save log directory (useful for debugging)
|
||||
if [ -d "$SHARE_DIR/log" ] && [ "$(ls -A "$SHARE_DIR/log" 2>/dev/null)" ]; then
|
||||
echo "Saving log data..."
|
||||
mkdir -p "$SAVE_DIR/share/log"
|
||||
cp -r "$SHARE_DIR/log/"* "$SAVE_DIR/share/log/" 2>/dev/null || true
|
||||
echo "Log files saved"
|
||||
fi
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Create manifest with metadata
|
||||
# ------------------------------------------------------------------------------
|
||||
create_manifest() {
|
||||
echo ""
|
||||
echo "=== Creating Manifest ==="
|
||||
|
||||
local manifest="$SAVE_DIR/manifest.json"
|
||||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
local config_count=$(find "$SAVE_DIR/config" -type f 2>/dev/null | wc -l)
|
||||
local share_count=$(find "$SAVE_DIR/share" -type f 2>/dev/null | wc -l)
|
||||
local total_size=$(du -sh "$SAVE_DIR" 2>/dev/null | cut -f1)
|
||||
|
||||
cat << EOF > "$manifest"
|
||||
{
|
||||
"version": "2.0",
|
||||
"timestamp": "$timestamp",
|
||||
"hostname": "$(hostname)",
|
||||
"stats": {
|
||||
"config_files": $config_count,
|
||||
"share_files": $share_count,
|
||||
"total_size": "$total_size"
|
||||
},
|
||||
"contents": {
|
||||
"config": $([ -d "$SAVE_DIR/config" ] && ls "$SAVE_DIR/config" 2>/dev/null | jq -R -s -c 'split("\n") | map(select(length > 0))' || echo '[]'),
|
||||
"share": $([ -d "$SAVE_DIR/share" ] && ls "$SAVE_DIR/share" 2>/dev/null | jq -R -s -c 'split("\n") | map(select(length > 0))' || echo '[]')
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Manifest created: $manifest"
|
||||
cat "$manifest"
|
||||
}
|
||||
|
||||
# ==============================================================================
|
||||
# Main Save Process
|
||||
# ==============================================================================
|
||||
|
||||
save_config
|
||||
save_share
|
||||
create_manifest
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Final Summary
|
||||
# ------------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "=== Save Summary ==="
|
||||
|
||||
# Calculate totals
|
||||
total_files=$(find "$SAVE_DIR" -type f 2>/dev/null | wc -l)
|
||||
total_size=$(du -sh "$SAVE_DIR" 2>/dev/null | cut -f1)
|
||||
|
||||
echo "Save directory structure:"
|
||||
if command -v tree &> /dev/null; then
|
||||
tree -L 2 "$SAVE_DIR" 2>/dev/null || ls -laR "$SAVE_DIR" | head -50
|
||||
else
|
||||
ls -laR "$SAVE_DIR" | head -50
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Total files to upload: $total_files"
|
||||
echo "Total size: $total_size"
|
||||
echo ""
|
||||
|
||||
# Verify we have something to save
|
||||
if [ "$total_files" -gt 1 ]; then
|
||||
echo "Session data prepared successfully for artifact upload!"
|
||||
else
|
||||
echo "Warning: Minimal data to save. Creating placeholder..."
|
||||
echo "{\"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"status\": \"empty\"}" > "$SAVE_DIR/placeholder.json"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Optional Encryption (if OPENCODE_SERVER_PASSWORD is set)
|
||||
# ------------------------------------------------------------------------------
|
||||
if [ -n "$ENCRYPTION_PASSWORD" ]; then
|
||||
echo ""
|
||||
echo "=== Encrypting Session Data ==="
|
||||
|
||||
TEMP_ARCHIVE="/tmp/opencode-session-data.tar.gz"
|
||||
ENCRYPTED_FILE="$SAVE_DIR.enc"
|
||||
|
||||
tar -czf "$TEMP_ARCHIVE" -C "$SAVE_DIR" .
|
||||
|
||||
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 \
|
||||
-in "$TEMP_ARCHIVE" \
|
||||
-out "$ENCRYPTED_FILE" \
|
||||
-pass pass:"$ENCRYPTION_PASSWORD"
|
||||
|
||||
rm -f "$TEMP_ARCHIVE"
|
||||
rm -rf "$SAVE_DIR"
|
||||
mkdir -p "$SAVE_DIR"
|
||||
mv "$ENCRYPTED_FILE" "$SAVE_DIR/session.enc"
|
||||
|
||||
echo '{"encrypted": true, "algorithm": "aes-256-cbc", "kdf": "pbkdf2", "iterations": 100000}' > "$SAVE_DIR/manifest.json"
|
||||
|
||||
echo "Encryption complete. Artifact is password-protected."
|
||||
echo "Encrypted file: $SAVE_DIR/session.enc"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Save location: $SAVE_DIR"
|
||||
echo "Ready for artifact upload!"
|
||||
Reference in New Issue
Block a user