Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions deploy/customer-bundle/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ cd "$SCRIPT_DIR"
DOCS_URL="https://github.com/carrtech-dev/ct-cve"
SUPPORT_URL="https://github.com/carrtech-dev/ct-cve/issues"
REQUIRED_FILES=(docker-compose.yml .env.example start.sh upgrade.sh)
DOCKER_CMD=(docker)

show_help() {
cat <<EOF
Expand Down Expand Up @@ -82,11 +83,28 @@ require_docker() {
echo "Install Docker Engine 24+ with the Compose plugin: https://docs.docker.com/engine/install/" >&2
exit 1
fi

if docker compose version >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
DOCKER_CMD=(docker)
return 0
fi

if command -v sudo >/dev/null 2>&1 && sudo docker compose version >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then
DOCKER_CMD=(sudo docker)
echo "Docker requires elevated privileges on this host; using sudo for Docker commands."
return 0
fi

if ! docker compose version >/dev/null 2>&1; then
echo "ERROR: 'docker compose' plugin not found." >&2
echo "Upgrade Docker Engine to a release that bundles the Compose plugin." >&2
exit 1
fi

echo "ERROR: Docker is installed, but this user cannot access the Docker daemon." >&2
echo "Run this script as a user with Docker access, or run it with sudo." >&2
echo "Original Docker error:" >&2
docker info >/dev/null
}

check_bundle_files() {
Expand Down Expand Up @@ -167,7 +185,7 @@ wait_for_health() {

echo "Waiting for CT-CVE health check; database migrations run automatically during startup..."
while [ "$SECONDS" -lt "$deadline" ]; do
if docker compose exec -T ct-cve wget -qO- http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
if "${DOCKER_CMD[@]}" compose exec -T ct-cve wget -qO- http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
return 0
fi
sleep 2
Expand All @@ -176,7 +194,7 @@ wait_for_health() {
echo "" >&2
echo "ERROR: CT-CVE did not become healthy after startup." >&2
echo "Recent logs:" >&2
docker compose logs --tail 80 ct-cve ct-cve-db || true
"${DOCKER_CMD[@]}" compose logs --tail 80 ct-cve ct-cve-db || true
exit 1
}

Expand All @@ -186,21 +204,21 @@ start_stack() {
check_env

echo "Pulling CT-CVE images from GHCR..."
if ! docker compose pull ct-cve ct-cve-db; then
if ! "${DOCKER_CMD[@]}" compose pull ct-cve ct-cve-db; then
echo "" >&2
echo "ERROR: failed to pull CT-CVE images." >&2
echo "Check network access to ghcr.io, or verify CT_CVE_IMAGE in .env." >&2
echo "Check Docker daemon access, network access to ghcr.io and Docker Hub, or verify CT_CVE_IMAGE in .env." >&2
exit 1
fi

docker compose down --remove-orphans >/dev/null 2>&1 || true
"${DOCKER_CMD[@]}" compose down --remove-orphans >/dev/null 2>&1 || true

echo "Starting CT-CVE..."
if ! docker compose up -d; then
if ! "${DOCKER_CMD[@]}" compose up -d; then
echo "" >&2
echo "ERROR: 'docker compose up' failed." >&2
echo "Recent logs:" >&2
docker compose logs --tail 80 || true
"${DOCKER_CMD[@]}" compose logs --tail 80 || true
exit 1
fi

Expand All @@ -215,13 +233,13 @@ start_stack() {
stop_stack() {
require_docker
echo "Stopping CT-CVE..."
docker compose down
"${DOCKER_CMD[@]}" compose down
echo "Stopped. Database volume data is preserved."
}

tail_logs() {
require_docker
exec docker compose logs -f --tail 100
exec "${DOCKER_CMD[@]}" compose logs -f --tail 100
}

if [ "$#" -eq 0 ]; then
Expand Down
101 changes: 101 additions & 0 deletions deploy/customer-bundle/start_script_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package customerbundle_test

import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)

func TestStartScriptFallsBackToSudoDockerWhenDaemonAccessIsDenied(t *testing.T) {
t.Parallel()

dir := t.TempDir()
copyFile(t, "start.sh", filepath.Join(dir, "start.sh"))
writeFile(t, filepath.Join(dir, "docker-compose.yml"), "services: {}\n")
writeFile(t, filepath.Join(dir, ".env.example"), "CT_CVE_IMAGE=ghcr.io/carrtech-dev/ct-cve:latest\n")
writeFile(t, filepath.Join(dir, "upgrade.sh"), "#!/usr/bin/env bash\n")
writeFile(t, filepath.Join(dir, ".env"), strings.Join([]string{
"CT_CVE_IMAGE=ghcr.io/carrtech-dev/ct-cve:latest",
"CT_CVE_HTTP_PORT=8080",
"POSTGRES_PASSWORD=test-password",
"CT_CVE_DATABASE_URL=postgres://ct_cve:test-password@ct-cve-db:5432/ct_cve?sslmode=disable",
"CT_CVE_CT_OPS_CONNECTIONS=[]",
"",
}, "\n"))

binDir := filepath.Join(dir, "bin")
if err := os.Mkdir(binDir, 0o755); err != nil {
t.Fatalf("Mkdir bin: %v", err)
}
writeExecutable(t, filepath.Join(binDir, "docker"), `#!/usr/bin/env bash
set -euo pipefail
if [ "${1:-}" = "compose" ] && [ "${2:-}" = "version" ]; then
exit 0
fi
if [ "${1:-}" = "info" ]; then
if [ "${CT_TEST_SUDO_DOCKER:-}" = "1" ]; then
exit 0
fi
echo "permission denied while trying to connect to the docker API" >&2
exit 1
fi
if [ "${1:-}" = "compose" ]; then
case "${2:-}" in
pull|down|up|exec|logs) exit 0 ;;
esac
fi
echo "unexpected docker args: $*" >&2
exit 1
`)
writeExecutable(t, filepath.Join(binDir, "sudo"), `#!/usr/bin/env bash
set -euo pipefail
if [ "${1:-}" != "docker" ]; then
echo "unexpected sudo args: $*" >&2
exit 1
fi
shift
CT_TEST_SUDO_DOCKER=1 docker "$@"
`)

cmd := exec.Command("bash", "./start.sh")
cmd.Dir = dir
cmd.Env = append(os.Environ(), "PATH="+binDir+":"+os.Getenv("PATH"))
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("start.sh failed: %v\n%s", err, output)
}
if !strings.Contains(string(output), "using sudo for Docker commands") {
t.Fatalf("start.sh did not report sudo Docker fallback:\n%s", output)
}
if !strings.Contains(string(output), "CT-CVE is running") {
t.Fatalf("start.sh did not complete startup:\n%s", output)
}
}

func copyFile(t *testing.T, source, destination string) {
t.Helper()
data, err := os.ReadFile(source)
if err != nil {
t.Fatalf("ReadFile %s: %v", source, err)
}
writeFile(t, destination, string(data))
if err := os.Chmod(destination, 0o755); err != nil {
t.Fatalf("Chmod %s: %v", destination, err)
}
}

func writeFile(t *testing.T, path, contents string) {
t.Helper()
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatalf("WriteFile %s: %v", path, err)
}
}

func writeExecutable(t *testing.T, path, contents string) {
t.Helper()
if err := os.WriteFile(path, []byte(contents), 0o755); err != nil {
t.Fatalf("WriteFile %s: %v", path, err)
}
}
20 changes: 19 additions & 1 deletion deploy/customer-bundle/upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cd "$SCRIPT_DIR"
FROM_ZIP=""
START_AFTER_UPGRADE=true
VERSION_OVERRIDE="${CT_CVE_VERSION:-}"
DOCKER_CMD=(docker)

show_help() {
cat <<EOF
Expand Down Expand Up @@ -99,10 +100,27 @@ require_existing_bundle() {

require_docker() {
need docker

if docker compose version >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
DOCKER_CMD=(docker)
return 0
fi

if command -v sudo >/dev/null 2>&1 && sudo docker compose version >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then
DOCKER_CMD=(sudo docker)
echo "Docker requires elevated privileges on this host; using sudo for Docker commands."
return 0
fi

if ! docker compose version >/dev/null 2>&1; then
echo "ERROR: 'docker compose' plugin not found." >&2
exit 1
fi

echo "ERROR: Docker is installed, but this user cannot access the Docker daemon." >&2
echo "Run this script as a user with Docker access, or run it with sudo." >&2
echo "Original Docker error:" >&2
docker info >/dev/null
}

normalize_tag() {
Expand Down Expand Up @@ -225,7 +243,7 @@ unpack_new_bundle() {

stop_stack() {
echo "Stopping CT-CVE stack; named volumes are preserved..."
docker compose down --remove-orphans >/dev/null 2>&1 || true
"${DOCKER_CMD[@]}" compose down --remove-orphans >/dev/null 2>&1 || true
}

upsert_env_var() {
Expand Down
3 changes: 3 additions & 0 deletions deploy/scripts/test-customer-bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ write_mock_docker() {
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >> "${MOCK_DOCKER_LOG}"
if [ "${1:-}" = "info" ]; then
exit 0
fi
if [ "${1:-}" != "compose" ]; then
echo "unexpected docker command: $*" >&2
exit 1
Expand Down
Loading