diff --git a/Makefile b/Makefile index 0326d7b..4928443 100755 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all clean norm author author-install author-start author-stop author-restart author-status author-logs author-health help +.PHONY: all clean norm test author author-install author-start author-stop author-stop-legacy author-restart author-status author-logs author-health author-diagnostics help VENV := .venv PY := $(VENV)/bin/python @@ -12,6 +12,10 @@ AUTHOR_SERVICE := org-web-authoring.service AUTHOR_UNIT_SRC := systemd/user/$(AUTHOR_SERVICE) SYSTEMD_USER_DIR := $(HOME)/.config/systemd/user AUTHOR_UNIT_DST := $(SYSTEMD_USER_DIR)/$(AUTHOR_SERVICE) +USER_ID := $(shell id -u) +SYSTEMD_RUNTIME_DIR ?= /run/user/$(USER_ID) +SYSTEMCTL_USER := env XDG_RUNTIME_DIR=$(SYSTEMD_RUNTIME_DIR) systemctl --user +JOURNALCTL_USER := env XDG_RUNTIME_DIR=$(SYSTEMD_RUNTIME_DIR) journalctl --user all: set-perms clean-output build search @@ -27,6 +31,9 @@ search: $(VENV) @echo "Generating search index" $(PY) search-index-json.py +test: $(VENV) + $(PY) -m unittest discover -s tests -p 'test_authoring_server.py' -v + $(VENV): @echo "Generating virtual environment" python3 -m venv $(VENV) @@ -50,31 +57,36 @@ author-install: $(VENV) @echo "Installing $(AUTHOR_SERVICE) for the current user..." @mkdir -p "$(SYSTEMD_USER_DIR)" install -m 0644 "$(AUTHOR_UNIT_SRC)" "$(AUTHOR_UNIT_DST)" - systemctl --user daemon-reload - systemctl --user enable "$(AUTHOR_SERVICE)" + $(SYSTEMCTL_USER) daemon-reload + $(SYSTEMCTL_USER) enable "$(AUTHOR_SERVICE)" -author-start: author-install +author-start: author-install author-stop-legacy @echo "Starting authoring UI with systemd..." - systemctl --user start "$(AUTHOR_SERVICE)" + $(SYSTEMCTL_USER) start "$(AUTHOR_SERVICE)" @$(MAKE) --no-print-directory author-health author-stop: @echo "Stopping authoring UI with systemd..." - systemctl --user stop "$(AUTHOR_SERVICE)" || true + $(SYSTEMCTL_USER) stop "$(AUTHOR_SERVICE)" || true + @$(MAKE) --no-print-directory author-stop-legacy -author-restart: author-install +author-stop-legacy: + @echo "Stopping any legacy process on authoring port $(AUTHOR_PORT)..." + @fuser -k $(AUTHOR_PORT)/tcp >/dev/null 2>&1 || true + +author-restart: author-install author-stop-legacy @echo "Restarting authoring UI with systemd..." - systemctl --user restart "$(AUTHOR_SERVICE)" + $(SYSTEMCTL_USER) restart "$(AUTHOR_SERVICE)" @$(MAKE) --no-print-directory author-health author-health: @echo "Waiting for authoring UI and page list to respond..." @for i in 1 2 3 4 5 6 7 8 9 10; do \ - if ! systemctl --user is-active --quiet "$(AUTHOR_SERVICE)"; then \ + if ! $(SYSTEMCTL_USER) is-active --quiet "$(AUTHOR_SERVICE)"; then \ echo "Authoring UI service is not active. Service status:"; \ - systemctl --user status --no-pager "$(AUTHOR_SERVICE)" || true; \ + $(SYSTEMCTL_USER) status --no-pager "$(AUTHOR_SERVICE)" || true; \ echo "Recent logs:"; \ - journalctl --user -u "$(AUTHOR_SERVICE)" -n 80 --no-pager || true; \ + $(JOURNALCTL_USER) -u "$(AUTHOR_SERVICE)" -n 80 --no-pager || true; \ exit 1; \ fi; \ if curl -fsS "$(AUTHOR_URL)/api/pages" -o "$(AUTHOR_PAGES_CHECK)" && $(PY) -c 'import json,sys; data=json.load(sys.stdin); sys.exit(0 if isinstance(data, list) and data else 1)' < "$(AUTHOR_PAGES_CHECK)"; then \ @@ -84,17 +96,20 @@ author-health: sleep 1; \ done; \ echo "Authoring UI failed to respond. Service status:"; \ - systemctl --user status --no-pager "$(AUTHOR_SERVICE)" || true; \ + $(SYSTEMCTL_USER) status --no-pager "$(AUTHOR_SERVICE)" || true; \ echo "Recent logs:"; \ - journalctl --user -u "$(AUTHOR_SERVICE)" -n 80 --no-pager || true; \ + $(JOURNALCTL_USER) -u "$(AUTHOR_SERVICE)" -n 80 --no-pager || true; \ exit 1 author-status: - @systemctl --user status --no-pager "$(AUTHOR_SERVICE)" || true + @$(SYSTEMCTL_USER) status --no-pager "$(AUTHOR_SERVICE)" || true @curl -fsS "$(AUTHOR_URL)/api/pages" -o "$(AUTHOR_PAGES_CHECK)" && $(PY) -c 'import json,sys; print(f"Pages: {len(json.load(sys.stdin))}")' < "$(AUTHOR_PAGES_CHECK)" || true +author-diagnostics: + @curl -fsS "$(AUTHOR_URL)/api/diagnostics" || true + author-logs: - journalctl --user -u "$(AUTHOR_SERVICE)" -n 100 --no-pager + $(JOURNALCTL_USER) -u "$(AUTHOR_SERVICE)" -n 100 --no-pager help: @echo "Available targets:" @@ -104,11 +119,13 @@ help: @echo " make clean-output - Remove output directory" @echo " make clean-venv - Remove venv directory" @echo " make norm - Move backup files" + @echo " make test - Run authoring server tests" @echo " make author - Install and start the authoring UI service" @echo " make author-install - Install and enable the authoring UI service" @echo " make author-stop - Stop the authoring UI service" @echo " make author-restart - Restart the authoring UI service" @echo " make author-status - Show authoring UI service status and page count" + @echo " make author-diagnostics - Show authoring UI runtime diagnostics" @echo " make author-logs - Show recent authoring UI service logs" @echo " make search - Create the search index" @echo " make help - Show this help message" diff --git a/authoring_server.py b/authoring_server.py index ebfd007..4189957 100755 --- a/authoring_server.py +++ b/authoring_server.py @@ -399,9 +399,13 @@ def page_to_dict(page: ContentPage) -> dict[str, Any]: def server_diagnostics() -> dict[str, Any]: pages = list_pages() + try: + cwd = Path.cwd().as_posix() + except OSError as exc: + cwd = f"" return { "root": ROOT.as_posix(), - "cwd": Path.cwd().as_posix(), + "cwd": cwd, "executable": sys.executable, "pid": os.getpid(), "pageCount": len(pages), diff --git a/posts/posts-list.org b/posts/posts-list.org index 6dbec2e..f1a4b79 100755 --- a/posts/posts-list.org +++ b/posts/posts-list.org @@ -4,7 +4,7 @@ See the categories: @@html:Categories@@ * Posts: -- [[file:career/career-list.org][Career List]] @@html:07-05-2026 23:44@@ +- [[file:career/career-list.org][Career List]] @@html:07-05-2026 23:53@@ - [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@ - [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:@@ @@html:@@ - [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:@@ @@html:@@ diff --git a/sitemap.org b/sitemap.org index 2789160..22a8fe4 100755 --- a/sitemap.org +++ b/sitemap.org @@ -11,10 +11,10 @@ - [[file:tags/notes.org][Tag: notes]] - [[file:tags/review.org][Tag: review]] - [[file:tags/website.org][Tag: website]] - - [[file:tags/update.org][Tag: update]] - [[file:tags/life.org][Tag: life]] - - [[file:tags/education.org][Tag: education]] + - [[file:tags/update.org][Tag: update]] - [[file:tags/insights.org][Tag: insights]] + - [[file:tags/education.org][Tag: education]] - [[file:tags/reading.org][Tag: reading]] - [[file:tags/emacs.org][Tag: emacs]] - [[file:tags/maths.org][Tag: maths]] diff --git a/systemd/user/org-web-authoring.service b/systemd/user/org-web-authoring.service index 568b3c4..133cabc 100644 --- a/systemd/user/org-web-authoring.service +++ b/systemd/user/org-web-authoring.service @@ -8,6 +8,7 @@ WorkingDirectory=/home/zaine/master-folder/org_files/org_web Environment=AUTHOR_ROOT=/home/zaine/master-folder/org_files/org_web Environment=AUTHOR_PORT=8765 Environment=PYTHONUNBUFFERED=1 +ExecStartPre=/home/zaine/master-folder/org_files/org_web/.venv/bin/python -c "import authoring_server as s; pages=s.list_pages(); print(f'Authoring root: {s.ROOT}'); print(f'Editable pages: {len(pages)}'); raise SystemExit(0 if pages else 1)" ExecStart=/home/zaine/master-folder/org_files/org_web/.venv/bin/python -u /home/zaine/master-folder/org_files/org_web/authoring_server.py Restart=on-failure RestartSec=5 diff --git a/tests/test_authoring_server.py b/tests/test_authoring_server.py index fe11075..69efabb 100755 --- a/tests/test_authoring_server.py +++ b/tests/test_authoring_server.py @@ -207,6 +207,13 @@ class PageRenderingTests(AuthoringServerTestCase): self.assertEqual(diagnostics["pageCount"], 1) self.assertEqual(diagnostics["firstPage"], "blogs/keep.org") + def test_server_diagnostics_handles_missing_cwd(self): + with mock.patch.object(server.Path, "cwd", side_effect=FileNotFoundError("missing cwd")): + diagnostics = server.server_diagnostics() + + self.assertIn("unavailable", diagnostics["cwd"]) + self.assertEqual(diagnostics["pageCount"], 0) + def test_relative_asset_path_is_calculated_from_lima_page_directory(self): self.assertEqual( server.relative_asset_path("lima/family/update.md", "assets/images/hzone/pic.png"),