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.)"