Add Clay agent interface, fix registry UI routing, and misc improvements

- Deploy Clay (claude Code web UI) at agent.ladkau.de behind basic auth;
  Traefik proxies to clay's HTTPS port 2633 with insecureSkipVerify
- Document MCP server persistence: binaries need to be baked into the
  Dockerfile, config in /opt/clay/data survives rebuilds
- Document scheduled agent workflows via Gitea Actions on.schedule with
  email reporting via Gmail SMTP
- Fix registry UI: split /v2/ (registry) and / (UI) into separate Traefik
  routers; add registry_internal network
- Add weekly registry GC cron job (/usr/local/bin/registry-gc)
- Remove rate-limit middleware from Gitea router (act_runner polling
  exceeded 60 req/min limit)
- Set Traefik websecure readTimeout: 0 to fix large layer upload 499s
This commit is contained in:
ml
2026-08-01 07:55:20 +02:00
parent 735563fc31
commit 8adbd3fac4
13 changed files with 321 additions and 1 deletions
+20
View File
@@ -0,0 +1,20 @@
FROM node:20-alpine
# Claude Code needs git and bash to operate on repositories
RUN apk add --no-cache git bash curl
# Install Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code
# Install clay web server
RUN npm install -g clay-server
# MCP servers and skills — add packages here to survive container rebuilds
# RUN npm install -g @clay-ai/clay-ralph
# RUN npm install -g @modelcontextprotocol/server-filesystem
RUN mkdir -p /workspace /root/.clay
WORKDIR /workspace
EXPOSE 2633
+16
View File
@@ -0,0 +1,16 @@
---
- name: Rebuild and restart clay
community.docker.docker_image:
name: clay:local
build:
path: "{{ clay_data_dir }}"
source: build
force_source: true
state: present
notify: Restart clay
- name: Restart clay
community.docker.docker_compose_v2:
project_src: "{{ clay_data_dir }}"
state: present
recreate: always
+48
View File
@@ -0,0 +1,48 @@
---
- name: Create clay directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ clay_data_dir }}"
- "{{ clay_data_dir }}/data"
- "{{ clay_data_dir }}/workspace"
tags: clay
- name: Deploy Dockerfile
ansible.builtin.copy:
src: Dockerfile
dest: "{{ clay_data_dir }}/Dockerfile"
owner: root
group: root
mode: "0644"
notify: Rebuild and restart clay
tags: clay
- name: Deploy Docker Compose file
ansible.builtin.template:
src: docker-compose.yml.j2
dest: "{{ clay_data_dir }}/docker-compose.yml"
owner: root
group: root
mode: "0644"
notify: Restart clay
tags: clay
- name: Build clay image
community.docker.docker_image:
name: clay:local
build:
path: "{{ clay_data_dir }}"
source: build
state: present
tags: clay
- name: Start clay
community.docker.docker_compose_v2:
project_src: "{{ clay_data_dir }}"
state: present
tags: clay
@@ -0,0 +1,35 @@
# Managed by Ansible — do not edit manually
services:
clay:
image: clay:local
build:
context: {{ clay_data_dir }}
dockerfile: Dockerfile
container_name: clay
restart: unless-stopped
tty: true
environment:
- ANTHROPIC_API_KEY={{ anthropic_api_key }}
volumes:
- {{ clay_data_dir }}/data:/root/.clay
- {{ clay_data_dir }}/workspace:/workspace
command: ["clay-server", "--yes", "-p", "2633"]
networks:
- traefik_public
labels:
- "traefik.enable=true"
- "traefik.http.routers.clay.rule=Host(`{{ domain_clay }}`)"
- "traefik.http.routers.clay.entrypoints=websecure"
- "traefik.http.routers.clay.tls.certresolver=letsencrypt"
- "traefik.http.services.clay.loadbalancer.server.scheme=https"
- "traefik.http.services.clay.loadbalancer.server.port=2633"
- "traefik.http.routers.clay.middlewares=clay-auth"
{% set ns = namespace(entries=[]) %}
{% for user in clay_users %}
{% set ns.entries = ns.entries + [user.username + ':' + (user.password | password_hash('bcrypt', (user.username | hash('md5'))[:22]) | replace('$', '$$'))] %}
{% endfor %}
- "traefik.http.middlewares.clay-auth.basicauth.users={{ ns.entries | join(',') }}"
networks:
traefik_public:
external: true
@@ -25,6 +25,7 @@ SERVICES = [
("k8s", "https://{{ domain_k8s }}", 200, "https://{{ domain_k8s }}"),
("Vaultwarden", "https://{{ domain_vault }}", 200, "https://{{ domain_vault }}"),
("Downloads", "https://{{ domain_dl }}", 200, "https://{{ domain_dl }}"),
("Clay", "https://{{ domain_clay }}", 401, "https://{{ domain_clay }}"),
]
{% raw %}
@@ -30,6 +30,9 @@ entryPoints:
imaps:
address: ":993"
serversTransport:
insecureSkipVerify: true # allows Traefik to proxy clay's self-signed d.clay.studio cert on port 2633
providers:
docker:
exposedByDefault: false