234 lines
8.4 KiB
YAML
234 lines
8.4 KiB
YAML
# Role: bunkerweb
|
|
#
|
|
# Deploy Docker Engine + BunkerWeb in front of the morphit
|
|
# indexer + relay per OPERATIONS.md §32 + §38.6 (network-layer
|
|
# squatter defense).
|
|
#
|
|
# Caveat: BunkerWeb's env-var names change between major versions.
|
|
# Verify against https://docs.bunkerweb.io at deploy time. This
|
|
# role targets the 1.5.x line.
|
|
|
|
---
|
|
- name: Install Docker prerequisites
|
|
ansible.builtin.apt:
|
|
name:
|
|
- apt-transport-https
|
|
- ca-certificates
|
|
- curl
|
|
- gnupg
|
|
- lsb-release
|
|
state: present
|
|
|
|
- name: Ensure /etc/apt/keyrings exists
|
|
# /etc/apt/keyrings is not present by default on Ubuntu 24.04; the morphit
|
|
# role's nodejs task also makes it, but ensuring it here removes the ordering
|
|
# dependency so bunkerweb is self-contained.
|
|
ansible.builtin.file:
|
|
path: /etc/apt/keyrings
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: '0755'
|
|
|
|
- name: Add Docker GPG key
|
|
# Skipped on a self-contained offline install: docker-ce/cli/containerd/buildx/
|
|
# compose all come from the bundled apt closure, so there is no need (and no way)
|
|
# to fetch Docker's repo key from the internet. The offline apt override ignores
|
|
# sources.list.d anyway, so the repo add below is moot offline too.
|
|
ansible.builtin.get_url:
|
|
url: https://download.docker.com/linux/ubuntu/gpg
|
|
dest: /etc/apt/keyrings/docker.asc
|
|
mode: '0644'
|
|
force: false
|
|
when: not morphit_offline_install
|
|
|
|
- name: Add Docker apt repo
|
|
ansible.builtin.apt_repository:
|
|
repo: "deb [arch={{ ansible_architecture | replace('x86_64', 'amd64') | replace('aarch64', 'arm64') }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ morphit_ubuntu_codename | default(ansible_distribution_release) }} stable"
|
|
filename: docker
|
|
state: present
|
|
when: not morphit_offline_install
|
|
|
|
- name: Install Docker Engine + compose plugin
|
|
ansible.builtin.apt:
|
|
name:
|
|
- docker-ce
|
|
- docker-ce-cli
|
|
- containerd.io
|
|
- docker-buildx-plugin
|
|
- docker-compose-plugin
|
|
state: present
|
|
update_cache: true
|
|
|
|
- name: Enable + start Docker
|
|
ansible.builtin.systemd:
|
|
name: docker
|
|
enabled: true
|
|
state: started
|
|
|
|
# cp663 #10 — the scheduler needs the Docker socket (root:docker 660);
|
|
# resolve the host's docker-group GID so the compose can group_add it to
|
|
# the scheduler container. The GID varies per host, so it must not be
|
|
# hard-coded.
|
|
- name: Resolve the host docker-group GID (for the scheduler's socket access)
|
|
ansible.builtin.getent:
|
|
database: group
|
|
key: docker
|
|
|
|
- name: Set bunkerweb_docker_gid
|
|
ansible.builtin.set_fact:
|
|
bunkerweb_docker_gid: "{{ getent_group['docker'][1] }}"
|
|
|
|
# ── Offline appliance: load bundled Docker images so compose doesn't pull. ──
|
|
# When the self-contained bundle ships saved images under vendor/docker, load
|
|
# them now (Docker is up, before compose) so "Bring BunkerWeb up" finds them
|
|
# locally and needs no Docker Hub. Gated on the bundle → a no-op online.
|
|
- name: Detect bundled Docker images
|
|
ansible.builtin.find:
|
|
paths: "{{ morphit_repo_path }}/vendor/docker"
|
|
patterns: "*.tar.gz"
|
|
register: morphit_vendor_docker
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: Load bundled Docker images (offline)
|
|
ansible.builtin.shell:
|
|
cmd: "set -o pipefail; gzip -dc {{ item.path | quote }} | docker load"
|
|
args:
|
|
executable: /bin/bash
|
|
loop: "{{ morphit_vendor_docker.files | default([]) }}"
|
|
loop_control:
|
|
label: "{{ item.path | basename }}"
|
|
when: (morphit_vendor_docker.files | default([]) | length) > 0
|
|
changed_when: true
|
|
|
|
- name: Create BunkerWeb config directory
|
|
ansible.builtin.file:
|
|
path: /etc/bunkerweb
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: '0750'
|
|
|
|
- name: Deploy BunkerWeb docker-compose file
|
|
ansible.builtin.template:
|
|
src: docker-compose.yml.j2
|
|
dest: /etc/bunkerweb/docker-compose.yml
|
|
owner: root
|
|
group: root
|
|
mode: '0640'
|
|
register: bunkerweb_compose
|
|
|
|
- name: Deploy BunkerWeb environment file
|
|
ansible.builtin.template:
|
|
src: bunkerweb.env.j2
|
|
dest: /etc/bunkerweb/bunkerweb.env
|
|
owner: root
|
|
group: root
|
|
mode: '0640'
|
|
register: bunkerweb_env
|
|
|
|
- name: Deploy frontend build context (serves the SvelteKit build + proxies the API)
|
|
ansible.builtin.copy:
|
|
src: "{{ morphit_repo_path }}/ops/bunkerweb/frontend/"
|
|
dest: /etc/bunkerweb/frontend/
|
|
remote_src: true
|
|
owner: root
|
|
group: root
|
|
mode: '0640'
|
|
directory_mode: '0750'
|
|
register: bunkerweb_frontend
|
|
|
|
# §32 — the frontend container reaches the host-resident relay +
|
|
# indexer from the Docker bridge, but UFW's default-deny (hardening
|
|
# role) blocks that traffic. Allow ONLY the bunkerweb_net CIDR to the
|
|
# service ports; the public stays blocked. Keep this CIDR in sync
|
|
# with the docker-compose subnet (172.20.0.0/16).
|
|
- name: Allow the bunkerweb Docker network to reach the relay + indexer
|
|
community.general.ufw:
|
|
rule: allow
|
|
src: 172.20.0.0/16
|
|
port: "{{ item }}"
|
|
proto: tcp
|
|
loop:
|
|
- "{{ morphit_relay_bind_port }}"
|
|
- "{{ morphit_indexer_bind_port }}"
|
|
|
|
# §45 — same as above, for the MCP HTTP transport, but only when the MCP
|
|
# is enabled (it's the most exposed surface, so don't open the port for
|
|
# a service that isn't running). The frontend nginx proxies /mcp here.
|
|
- name: Allow the bunkerweb Docker network to reach the MCP
|
|
community.general.ufw:
|
|
rule: allow
|
|
src: 172.20.0.0/16
|
|
port: "{{ morphit_mcp_bind_port }}"
|
|
proto: tcp
|
|
when: morphit_mcp_enabled | bool
|
|
|
|
- name: Bring BunkerWeb up
|
|
community.docker.docker_compose_v2:
|
|
project_src: /etc/bunkerweb
|
|
state: present
|
|
build: always
|
|
when: bunkerweb_compose.changed or bunkerweb_env.changed or bunkerweb_frontend.changed
|
|
|
|
- name: Verify the update surface is served no-cache (stale-worker guard)
|
|
# A missing no-cache header on /service-worker.js + /verify.json lets an edge
|
|
# cache pin a stale service worker on visitors' devices, so they stop receiving
|
|
# deploys until they hard-refresh. The shipped frontend config sets no-cache;
|
|
# this confirms it actually reached the wire. Non-fatal (a not-yet-ready site
|
|
# shouldn't abort the run) but it reports loudly so the operator can't miss it.
|
|
ansible.builtin.shell: >-
|
|
set -o pipefail;
|
|
curl -fsSI "https://{{ morphit_domain }}/verify.json"
|
|
| grep -i '^cache-control:' | grep -qi 'no-cache'
|
|
args:
|
|
executable: /bin/bash
|
|
register: morphit_nocache_check
|
|
changed_when: false
|
|
failed_when: false
|
|
retries: 10
|
|
delay: 6
|
|
until: morphit_nocache_check.rc == 0
|
|
# Tor-only: no clearnet https://<domain> to curl — the same no-cache frontend
|
|
# config is served via the onion; skip the clearnet verification.
|
|
when: >-
|
|
(bunkerweb_compose.changed or bunkerweb_env.changed or bunkerweb_frontend.changed)
|
|
and not (morphit_tor_only | default(false))
|
|
|
|
- name: Report update-surface no-cache result
|
|
ansible.builtin.debug:
|
|
msg: >-
|
|
{{ '✓ /verify.json is served no-cache — the update surface is fresh; visitors will auto-receive deploys.'
|
|
if morphit_nocache_check.rc == 0
|
|
else '✗ WARNING: could not confirm /verify.json is no-cache. If the site is up,
|
|
visitors may be stuck on a stale service worker and stop receiving deploys.
|
|
Ensure the server config that serves the build carries the no-cache blocks for
|
|
/service-worker.js and /verify.json, then re-check with:
|
|
curl -sI https://' ~ morphit_domain ~ '/verify.json | grep -i cache-control
|
|
— see docs/OPERATIONS.md §"Caching the update surface".' }}
|
|
when: >-
|
|
(bunkerweb_compose.changed or bunkerweb_env.changed or bunkerweb_frontend.changed)
|
|
and not (morphit_tor_only | default(false))
|
|
|
|
- name: Print BunkerWeb verification reminder
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
BunkerWeb container started. Verify:
|
|
|
|
docker ps | grep -E 'bunkerweb|morphit-frontend'
|
|
docker logs bunkerweb --tail 50
|
|
docker logs morphit-frontend --tail 50
|
|
curl -v https://{{ morphit_domain }}/ # SvelteKit app
|
|
curl -v https://{{ morphit_domain }}/v1/instance # indexer JSON
|
|
|
|
If the relay sees every user as the BunkerWeb Docker IP
|
|
(rate limits look weird), the trusted-proxy IPs setting
|
|
in /etc/morphit/relay.env is wrong. Verify it matches
|
|
the Docker network CIDR:
|
|
|
|
docker network inspect bunkerweb_net --format \
|
|
'{% raw %}{{ range .IPAM.Config }}{{ .Subnet }}{{ end }}{% endraw %}'
|
|
|
|
Whatever that prints MUST appear in
|
|
MORPHIT_RELAY_TRUSTED_PROXY_IPS.
|