From 596eedfbd0bf7b9f9cc0ae4a7e4145a5489235f5 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 14 Jan 2026 22:20:51 -0800 Subject: [PATCH 1/2] Add devpi PyPI caching proxy role for indri Set up devpi-server as a transparent PyPI caching proxy on indri, accessible via Tailscale at pypi.tail8d86e.ts.net. - Add ansible role with LaunchAgent (KeepAlive service via mise x) - Add health checks to indri-services-check script - Configure to listen on port 3141, data stored in ~/devpi Note: Manual setup required on indri before provisioning: 1. Add devpi packages to ~/.config/mise/config.toml 2. Run mise install && mise x -- devpi-init --serverdir ~/devpi 3. Set up Tailscale service after ansible provisioning Co-Authored-By: Claude Opus 4.5 --- ansible/playbooks/indri.yml | 2 ++ ansible/roles/devpi/defaults/main.yml | 5 +++ ansible/roles/devpi/handlers/main.yml | 5 +++ ansible/roles/devpi/tasks/main.yml | 28 ++++++++++++++++ ansible/roles/devpi/templates/devpi.plist.j2 | 35 ++++++++++++++++++++ mise-tasks/indri-services-check | 2 ++ 6 files changed, 77 insertions(+) create mode 100644 ansible/roles/devpi/defaults/main.yml create mode 100644 ansible/roles/devpi/handlers/main.yml create mode 100644 ansible/roles/devpi/tasks/main.yml create mode 100644 ansible/roles/devpi/templates/devpi.plist.j2 diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index d6cf954..fbad4c7 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -18,3 +18,5 @@ tags: borgmatic - role: forgejo tags: forgejo + - role: devpi + tags: devpi diff --git a/ansible/roles/devpi/defaults/main.yml b/ansible/roles/devpi/defaults/main.yml new file mode 100644 index 0000000..358a6bc --- /dev/null +++ b/ansible/roles/devpi/defaults/main.yml @@ -0,0 +1,5 @@ +--- +devpi_port: 3141 +devpi_serverdir: /Users/erichblume/devpi +devpi_log_dir: /Users/erichblume/Library/Logs +devpi_host: 0.0.0.0 # Listen on all interfaces for Tailscale diff --git a/ansible/roles/devpi/handlers/main.yml b/ansible/roles/devpi/handlers/main.yml new file mode 100644 index 0000000..788895e --- /dev/null +++ b/ansible/roles/devpi/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload devpi + ansible.builtin.shell: | + launchctl unload ~/Library/LaunchAgents/mcquack.eblume.devpi.plist 2>/dev/null || true + launchctl load ~/Library/LaunchAgents/mcquack.eblume.devpi.plist diff --git a/ansible/roles/devpi/tasks/main.yml b/ansible/roles/devpi/tasks/main.yml new file mode 100644 index 0000000..e6b3a3f --- /dev/null +++ b/ansible/roles/devpi/tasks/main.yml @@ -0,0 +1,28 @@ +--- +# Note: devpi is installed via mise (pipx), not managed here. +# Required packages: devpi-server, devpi-web, devpi-client +# Initialization must be done manually: mise x -- devpi-init --serverdir {{ devpi_serverdir }} + +- name: Ensure devpi data directory exists + ansible.builtin.file: + path: "{{ devpi_serverdir }}" + state: directory + mode: '0755' + +- name: Deploy devpi LaunchAgent plist + ansible.builtin.template: + src: devpi.plist.j2 + dest: ~/Library/LaunchAgents/mcquack.eblume.devpi.plist + mode: '0644' + notify: reload devpi + +- name: Check if devpi LaunchAgent is loaded + ansible.builtin.command: launchctl list mcquack.eblume.devpi + register: launchctl_check + changed_when: false + failed_when: false + +- name: Load devpi LaunchAgent if not loaded + ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.devpi.plist + when: launchctl_check.rc != 0 + failed_when: false diff --git a/ansible/roles/devpi/templates/devpi.plist.j2 b/ansible/roles/devpi/templates/devpi.plist.j2 new file mode 100644 index 0000000..3ab572f --- /dev/null +++ b/ansible/roles/devpi/templates/devpi.plist.j2 @@ -0,0 +1,35 @@ + + + + + + KeepAlive + + Label + mcquack.eblume.devpi + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/bin:/bin + + ProgramArguments + + /opt/homebrew/opt/mise/bin/mise + x + -- + devpi-server + --serverdir + {{ devpi_serverdir }} + --host + {{ devpi_host }} + --port + {{ devpi_port }} + + RunAtLoad + + StandardErrorPath + {{ devpi_log_dir }}/mcquack.devpi.err.log + StandardOutPath + {{ devpi_log_dir }}/mcquack.devpi.out.log + + diff --git a/mise-tasks/indri-services-check b/mise-tasks/indri-services-check index beeb6de..ea794fd 100755 --- a/mise-tasks/indri-services-check +++ b/mise-tasks/indri-services-check @@ -50,6 +50,7 @@ check_service "transmission" "ssh indri 'brew services list | grep transmission check_service "transmission-metrics" "ssh indri 'launchctl list | grep transmission-metrics | grep -v \"^-\"'" check_service "kiwix-serve" "ssh indri 'launchctl list | grep kiwix | grep -v \"^-\"'" check_service "forgejo" "ssh indri 'brew services list | grep forgejo | grep started'" +check_service "devpi" "ssh indri 'launchctl list | grep devpi | grep -v \"^-\"'" echo "" echo "HTTP endpoints (via Tailscale):" @@ -57,6 +58,7 @@ check_http "Prometheus" "http://indri:9090/-/healthy" check_http "Grafana" "http://indri:3000/api/health" check_http "Kiwix" "http://indri:5501/" check_http "Forgejo" "http://indri:3001/" +check_http "Devpi" "http://indri:3141/+api" # Transmission RPC is localhost-only by design, check via SSH check_service "Transmission RPC" "ssh indri 'curl -sf http://127.0.0.1:9091/transmission/rpc'" # Check that transmission metrics are being collected From 842df27532ac04ae5e461b5f5c75dd6a11e1976b Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Wed, 14 Jan 2026 22:20:51 -0800 Subject: [PATCH 2/2] Add devpi PyPI caching proxy role for indri Set up devpi-server as a transparent PyPI caching proxy on indri, accessible via Tailscale at pypi.tail8d86e.ts.net. - Add ansible role with LaunchAgent (KeepAlive service via mise x) - Add health checks to indri-services-check script - Configure to listen on port 3141, data stored in ~/devpi Note: Manual setup required on indri before provisioning: 1. Add devpi packages to ~/.config/mise/config.toml 2. Run mise install && mise x -- devpi-init --serverdir ~/devpi 3. Set up Tailscale service after ansible provisioning Co-Authored-By: Claude Opus 4.5 --- ansible/playbooks/indri.yml | 2 ++ ansible/roles/devpi/defaults/main.yml | 5 +++ ansible/roles/devpi/handlers/main.yml | 5 +++ ansible/roles/devpi/tasks/main.yml | 34 +++++++++++++++++++ ansible/roles/devpi/templates/devpi.plist.j2 | 35 ++++++++++++++++++++ mise-tasks/indri-services-check | 2 ++ 6 files changed, 83 insertions(+) create mode 100644 ansible/roles/devpi/defaults/main.yml create mode 100644 ansible/roles/devpi/handlers/main.yml create mode 100644 ansible/roles/devpi/tasks/main.yml create mode 100644 ansible/roles/devpi/templates/devpi.plist.j2 diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index d6cf954..fbad4c7 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -18,3 +18,5 @@ tags: borgmatic - role: forgejo tags: forgejo + - role: devpi + tags: devpi diff --git a/ansible/roles/devpi/defaults/main.yml b/ansible/roles/devpi/defaults/main.yml new file mode 100644 index 0000000..358a6bc --- /dev/null +++ b/ansible/roles/devpi/defaults/main.yml @@ -0,0 +1,5 @@ +--- +devpi_port: 3141 +devpi_serverdir: /Users/erichblume/devpi +devpi_log_dir: /Users/erichblume/Library/Logs +devpi_host: 0.0.0.0 # Listen on all interfaces for Tailscale diff --git a/ansible/roles/devpi/handlers/main.yml b/ansible/roles/devpi/handlers/main.yml new file mode 100644 index 0000000..788895e --- /dev/null +++ b/ansible/roles/devpi/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: reload devpi + ansible.builtin.shell: | + launchctl unload ~/Library/LaunchAgents/mcquack.eblume.devpi.plist 2>/dev/null || true + launchctl load ~/Library/LaunchAgents/mcquack.eblume.devpi.plist diff --git a/ansible/roles/devpi/tasks/main.yml b/ansible/roles/devpi/tasks/main.yml new file mode 100644 index 0000000..cb25e0e --- /dev/null +++ b/ansible/roles/devpi/tasks/main.yml @@ -0,0 +1,34 @@ +--- +# Note: devpi is installed via mise (pipx/uvx), not managed here. +# Add to ~/.config/mise/config.toml on indri: +# +# [tools] +# "pipx:devpi-server" = { version = "latest", uvx = "true", uvx_args = "--with devpi-web" } +# "pipx:devpi-client" = { version = "latest", uvx = "true" } +# +# Then run: mise install +# Initialize: mise x -- devpi-init --serverdir {{ devpi_serverdir }} + +- name: Ensure devpi data directory exists + ansible.builtin.file: + path: "{{ devpi_serverdir }}" + state: directory + mode: '0755' + +- name: Deploy devpi LaunchAgent plist + ansible.builtin.template: + src: devpi.plist.j2 + dest: ~/Library/LaunchAgents/mcquack.eblume.devpi.plist + mode: '0644' + notify: reload devpi + +- name: Check if devpi LaunchAgent is loaded + ansible.builtin.command: launchctl list mcquack.eblume.devpi + register: launchctl_check + changed_when: false + failed_when: false + +- name: Load devpi LaunchAgent if not loaded + ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.devpi.plist + when: launchctl_check.rc != 0 + failed_when: false diff --git a/ansible/roles/devpi/templates/devpi.plist.j2 b/ansible/roles/devpi/templates/devpi.plist.j2 new file mode 100644 index 0000000..3ab572f --- /dev/null +++ b/ansible/roles/devpi/templates/devpi.plist.j2 @@ -0,0 +1,35 @@ + + + + + + KeepAlive + + Label + mcquack.eblume.devpi + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/bin:/bin + + ProgramArguments + + /opt/homebrew/opt/mise/bin/mise + x + -- + devpi-server + --serverdir + {{ devpi_serverdir }} + --host + {{ devpi_host }} + --port + {{ devpi_port }} + + RunAtLoad + + StandardErrorPath + {{ devpi_log_dir }}/mcquack.devpi.err.log + StandardOutPath + {{ devpi_log_dir }}/mcquack.devpi.out.log + + diff --git a/mise-tasks/indri-services-check b/mise-tasks/indri-services-check index beeb6de..ea794fd 100755 --- a/mise-tasks/indri-services-check +++ b/mise-tasks/indri-services-check @@ -50,6 +50,7 @@ check_service "transmission" "ssh indri 'brew services list | grep transmission check_service "transmission-metrics" "ssh indri 'launchctl list | grep transmission-metrics | grep -v \"^-\"'" check_service "kiwix-serve" "ssh indri 'launchctl list | grep kiwix | grep -v \"^-\"'" check_service "forgejo" "ssh indri 'brew services list | grep forgejo | grep started'" +check_service "devpi" "ssh indri 'launchctl list | grep devpi | grep -v \"^-\"'" echo "" echo "HTTP endpoints (via Tailscale):" @@ -57,6 +58,7 @@ check_http "Prometheus" "http://indri:9090/-/healthy" check_http "Grafana" "http://indri:3000/api/health" check_http "Kiwix" "http://indri:5501/" check_http "Forgejo" "http://indri:3001/" +check_http "Devpi" "http://indri:3141/+api" # Transmission RPC is localhost-only by design, check via SSH check_service "Transmission RPC" "ssh indri 'curl -sf http://127.0.0.1:9091/transmission/rpc'" # Check that transmission metrics are being collected