|
6 | 6 | # curl -fsSL https://.../bootstrap-server.sh | sudo bash |
7 | 7 | # Or copy and run locally: |
8 | 8 | # sudo ./scripts/self-host/bootstrap-server.sh |
| 9 | +# |
| 10 | +# Handles Debian/Ubuntu (apt) and Amazon Linux 2023 / RHEL family (dnf). The two |
| 11 | +# differ in ways that are easy to miss and fatal to a first install: |
| 12 | +# |
| 13 | +# * get.docker.com refuses to run on Amazon Linux ("unsupported distribution |
| 14 | +# amzn"), so Docker there comes from the distro package instead. |
| 15 | +# * The AL2023 `docker` package ships NEITHER the Compose v2 plugin NOR buildx. |
| 16 | +# Compose delegates every build to buildx and refuses to start without |
| 17 | +# >= 0.17.0, so `docker compose up --build` — the entire distribution model |
| 18 | +# for this project — fails on a box that looks correctly set up. |
| 19 | +# * The unprivileged account is `ubuntu` on Ubuntu images and `ec2-user` on |
| 20 | +# Amazon Linux ones. |
| 21 | +# |
| 22 | +# Overrides: DEEPSQL_DEPLOY_DIR, DEEPSQL_DEPLOY_USER. |
9 | 23 |
|
10 | 24 | set -euo pipefail |
11 | 25 |
|
12 | 26 | DEPLOY_DIR="${DEEPSQL_DEPLOY_DIR:-/opt/deepsql}" |
13 | | -DEPLOY_USER="${DEEPSQL_DEPLOY_USER:-ubuntu}" |
| 27 | +CLI_PLUGIN_DIR="/usr/local/lib/docker/cli-plugins" |
14 | 28 |
|
15 | 29 | echo "=== DeepSQL server bootstrap ===" |
16 | 30 |
|
17 | | -# ── Install Docker if missing ───────────────────────────────────────────────── |
18 | | -if ! command -v docker >/dev/null 2>&1; then |
19 | | - echo "Installing Docker..." |
20 | | - curl -fsSL https://get.docker.com | sh |
21 | | - usermod -aG docker "$DEPLOY_USER" |
22 | | - echo "Docker installed. Note: log out and back in as $DEPLOY_USER for group to take effect." |
| 31 | +if [[ "${EUID}" -ne 0 ]]; then |
| 32 | + echo "Error: run this as root or with sudo." >&2 |
| 33 | + exit 1 |
| 34 | +fi |
| 35 | + |
| 36 | +# ── Detect the platform ─────────────────────────────────────────────────────── |
| 37 | +if command -v apt-get >/dev/null 2>&1; then |
| 38 | + PKG=apt |
| 39 | +elif command -v dnf >/dev/null 2>&1; then |
| 40 | + PKG=dnf |
| 41 | +elif command -v yum >/dev/null 2>&1; then |
| 42 | + PKG=yum |
23 | 43 | else |
| 44 | + echo "Error: no supported package manager found (apt-get, dnf, yum)." >&2 |
| 45 | + exit 1 |
| 46 | +fi |
| 47 | + |
| 48 | +# Prefer an explicit override, then the account that invoked sudo, then the |
| 49 | +# conventional image default. Guessing wrong here silently creates a deploy |
| 50 | +# directory nobody can write to. |
| 51 | +if [[ -n "${DEEPSQL_DEPLOY_USER:-}" ]]; then |
| 52 | + DEPLOY_USER="$DEEPSQL_DEPLOY_USER" |
| 53 | +elif [[ -n "${SUDO_USER:-}" && "$SUDO_USER" != "root" ]]; then |
| 54 | + DEPLOY_USER="$SUDO_USER" |
| 55 | +else |
| 56 | + DEPLOY_USER="" |
| 57 | + for candidate in ubuntu ec2-user debian admin rocky fedora cloud-user; do |
| 58 | + if id -u "$candidate" >/dev/null 2>&1; then DEPLOY_USER="$candidate"; break; fi |
| 59 | + done |
| 60 | +fi |
| 61 | +if [[ -z "$DEPLOY_USER" ]]; then |
| 62 | + echo "Error: could not determine the deploy user. Set DEEPSQL_DEPLOY_USER." >&2 |
| 63 | + exit 1 |
| 64 | +fi |
| 65 | + |
| 66 | +. /etc/os-release 2>/dev/null || true |
| 67 | +echo "Platform: ${PRETTY_NAME:-unknown} (package manager: $PKG, deploy user: $DEPLOY_USER)" |
| 68 | + |
| 69 | +pkg_install() { |
| 70 | + case "$PKG" in |
| 71 | + apt) DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "$@" >/dev/null ;; |
| 72 | + dnf) dnf install -y -q "$@" >/dev/null ;; |
| 73 | + yum) yum install -y -q "$@" >/dev/null ;; |
| 74 | + esac |
| 75 | +} |
| 76 | + |
| 77 | +# ── Base tools ──────────────────────────────────────────────────────────────── |
| 78 | +# install.sh needs curl for the health probes and the bootstrap call, and openssl to |
| 79 | +# generate the JWT secret and the vault encryption key. git is here because the very |
| 80 | +# first documented step is `git clone` — and a vanilla Amazon Linux 2023 image does not |
| 81 | +# ship it, so the README's step 2 fails before DeepSQL is involved at all. |
| 82 | +[[ "$PKG" == apt ]] && apt-get update -qq >/dev/null |
| 83 | +for tool in curl openssl tar git; do |
| 84 | + if ! command -v "$tool" >/dev/null 2>&1; then |
| 85 | + echo "Installing $tool..." |
| 86 | + pkg_install "$tool" |
| 87 | + fi |
| 88 | +done |
| 89 | + |
| 90 | +# ── Docker engine ───────────────────────────────────────────────────────────── |
| 91 | +if command -v docker >/dev/null 2>&1; then |
24 | 92 | echo "Docker already installed: $(docker --version)" |
| 93 | +else |
| 94 | + echo "Installing Docker..." |
| 95 | + case "$PKG" in |
| 96 | + apt) |
| 97 | + # The convenience script pulls docker-ce, which bundles the compose and |
| 98 | + # buildx plugins, so those checks below become no-ops on Debian/Ubuntu. |
| 99 | + curl -fsSL https://get.docker.com | sh |
| 100 | + ;; |
| 101 | + dnf|yum) |
| 102 | + pkg_install docker |
| 103 | + ;; |
| 104 | + esac |
25 | 105 | fi |
26 | 106 |
|
27 | | -# Ensure docker compose v2 plugin is available |
28 | | -if ! docker compose version >/dev/null 2>&1; then |
29 | | - echo "Installing docker compose plugin..." |
30 | | - apt-get install -y docker-compose-plugin |
| 107 | +systemctl enable --now docker >/dev/null 2>&1 || true |
| 108 | +if ! docker info >/dev/null 2>&1; then |
| 109 | + echo "Error: the Docker daemon is not running after install." >&2 |
| 110 | + exit 1 |
31 | 111 | fi |
32 | 112 |
|
33 | | -# ── Install curl if missing ─────────────────────────────────────────────────── |
34 | | -if ! command -v curl >/dev/null 2>&1; then |
35 | | - apt-get update && apt-get install -y curl |
| 113 | +usermod -aG docker "$DEPLOY_USER" |
| 114 | + |
| 115 | +# ── Compose v2 and buildx ───────────────────────────────────────────────────── |
| 116 | +install_cli_plugin() { |
| 117 | + local name="$1" url="$2" |
| 118 | + # /usr/local/lib takes precedence over the distro's /usr/libexec, so this also |
| 119 | + # shadows a too-old plugin shipped by the package manager. |
| 120 | + mkdir -p "$CLI_PLUGIN_DIR" |
| 121 | + curl -fsSL "$url" -o "$CLI_PLUGIN_DIR/docker-$name" |
| 122 | + chmod +x "$CLI_PLUGIN_DIR/docker-$name" |
| 123 | +} |
| 124 | + |
| 125 | +# Presence is not the question — the version is. The Amazon Linux 2023 `docker` |
| 126 | +# package bundles buildx 0.12.1, and Compose refuses to build with anything below |
| 127 | +# 0.17.0. A plain `command -v` style check passes there and then fails at |
| 128 | +# `docker compose up --build`, which is the least useful place to find out. |
| 129 | +version_ge() { |
| 130 | + [[ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -1)" == "$2" ]] |
| 131 | +} |
| 132 | +# `|| true` is load-bearing. This script runs under `set -euo pipefail`, and on a host |
| 133 | +# with no compose plugin `docker compose version` exits non-zero — as does the grep when |
| 134 | +# there is nothing to match. Without the guard that failing pipeline propagates out of |
| 135 | +# the command substitution and kills the script at the very check whose job is to notice |
| 136 | +# the plugin is missing, which is the one case it has to survive. |
| 137 | +plugin_version() { |
| 138 | + docker "$1" version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true |
| 139 | +} |
| 140 | + |
| 141 | +COMPOSE_MIN="2.0.0" |
| 142 | +BUILDX_MIN="0.17.0" |
| 143 | + |
| 144 | +compose_have="$(plugin_version compose)" |
| 145 | +if [[ -n "$compose_have" ]] && version_ge "$compose_have" "$COMPOSE_MIN"; then |
| 146 | + echo "Compose already available: $compose_have" |
| 147 | +else |
| 148 | + echo "Installing Docker Compose v2 plugin (have: ${compose_have:-none}, need >= $COMPOSE_MIN)..." |
| 149 | + if [[ "$PKG" == apt ]]; then |
| 150 | + pkg_install docker-compose-plugin |
| 151 | + else |
| 152 | + install_cli_plugin compose \ |
| 153 | + "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" |
| 154 | + fi |
36 | 155 | fi |
37 | 156 |
|
38 | | -# ── Create deploy directory ─────────────────────────────────────────────────── |
| 157 | +buildx_have="$(plugin_version buildx)" |
| 158 | +if [[ -n "$buildx_have" ]] && version_ge "$buildx_have" "$BUILDX_MIN"; then |
| 159 | + echo "buildx already available: $buildx_have" |
| 160 | +else |
| 161 | + echo "Installing Docker buildx plugin (have: ${buildx_have:-none}, need >= $BUILDX_MIN)..." |
| 162 | + if [[ "$PKG" == apt ]]; then |
| 163 | + pkg_install docker-buildx-plugin |
| 164 | + else |
| 165 | + # The buildx release assets embed the version in the filename, so there is no |
| 166 | + # /latest/download shortcut as there is for compose — resolve the real URL. |
| 167 | + arch="$(uname -m)" |
| 168 | + [[ "$arch" == "x86_64" ]] && arch="amd64" |
| 169 | + [[ "$arch" == "aarch64" ]] && arch="arm64" |
| 170 | + bx_url="$(curl -fsSL https://api.github.com/repos/docker/buildx/releases/latest \ |
| 171 | + | grep -o "\"browser_download_url\": *\"[^\"]*linux-${arch}\"" | head -1 | cut -d'"' -f4)" |
| 172 | + if [[ -z "$bx_url" ]]; then |
| 173 | + echo "Error: could not resolve a buildx release for linux-${arch}." >&2 |
| 174 | + exit 1 |
| 175 | + fi |
| 176 | + install_cli_plugin buildx "$bx_url" |
| 177 | + fi |
| 178 | +fi |
| 179 | + |
| 180 | +# ── Verify before declaring success ─────────────────────────────────────────── |
| 181 | +# Checked explicitly because each of these failing produces an error at |
| 182 | +# `docker compose up --build` time that names neither the missing plugin nor |
| 183 | +# this script. |
| 184 | +fail=0 |
| 185 | +compose_have="$(plugin_version compose)" |
| 186 | +buildx_have="$(plugin_version buildx)" |
| 187 | +if [[ -z "$compose_have" ]] || ! version_ge "$compose_have" "$COMPOSE_MIN"; then |
| 188 | + echo "FAIL: docker compose is ${compose_have:-unavailable}, need >= $COMPOSE_MIN" >&2; fail=1 |
| 189 | +fi |
| 190 | +if [[ -z "$buildx_have" ]] || ! version_ge "$buildx_have" "$BUILDX_MIN"; then |
| 191 | + echo "FAIL: docker buildx is ${buildx_have:-unavailable}, need >= $BUILDX_MIN" >&2; fail=1 |
| 192 | +fi |
| 193 | +for t in openssl curl git; do |
| 194 | + command -v "$t" >/dev/null 2>&1 || { echo "FAIL: $t missing" >&2; fail=1; } |
| 195 | +done |
| 196 | +[[ "$fail" -eq 0 ]] || exit 1 |
| 197 | + |
| 198 | +# ── Deploy directory ────────────────────────────────────────────────────────── |
39 | 199 | mkdir -p "$DEPLOY_DIR" |
40 | 200 | chown "$DEPLOY_USER:$DEPLOY_USER" "$DEPLOY_DIR" |
41 | 201 | echo "Deploy directory: $DEPLOY_DIR" |
|
56 | 216 |
|
57 | 217 | echo |
58 | 218 | echo "=== Bootstrap complete ===" |
| 219 | +echo " docker : $(docker --version)" |
| 220 | +echo " compose : $(docker compose version --short 2>/dev/null)" |
| 221 | +echo " buildx : $(docker buildx version 2>/dev/null | awk '{print $2}')" |
| 222 | +echo |
| 223 | +echo "Log out and back in as $DEPLOY_USER before continuing, so the docker group applies." |
| 224 | +echo |
59 | 225 | echo "Next steps:" |
60 | | -echo " 1. Put a checkout of the DeepSQL source in $DEPLOY_DIR (git clone), if it is not there already." |
61 | | -echo " 2. cd $DEPLOY_DIR && cp .env.example .env (skip if .env was created above)" |
62 | | -echo " 3. Edit .env — at minimum DEEPSQL_CHAT_PROVIDER, DEEPSQL_CHAT_API_KEY, DEEPSQL_CHAT_ENDPOINT, DEEPSQL_CHAT_MODEL." |
| 226 | +echo " 1. Put a checkout of the DeepSQL source in $DEPLOY_DIR (git clone), if not already there." |
| 227 | +echo " 2. cd $DEPLOY_DIR && cp .env.example .env (skip if .env was created above)" |
| 228 | +echo " 3. Edit .env — at minimum DEEPSQL_CHAT_PROVIDER, DEEPSQL_CHAT_API_KEY," |
| 229 | +echo " DEEPSQL_CHAT_ENDPOINT, DEEPSQL_CHAT_MODEL." |
63 | 230 | echo " 4. Run: ./scripts/self-host/install.sh" |
64 | | -echo " (or: docker compose up -d --build — the first build takes several minutes)" |
|
0 commit comments