Caddy: adopt dual-role snippet (PB api+admin on main host), relative PB file urls

- www host: /_* and /api/* proxy to pocketbase :8090, everything else to
  the TanStack app; redir /_ /_/ for the admin path
- cms host unchanged: redir / -> /_/
- migration: legacy per-host marker blocks are removed before the new
  combined block is installed
- cms.ts: PB file urls are now same-origin relative (/api/files/...) —
  browsers could never load the old 127.0.0.1:8090 urls; validated the
  rendered Caddyfile with caddy:2
This commit is contained in:
2026-09-06 14:22:09 +05:30
parent f5d5dcac94
commit c4a6425926
5 changed files with 39 additions and 31 deletions
+1 -13
View File
@@ -1,15 +1,3 @@
--- ---
- name: Read current Caddyfile - name: Install mozimo Caddy snippet (web + cms)
ansible.builtin.slurp:
path: "{{ caddyfile_path }}"
register: caddyfile_raw
- name: Decode Caddyfile content
set_fact:
caddyfile_content: "{{ caddyfile_raw['content'] | b64decode }}"
- name: Add Caddy snippets for each published host
ansible.builtin.include_tasks: snippet.yaml ansible.builtin.include_tasks: snippet.yaml
loop: "{{ caddy_hosts }}"
loop_control:
loop_var: snippet
+15 -5
View File
@@ -1,12 +1,22 @@
--- ---
- name: Build Caddy snippet block for {{ snippet.host }} - name: Read snippet from template
ansible.builtin.set_fact: ansible.builtin.set_fact:
snippet_block: "{{ lookup('ansible.builtin.template', 'snippet.j2') }}" caddy_snippet_block: "{{ lookup('ansible.builtin.template', 'snippet.j2') }}"
- name: Install snippet block for {{ snippet.host }} # migrate away from the older per-host marker blocks (pre dual-role snippet)
- name: Remove legacy per-host snippet blocks
ansible.builtin.blockinfile: ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}" path: "{{ caddyfile_path }}"
marker: "# {mark} Ansible managed Caddy snippet for {{ snippet.host }}" marker: "# {mark} Ansible managed Caddy snippet for {{ item }}"
block: "{{ snippet_block }}" state: absent
loop:
- "{{ www_host }}"
- "{{ cms_host }}"
- name: Install mozimo Caddy snippet (web + cms)
ansible.builtin.blockinfile:
path: "{{ caddyfile_path }}"
marker: "# {mark} Ansible managed Caddy snippet (mozimo web + cms)"
block: "{{ caddy_snippet_block }}"
insertafter: EOF insertafter: EOF
notify: "Reload Caddy" notify: "Reload Caddy"
+18 -4
View File
@@ -1,6 +1,20 @@
{{ snippet.host }} { {{ www_host }} {
{% if snippet.pb_root | default(false) %} # PocketBase Admin UI and REST API (files included)
redir /_ /_/
handle /_/* {
reverse_proxy {{ docker_container }}:{{ cms_port }}
}
handle /api/* {
reverse_proxy {{ docker_container }}:{{ cms_port }}
}
# TanStack Start frontend
handle {
reverse_proxy {{ docker_container }}:{{ www_port }}
}
}
{{ cms_host }} {
redir / /_/ redir / /_/
{% endif %} reverse_proxy {{ docker_container }}:{{ cms_port }}
reverse_proxy {{ snippet.upstream }}
} }
+1 -7
View File
@@ -20,13 +20,7 @@ docker_network: "{{ title }}_net"
docker_image: "{{ registry }}/{{ title }}:{{ image_tag | default(tag) }}" docker_image: "{{ registry }}/{{ title }}:{{ image_tag | default(tag) }}"
docker_container: "{{ title }}-staging" docker_container: "{{ title }}-staging"
# hosts Caddy publishes on this box: web (SSR) + pocketbase (cms) # caddy publishes www (SSR + /api/* + /_/* -> pocketbase) and cms (admin)
caddy_hosts:
- host: "{{ www_host }}"
upstream: "{{ docker_container }}:{{ www_port }}"
- host: "{{ cms_host }}"
upstream: "{{ docker_container }}:{{ cms_port }}"
pb_root: true # dashboard at / (Caddy redirs / -> /_/)
caddy_container: caddy caddy_container: caddy
caddyfile_path: /var/lib/caddy/conf/Caddyfile caddyfile_path: /var/lib/caddy/conf/Caddyfile
+3 -1
View File
@@ -63,9 +63,11 @@ async function pbList<T extends PbRecord>(
} }
} }
// same-origin relative url: caddy routes /api/* to pocketbase on every
// public host, so browsers can load cms-uploaded files directly
const fileUrl = (r: PbRecord, filename: unknown) => const fileUrl = (r: PbRecord, filename: unknown) =>
typeof filename === "string" && filename typeof filename === "string" && filename
? `${PB_URL}/api/files/${r.collectionId}/${r.id}/${filename}` ? `/api/files/${r.collectionId}/${r.id}/${filename}`
: null; : null;
const str = (v: unknown) => (typeof v === "string" ? v.trim() : ""); const str = (v: unknown) => (typeof v === "string" ? v.trim() : "");