hub/setup.shblame
View source
135dfe51#!/bin/bash
135dfe52# Sets up the Grove hub on a fresh DigitalOcean droplet.
5f0fbcf3# The hub is a full Grove instance: Mononoke + Bridge + API + Hub API + Web + Registry.
5f0fbcf4#
135dfe55# Usage: bash setup.sh <domain>
135dfe56# e.g. bash setup.sh grove.host
135dfe57
135dfe58set -euo pipefail
135dfe59
135dfe510DOMAIN="${1:?Usage: setup.sh <domain>}"
135dfe511SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5f0fbcf12GROVE_DIR="/opt/grove"
135dfe513
5f0fbcf14mkdir -p "$GROVE_DIR"
5f0fbcf15mkdir -p "$GROVE_DIR/data/mononoke"
5f0fbcf16mkdir -p "$GROVE_DIR/data/api"
135dfe517
5f0fbcf18# ── Copy configs ──────────────────────────────────────────────────
5f0fbcf19
5f0fbcf20cp "$SCRIPT_DIR/Caddyfile" "$GROVE_DIR/"
5f0fbcf21cp "$SCRIPT_DIR/docker-compose.yml" "$GROVE_DIR/"
5f0fbcf22
5f0fbcf23# ── Generate JWT secret and write .env ────────────────────────────
135dfe524
135dfe525JWT_SECRET=$(openssl rand -base64 32)
5f0fbcf26cat > "$GROVE_DIR/.env" <<EOF
135dfe527DOMAIN=$DOMAIN
135dfe528JWT_SECRET=$JWT_SECRET
135dfe529EOF
135dfe530
0d60b2031# ── Generate TLS certificates for Mononoke ─────────────────────
0d60b2032
0d60b2033GROVE_TLS_DIR=/data/grove/tls "$SCRIPT_DIR/../scripts/generate-certs.sh" "$DOMAIN"
0d60b2034
5f0fbcf35# ── Write Mononoke config ────────────────────────────────────────
5f0fbcf36
5f0fbcf37mkdir -p "$GROVE_DIR/config/mononoke/common"
36387cc38mkdir -p "$GROVE_DIR/config/mononoke/repo_definitions/grove"
36387cc39mkdir -p "$GROVE_DIR/config/mononoke/repos/grove"
5f0fbcf40
5f0fbcf41cat > "$GROVE_DIR/config/mononoke/common/common.toml" <<'TOML'
5f0fbcf42enable_http_control_api = true
5f0fbcf43
5f0fbcf44[internal_identity]
5f0fbcf45identity_type = "SERVICE"
5f0fbcf46identity_data = "grove"
5f0fbcf47TOML
5f0fbcf48
5f0fbcf49cat > "$GROVE_DIR/config/mononoke/common/storage.toml" <<'TOML'
5f0fbcf50[default]
5f0fbcf51
5f0fbcf52[default.metadata]
5f0fbcf53[default.metadata.local]
5f0fbcf54local_db_path = "/data/mononoke/metadata"
5f0fbcf55
5f0fbcf56[default.blobstore]
5f0fbcf57[default.blobstore.blob_files]
5f0fbcf58path = "/data/mononoke/blobs"
5f0fbcf59
5f0fbcf60[default.mutable_blobstore]
5f0fbcf61[default.mutable_blobstore.blob_files]
5f0fbcf62path = "/data/mononoke/mutable_blobs"
5f0fbcf63TOML
5f0fbcf64
36387cc65cat > "$GROVE_DIR/config/mononoke/repo_definitions/grove/server.toml" <<'TOML'
5f0fbcf66repo_id = 0
36387cc67repo_name = "grove"
36387cc68repo_config = "grove"
5f0fbcf69enabled = true
6c9fcae70hipster_acl = "default"
5f0fbcf71TOML
5f0fbcf72
36387cc73cat > "$GROVE_DIR/config/mononoke/repos/grove/server.toml" <<'TOML'
5f0fbcf74storage_config = "default"
5f0fbcf75
0d60b2076[hook_manager_params]
0d60b2077disable_acl_checker = true
0d60b2078
5f0fbcf79[push]
5f0fbcf80pure_push_allowed = true
5f0fbcf81
5f0fbcf82[pushrebase]
5f0fbcf83rewritedates = false
5f0fbcf84
5f0fbcf85[source_control_service]
5f0fbcf86permit_writes = true
5f0fbcf87permit_service_writes = true
5f0fbcf88
6c9fcae89[git_configs.git_bundle_uri_config.uri_generator_type.local_fs]
6c9fcae90
0d60b2091[infinitepush]
0d60b2092allow_writes = true
0d60b2093
0d60b2094[commit_cloud_config]
0d60b2095
5f0fbcf96[derived_data_config]
5f0fbcf97enabled_config_name = "default"
5f0fbcf98
5f0fbcf99[derived_data_config.available_configs.default]
5f0fbcf100types = [
5f0fbcf101 "blame",
5f0fbcf102 "changeset_info",
5f0fbcf103 "fastlog",
5f0fbcf104 "filenodes",
5f0fbcf105 "fsnodes",
5f0fbcf106 "git_commits",
5f0fbcf107 "git_delta_manifests_v2",
6c9fcae108 "unodes",
5f0fbcf109 "hgchangesets",
5f0fbcf110 "skeleton_manifests",
1688ad1111 "skeleton_manifests_v2",
6c9fcae112 "ccsm",
5f0fbcf113]
0d60b20114
0d60b20115[derived_data_config.available_configs.default.git_delta_manifest_v2_config]
0d60b20116max_inlined_object_size = 2000
0d60b20117max_inlined_delta_size = 2000
0d60b20118delta_chunk_size = 1000000
5f0fbcf119TOML
5f0fbcf120
5f0fbcf121# ── Pull bootstrap images from ghcr.io ───────────────────────────
5f0fbcf122
5f0fbcf123docker pull ghcr.io/letterpress-labs/grove-mononoke:latest
5f0fbcf124docker pull ghcr.io/letterpress-labs/grove-api:latest
5f0fbcf125docker pull ghcr.io/letterpress-labs/grove-hub-api:latest
5f0fbcf126docker pull ghcr.io/letterpress-labs/grove-web:latest
5f0fbcf127
5f0fbcf128# Tag for local registry (images now in Docker cache)
5f0fbcf129docker tag ghcr.io/letterpress-labs/grove-api:latest localhost:5000/grove-api:latest
5f0fbcf130docker tag ghcr.io/letterpress-labs/grove-hub-api:latest localhost:5000/grove-hub-api:latest
5f0fbcf131docker tag ghcr.io/letterpress-labs/grove-web:latest localhost:5000/grove-web:latest
5f0fbcf132
5f0fbcf133# ── Start ─────────────────────────────────────────────────────────
5f0fbcf134
5f0fbcf135cd "$GROVE_DIR"
135dfe5136docker compose up -d
135dfe5137
5f0fbcf138# ── Seed local registry ──────────────────────────────────────────
5f0fbcf139
5f0fbcf140echo "Seeding local registry..."
5f0fbcf141for i in $(seq 1 30); do
5f0fbcf142 if curl -sf http://localhost:5000/v2/ > /dev/null 2>&1; then
5f0fbcf143 docker push localhost:5000/grove-api:latest
5f0fbcf144 docker push localhost:5000/grove-hub-api:latest
5f0fbcf145 docker push localhost:5000/grove-web:latest
5f0fbcf146 echo "Local registry seeded."
5f0fbcf147 break
5f0fbcf148 fi
5f0fbcf149 sleep 2
5f0fbcf150done
5f0fbcf151
5f0fbcf152# ── Wait for health ──────────────────────────────────────────────
5f0fbcf153
5f0fbcf154echo "Waiting for Grove Bridge to be healthy..."
5f0fbcf155for i in $(seq 1 60); do
5f0fbcf156 if curl -sf http://localhost:3100/health > /dev/null 2>&1; then
5f0fbcf157 echo "Grove Bridge is healthy."
5f0fbcf158 break
5f0fbcf159 fi
5f0fbcf160 sleep 5
5f0fbcf161done
5f0fbcf162
135dfe5163echo "Hub is running at https://$DOMAIN"