rosdiff / deploy /oracle /setup.sh
Chandrakiran-08
Deploy to HF Spaces
8501103
Raw History Blame Contribute Delete
6.55 kB
#!/usr/bin/env bash
# RosDiff on an Oracle Cloud "Always Free" Ubuntu machine (Ampere A1 / ARM, or x86). See deploy/oracle/README.md.
#
# Run it from the RosDiff folder on the machine: bash deploy/oracle/setup.sh
# It is safe to run again (after copying an updated RosDiff over): it only does what is still missing, then
# restarts the service.
#
# What it sets up:
# - system packages, Python venv with RosDiff's requirements, Node.js + OpenCode (free models)
# - the documentation search index (python -m rag.ingest), built once
# - a systemd service that keeps RosDiff running and restarts it after crashes and reboots
# - Caddy in front of it with a free HTTPS certificate: https://<public-ip-with-dashes>.sslip.io
# (or your own domain: DOMAIN=rosdiff.example.com bash deploy/oracle/setup.sh)
# - the machine's firewall opened for ports 80/443 (Oracle's Ubuntu images block them by default)
# - a daily backup of the database (testers' work, budgets, feedback), 14 days kept
set -euo pipefail
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
RUN_USER="$(id -un)"
cd "$APP_DIR"
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m!! %s\033[0m\n' "$*"; }
[[ $EUID -eq 0 ]] && { echo "Run as your user, not root (the script uses sudo where needed)"; exit 1; }
[[ -f api/main.py ]] || { echo "Run this from the RosDiff folder (api/main.py not found)"; exit 1; }
[[ -f .env ]] || { echo "Copy your .env into $APP_DIR first (see deploy/oracle/README.md, step 5)"; exit 1; }
[[ -f testers.yaml ]] || warn "testers.yaml not found: nobody can sign in until you add testers (python -m api.keys add <name>)"
say "System packages"
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
python3 python3-venv python3-pip python3-dev build-essential git curl sqlite3 nodejs npm iptables-persistent libgl1 \
debian-keyring debian-archive-keyring apt-transport-https gnupg >/dev/null
if ! command -v caddy >/dev/null; then
if ! sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq caddy >/dev/null 2>&1; then
# not in this Ubuntu release's archive: use Caddy's official repository
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key |
sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt |
sudo tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null
sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq caddy >/dev/null
fi
fi
say "Python environment"
[[ -d .venv ]] || python3 -m venv .venv
.venv/bin/pip install -q --upgrade pip
.venv/bin/pip install -q -r requirements.txt
say "OpenCode (free prompt enhancement and free debugging)"
if ! command -v opencode >/dev/null; then
sudo npm install -g --silent opencode-ai || warn "OpenCode did not install; RosDiff still works, on paid models only"
fi
command -v opencode >/dev/null && opencode --version || true
say "Documentation search index"
if [[ ! -d data/chroma || ! -f data/ros2_interfaces.json ]]; then
set -a; source .env; set +a
.venv/bin/python -m rag.ingest # clones MuJoCo/ROS 2 docs and the robot models; a few minutes
else
echo "already built (delete data/chroma to rebuild)"
fi
say "Public address"
if [[ -z "${DOMAIN:-}" ]]; then
IP="$(curl -fsS --max-time 10 https://api.ipify.org || curl -fsS --max-time 10 https://ifconfig.me)"
DOMAIN="${IP//./-}.sslip.io"
fi
URL="https://$DOMAIN"
echo "$URL"
# the app puts this address in report links
if grep -q '^PUBLIC_BASE_URL=' .env; then
existing="$(grep '^PUBLIC_BASE_URL=' .env | head -1 | cut -d= -f2-)"
if [[ -z "$existing" || "$existing" == *sslip.io* ]]; then
sed -i "s|^PUBLIC_BASE_URL=.*|PUBLIC_BASE_URL=$URL|" .env
fi
else
printf '\nPUBLIC_BASE_URL=%s\n' "$URL" >> .env
fi
grep -q '^REPORT_SECRET=..' .env || {
sed -i '/^REPORT_SECRET=/d' .env
printf 'REPORT_SECRET=%s\n' "$(openssl rand -hex 32)" >> .env
}
chmod 600 .env
say "RosDiff service"
sudo tee /etc/systemd/system/rosdiff.service >/dev/null <<EOF
[Unit]
Description=RosDiff API and web app
After=network-online.target
Wants=network-online.target
[Service]
User=$RUN_USER
WorkingDirectory=$APP_DIR
# .env is read by bash (it has comments after values, which systemd's EnvironmentFile would keep)
ExecStart=/bin/bash -c 'set -a; source .env; set +a; exec .venv/bin/uvicorn api.main:app --host 127.0.0.1 --port 8000 --workers 1 --timeout-keep-alive 75'
Restart=always
RestartSec=5
Environment=PATH=$APP_DIR/.venv/bin:/usr/local/bin:/usr/bin:/bin
Environment=HOME=$HOME
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now rosdiff >/dev/null
sudo systemctl restart rosdiff
say "HTTPS (Caddy)"
sudo tee /etc/caddy/Caddyfile >/dev/null <<EOF
$DOMAIN {
encode gzip
reverse_proxy 127.0.0.1:8000
}
EOF
sudo systemctl enable --now caddy >/dev/null
sudo systemctl reload caddy || sudo systemctl restart caddy
say "Firewall: allow 80 and 443"
for port in 80 443; do
if ! sudo iptables -C INPUT -p tcp --dport "$port" -m state --state NEW -j ACCEPT 2>/dev/null; then
# insert above Oracle's catch-all REJECT rule
pos="$(sudo iptables -L INPUT --line-numbers -n | awk '/REJECT/ {print $1; exit}')"
sudo iptables -I INPUT "${pos:-1}" -p tcp --dport "$port" -m state --state NEW -j ACCEPT
fi
done
sudo netfilter-persistent save >/dev/null
say "Daily database backup"
mkdir -p "$HOME/backups"
DB="$APP_DIR/data/generations.sqlite3"
CRON="15 3 * * * [ -f $DB ] && sqlite3 $DB \".backup '$HOME/backups/generations-\$(date +\\%F).sqlite3'\" && find $HOME/backups -name 'generations-*' -mtime +14 -delete"
( { crontab -l 2>/dev/null || true; } | { grep -v 'generations-' || true; } ; echo "$CRON" ) | crontab -
say "Checking"
for i in $(seq 1 30); do
curl -fsS localhost:8000/health >/dev/null 2>&1 && break
sleep 2
done
if curl -fsS localhost:8000/health >/dev/null 2>&1; then
curl -fsS localhost:8000/health | .venv/bin/python -c 'import json,sys; h=json.load(sys.stdin); print(json.dumps({k: h.get(k) for k in ("status","llm_configured","runs_enabled","prompt_enhance","index")}, indent=1))'
else
warn "RosDiff is not answering yet; see: journalctl -u rosdiff -n 50"
fi
echo
echo "Done. Testers open: $URL"
echo "(The HTTPS certificate can take a minute the first time. If the page doesn't load, check step 3 of the guide:"
echo " ports 80 and 443 must be open in the Oracle security list.)"