Fix: harden local dev startup flow
This commit is contained in:
@@ -6,6 +6,9 @@ WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache ca-certificates curl git tzdata && update-ca-certificates
|
||||
|
||||
RUN mkdir -p /tmp/home /tmp/go/pkg/mod /tmp/go-build \
|
||||
&& chmod 0777 /tmp/home /tmp/go /tmp/go/pkg /tmp/go/pkg/mod /tmp/go-build
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
project_root := justfile_directory()
|
||||
backend_dir := project_root + "/Backend"
|
||||
common_sh := project_root + "/Commands/Local/scripts/common.sh"
|
||||
posix_root := project_root + "/POSIX"
|
||||
|
||||
# Apply embedded database migrations.
|
||||
migrate-up:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/migrate up
|
||||
|
||||
# Roll back the most recent embedded database migration.
|
||||
# Roll back the most recent embedded database migration (confirmation required).
|
||||
migrate-down:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will roll back the most recent embedded database migration. Continue?"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/migrate down
|
||||
|
||||
# Reset all embedded database migrations.
|
||||
# Reset all embedded database migrations (confirmation required).
|
||||
migrate-reset:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will reset all embedded database migrations. Continue?"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/migrate reset
|
||||
|
||||
# Reset embedded database migrations and apply them again from scratch.
|
||||
# Reset embedded database migrations and apply them again from scratch (confirmation required).
|
||||
migrate-rebuild:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will reset all embedded database migrations and reapply them from scratch. Continue?"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/migrate reset && go run ./cmd/migrate up
|
||||
|
||||
# Show the embedded database migration status.
|
||||
migrate-status:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/migrate status
|
||||
|
||||
# Rebuild the POSIX-to-DB projection from the current POSIX root.
|
||||
posix-rebuild:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{backend_dir}}' && go run ./cmd/posix rebuild
|
||||
|
||||
# Format backend Go source files.
|
||||
fmt:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{backend_dir}}' && gofmt -w ./cmd ./db ./internal
|
||||
|
||||
@@ -2,13 +2,17 @@ project_root := justfile_directory()
|
||||
local_compose := project_root + "/Docker/docker-compose.local.dev.yaml"
|
||||
frontend_dir := project_root + "/Frontend"
|
||||
node_modules_volume := "moku_work_frontend_node_modules"
|
||||
common_sh := project_root + "/Commands/Local/scripts/common.sh"
|
||||
posix_root := project_root + "/POSIX"
|
||||
|
||||
# Recreate the frontend node_modules Docker volume.
|
||||
# Recreate the frontend node_modules Docker volume (confirmation required).
|
||||
node_modules:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will recreate the frontend node_modules Docker volume. Continue?"'
|
||||
docker compose -f '{{local_compose}}' rm -sf frontend >/dev/null 2>&1 || true
|
||||
docker volume rm -f '{{node_modules_volume}}' >/dev/null 2>&1 || true
|
||||
docker compose -f '{{local_compose}}' up -d --remove-orphans --force-recreate frontend
|
||||
|
||||
# Run the frontend TypeScript check.
|
||||
tsc:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{frontend_dir}}' && pnpm typecheck
|
||||
|
||||
@@ -18,7 +18,7 @@ start:
|
||||
# Alias for the main full local development flow.
|
||||
dev: up
|
||||
|
||||
# Stop and remove the local development stack.
|
||||
# Stop and remove the local development stack (confirmation required).
|
||||
down:
|
||||
bash '{{stack_runner}}' down
|
||||
|
||||
@@ -34,6 +34,6 @@ logs:
|
||||
restart:
|
||||
bash '{{stack_runner}}' restart
|
||||
|
||||
# Stop the local development stack and remove local images, volumes, and backend dev state.
|
||||
# Stop the local development stack and remove local images, volumes, backend dev state, and the local POSIX folder (confirmation required).
|
||||
clean:
|
||||
bash '{{stack_runner}}' clean
|
||||
|
||||
@@ -7,6 +7,7 @@ action=${1:-up}
|
||||
|
||||
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
project_root=$(cd -- "$script_dir/../../../.." && pwd)
|
||||
posix_root="$project_root/POSIX"
|
||||
backend_dir="$project_root/Backend"
|
||||
backend_bake="$backend_dir/docker-bake.hcl"
|
||||
env_dir="$project_root/Env"
|
||||
@@ -15,11 +16,14 @@ runtime_dir="$backend_dir/tmp/dev"
|
||||
backend_image="moku/work-backend:dev"
|
||||
backend_go_pkg_volume="moku_work_backend_go_pkg"
|
||||
backend_go_build_volume="moku_work_backend_go_build"
|
||||
local_uid=$(id -u)
|
||||
local_gid=$(id -g)
|
||||
|
||||
services=(web api worker)
|
||||
|
||||
source "$script_dir/docker.sh"
|
||||
source "$script_dir/env.sh"
|
||||
source "$project_root/Commands/Local/scripts/common.sh"
|
||||
|
||||
build_backend() {
|
||||
cd "$backend_dir"
|
||||
@@ -27,20 +31,20 @@ build_backend() {
|
||||
}
|
||||
|
||||
up_backend() {
|
||||
docker compose -f "$compose_file" up -d --remove-orphans --force-recreate "${services[@]}"
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" up -d --remove-orphans --force-recreate "${services[@]}"
|
||||
}
|
||||
|
||||
down_backend() {
|
||||
docker compose -f "$compose_file" stop "${services[@]}" >/dev/null 2>&1 || true
|
||||
docker compose -f "$compose_file" rm -f "${services[@]}" >/dev/null 2>&1 || true
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" stop "${services[@]}" >/dev/null 2>&1 || true
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" rm -f "${services[@]}" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
restart_backend() {
|
||||
docker compose -f "$compose_file" restart "${services[@]}"
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" restart "${services[@]}"
|
||||
}
|
||||
|
||||
follow_logs() {
|
||||
docker compose -f "$compose_file" logs -f "${services[@]}"
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" logs -f "${services[@]}"
|
||||
}
|
||||
|
||||
clean_runtime() {
|
||||
@@ -50,34 +54,44 @@ clean_runtime() {
|
||||
case "$action" in
|
||||
check)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
;;
|
||||
build)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
build_backend
|
||||
;;
|
||||
up)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
up_backend
|
||||
run_compose_api_migrations "$compose_file" "api"
|
||||
;;
|
||||
down)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
confirm_destructive_action 'This will stop and remove the local backend dev containers. Continue?'
|
||||
down_backend
|
||||
;;
|
||||
restart)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
restart_backend
|
||||
;;
|
||||
logs)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
follow_logs
|
||||
;;
|
||||
clean)
|
||||
ensure_docker 'docker is required for the local backend dev runtime. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
confirm_destructive_action 'This will remove the local backend dev containers, images, volumes, and runtime state. Continue?'
|
||||
down_backend
|
||||
remove_docker_image_if_present "$backend_image"
|
||||
remove_docker_volume_if_present "$backend_go_pkg_volume"
|
||||
|
||||
@@ -7,12 +7,15 @@ action=${1:-up}
|
||||
|
||||
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
project_root=$(cd -- "$script_dir/../../../.." && pwd)
|
||||
posix_root="$project_root/POSIX"
|
||||
frontend_dir="$project_root/Frontend"
|
||||
frontend_bake="$frontend_dir/docker-bake.hcl"
|
||||
backend_dir="$project_root/Backend"
|
||||
backend_bake="$backend_dir/docker-bake.hcl"
|
||||
env_dir="$project_root/Env"
|
||||
compose_file="$project_root/Docker/docker-compose.local.dev.yaml"
|
||||
local_uid=$(id -u)
|
||||
local_gid=$(id -g)
|
||||
frontend_image="moku/work-frontend:dev"
|
||||
backend_image="moku/work-backend:dev"
|
||||
frontend_volume="moku_work_frontend_node_modules"
|
||||
@@ -22,6 +25,7 @@ backend_runtime_dir="$backend_dir/tmp/dev"
|
||||
|
||||
source "$script_dir/docker.sh"
|
||||
source "$script_dir/env.sh"
|
||||
source "$project_root/Commands/Local/scripts/common.sh"
|
||||
|
||||
build_frontend() {
|
||||
cd "$frontend_dir"
|
||||
@@ -39,19 +43,19 @@ build_images() {
|
||||
}
|
||||
|
||||
up_stack() {
|
||||
docker compose -f "$compose_file" up -d --remove-orphans --force-recreate
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" up -d --remove-orphans --force-recreate
|
||||
}
|
||||
|
||||
down_stack() {
|
||||
docker compose -f "$compose_file" down --remove-orphans --volumes
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" down --remove-orphans --volumes
|
||||
}
|
||||
|
||||
follow_logs() {
|
||||
docker compose -f "$compose_file" logs -f
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" logs -f
|
||||
}
|
||||
|
||||
clean_stack() {
|
||||
docker compose -f "$compose_file" down --remove-orphans --volumes >/dev/null 2>&1 || true
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" down --remove-orphans --volumes >/dev/null 2>&1 || true
|
||||
remove_docker_image_if_present "$frontend_image"
|
||||
remove_docker_image_if_present "$backend_image"
|
||||
remove_docker_volume_if_present "$frontend_volume"
|
||||
@@ -63,37 +67,47 @@ clean_stack() {
|
||||
start_stack() {
|
||||
build_images
|
||||
up_stack
|
||||
run_compose_api_migrations "$compose_file" "api"
|
||||
}
|
||||
|
||||
case "$action" in
|
||||
build)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
build_images
|
||||
;;
|
||||
up|start|rebuild)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
start_stack
|
||||
;;
|
||||
down)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
confirm_destructive_action 'This will stop the local development stack and remove its containers and volumes. Continue?'
|
||||
down_stack
|
||||
;;
|
||||
restart)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
docker compose -f "$compose_file" restart
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
LOCAL_UID="$local_uid" LOCAL_GID="$local_gid" docker compose -f "$compose_file" restart
|
||||
;;
|
||||
logs)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
follow_logs
|
||||
;;
|
||||
clean)
|
||||
ensure_docker 'docker is required for the local development stack. Install Docker first.'
|
||||
ensure_local_env_file "$env_dir"
|
||||
ensure_posix_root "$project_root" "$posix_root"
|
||||
confirm_destructive_action 'This will remove the local development stack, local images, volumes, backend dev state, and the local POSIX folder. Continue?'
|
||||
clean_stack
|
||||
rm -rf "$posix_root"
|
||||
;;
|
||||
*)
|
||||
printf 'Unsupported dev stack action: %s\n' "$action" >&2
|
||||
|
||||
@@ -5,40 +5,49 @@ local_compose := project_root + "/Docker/docker-compose.local.prod.yaml"
|
||||
proxy_image := "moku/work-proxy:local-prod"
|
||||
backend_api_image := "moku/work-backend:local-prod-api"
|
||||
backend_worker_image := "moku/work-backend:local-prod-worker"
|
||||
common_sh := project_root + "/Commands/Local/scripts/common.sh"
|
||||
posix_root := project_root + "/POSIX"
|
||||
|
||||
# Build the local production proxy image locally.
|
||||
build:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{project_root}}' && docker buildx bake -f '{{proxy_bake}}' prod
|
||||
cd '{{project_root}}' && docker buildx bake -f '{{backend_bake}}' prod-api prod-worker
|
||||
|
||||
# Start the local production stack in the background using the current image.
|
||||
up:
|
||||
docker compose -f '{{local_compose}}' up -d --remove-orphans --force-recreate
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' up -d --remove-orphans --force-recreate
|
||||
|
||||
# Build first, then start the local production stack in the background.
|
||||
start: build up
|
||||
|
||||
# Rebuild the local production proxy image locally.
|
||||
rebuild:
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
cd '{{project_root}}' && docker buildx bake -f '{{proxy_bake}}' --set '*.no-cache=true' prod
|
||||
cd '{{project_root}}' && docker buildx bake -f '{{backend_bake}}' --set '*.no-cache=true' prod-api prod-worker
|
||||
docker compose -f '{{local_compose}}' up -d --remove-orphans --force-recreate
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' up -d --remove-orphans --force-recreate
|
||||
|
||||
# Stop and remove the local production stack.
|
||||
# Stop and remove the local production stack (confirmation required).
|
||||
down:
|
||||
docker compose -f '{{local_compose}}' down --remove-orphans --volumes
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will stop the local production stack and remove its containers and volumes. Continue?"'
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' down --remove-orphans --volumes
|
||||
|
||||
# Follow logs for the local production stack.
|
||||
logs:
|
||||
docker compose -f '{{local_compose}}' logs -f
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' logs -f
|
||||
|
||||
# Restart the local production stack.
|
||||
restart:
|
||||
docker compose -f '{{local_compose}}' restart
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"'
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' restart
|
||||
|
||||
# Stop the local production stack and remove local images.
|
||||
# Stop the local production stack and remove local images (confirmation required).
|
||||
clean:
|
||||
docker compose -f '{{local_compose}}' down --remove-orphans --volumes
|
||||
bash -c 'source "{{common_sh}}"; ensure_posix_root "{{project_root}}" "{{posix_root}}"; confirm_destructive_action "This will remove the local production stack, its volumes, and the local production images. Continue?"'
|
||||
LOCAL_UID="$$(id -u)" LOCAL_GID="$$(id -g)" docker compose -f '{{local_compose}}' down --remove-orphans --volumes
|
||||
docker image rm -f '{{proxy_image}}' >/dev/null 2>&1 || true
|
||||
docker image rm -f '{{backend_api_image}}' >/dev/null 2>&1 || true
|
||||
docker image rm -f '{{backend_worker_image}}' >/dev/null 2>&1 || true
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ensure_posix_root() {
|
||||
local project_root=${1:?project root is required}
|
||||
local posix_root=${2:-"$project_root/POSIX"}
|
||||
|
||||
mkdir -p "$posix_root"
|
||||
}
|
||||
|
||||
confirm_destructive_action() {
|
||||
local prompt=${1:-Are you sure you want to continue?}
|
||||
local response_input=""
|
||||
|
||||
if [[ "${JUST_YES:-0}" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -t 0 ]]; then
|
||||
printf '%s [y/N] ' "$prompt"
|
||||
IFS= read -r response_input
|
||||
elif [[ -r /dev/tty ]]; then
|
||||
printf '%s [y/N] ' "$prompt" > /dev/tty
|
||||
IFS= read -r response_input < /dev/tty
|
||||
else
|
||||
printf 'Refusing destructive action without interactive confirmation. Re-run with JUST_YES=1 to continue.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$response_input" in
|
||||
y|Y|yes|YES|Yes)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
printf 'Aborted.\n' >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_compose_api_migrations() {
|
||||
local compose_file=${1:?compose file is required}
|
||||
local service_name=${2:-api}
|
||||
local max_attempts=${3:-20}
|
||||
local sleep_seconds=${4:-2}
|
||||
local attempt=1
|
||||
local output=""
|
||||
|
||||
while (( attempt <= max_attempts )); do
|
||||
if output=$(docker compose -f "$compose_file" exec -T "$service_name" sh -lc 'go run ./cmd/migrate up' 2>&1); then
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if (( attempt == max_attempts )); then
|
||||
printf 'Failed to apply database migrations automatically after %d attempts.\n' "$max_attempts" >&2
|
||||
printf '%s\n' "$output" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
sleep "$sleep_seconds"
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
x-backend-service: &backend-service
|
||||
image: moku/work-backend:dev
|
||||
restart: unless-stopped
|
||||
user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
|
||||
env_file:
|
||||
- ../Env/.env.local
|
||||
environment:
|
||||
DATABASE_URL: postgres://moku:moku_dev_password@postgres:5432/moku?sslmode=disable
|
||||
VALKEY_URL: redis://valkey:6379/0
|
||||
POSIX_ROOT: /posix
|
||||
HOME: /tmp/home
|
||||
GOMODCACHE: /tmp/go/pkg/mod
|
||||
GOCACHE: /tmp/go-build
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
@@ -15,8 +19,8 @@ x-backend-service: &backend-service
|
||||
volumes:
|
||||
- ../Backend:/app
|
||||
- ../POSIX:/posix
|
||||
- moku_work_backend_go_pkg:/go/pkg/mod
|
||||
- moku_work_backend_go_build:/root/.cache/go-build
|
||||
- moku_work_backend_go_pkg:/tmp/go/pkg/mod
|
||||
- moku_work_backend_go_build:/tmp/go-build
|
||||
|
||||
services:
|
||||
postgres:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
x-backend-service: &backend-service
|
||||
restart: unless-stopped
|
||||
user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
|
||||
env_file:
|
||||
- ../Env/.env.local
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user