Hostityourself/builder/build.sh
Claude 3d35e220e4
build.sh: give actionable instructions when Caddy is unreachable
The old message just said 'expose manually if needed' with no guidance.
Now it prints the exact docker commands to publish the port directly
and how to find the container IP for a custom reverse proxy.

https://claude.ai/code/session_01FKCW3FDjNFj6jve4niMFXH
2026-03-19 10:27:17 +00:00

162 lines
6.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# HIY Build Engine
# Environment variables injected by hiy-server:
# APP_ID, APP_NAME, REPO_URL, BRANCH, PORT, ENV_FILE, SHA, BUILD_DIR
set -euo pipefail
log() { echo "[hiy] $*"; }
log "=== HostItYourself Build Engine ==="
log "App: $APP_NAME ($APP_ID)"
log "Repo: $REPO_URL"
log "Branch: $BRANCH"
log "Build dir: $BUILD_DIR"
# ── 1. Clone or pull ───────────────────────────────────────────────────────────
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
if [ -d ".git" ]; then
log "Updating existing clone…"
git fetch origin "$BRANCH" --depth=50
git checkout "$BRANCH"
git reset --hard "origin/$BRANCH"
else
log "Cloning repository…"
git clone --depth=50 --branch "$BRANCH" "$REPO_URL" .
fi
ACTUAL_SHA=$(git rev-parse HEAD)
log "SHA: $ACTUAL_SHA"
# ── 2. Detect build strategy ──────────────────────────────────────────────────
IMAGE_TAG="hiy/${APP_ID}:${ACTUAL_SHA}"
CONTAINER_NAME="hiy-${APP_ID}"
if [ -f "Dockerfile" ]; then
log "Strategy: Dockerfile"
docker build --tag "$IMAGE_TAG" .
elif [ -f "package.json" ] || [ -f "yarn.lock" ]; then
log "Strategy: Node.js (Cloud Native Buildpack)"
if ! command -v pack &>/dev/null; then
log "ERROR: 'pack' CLI not found. Install it: https://buildpacks.io/docs/tools/pack/"
exit 1
fi
pack build "$IMAGE_TAG" --builder paketobuildpacks/builder-jammy-base
elif [ -f "requirements.txt" ] || [ -f "pyproject.toml" ]; then
log "Strategy: Python (Cloud Native Buildpack)"
if ! command -v pack &>/dev/null; then
log "ERROR: 'pack' CLI not found. Install it: https://buildpacks.io/docs/tools/pack/"
exit 1
fi
pack build "$IMAGE_TAG" --builder paketobuildpacks/builder-jammy-base
elif [ -f "go.mod" ]; then
log "Strategy: Go (Cloud Native Buildpack)"
if ! command -v pack &>/dev/null; then
log "ERROR: 'pack' CLI not found. Install it: https://buildpacks.io/docs/tools/pack/"
exit 1
fi
pack build "$IMAGE_TAG" --builder paketobuildpacks/builder-jammy-base
elif [ -d "static" ] || [ -d "public" ]; then
STATIC_DIR="static"
[ -d "public" ] && STATIC_DIR="public"
log "Strategy: Static files (Caddy) from ./$STATIC_DIR"
cat > Dockerfile.hiy <<EOF
FROM caddy:2-alpine
COPY $STATIC_DIR /srv
EOF
docker build --file Dockerfile.hiy --tag "$IMAGE_TAG" .
rm -f Dockerfile.hiy
else
log "ERROR: Could not detect build strategy."
log "Add a Dockerfile, package.json, requirements.txt, go.mod, or a static/ directory."
exit 1
fi
# ── 3. Ensure Docker network ───────────────────────────────────────────────────
docker network create hiy-net 2>/dev/null || true
# ── 4. Stop & remove previous container ───────────────────────────────────────
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log "Stopping old container…"
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm "$CONTAINER_NAME" >/dev/null 2>&1 || true
fi
# ── 5. Start new container ────────────────────────────────────────────────────
log "Starting container ${CONTAINER_NAME}"
ENV_FILE_ARG=()
if [ -n "${ENV_FILE:-}" ] && [ -f "$ENV_FILE" ]; then
ENV_FILE_ARG=(--env-file "$ENV_FILE")
else
log "No env file found at '${ENV_FILE:-}'; starting without one."
fi
docker run --detach \
--name "$CONTAINER_NAME" \
--network hiy-net \
"${ENV_FILE_ARG[@]+"${ENV_FILE_ARG[@]}"}" \
--expose "$PORT" \
--label "hiy.app=${APP_ID}" \
--label "hiy.port=${PORT}" \
--restart unless-stopped \
--memory="512m" \
--cpus="0.5" \
"$IMAGE_TAG"
# ── 6. Update Caddy via its admin API ─────────────────────────────────────────
CADDY_API="${CADDY_API_URL:-http://localhost:2019}"
DOMAIN_SUFFIX="${DOMAIN_SUFFIX:-localhost}"
if curl --silent --fail "${CADDY_API}/config/" >/dev/null 2>&1; then
CONTAINER_IP=$(docker inspect "$CONTAINER_NAME" \
--format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
UPSTREAM="${CONTAINER_IP}:${PORT}"
log "Updating Caddy: ${APP_ID}.${DOMAIN_SUFFIX}${UPSTREAM}"
ROUTE_JSON=$(cat <<EOF
{
"match": [{"host": ["${APP_ID}.${DOMAIN_SUFFIX}"]}],
"handle": [{"handler": "reverse_proxy", "upstreams": [{"dial": "${UPSTREAM}"}]}]
}
EOF
)
# Upsert the route for this app.
ROUTES=$(curl --silent "${CADDY_API}/config/apps/http/servers/hiy/routes" 2>/dev/null || echo "[]")
# Remove existing route for the same host, then append the new one.
UPDATED=$(echo "$ROUTES" | python3 -c "
import sys, json
routes = json.load(sys.stdin)
new_host = '${APP_ID}.${DOMAIN_SUFFIX}'
routes = [r for r in routes if new_host not in r.get('match',[{}])[0].get('host',[])]
routes.append(json.loads(sys.argv[1]))
print(json.dumps(routes))
" "$ROUTE_JSON")
curl --silent --fail "${CADDY_API}/config/apps/http/servers/hiy/routes" \
--header "Content-Type: application/json" \
--request PUT \
--data "$UPDATED" && log "Caddy updated." \
|| log "WARNING: Caddy update failed (app is running; fix routing manually)."
else
log "Caddy admin API not reachable; skipping route update."
log "Container ${CONTAINER_NAME} is running on port ${PORT} but not publicly routed."
log "To reach it directly, re-run the container with a published port:"
log " docker rm -f ${CONTAINER_NAME}"
log " docker run -d --name ${CONTAINER_NAME} -p ${PORT}:${PORT} ${IMAGE_TAG}"
log "Or point any reverse proxy at: \$(docker inspect ${CONTAINER_NAME} --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'):${PORT}"
fi
# ── 7. Prune old images ───────────────────────────────────────────────────────
log "Pruning old images (keeping last 3)…"
docker images "hiy/${APP_ID}" --format "{{.ID}}\t{{.CreatedAt}}" \
| sort --reverse --key=2 \
| tail -n +4 \
| awk '{print $1}' \
| xargs --no-run-if-empty docker rmi 2>/dev/null || true
log "=== Build complete: $APP_NAME @ $ACTUAL_SHA ==="