diff --git a/devserver/ansible/deploy-svc-caddy.yaml b/devserver/ansible/deploy-svc-caddy.yaml new file mode 100644 index 0000000..2e02219 --- /dev/null +++ b/devserver/ansible/deploy-svc-caddy.yaml @@ -0,0 +1,45 @@ +--- +# Lightweight deploy: pull the latest caddy image and restart the +# caddy systemd service — without re-applying the whole playbook. +# +# Caddy has no infisical secrets, so a plain `docker compose pull` works +# directly. We pull explicitly first so a registry/network failure +# surfaces *before* the running container is taken down. +# +# Run from devserver/ansible/: +# ansible-playbook deploy-svc-caddy.yaml + +- name: Pull latest caddy image and restart service + hosts: devserver + become: true + vars_files: + - roles/svc-caddy/defaults/main.yaml + + tasks: + - name: Pull latest caddy image + ansible.builtin.command: + cmd: "{{ caddy_deploy_dir }}/run.sh pull" + chdir: "{{ caddy_deploy_dir }}" + changed_when: true + register: _svc_caddy_pull + + - name: Show pull output + ansible.builtin.debug: + var: _svc_caddy_pull.stdout_lines + + - name: Restart caddy.service + ansible.builtin.systemd: + name: caddy.service + state: restarted + daemon_reload: true + + - name: Show caddy service status + ansible.builtin.command: + cmd: systemctl --no-pager status caddy.service + changed_when: false + failed_when: false + register: _svc_caddy_status + + - name: Print service status + ansible.builtin.debug: + var: _svc_caddy_status.stdout_lines diff --git a/devserver/ansible/deploy-svc-dozzle.yaml b/devserver/ansible/deploy-svc-dozzle.yaml new file mode 100644 index 0000000..76c9d63 --- /dev/null +++ b/devserver/ansible/deploy-svc-dozzle.yaml @@ -0,0 +1,45 @@ +--- +# Lightweight deploy: pull the latest dozzle image and restart the +# svc-dozzle systemd service — without re-applying the whole playbook. +# +# Pull goes through run.sh so it runs in the same infisical context as +# the service (run.sh renders users.yml from the infisical-injected +# DOZZLE_PASSWORD_HASH before starting compose). +# +# Run from devserver/ansible/: +# ansible-playbook deploy-svc-dozzle.yaml + +- name: Pull latest dozzle image and restart service + hosts: devserver + become: true + vars_files: + - roles/svc-dozzle/defaults/main.yaml + + tasks: + - name: Pull latest dozzle image + ansible.builtin.command: + cmd: "{{ svc_dozzle_deploy_dir }}/run.sh pull" + chdir: "{{ svc_dozzle_deploy_dir }}" + changed_when: true + register: _svc_dozzle_pull + + - name: Show pull output + ansible.builtin.debug: + var: _svc_dozzle_pull.stdout_lines + + - name: Restart svc-dozzle.service + ansible.builtin.systemd: + name: svc-dozzle.service + state: restarted + daemon_reload: true + + - name: Show svc-dozzle service status + ansible.builtin.command: + cmd: systemctl --no-pager status svc-dozzle.service + changed_when: false + failed_when: false + register: _svc_dozzle_status + + - name: Print service status + ansible.builtin.debug: + var: _svc_dozzle_status.stdout_lines diff --git a/devserver/ansible/roles/docker-network/defaults/main.yaml b/devserver/ansible/roles/docker-network/defaults/main.yaml new file mode 100644 index 0000000..33c5a21 --- /dev/null +++ b/devserver/ansible/roles/docker-network/defaults/main.yaml @@ -0,0 +1,4 @@ +--- +# docker-network defaults. + +docker_network_name: devserver-ingress diff --git a/devserver/ansible/roles/docker-network/tasks/main.yaml b/devserver/ansible/roles/docker-network/tasks/main.yaml new file mode 100644 index 0000000..207150d --- /dev/null +++ b/devserver/ansible/roles/docker-network/tasks/main.yaml @@ -0,0 +1,16 @@ +--- +# docker-network — create the shared external Docker network that every +# service (caddy, events, bot, dozzle) joins for internal routing. +# Created here (not per-service) so role order doesn't matter among +# services: only `docker` must run before this. + +- name: Check if devserver-ingress network exists + ansible.builtin.command: docker network inspect {{ docker_network_name }} + register: _docker_network_check + changed_when: false + failed_when: false + +- name: Create devserver-ingress network + ansible.builtin.command: docker network create {{ docker_network_name }} + when: _docker_network_check.rc != 0 + changed_when: true diff --git a/devserver/ansible/roles/svc-bot/defaults/main.yaml b/devserver/ansible/roles/svc-bot/defaults/main.yaml index af54d75..aaf6133 100644 --- a/devserver/ansible/roles/svc-bot/defaults/main.yaml +++ b/devserver/ansible/roles/svc-bot/defaults/main.yaml @@ -12,6 +12,3 @@ svc_bot_deploy_dir: /opt/devict/bot # Container image. Placeholder until the bot repo publishes # (buoy.cr/devict.org/bot:latest). svc_bot_image: "buoy.cr/devict.org/bot:latest" - -# Host port the bot container publishes on (events owns 3001). -svc_bot_host_port: "3002" diff --git a/devserver/ansible/roles/svc-bot/templates/compose.yaml.j2 b/devserver/ansible/roles/svc-bot/templates/compose.yaml.j2 index 607b2f4..fcb6a4f 100644 --- a/devserver/ansible/roles/svc-bot/templates/compose.yaml.j2 +++ b/devserver/ansible/roles/svc-bot/templates/compose.yaml.j2 @@ -2,9 +2,13 @@ services: bot: image: {{ svc_bot_image }} restart: unless-stopped - ports: - - "{{ svc_bot_host_port }}:3000" environment: SLACK_SIGNING_SECRET: ${SLACK_SIGNING_SECRET} SLACK_TOKEN: ${SLACK_TOKEN} SLACK_ADMIN_TOKEN: ${SLACK_ADMIN_TOKEN} + networks: + - devserver-ingress + +networks: + devserver-ingress: + external: true diff --git a/devserver/ansible/roles/svc-caddy/defaults/main.yaml b/devserver/ansible/roles/svc-caddy/defaults/main.yaml index 3ab556d..c2dcfb1 100644 --- a/devserver/ansible/roles/svc-caddy/defaults/main.yaml +++ b/devserver/ansible/roles/svc-caddy/defaults/main.yaml @@ -1,9 +1,33 @@ --- -# svc-caddy defaults. +# svc-caddy — Caddy as a docker compose service (reverse proxy / TLS +# terminator for *.devict.org). Replaces the prior native apt install so +# its logs surface in Dozzle and it shares the devserver-ingress network +# with the other services. +# +# Existing Let's Encrypt certs + ACME account under +# caddy_native_data_dir are migrated (one-time) into caddy_data_dir, +# which the container mounts at /data — Caddy keeps using them, no +# re-issuance. + +caddy_deploy_dir: /opt/devict/caddy +caddy_data_dir: "{{ caddy_deploy_dir }}/data" +caddy_config_dir: "{{ caddy_deploy_dir }}/config" + +# Compose container name (project = deploy dir basename, service = caddy). +caddy_container_name: caddy-caddy-1 + +# Where the native apt caddy stored certs/account (pre-migration source). +caddy_native_data_dir: /var/lib/caddy/.local/share/caddy + +caddy_image: caddy:latest caddy_email: "{{ lookup('env', 'CADDY_EMAIL') }}" +# Hostnames. caddy_events_hostname: events.devict.org -caddy_events_upstream: "localhost:3001" - caddy_bot_hostname: bot.devict.org -caddy_bot_upstream: "localhost:3002" +caddy_logs_hostname: logs.devict.org + +# Upstreams are container names on the devserver-ingress network. +caddy_events_upstream: "events:3000" +caddy_bot_upstream: "bot:3000" +caddy_logs_upstream: "dozzle:8080" diff --git a/devserver/ansible/roles/svc-caddy/handlers/main.yaml b/devserver/ansible/roles/svc-caddy/handlers/main.yaml index 6dc8cc8..c14de3b 100644 --- a/devserver/ansible/roles/svc-caddy/handlers/main.yaml +++ b/devserver/ansible/roles/svc-caddy/handlers/main.yaml @@ -1,12 +1,19 @@ --- +# Graceful config reload — zero-downtime, no container recreate. +# Used for Caddyfile-only changes (avoids the stop/start port race that +# a full restart can hit). compose.yaml / unit changes use `restart caddy`. +# If a restart is also queued, the container may be mid-recreate when +# this runs; failed_when: false makes that a no-op rather than a failure. - name: reload caddy become: true - ansible.builtin.systemd: - name: caddy.service - state: reloaded + ansible.builtin.command: + cmd: docker exec {{ caddy_container_name }} caddy reload --adapter caddyfile --config /etc/caddy/Caddyfile + changed_when: true + failed_when: false - name: restart caddy become: true ansible.builtin.systemd: name: caddy.service state: restarted + daemon_reload: true diff --git a/devserver/ansible/roles/svc-caddy/tasks/main.yaml b/devserver/ansible/roles/svc-caddy/tasks/main.yaml index 253102c..c8bcc69 100644 --- a/devserver/ansible/roles/svc-caddy/tasks/main.yaml +++ b/devserver/ansible/roles/svc-caddy/tasks/main.yaml @@ -1,46 +1,156 @@ --- -# svc-caddy — install Caddy (native apt) and serve as the devserver's -# reverse proxy / TLS terminator. Caddy ships its own systemd unit; this -# role only adds the repo, installs the package, renders the Caddyfile, -# and ensures the service is enabled + running. +# svc-caddy — run Caddy as a docker compose service, mirroring the +# svc-events/svc-bot pattern (deploy dir + compose.yaml + run.sh + +# systemd unit). Replaces the prior native apt install so Caddy's logs +# show up in Dozzle and it shares the devserver-ingress network. +# +# Existing Let's Encrypt certs + ACME account are copied from the native +# data dir into the container's /data volume so Caddy reuses them. +# +# Prerequisites (satisfied by playbook role order): +# - docker role: docker + compose plugin installed +# - docker-network role: devserver-ingress network exists -- name: install caddy apt repository (deb822) - become: true - ansible.builtin.deb822_repository: - name: caddy - types: deb - uris: https://dl.cloudsmith.io/public/caddy/stable/deb/debian - suites: any-version - components: main - signed_by: https://dl.cloudsmith.io/public/caddy/stable/gpg.key - state: present +# --- Tear down the native apt caddy (one-time, frees 80/443 + the unit name) --- +# Guarded on the native binary so this only runs while the apt caddy is +# still installed. After first apply the binary is gone and these become +# no-ops — they must NOT touch the compose caddy.service we install below +# (same unit name, would stop our own container on every run). + +- name: Check if native caddy binary is installed + ansible.builtin.command: command -v caddy + register: _caddy_native_bin + changed_when: false + failed_when: false -- name: update apt cache after adding caddy repository +- name: Stop and disable native caddy.service become: true - ansible.builtin.apt: - update_cache: true + ansible.builtin.systemd: + name: caddy.service + state: stopped + enabled: false + when: _caddy_native_bin.rc == 0 + failed_when: false -- name: install caddy +- name: Remove native caddy apt package (keep /var/lib/caddy for migration) become: true ansible.builtin.apt: name: caddy - state: present + state: absent + purge: false + when: _caddy_native_bin.rc == 0 + +- name: Remove caddy apt repo source (deb822) + become: true + ansible.builtin.file: + path: /etc/apt/sources.list.d/caddy.sources + state: absent + +# --- Directories --- + +- name: Ensure caddy deploy directory exists + become: true + ansible.builtin.file: + path: "{{ caddy_deploy_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Ensure caddy data directory exists + become: true + ansible.builtin.file: + path: "{{ caddy_data_dir }}" + state: directory + owner: root + group: root + mode: "0750" + +- name: Ensure caddy config directory exists + become: true + ansible.builtin.file: + path: "{{ caddy_config_dir }}" + state: directory + owner: root + group: root + mode: "0750" + +# --- One-time cert migration (native data dir -> container /data) --- + +- name: Stat native caddy data dir (source of existing certs) + become: true + ansible.builtin.stat: + path: "{{ caddy_native_data_dir }}" + register: _caddy_native_data + +- name: List files already in caddy data dir + become: true + ansible.builtin.find: + paths: "{{ caddy_data_dir }}" + recurse: true + hidden: true + register: _caddy_data_files + failed_when: false + +- name: Migrate existing Let's Encrypt certs + ACME account (one-time) + become: true + ansible.builtin.command: + cmd: "cp -a {{ caddy_native_data_dir }}/. {{ caddy_data_dir }}/" + when: + - _caddy_native_data.stat.exists + - _caddy_data_files.matched | default(0) == 0 + changed_when: true + +# --- Render compose stack --- +# compose.yaml / run.sh / unit changes notify `restart caddy` (full recreate). +# Caddyfile-only changes notify `reload caddy` (zero-downtime, no recreate, +# avoids the stop/start port race). If both are notified in one run, the +# reload is redundant after a recreate; its failed_when:false covers the +# container being mid-restart. + +- name: Render compose.yaml + become: true + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ caddy_deploy_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" notify: restart caddy -- name: render Caddyfile +- name: Render Caddyfile become: true ansible.builtin.template: src: Caddyfile.j2 - dest: /etc/caddy/Caddyfile + dest: "{{ caddy_deploy_dir }}/Caddyfile" owner: root group: root mode: "0644" notify: reload caddy -- name: ensure caddy.service is enabled and running +- name: Render run.sh + become: true + ansible.builtin.template: + src: run.sh.j2 + dest: "{{ caddy_deploy_dir }}/run.sh" + owner: root + group: root + mode: "0755" + notify: restart caddy + +- name: Install systemd unit + become: true + ansible.builtin.template: + src: caddy.service.j2 + dest: /etc/systemd/system/caddy.service + owner: root + group: root + mode: "0644" + notify: restart caddy + +- name: Enable caddy.service become: true ansible.builtin.systemd: name: caddy.service enabled: true - state: started daemon_reload: true diff --git a/devserver/ansible/roles/svc-caddy/templates/Caddyfile.j2 b/devserver/ansible/roles/svc-caddy/templates/Caddyfile.j2 index 1668137..699edfa 100644 --- a/devserver/ansible/roles/svc-caddy/templates/Caddyfile.j2 +++ b/devserver/ansible/roles/svc-caddy/templates/Caddyfile.j2 @@ -9,3 +9,7 @@ {{ caddy_bot_hostname }} { reverse_proxy {{ caddy_bot_upstream }} } + +{{ caddy_logs_hostname }} { + reverse_proxy {{ caddy_logs_upstream }} +} diff --git a/devserver/ansible/roles/svc-caddy/templates/caddy.service.j2 b/devserver/ansible/roles/svc-caddy/templates/caddy.service.j2 new file mode 100644 index 0000000..fe379d5 --- /dev/null +++ b/devserver/ansible/roles/svc-caddy/templates/caddy.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=devICT caddy reverse proxy (docker compose) +Requires=docker.service +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory={{ caddy_deploy_dir }} +ExecStart={{ caddy_deploy_dir }}/run.sh +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/devserver/ansible/roles/svc-caddy/templates/compose.yaml.j2 b/devserver/ansible/roles/svc-caddy/templates/compose.yaml.j2 new file mode 100644 index 0000000..c39538f --- /dev/null +++ b/devserver/ansible/roles/svc-caddy/templates/compose.yaml.j2 @@ -0,0 +1,18 @@ +services: + caddy: + image: {{ caddy_image }} + restart: unless-stopped + ports: + - "80:80" + - "443:443" + - "443:443/udp" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - {{ caddy_data_dir }}:/data + - {{ caddy_config_dir }}:/config + networks: + - devserver-ingress + +networks: + devserver-ingress: + external: true diff --git a/devserver/ansible/roles/svc-caddy/templates/run.sh.j2 b/devserver/ansible/roles/svc-caddy/templates/run.sh.j2 new file mode 100644 index 0000000..61c0dab --- /dev/null +++ b/devserver/ansible/roles/svc-caddy/templates/run.sh.j2 @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Runs the caddy compose stack. Invoked by caddy.service. +# +# run.sh -> docker compose up +# run.sh pull -> docker compose pull +# run.sh -> docker compose +# +# Caddy has no secrets, so no infisical injection (unlike svc-events/bot). +set -euo pipefail +cd "$(dirname "$0")" +exec docker compose "${@:-up}" diff --git a/devserver/ansible/roles/svc-dozzle/defaults/main.yaml b/devserver/ansible/roles/svc-dozzle/defaults/main.yaml new file mode 100644 index 0000000..6c12b03 --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/defaults/main.yaml @@ -0,0 +1,10 @@ +--- +# svc-dozzle: Dozzle web log viewer. Reads the Docker socket to tail +# container logs. Auth via Dozzle's native `simple` provider — the +# bcrypt password hash is injected from infisical (DOZZLE_PASSWORD_HASH). +# +# No host port published; Caddy proxies logs.devict.org -> dozzle:8080. + +svc_dozzle_deploy_dir: /opt/devict/dozzle +svc_dozzle_image: amir20/dozzle:latest +svc_dozzle_username: admin diff --git a/devserver/ansible/roles/svc-dozzle/handlers/main.yaml b/devserver/ansible/roles/svc-dozzle/handlers/main.yaml new file mode 100644 index 0000000..a63749c --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/handlers/main.yaml @@ -0,0 +1,7 @@ +--- +- name: restart svc-dozzle + become: true + ansible.builtin.systemd: + name: svc-dozzle.service + state: restarted + daemon_reload: true diff --git a/devserver/ansible/roles/svc-dozzle/tasks/main.yaml b/devserver/ansible/roles/svc-dozzle/tasks/main.yaml new file mode 100644 index 0000000..2bc5074 --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/tasks/main.yaml @@ -0,0 +1,55 @@ +--- +# svc-dozzle — deploy Dozzle log viewer, native simple auth, password +# hash from infisical. +# +# Prerequisites (satisfied by playbook role order): +# - docker role: docker + compose plugin installed +# - docker-network role: devserver-ingress network exists +# - infisical role: CLI installed + shared machine-identity env +# file written (sourced by run.sh) + +- name: Ensure deploy directory exists + become: true + ansible.builtin.file: + path: "{{ svc_dozzle_deploy_dir }}" + state: directory + owner: root + group: root + mode: "0755" + +- name: Render compose.yaml + become: true + ansible.builtin.template: + src: compose.yaml.j2 + dest: "{{ svc_dozzle_deploy_dir }}/compose.yaml" + owner: root + group: root + mode: "0644" + notify: restart svc-dozzle + +- name: Render run.sh + become: true + ansible.builtin.template: + src: run.sh.j2 + dest: "{{ svc_dozzle_deploy_dir }}/run.sh" + owner: root + group: root + mode: "0755" + notify: restart svc-dozzle + +- name: Install systemd unit + become: true + ansible.builtin.template: + src: svc-dozzle.service.j2 + dest: /etc/systemd/system/svc-dozzle.service + owner: root + group: root + mode: "0644" + notify: restart svc-dozzle + +- name: Enable svc-dozzle.service + become: true + ansible.builtin.systemd: + name: svc-dozzle.service + enabled: true + daemon_reload: true diff --git a/devserver/ansible/roles/svc-dozzle/templates/compose.yaml.j2 b/devserver/ansible/roles/svc-dozzle/templates/compose.yaml.j2 new file mode 100644 index 0000000..0cf229b --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/templates/compose.yaml.j2 @@ -0,0 +1,15 @@ +services: + dozzle: + image: {{ svc_dozzle_image }} + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./users.yml:/data/users.yml:ro + environment: + DOZZLE_AUTH_PROVIDER: simple + networks: + - devserver-ingress + +networks: + devserver-ingress: + external: true diff --git a/devserver/ansible/roles/svc-dozzle/templates/run.sh.j2 b/devserver/ansible/roles/svc-dozzle/templates/run.sh.j2 new file mode 100644 index 0000000..e989c2c --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/templates/run.sh.j2 @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# Runs the dozzle compose stack with secrets injected at runtime by the +# infisical CLI. Invoked by svc-dozzle.service. +# +# run.sh -> docker compose up +# run.sh pull -> docker compose pull +# run.sh -> docker compose +set -euo pipefail +cd "$(dirname "$0")" + +# Shared devserver infisical machine identity — written by the infisical +# role. Holds the client id + secret used by every service on this host. +# shellcheck source=/dev/null +source {{ infisical_env_file }} + +# Exchange the machine-identity credentials for a short-lived access +# token. Exported (not passed via --token=) so it never appears on a +# command line (visible via ps). +export INFISICAL_TOKEN="$(infisical login \ + --silent --plain \ + --method=universal-auth \ + --client-id="$INFISICAL_CLIENT_ID" \ + --client-secret="$INFISICAL_CLIENT_SECRET")" + +# Dozzle reads users from /data/users.yml (a mounted file, so compose +# ${...} interpolation does NOT apply) and expects a literal bcrypt hash. +# Render users.yml at runtime from the infisical-injected hash, then run +# compose — both inside `infisical run` so DOZZLE_PASSWORD_HASH is set. +exec infisical run \ + --env=prod \ + --projectId={{ infisical_project_id }} \ + -- bash -euo pipefail -c ' + printf "users:\n {{ svc_dozzle_username }}:\n password: %s\n" "$DOZZLE_PASSWORD_HASH" > users.yml + exec docker compose "${@:-up}" + ' _ "$@" diff --git a/devserver/ansible/roles/svc-dozzle/templates/svc-dozzle.service.j2 b/devserver/ansible/roles/svc-dozzle/templates/svc-dozzle.service.j2 new file mode 100644 index 0000000..f314330 --- /dev/null +++ b/devserver/ansible/roles/svc-dozzle/templates/svc-dozzle.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=devICT dozzle log viewer (docker compose via infisical) +Requires=docker.service +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=simple +WorkingDirectory={{ svc_dozzle_deploy_dir }} +ExecStart={{ svc_dozzle_deploy_dir }}/run.sh +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target diff --git a/devserver/ansible/roles/svc-events/templates/compose.yaml.j2 b/devserver/ansible/roles/svc-events/templates/compose.yaml.j2 index 0b9053a..0de8962 100644 --- a/devserver/ansible/roles/svc-events/templates/compose.yaml.j2 +++ b/devserver/ansible/roles/svc-events/templates/compose.yaml.j2 @@ -2,8 +2,6 @@ services: events: image: buoy.cr/devict.org/events:latest restart: unless-stopped - ports: - - 3001:3000 environment: ATPROTO_HANDLE: "devict.org" ATPROTO_APP_PASSWORD: ${ATPROTO_APP_PASSWORD} @@ -13,3 +11,9 @@ services: WEEKLY_NOTIFY_DAY: "Monday" CHECK_INTERVAL: "15m" ENABLE_STDOUT: "true" + networks: + - devserver-ingress + +networks: + devserver-ingress: + external: true diff --git a/devserver/ansible/site.yaml b/devserver/ansible/site.yaml index c87f83b..67ae93b 100644 --- a/devserver/ansible/site.yaml +++ b/devserver/ansible/site.yaml @@ -5,8 +5,9 @@ roles: - common - docker - - seamark + - docker-network - infisical - svc-caddy - svc-events - svc-bot + - svc-dozzle diff --git a/devserver/justfile b/devserver/justfile index 793dd38..d6c6927 100644 --- a/devserver/justfile +++ b/devserver/justfile @@ -35,3 +35,11 @@ deploy-events: # pull latest bot image + restart the service deploy-bot: cd ansible && ansible-playbook deploy-svc-bot.yaml + +# pull latest caddy image + restart the service +deploy-caddy: + cd ansible && ansible-playbook deploy-svc-caddy.yaml + +# pull latest dozzle image + restart the service +deploy-dozzle: + cd ansible && ansible-playbook deploy-svc-dozzle.yaml