diff --git a/.gitignore b/.gitignore index 146355b..fb99cf5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .env .env.prod .env.test +# Per-worktree dev-server identity, written by `make worktree-new` +.worktree-env # Grav CMS /user/ diff --git a/CLAUDE.md b/CLAUDE.md index 3e6bcb7..ffaa7b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -155,7 +155,7 @@ Only these folders are tracked in the `user/` Git repo: `pages/`, `config/`, `ac - **Two repos, two cadences.** Outer repo = dev environment (tests/docs/scripts/Docker). `user/` = content + theme, with its own remote and `make content-push` cadence. The outer repo pins an exact `user/` commit via the `user` gitlink. - **Pointer-bump convention.** Routine content changes → **do not** bump the pin (leave it stale; harmless). At the **end of a cross-repo feature** → bump the pin once to the finished `user/` commit. Pin a commit reachable from `user/`'s published `main` (prefer the merge-to-main commit, not a squash-away branch tip), and **push `user/` before the outer repo** (superproject references a child SHA that must already exist upstream). The pin is dev-side coordination only — production pulls `user/` via the content webhook independently. - **`M user` / `m user` is normal.** `M` = pin differs from `user/` HEAD (bump pending/intentional). `m` = submodule working tree dirty (e.g. local-testing `config/site.yaml`). Neither is an error — do not "fix" them by committing the gitlink or the `site.yaml`. -- **Worktrees for parallel work.** A worktree off `main` gets its own `user/` (`git submodule update --init user`) and can run its own dev server (`docker compose -p itte- up` — the `./user` mount is relative, so each worktree serves its own content). Tooling worktrees live under `.worktrees/` (excluded via `.git/info/exclude`). To add a commit to `main` while the main checkout is on another branch, use a throwaway `main` worktree rather than `git checkout main`. +- **Worktrees for parallel work — use the make targets, don't do it by hand.** `make worktree-new NAME=` (from the main checkout) creates the outer worktree off `main`, initialises its own `user/` submodule, branches both, and starts an **isolated** dev server (own container name + auto-assigned port `8090+`, persisted in a git-ignored `.worktree-env` so every `make`/compose command in that worktree targets its own server). `make worktree-rm NAME=` tears it down cleanly (compose down → `submodule deinit` → `worktree remove` → `prune`) — skipping the deinit is what leaves orphaned `.worktrees/` dirs. Worktrees live under `.worktrees/` (excluded via `.git/info/exclude`). A fresh worktree's `user/` is empty until the submodule init runs, and `M user`/`m user` is normal (see above) — do not "fix" either. To add a commit to `main` while the main checkout is on another branch, use a throwaway `main` worktree rather than `git checkout main`. ## 1. Environment modes diff --git a/Makefile b/Makefile index d885d7e..dfa6462 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,12 @@ # for ${UID}/${GID} substitution and the travel-memories env_file.) -include .env +# Per-worktree dev-server identity, written by `make worktree-new` into the new +# worktree only (git-ignored). Absent in the main checkout, so the defaults below +# apply there. Loaded here so every local target + compose call in a worktree +# targets that worktree's own container and ports. +-include .worktree-env + # Remote config — loaded only when targeting an environment. ENV is set # automatically by the env-suffixed remote targets (e.g. `make remote-install-prod`); # each .env. holds a full, self-contained set of remote vars. @@ -49,7 +55,7 @@ GRAV_TEST_USER ?= testrunner GRAV_TEST_PASS ?= Testpass1234 test-account: - @docker exec intotheeast_grav sh -c 'test -f /var/www/html/user/accounts/$(GRAV_TEST_USER).yaml \ + @docker exec $(GRAV_CONTAINER) sh -c 'test -f /var/www/html/user/accounts/$(GRAV_TEST_USER).yaml \ || php bin/plugin login new-user -u $(GRAV_TEST_USER) -p "$(GRAV_TEST_PASS)" \ -e $(GRAV_TEST_USER)@example.test -N "Test Runner" -P b --admin-type both -s enabled -n' @@ -66,6 +72,13 @@ test: test-config test-post test-ui # ── Local dev ────────────────────────────────────────────────────────────────── +# Dev-server identity. Defaults are the main checkout's canonical values; a +# worktree's .worktree-env (above) overrides them so servers never collide. +# Exported (via the top-of-file `export`) so `docker compose` picks them up. +GRAV_CONTAINER ?= intotheeast_grav +GRAV_PORT ?= 8081 +TM_PORT ?= 8082 + build: docker compose build @@ -78,19 +91,24 @@ build-assets: start: docker compose up -d +# Grav service only — used by `make worktree-new` (a worktree rarely needs the +# travel-memories service, and this keeps its footprint minimal). +start-grav: + docker compose up -d grav + stop: docker compose down setup: build start install-plugins fix-perms fix-perms: - docker exec intotheeast_grav bash -c "getent passwd 1000 > /dev/null || useradd -u 1000 -M hostuser" - docker exec intotheeast_grav chown -R 1000:1000 /var/www/html - docker exec intotheeast_grav apachectl graceful + docker exec $(GRAV_CONTAINER) bash -c "getent passwd 1000 > /dev/null || useradd -u 1000 -M hostuser" + docker exec $(GRAV_CONTAINER) chown -R 1000:1000 /var/www/html + docker exec $(GRAV_CONTAINER) apachectl graceful install-plugins: - docker exec -w /var/www/html intotheeast_grav php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y + docker exec -w /var/www/html $(GRAV_CONTAINER) php bin/gpm install $(shell cat plugins.txt | tr '\n' ' ') -y $(MAKE) apply-plugin-patches # Re-apply local fixes to git-ignored, GPM-managed third-party plugins. Run this @@ -105,13 +123,54 @@ apply-plugin-patches: fi; \ done +# ── Worktrees ───────────────────────────────────────────────────────────────── +# Isolated outer-repo worktree + its own user/ submodule checkout + its own dev +# server (distinct container name & ports), for long-running feature work that +# runs in parallel with the main checkout without collisions. Encodes the full +# SOP from docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md +# so no step (submodule init, per-server isolation, clean teardown) is skipped. +# +# make worktree-new NAME=my-feature [PORT=8090] # create branch + start server +# make worktree-rm NAME=my-feature # tear down cleanly +# +# Run both from the MAIN checkout. After worktree-new, `cd .worktrees/` +# and use make as normal — it targets that worktree's own server automatically. + +WT_DIR = .worktrees/$(NAME) + +guard-name: + @test -n "$(NAME)" || { echo "ERROR: set NAME=, e.g. 'make worktree-new NAME=my-feature'."; exit 1; } + +worktree-new: guard-name + @test ! -e "$(WT_DIR)" || { echo "ERROR: $(WT_DIR) already exists."; exit 1; } + git worktree add "$(WT_DIR)" -b feat/$(NAME) main + git -C "$(WT_DIR)" submodule update --init user + git -C "$(WT_DIR)/user" checkout -b feat/$(NAME) + @port=$${PORT:-$$(for p in $$(seq 8090 8099); do \ + docker ps --format '{{.Ports}}' | grep -q ":$$p->" || { echo $$p; break; }; \ + done)}; \ + test -n "$$port" || { echo "ERROR: no free port in 8090-8099; pass PORT= explicitly."; exit 1; }; \ + printf 'COMPOSE_PROJECT_NAME=itte-%s\nGRAV_CONTAINER=itte_%s_grav\nGRAV_PORT=%s\nTM_PORT=%s\n' \ + "$(NAME)" "$(NAME)" "$$port" "$$((port + 100))" > "$(WT_DIR)/.worktree-env"; \ + echo "→ starting this worktree's Grav dev server on http://localhost:$$port"; \ + $(MAKE) -C "$(WT_DIR)" start-grav + @echo "Worktree ready: $(WT_DIR) (outer + user/ on branch feat/$(NAME))" + +worktree-rm: guard-name + @test -e "$(WT_DIR)" || { echo "ERROR: $(WT_DIR) does not exist."; exit 1; } + -$(MAKE) -C "$(WT_DIR)" stop + -git -C "$(WT_DIR)" submodule deinit -f user + git worktree remove --force "$(WT_DIR)" + git worktree prune + @echo "Removed $(WT_DIR). If feat/$(NAME) is merged, drop it: git branch -d feat/$(NAME)" + # ── Demo content ────────────────────────────────────────────────────────────── demo-load: # Load every fixture trip under docs/demo/trips/ into the pages tree. # Source uses dailies/ + 04.stories/; dailies/ maps to 01.dailies/ on copy. # All copies are `|| true` so a fixture absent from an older user/ is skipped. - docker exec intotheeast_grav bash -c 'for src in /var/www/html/user/docs/demo/trips/*/; do \ + docker exec $(GRAV_CONTAINER) bash -c 'for src in /var/www/html/user/docs/demo/trips/*/; do \ slug=$$(basename "$$src"); dst=/var/www/html/user/pages/01.trips/$$slug; \ mkdir -p "$$dst/01.dailies" "$$dst/04.stories"; \ cp "$$src/trip.md" "$$dst/trip.md" 2>/dev/null || true; \ @@ -123,15 +182,15 @@ demo-load: done; cd /var/www/html && php bin/grav clearcache' demo-reset: - docker exec intotheeast_grav bash -c 'for src in /var/www/html/user/docs/demo/trips/*/; do \ + docker exec $(GRAV_CONTAINER) bash -c 'for src in /var/www/html/user/docs/demo/trips/*/; do \ rm -rf /var/www/html/user/pages/01.trips/$$(basename "$$src"); \ done; cd /var/www/html && php bin/grav clearcache' pixelfed-import: - docker exec intotheeast_grav bash -c "which python3 || apt-get install -y python3 --no-install-recommends -q" - docker cp /home/mischa/Nextcloud/Downloads/pixelfed/pixelfed-statuses.json intotheeast_grav:/tmp/pixelfed-statuses.json - docker cp scripts/pixelfed-import.py intotheeast_grav:/tmp/pixelfed-import.py - docker exec -w /var/www/html intotheeast_grav python3 /tmp/pixelfed-import.py + docker exec $(GRAV_CONTAINER) bash -c "which python3 || apt-get install -y python3 --no-install-recommends -q" + docker cp /home/mischa/Nextcloud/Downloads/pixelfed/pixelfed-statuses.json $(GRAV_CONTAINER):/tmp/pixelfed-statuses.json + docker cp scripts/pixelfed-import.py $(GRAV_CONTAINER):/tmp/pixelfed-import.py + docker exec -w /var/www/html $(GRAV_CONTAINER) python3 /tmp/pixelfed-import.py # ── Content sync (user repo ↔ Gitea) ────────────────────────────────────────── diff --git a/docker-compose.yml b/docker-compose.yml index 4e06171..1903a0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,15 @@ services: grav: build: . - container_name: intotheeast_grav + # Overridable so a git worktree can run its own isolated dev server (see + # `make worktree-new`); unset → the canonical main-checkout values below. + container_name: ${GRAV_CONTAINER:-intotheeast_grav} environment: - GRAV_CHANNEL=production - APACHE_RUN_USER=#1000 - APACHE_RUN_GROUP=#1000 ports: - - "8081:80" + - "${GRAV_PORT:-8081}:80" volumes: - ./user:/var/www/html/user - ./php/php-local.ini:/usr/local/etc/php/conf.d/php-local.ini @@ -16,7 +18,7 @@ services: travel-memories: build: ./services/travel-memories ports: - - "8082:8082" + - "${TM_PORT:-8082}:8082" volumes: - ./docs/immich-workflow:/app/state - ./user/pages:/app/pages