| 1 | #!/bin/bash |
| 2 | # Sets up the Grove hub on a fresh DigitalOcean droplet. |
| 3 | # The hub is a full Grove instance: Mononoke + Bridge + API + Hub API + Web + Registry. |
| 4 | # |
| 5 | # Usage: bash setup.sh <domain> |
| 6 | # e.g. bash setup.sh grove.host |
| 7 | |
| 8 | set -euo pipefail |
| 9 | |
| 10 | DOMAIN="${1:?Usage: setup.sh <domain>}" |
| 11 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 12 | GROVE_DIR="/opt/grove" |
| 13 | |
| 14 | mkdir -p "$GROVE_DIR" |
| 15 | mkdir -p "$GROVE_DIR/data/mononoke" |
| 16 | mkdir -p "$GROVE_DIR/data/api" |
| 17 | |
| 18 | # ── Copy configs ────────────────────────────────────────────────── |
| 19 | |
| 20 | cp "$SCRIPT_DIR/Caddyfile" "$GROVE_DIR/" |
| 21 | cp "$SCRIPT_DIR/docker-compose.yml" "$GROVE_DIR/" |
| 22 | |
| 23 | # ── Generate JWT secret and write .env ──────────────────────────── |
| 24 | |
| 25 | JWT_SECRET=$(openssl rand -base64 32) |
| 26 | cat > "$GROVE_DIR/.env" <<EOF |
| 27 | DOMAIN=$DOMAIN |
| 28 | JWT_SECRET=$JWT_SECRET |
| 29 | EOF |
| 30 | |
| 31 | # ── Generate TLS certificates for Mononoke ───────────────────── |
| 32 | |
| 33 | GROVE_TLS_DIR=/data/grove/tls "$SCRIPT_DIR/../scripts/generate-certs.sh" "$DOMAIN" |
| 34 | |
| 35 | # ── Write Mononoke config ──────────────────────────────────────── |
| 36 | |
| 37 | mkdir -p "$GROVE_DIR/config/mononoke/common" |
| 38 | mkdir -p "$GROVE_DIR/config/mononoke/repo_definitions/grove" |
| 39 | mkdir -p "$GROVE_DIR/config/mononoke/repos/grove" |
| 40 | |
| 41 | cat > "$GROVE_DIR/config/mononoke/common/common.toml" <<'TOML' |
| 42 | enable_http_control_api = true |
| 43 | |
| 44 | [internal_identity] |
| 45 | identity_type = "SERVICE" |
| 46 | identity_data = "grove" |
| 47 | TOML |
| 48 | |
| 49 | cat > "$GROVE_DIR/config/mononoke/common/storage.toml" <<'TOML' |
| 50 | [default] |
| 51 | |
| 52 | [default.metadata] |
| 53 | [default.metadata.local] |
| 54 | local_db_path = "/data/mononoke/metadata" |
| 55 | |
| 56 | [default.blobstore] |
| 57 | [default.blobstore.blob_files] |
| 58 | path = "/data/mononoke/blobs" |
| 59 | |
| 60 | [default.mutable_blobstore] |
| 61 | [default.mutable_blobstore.blob_files] |
| 62 | path = "/data/mononoke/mutable_blobs" |
| 63 | TOML |
| 64 | |
| 65 | cat > "$GROVE_DIR/config/mononoke/repo_definitions/grove/server.toml" <<'TOML' |
| 66 | repo_id = 0 |
| 67 | repo_name = "grove" |
| 68 | repo_config = "grove" |
| 69 | enabled = true |
| 70 | hipster_acl = "default" |
| 71 | TOML |
| 72 | |
| 73 | cat > "$GROVE_DIR/config/mononoke/repos/grove/server.toml" <<'TOML' |
| 74 | storage_config = "default" |
| 75 | |
| 76 | [hook_manager_params] |
| 77 | disable_acl_checker = true |
| 78 | |
| 79 | [push] |
| 80 | pure_push_allowed = true |
| 81 | |
| 82 | [pushrebase] |
| 83 | rewritedates = false |
| 84 | |
| 85 | [source_control_service] |
| 86 | permit_writes = true |
| 87 | permit_service_writes = true |
| 88 | |
| 89 | [git_configs.git_bundle_uri_config.uri_generator_type.local_fs] |
| 90 | |
| 91 | [infinitepush] |
| 92 | allow_writes = true |
| 93 | |
| 94 | [commit_cloud_config] |
| 95 | |
| 96 | [derived_data_config] |
| 97 | enabled_config_name = "default" |
| 98 | |
| 99 | [derived_data_config.available_configs.default] |
| 100 | types = [ |
| 101 | "blame", |
| 102 | "changeset_info", |
| 103 | "fastlog", |
| 104 | "filenodes", |
| 105 | "fsnodes", |
| 106 | "git_commits", |
| 107 | "git_delta_manifests_v2", |
| 108 | "unodes", |
| 109 | "hgchangesets", |
| 110 | "skeleton_manifests", |
| 111 | "skeleton_manifests_v2", |
| 112 | "ccsm", |
| 113 | ] |
| 114 | |
| 115 | [derived_data_config.available_configs.default.git_delta_manifest_v2_config] |
| 116 | max_inlined_object_size = 2000 |
| 117 | max_inlined_delta_size = 2000 |
| 118 | delta_chunk_size = 1000000 |
| 119 | TOML |
| 120 | |
| 121 | # ── Pull bootstrap images from ghcr.io ─────────────────────────── |
| 122 | |
| 123 | docker pull ghcr.io/letterpress-labs/grove-mononoke:latest |
| 124 | docker pull ghcr.io/letterpress-labs/grove-api:latest |
| 125 | docker pull ghcr.io/letterpress-labs/grove-hub-api:latest |
| 126 | docker pull ghcr.io/letterpress-labs/grove-web:latest |
| 127 | |
| 128 | # Tag for local registry (images now in Docker cache) |
| 129 | docker tag ghcr.io/letterpress-labs/grove-api:latest localhost:5000/grove-api:latest |
| 130 | docker tag ghcr.io/letterpress-labs/grove-hub-api:latest localhost:5000/grove-hub-api:latest |
| 131 | docker tag ghcr.io/letterpress-labs/grove-web:latest localhost:5000/grove-web:latest |
| 132 | |
| 133 | # ── Start ───────────────────────────────────────────────────────── |
| 134 | |
| 135 | cd "$GROVE_DIR" |
| 136 | docker compose up -d |
| 137 | |
| 138 | # ── Seed local registry ────────────────────────────────────────── |
| 139 | |
| 140 | echo "Seeding local registry..." |
| 141 | for i in $(seq 1 30); do |
| 142 | if curl -sf http://localhost:5000/v2/ > /dev/null 2>&1; then |
| 143 | docker push localhost:5000/grove-api:latest |
| 144 | docker push localhost:5000/grove-hub-api:latest |
| 145 | docker push localhost:5000/grove-web:latest |
| 146 | echo "Local registry seeded." |
| 147 | break |
| 148 | fi |
| 149 | sleep 2 |
| 150 | done |
| 151 | |
| 152 | # ── Wait for health ────────────────────────────────────────────── |
| 153 | |
| 154 | echo "Waiting for Grove Bridge to be healthy..." |
| 155 | for i in $(seq 1 60); do |
| 156 | if curl -sf http://localhost:3100/health > /dev/null 2>&1; then |
| 157 | echo "Grove Bridge is healthy." |
| 158 | break |
| 159 | fi |
| 160 | sleep 5 |
| 161 | done |
| 162 | |
| 163 | echo "Hub is running at https://$DOMAIN" |
| 164 | |