Spaces:
Paused
Paused
File size: 6,545 Bytes
85a16bc 8501103 85a16bc 8501103 85a16bc 8501103 85a16bc 8501103 85a16bc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | #!/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.)"
|