This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/trafficserver-ci.git


The following commit(s) were added to refs/heads/main by this push:
     new abf842b  Run GitHub mirror from Compose (#445)
abf842b is described below

commit abf842b1721b728f592341836c28c63a9c2344a5
Author: Brian Neradt <[email protected]>
AuthorDate: Mon Jun 15 11:50:57 2026 -0500

    Run GitHub mirror from Compose (#445)
    
    GitHub mirror operations were split across the repo checkout, /etc secret
    state, host-level webhook services, and a smart-HTTP-only Compose file. That
    made controller backup and restore more awkward than the other controller
    services that live under a single /opt directory.
    
    This makes /opt/github-mirror the canonical mirror root and puts the 
long-lived
    webhook and smart HTTP services behind one docker-compose.yml. The installer
    preserves config across refreshes, writes the Compose UID/GID environment,
    installs small systemd boot hooks, and migrates the old secret and service
    layout when present.
    
    This also updates the backup helper and runbook for the Compose layout, so 
the
    normal mirror backup is just the /opt/github-mirror tree plus optional 
Jenkins
    and live ATS config snapshots.
---
 github-mirror/README.md                            | 326 ++++++++++++---------
 github-mirror/bin/backup-controller-config.sh      |  28 +-
 github-mirror/bin/generate-webhook-secret.sh       |   2 +-
 github-mirror/bin/github-mirror-webhook.py         |   2 +-
 github-mirror/bin/install-controller.sh            | 119 +++++---
 .../{git-daemon => config}/git-daemon.default      |   0
 .../github-mirror-webhook.env.example              |   6 +-
 github-mirror/cron/github-mirror                   |  15 -
 github-mirror/docker-compose.yml                   |  46 +++
 github-mirror/httpd/docker-compose.yml             |  13 -
 .../systemd/github-mirror-fallback.service         |   6 +-
 .../systemd/github-mirror-smart-http.service       |  20 --
 .../systemd/github-mirror-webhook.service          |  24 --
 github-mirror/systemd/github-mirror.service        |  21 ++
 github-mirror/webhook/Dockerfile                   |  13 +
 15 files changed, 368 insertions(+), 273 deletions(-)

diff --git a/github-mirror/README.md b/github-mirror/README.md
index 130dd66..4d8b604 100644
--- a/github-mirror/README.md
+++ b/github-mirror/README.md
@@ -16,7 +16,9 @@ 
https://ci.trafficserver.apache.org/mirror/trafficserver-ci.git
 ```
 
 The package is intentionally self-contained. If the controller is lost, this
-README plus the files in this directory are enough to rebuild the mirror.
+README plus the files in this directory are enough to rebuild the mirror. The
+installed controller copy lives under `/opt/github-mirror`, including the
+Docker Compose file and mirror-specific configuration.
 
 ## Architecture
 
@@ -31,7 +33,7 @@ https://ci.trafficserver.apache.org/github-mirror-webhook
   v
 127.0.0.1:9419/github-mirror-webhook
   |
-  | signed webhook receiver, running as gitdaemon
+  | github-mirror compose service, signed webhook receiver
   v
 /home/mirror/trafficserver.git
 /home/mirror/trafficserver-ci.git
@@ -40,7 +42,7 @@ https://ci.trafficserver.apache.org/github-mirror-webhook
   |
 127.0.0.1:9417/mirror/
   |
-  | dedicated httpd container running git-http-backend
+  | github-mirror-smart-http compose service, git-http-backend
   v
 https://ci.trafficserver.apache.org/mirror/
   |
@@ -51,14 +53,29 @@ Jenkins controller and docker agents
 
 The supported Jenkins serving path is smart Git HTTP behind ATS. The public
 URLs stay under `https://ci.trafficserver.apache.org/mirror/`, but ATS remaps
-that path to a dedicated controller-local httpd container on `127.0.0.1:9417`.
-The container runs `git-http-backend` and mounts `/home/mirror` read-only.
+that path to a controller-local Compose service on `127.0.0.1:9417`. That
+service runs `git-http-backend` and mounts `/home/mirror` read-only.
 
 Static dumb HTTP is not acceptable for the Jenkins fanout path. It cannot
 negotiate packs with the client, so many child jobs can repeatedly download
 large static pack files and probe missing loose objects. Smart HTTP uses
 `git-upload-pack` so each clone/fetch gets a negotiated pack.
 
+The normal controller process model is:
+
+```text
+/opt/github-mirror/docker-compose.yml
+  github-mirror              webhook receiver on 127.0.0.1:9419
+  github-mirror-smart-http   smart Git HTTP on 127.0.0.1:9417
+  github-mirror-fallback     manual/timer mirror refresh helper
+
+/etc/systemd/system/github-mirror.service
+  boot hook for the two long-running Compose services
+
+/etc/systemd/system/github-mirror-fallback.timer
+  safety-net timer that runs docker-compose run --rm github-mirror-fallback
+```
+
 `git-daemon` on port 9418 is kept only as a diagnostic or emergency fallback.
 Do not use `git://` URLs as the normal Jenkins configuration.
 
@@ -80,7 +97,9 @@ These steps assume Ubuntu and a controller that serves
 
 ### ASF Infra Request
 
-Ask ASF Infra to add the following GitHub webhooks.
+Ask ASF Infra to add the following GitHub webhooks. Generate the shared secret
+with `github-mirror/bin/generate-webhook-secret.sh` and share only the secret
+value, not the `GITHUB_WEBHOOK_SECRET=` prefix.
 
 ```text
 Hello ASF Infra,
@@ -99,7 +118,7 @@ Secret:
     github-mirror/bin/generate-webhook-secret.sh
   We will share it with ASF Infra out of band and install it only on the
   Jenkins controller in:
-    /etc/trafficserver-github-mirror/github-mirror-webhook.env
+    /opt/github-mirror/config/github-mirror-webhook.env
 
 Repositories and events:
   apache/trafficserver:
@@ -140,24 +159,29 @@ Thanks.
 
    - installs `git`, `git-daemon-sysvinit`, `docker.io`, `docker-compose`,
      `python3`, `rsync`, and `util-linux`;
-   - installs this package to `/opt/trafficserver-ci/github-mirror`;
+   - installs this package to `/opt/github-mirror`;
+   - preserves `/opt/github-mirror/config` across reinstalls;
+   - creates `/opt/github-mirror/config/github-mirror-webhook.env` if needed;
+   - writes `/opt/github-mirror/.env` with the host mirror UID/GID for Compose;
    - creates/configures `/home/mirror`;
    - creates `/home/mirror/trafficserver.git`;
    - creates `/home/mirror/trafficserver-ci.git`;
    - configures both bare repos with `http.uploadpack=true` and
      `http.receivepack=false`;
-   - installs systemd units;
-   - installs `/etc/default/git-daemon`;
-   - enables the smart HTTP container service;
-   - enables the fallback refresh timer.
+   - installs systemd boot/timer hooks;
+   - links `/etc/default/git-daemon` to
+     `/opt/github-mirror/config/git-daemon.default`;
+   - builds the Compose images.
+
+   If the webhook secret is still `CHANGE_ME`, the installer leaves the Compose
+   stack stopped and prints the command to start it after the secret is set.
 
 3. Install the GitHub webhook secret.
 
    ```bash
-   github-mirror/bin/generate-webhook-secret.sh
-
-   sudo install -d -m 0700 /etc/trafficserver-github-mirror
-   sudo editor /etc/trafficserver-github-mirror/github-mirror-webhook.env
+   /opt/github-mirror/bin/generate-webhook-secret.sh
+   sudo editor /opt/github-mirror/config/github-mirror-webhook.env
+   sudo chmod 0600 /opt/github-mirror/config/github-mirror-webhook.env
    ```
 
    Paste the generated env line into the file:
@@ -166,23 +190,31 @@ Thanks.
    GITHUB_WEBHOOK_SECRET=<generated secret shared with ASF Infra>
    ```
 
-   Share only the secret value, not the `GITHUB_WEBHOOK_SECRET=` prefix, with
-   ASF Infra.
-
    If the old controller is gone and the previous secret is unavailable,
-   generate a new secret with `github-mirror/bin/generate-webhook-secret.sh`
-   and ask ASF Infra to update both GitHub webhooks.
+   generate a new secret and ask ASF Infra to update both GitHub webhooks.
+
+4. Start the Compose stack.
+
+   ```bash
+   sudo systemctl enable --now github-mirror.service
+   sudo systemctl enable --now github-mirror-fallback.timer
 
-   Keep the file root-owned and private:
+   cd /opt/github-mirror
+   sudo docker-compose ps
+   ```
+
+   Common service operations:
 
    ```bash
-   sudo chown root:root 
/etc/trafficserver-github-mirror/github-mirror-webhook.env
-   sudo chmod 0600 /etc/trafficserver-github-mirror/github-mirror-webhook.env
+   cd /opt/github-mirror
+   sudo docker-compose restart github-mirror
+   sudo docker-compose restart github-mirror-smart-http
+   sudo docker-compose run --rm github-mirror-fallback
    ```
 
-4. Configure ATS remaps.
+5. Configure ATS remaps.
 
-   Add `github-mirror/ats/remap-snippet.config` before the generic
+   Add `/opt/github-mirror/ats/remap-snippet.config` before the generic
    `ci.trafficserver.apache.org` Jenkins remap in:
 
    ```text
@@ -190,16 +222,16 @@ Thanks.
    ```
 
    Add or update the `/mirror/` remap with
-   `github-mirror/ats/mirror-smart-http-remap-snippet.config`. The important
-   change is:
+   `/opt/github-mirror/ats/mirror-smart-http-remap-snippet.config`. The
+   important target is:
 
    ```text
    https://ci.trafficserver.apache.org/mirror/ -> http://localhost:9417/mirror/
    ```
 
    Keep `proxy.config.http.cache.http=0`. Keep `hdr_rw_git.config` unless
-   testing proves it interferes with smart Git POSTs. Remove the mirror purge
-   plugin from this remap. Do not change the docs httpd/container remaps.
+   testing proves it interferes with smart Git POSTs. Do not change the docs
+   httpd/container remaps.
 
    Reload ATS:
 
@@ -207,34 +239,18 @@ Thanks.
    sudo /opt/ats/bin/traffic_ctl config reload
    ```
 
-5. Verify the smart HTTP service.
+6. Verify the smart HTTP service.
 
    ```bash
-   sudo systemctl status github-mirror-smart-http.service
-
-   cd /opt/trafficserver-ci/github-mirror/httpd
+   cd /opt/github-mirror
    sudo docker-compose config
-   sudo docker exec github-mirror-smart-http httpd -t
+   sudo docker-compose exec github-mirror-smart-http httpd -t
 
    git ls-remote http://127.0.0.1:9417/mirror/trafficserver.git 
refs/heads/master
    git ls-remote http://127.0.0.1:9417/mirror/trafficserver-ci.git 
refs/heads/main
    ```
 
-6. Start the webhook receiver after the secret is installed.
-
-   ```bash
-   sudo systemctl restart github-mirror-webhook.service
-   sudo systemctl status github-mirror-webhook.service
-   ```
-
-7. Confirm the timer and diagnostic git-daemon are active.
-
-   ```bash
-   systemctl list-timers github-mirror-fallback.timer
-   sudo service git-daemon status
-   ```
-
-8. Configure Jenkins top-level jobs.
+7. Configure Jenkins top-level jobs.
 
    For GitHub PR and branch jobs, set `GITHUB_URL` to the ATS mirror URL:
 
@@ -246,13 +262,13 @@ Thanks.
    repo-managed top-level PR pipelines wait up to two minutes for the mirrored
    PR head and merge refs before starting child jobs.
 
-9. Verify the public HTTPS mirror and at least one docker host.
+8. Verify the public HTTPS mirror and at least one docker host.
 
    ```bash
-   /opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 
<open-pr-number>
+   /opt/github-mirror/bin/check-mirror.sh --pr <open-pr-number>
 
    CONTROLLER=- \
-     /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh \
+     /opt/github-mirror/bin/check-docker-access.sh \
        --pr <open-pr-number> docker12
    ```
 
@@ -260,7 +276,7 @@ Thanks.
 
    ```bash
    GITHUB_PR_HEAD_SHA=<sha-from-jenkins-or-github> \
-     /opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 
<open-pr-number>
+     /opt/github-mirror/bin/check-mirror.sh --pr <open-pr-number>
    ```
 
 ### Webhook Update Behavior
@@ -283,78 +299,104 @@ before fanout starts.
 Initialize or reconfigure the mirrors:
 
 ```bash
-sudo /opt/trafficserver-ci/github-mirror/bin/init-mirrors.sh
+sudo /opt/github-mirror/bin/init-mirrors.sh
 ```
 
 Recreate mirrors from scratch:
 
 ```bash
-sudo /opt/trafficserver-ci/github-mirror/bin/init-mirrors.sh --force
+sudo /opt/github-mirror/bin/init-mirrors.sh --force
 ```
 
 Refresh both mirrors manually:
 
 ```bash
-sudo -u gitdaemon \
-  /opt/trafficserver-ci/github-mirror/bin/update-mirror.sh --all
+cd /opt/github-mirror
+sudo docker-compose run --rm github-mirror-fallback
 ```
 
 Refresh one ATS PR:
 
 ```bash
 sudo -u gitdaemon \
-  /opt/trafficserver-ci/github-mirror/bin/update-mirror.sh trafficserver --pr 
12345
+  /opt/github-mirror/bin/update-mirror.sh trafficserver --pr 12345
 ```
 
 Check local and public refs:
 
 ```bash
-/opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 12345
+/opt/github-mirror/bin/check-mirror.sh --pr 12345
 ```
 
 Check from docker agents:
 
 ```bash
-/opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh --pr 12345 
docker1 docker12
+/opt/github-mirror/bin/check-docker-access.sh --pr 12345 docker1 docker12
 ```
 
-Back up the controller-local configuration:
+Inspect services:
 
 ```bash
-sudo /opt/trafficserver-ci/github-mirror/bin/backup-controller-config.sh \
-  /secure/backup/location
+sudo systemctl status github-mirror.service
+systemctl list-timers github-mirror-fallback.timer
+
+cd /opt/github-mirror
+sudo docker-compose ps
+sudo docker-compose logs --tail=100 github-mirror
+sudo docker-compose logs --tail=100 github-mirror-smart-http
+sudo tail -n 100 /var/log/github-mirror-smart-http/access_log
 ```
 
-The backup is written to a timestamped directory under the destination, with
-absolute paths preserved under `rootfs/`. It includes the webhook secret and
-Jenkins job `config.xml` files, so keep the destination private. To restore a
-backup onto a rebuilt controller, inspect `MANIFEST.txt`, then run:
+Use `git-daemon` only as a diagnostic fallback:
 
 ```bash
-cd /secure/backup/location/<backup-name>
-sudo rsync -a rootfs/ /
-sudo systemctl daemon-reload
-sudo /opt/ats/bin/traffic_ctl config reload
-sudo systemctl restart github-mirror-webhook.service
-sudo systemctl restart github-mirror-smart-http.service
+git ls-remote git://ci.trafficserver.apache.org/trafficserver.git 
refs/heads/master
 ```
 
-Use `--no-jenkins` to skip Jenkins job configs, or `--no-package` to skip the
-installed `/opt/trafficserver-ci/github-mirror` package copy.
+## Backups
+
+All mirror-specific application and configuration files live under:
 
-Inspect smart HTTP:
+```text
+/opt/github-mirror
+```
+
+That means the simplest mirror backup is:
 
 ```bash
-sudo systemctl status github-mirror-smart-http.service
-cd /opt/trafficserver-ci/github-mirror/httpd
-sudo docker-compose logs --tail=100 github-mirror-smart-http
-sudo tail -n 100 /var/log/github-mirror-smart-http/access_log
+sudo rsync -a /opt/github-mirror/ backup-host:/secure/backups/github-mirror/
 ```
 
-Use `git-daemon` only as a diagnostic fallback:
+The backup includes `/opt/github-mirror/config/github-mirror-webhook.env`, so
+store it in a private, access-controlled location.
+
+The helper script creates a timestamped path-preserving backup. It includes
+Jenkins job XML files by default because Jenkins stores `GITHUB_URL` and
+`quietPeriod` outside `/opt/github-mirror`:
 
 ```bash
-git ls-remote git://ci.trafficserver.apache.org/trafficserver.git 
refs/heads/master
+sudo /opt/github-mirror/bin/backup-controller-config.sh /secure/backup/location
+```
+
+To include the live ATS config files as well:
+
+```bash
+sudo /opt/github-mirror/bin/backup-controller-config.sh --include-ats \
+  /secure/backup/location
+```
+
+Use `--no-jenkins` when you only want the mirror package and OS integration
+stubs.
+
+To restore a helper-script backup onto a rebuilt controller, inspect
+`MANIFEST.txt`, then run:
+
+```bash
+cd /secure/backup/location/<backup-name>
+sudo rsync -a rootfs/ /
+sudo systemctl daemon-reload
+sudo /opt/ats/bin/traffic_ctl config reload
+sudo systemctl restart github-mirror.service
 ```
 
 ## Webhook Testing
@@ -365,7 +407,8 @@ The response should be HTTP 200.
 View webhook service logs and ATS access logs:
 
 ```bash
-journalctl -u github-mirror-webhook.service -f
+cd /opt/github-mirror
+sudo docker-compose logs -f github-mirror
 sudo tail -f /opt/ats/var/log/trafficserver/access.log
 ```
 
@@ -373,7 +416,7 @@ Local signed ping test:
 
 ```bash
 secret=$(sudo awk -F= '/^GITHUB_WEBHOOK_SECRET=/ { print $2 }' \
-  /etc/trafficserver-github-mirror/github-mirror-webhook.env)
+  /opt/github-mirror/config/github-mirror-webhook.env)
 body='{"repository":{"full_name":"apache/trafficserver"}}'
 sig=$(SECRET="$secret" BODY="$body" python3 - <<'PY'
 import hashlib
@@ -494,20 +537,14 @@ from GitHub again.
 2. Re-run or restart the affected Jenkins jobs.
 
 3. If the mirror should not keep updating while GitHub URLs are in use, stop
-   the webhook service and fallback timer.
+   the Compose stack and fallback timer.
 
    ```bash
-   sudo systemctl stop github-mirror-webhook.service
+   sudo systemctl stop github-mirror.service
    sudo systemctl stop github-mirror-fallback.timer
    ```
 
-4. If the smart HTTP endpoint should also be taken offline, stop its service.
-
-   ```bash
-   sudo systemctl disable --now github-mirror-smart-http.service
-   ```
-
-Rollback does not require deleting `/home/mirror`.
+Rollback does not require deleting `/opt/github-mirror` or `/home/mirror`.
 
 ## Troubleshooting
 
@@ -515,7 +552,7 @@ Missing PR ref:
 
 ```bash
 sudo -u gitdaemon \
-  /opt/trafficserver-ci/github-mirror/bin/update-mirror.sh trafficserver --pr 
<number>
+  /opt/github-mirror/bin/update-mirror.sh trafficserver --pr <number>
 git --git-dir=/home/mirror/trafficserver.git show-ref refs/pull/<number>/head
 git --git-dir=/home/mirror/trafficserver.git show-ref refs/pull/<number>/merge
 ```
@@ -523,34 +560,35 @@ git --git-dir=/home/mirror/trafficserver.git show-ref 
refs/pull/<number>/merge
 Webhook returns 401:
 
 - Confirm ASF Infra and the controller have the same secret.
-- Confirm the env file is readable by systemd and not world-readable:
+- Confirm the env file is readable by Docker Compose and not world-readable:
 
   ```bash
-  sudo systemctl cat github-mirror-webhook.service
-  sudo ls -l /etc/trafficserver-github-mirror/github-mirror-webhook.env
+  sudo ls -l /opt/github-mirror/config/github-mirror-webhook.env
+  cd /opt/github-mirror
+  sudo docker-compose config
   ```
 
 Jenkins cannot clone from HTTPS:
 
 - Verify ATS remap order.
 - Verify `/mirror/` points to `http://localhost:9417/mirror/`.
-- Verify the smart HTTP service is healthy.
-- If the service logs say `detected dubious ownership`, rebuild the current
-  image so Git trusts the bind-mounted mirror repositories.
+- Verify the Compose services are healthy.
+- If the smart HTTP service logs say `detected dubious ownership`, rebuild the
+  current image so Git trusts the bind-mounted mirror repositories.
 - Verify the public URL:
 
   ```bash
-  sudo systemctl status github-mirror-smart-http.service
-  cd /opt/trafficserver-ci/github-mirror/httpd
-  sudo docker-compose build
-  sudo systemctl restart github-mirror-smart-http.service
-  sudo docker exec github-mirror-smart-http httpd -t
+  sudo systemctl status github-mirror.service
+  cd /opt/github-mirror
+  sudo docker-compose build github-mirror-smart-http
+  sudo docker-compose restart github-mirror-smart-http
+  sudo docker-compose exec github-mirror-smart-http httpd -t
   git ls-remote https://ci.trafficserver.apache.org/mirror/trafficserver.git 
refs/heads/master
   ```
 
 Jenkins fetches look like dumb HTTP:
 
-- Confirm ATS is using the smart HTTP remap, not 
`http://localhost:8080/mirror/`.
+- Confirm ATS is using the smart HTTP remap, not another `/mirror/` backend.
 - Confirm logs include `git-upload-pack`:
 
   ```bash
@@ -563,22 +601,23 @@ Jenkins fetches fail with HTTP 502 after about 60 seconds:
   `git-upload-pack` more time to generate large packs during CI fanout.
 
   ```bash
-  cd /opt/trafficserver-ci/github-mirror/httpd
-  sudo docker-compose build
-  sudo systemctl restart github-mirror-smart-http.service
+  cd /opt/github-mirror
+  sudo docker-compose build github-mirror-smart-http
+  sudo docker-compose restart github-mirror-smart-http
   ```
 
 Docker hosts cannot reach the mirror:
 
 ```bash
-/opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh docker12
+/opt/github-mirror/bin/check-docker-access.sh docker12
 ```
 
 Webhook service will not start:
 
 ```bash
-journalctl -u github-mirror-webhook.service -n 100 --no-pager
-sudo systemctl status github-mirror-webhook.service
+sudo systemctl status github-mirror.service
+cd /opt/github-mirror
+sudo docker-compose logs --tail=100 github-mirror
 ```
 
 The service intentionally refuses to start when `GITHUB_WEBHOOK_SECRET` is
@@ -586,46 +625,43 @@ unset or still set to `CHANGE_ME`.
 
 ## Controller File Inventory
 
-The installer copies this repo-managed package to:
+The installer copies this repo-managed package and all mirror-specific config
+to:
 
 ```text
-/opt/trafficserver-ci/github-mirror/
+/opt/github-mirror/
 ```
 
-The key repo-managed files under that directory are:
+The key files under that directory are:
 
 ```text
-/opt/trafficserver-ci/github-mirror/ats/remap-snippet.config
-/opt/trafficserver-ci/github-mirror/ats/mirror-smart-http-remap-snippet.config
-/opt/trafficserver-ci/github-mirror/bin/backup-controller-config.sh
-/opt/trafficserver-ci/github-mirror/bin/generate-webhook-secret.sh
-/opt/trafficserver-ci/github-mirror/bin/github-mirror-webhook.py
-/opt/trafficserver-ci/github-mirror/bin/init-mirrors.sh
-/opt/trafficserver-ci/github-mirror/bin/update-mirror.sh
-/opt/trafficserver-ci/github-mirror/env/github-mirror-webhook.env.example
-/opt/trafficserver-ci/github-mirror/git-daemon/git-daemon.default
-/opt/trafficserver-ci/github-mirror/httpd/docker-compose.yml
-/opt/trafficserver-ci/github-mirror/httpd/mirror.conf
-/opt/trafficserver-ci/github-mirror/systemd/github-mirror-webhook.service
-/opt/trafficserver-ci/github-mirror/systemd/github-mirror-fallback.service
-/opt/trafficserver-ci/github-mirror/systemd/github-mirror-fallback.timer
-/opt/trafficserver-ci/github-mirror/systemd/github-mirror-smart-http.service
-```
-
-The installer creates or updates these controller files:
+/opt/github-mirror/docker-compose.yml
+/opt/github-mirror/.env
+/opt/github-mirror/ats/remap-snippet.config
+/opt/github-mirror/ats/mirror-smart-http-remap-snippet.config
+/opt/github-mirror/bin/backup-controller-config.sh
+/opt/github-mirror/bin/generate-webhook-secret.sh
+/opt/github-mirror/bin/github-mirror-webhook.py
+/opt/github-mirror/bin/init-mirrors.sh
+/opt/github-mirror/bin/update-mirror.sh
+/opt/github-mirror/config/github-mirror-webhook.env
+/opt/github-mirror/config/github-mirror-webhook.env.example
+/opt/github-mirror/config/git-daemon.default
+/opt/github-mirror/httpd/Dockerfile
+/opt/github-mirror/httpd/mirror.conf
+/opt/github-mirror/systemd/github-mirror.service
+/opt/github-mirror/systemd/github-mirror-fallback.service
+/opt/github-mirror/systemd/github-mirror-fallback.timer
+/opt/github-mirror/webhook/Dockerfile
+```
+
+The installer creates these small OS integration files:
 
 ```text
-/etc/default/git-daemon
-/etc/systemd/system/github-mirror-webhook.service
+/etc/default/git-daemon -> /opt/github-mirror/config/git-daemon.default
+/etc/systemd/system/github-mirror.service
 /etc/systemd/system/github-mirror-fallback.service
 /etc/systemd/system/github-mirror-fallback.timer
-/etc/systemd/system/github-mirror-smart-http.service
-```
-
-The webhook secret lives outside the repo-managed package:
-
-```text
-/etc/trafficserver-github-mirror/github-mirror-webhook.env
 ```
 
 The local bare mirrors live under:
@@ -650,8 +686,8 @@ ATS needs the webhook and mirror remap entries in:
 Use these repo snippets as the source of truth for those remaps:
 
 ```text
-/opt/trafficserver-ci/github-mirror/ats/remap-snippet.config
-/opt/trafficserver-ci/github-mirror/ats/mirror-smart-http-remap-snippet.config
+/opt/github-mirror/ats/remap-snippet.config
+/opt/github-mirror/ats/mirror-smart-http-remap-snippet.config
 ```
 
 The mirror remap also references the existing ATS header rewrite file:
diff --git a/github-mirror/bin/backup-controller-config.sh 
b/github-mirror/bin/backup-controller-config.sh
index 4adda99..7942665 100755
--- a/github-mirror/bin/backup-controller-config.sh
+++ b/github-mirror/bin/backup-controller-config.sh
@@ -6,6 +6,8 @@ set -euo pipefail
 
 INCLUDE_JENKINS=${INCLUDE_JENKINS:-1}
 INCLUDE_PACKAGE=${INCLUDE_PACKAGE:-1}
+INCLUDE_ATS_LIVE=${INCLUDE_ATS_LIVE:-0}
+INSTALL_ROOT=${INSTALL_ROOT:-/opt/github-mirror}
 DRY_RUN=0
 BACKUP_NAME=${BACKUP_NAME:-}
 DESTINATION=
@@ -21,14 +23,17 @@ path-preserving rootfs/ tree and MANIFEST.txt.
 Options:
   --name NAME       Use NAME instead of the generated backup directory name.
   --no-jenkins     Do not include Jenkins job config.xml files.
-  --no-package     Do not include /opt/trafficserver-ci/github-mirror files.
+  --no-package     Do not include /opt/github-mirror files.
+  --include-ats    Include live ATS remap.config and hdr_rw_git.config.
   --dry-run        Show what rsync would copy without writing files.
   -h, --help       Show this help.
 
 Environment:
+  INSTALL_ROOT     github-mirror install root. Default: /opt/github-mirror
   BACKUP_NAME      Default backup directory name.
   INCLUDE_JENKINS  Include Jenkins job config.xml files. Default: 1
   INCLUDE_PACKAGE  Include the installed github-mirror package. Default: 1
+  INCLUDE_ATS_LIVE Include live ATS config files. Default: 0
 
 DESTINATION may be a local path or an rsync remote such as host:/path. The
 destination contains the webhook secret, so use a private, access-controlled
@@ -58,6 +63,9 @@ while [ $# -gt 0 ]; do
     --no-package)
       INCLUDE_PACKAGE=0
       ;;
+    --include-ats)
+      INCLUDE_ATS_LIVE=1
+      ;;
     --dry-run)
       DRY_RUN=1
       ;;
@@ -134,17 +142,18 @@ add_jenkins_job_configs() {
 }
 
 if [ "${INCLUDE_PACKAGE}" = "1" ]; then
-  add_tree /opt/trafficserver-ci/github-mirror
+  add_tree "${INSTALL_ROOT}"
 fi
 
-add_existing_path /etc/default/git-daemon
-add_existing_path /etc/systemd/system/github-mirror-webhook.service
+add_existing_path /etc/systemd/system/github-mirror.service
 add_existing_path /etc/systemd/system/github-mirror-fallback.service
 add_existing_path /etc/systemd/system/github-mirror-fallback.timer
-add_existing_path /etc/systemd/system/github-mirror-smart-http.service
-add_existing_path /etc/trafficserver-github-mirror/github-mirror-webhook.env
-add_existing_path /opt/ats/etc/trafficserver/remap.config
-add_existing_path /opt/ats/etc/trafficserver/hdr_rw_git.config
+add_existing_path /etc/default/git-daemon
+
+if [ "${INCLUDE_ATS_LIVE}" = "1" ]; then
+  add_existing_path /opt/ats/etc/trafficserver/remap.config
+  add_existing_path /opt/ats/etc/trafficserver/hdr_rw_git.config
+fi
 
 if [ "${INCLUDE_JENKINS}" = "1" ]; then
   add_existing_path /opt/jenkins/home/config.xml
@@ -172,8 +181,7 @@ fi
   printf '  sudo rsync -a rootfs/ /\n'
   printf '  sudo systemctl daemon-reload\n'
   printf '  sudo /opt/ats/bin/traffic_ctl config reload\n'
-  printf '  sudo systemctl restart github-mirror-webhook.service\n'
-  printf '  sudo systemctl restart github-mirror-smart-http.service\n'
+  printf '  sudo systemctl restart github-mirror.service\n'
   printf '\n'
   printf 'Files:\n'
   tr '\0' '\n' < "${file_list}" | sed 's#^#/#'
diff --git a/github-mirror/bin/generate-webhook-secret.sh 
b/github-mirror/bin/generate-webhook-secret.sh
index b977837..9a2082a 100755
--- a/github-mirror/bin/generate-webhook-secret.sh
+++ b/github-mirror/bin/generate-webhook-secret.sh
@@ -28,7 +28,7 @@ cat <<EOF
 GITHUB_WEBHOOK_SECRET=${secret}
 
 Add this to:
-  /etc/trafficserver-github-mirror/github-mirror-webhook.env
+  /opt/github-mirror/config/github-mirror-webhook.env
 
 Share only the secret value with ASF Infra:
   ${secret}
diff --git a/github-mirror/bin/github-mirror-webhook.py 
b/github-mirror/bin/github-mirror-webhook.py
index b0593b4..347501c 100755
--- a/github-mirror/bin/github-mirror-webhook.py
+++ b/github-mirror/bin/github-mirror-webhook.py
@@ -37,7 +37,7 @@ WEBHOOK_PATH = os.environ.get("WEBHOOK_PATH", 
"/github-mirror-webhook")
 WEBHOOK_SECRET = os.environ.get("GITHUB_WEBHOOK_SECRET")
 UPDATE_MIRROR = os.environ.get(
     "UPDATE_MIRROR",
-    "/opt/trafficserver-ci/github-mirror/bin/update-mirror.sh",
+    "/opt/github-mirror/bin/update-mirror.sh",
 )
 MIRROR_ROOT = os.environ.get("MIRROR_ROOT", "/home/mirror")
 MAX_BODY_BYTES = getenv_int("MAX_BODY_BYTES", 1024 * 1024)
diff --git a/github-mirror/bin/install-controller.sh 
b/github-mirror/bin/install-controller.sh
index 730141f..87f1d01 100755
--- a/github-mirror/bin/install-controller.sh
+++ b/github-mirror/bin/install-controller.sh
@@ -7,17 +7,18 @@ set -euo pipefail
 SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
 PACKAGE_ROOT=$(cd "${SCRIPT_DIR}/.." && pwd)
 
-INSTALL_ROOT=${INSTALL_ROOT:-/opt/trafficserver-ci/github-mirror}
+INSTALL_ROOT=${INSTALL_ROOT:-/opt/github-mirror}
 MIRROR_ROOT=${MIRROR_ROOT:-/home/mirror}
 MIRROR_USER=${MIRROR_USER:-gitdaemon}
 MIRROR_GROUP=${MIRROR_GROUP:-nogroup}
-ENV_DIR=${ENV_DIR:-/etc/trafficserver-github-mirror}
-ENV_FILE=${ENV_FILE:-${ENV_DIR}/github-mirror-webhook.env}
+CONFIG_DIR=${CONFIG_DIR:-${INSTALL_ROOT}/config}
+ENV_FILE=${ENV_FILE:-${CONFIG_DIR}/github-mirror-webhook.env}
+COMPOSE_ENV_FILE=${COMPOSE_ENV_FILE:-${INSTALL_ROOT}/.env}
 APT_INSTALL=${APT_INSTALL:-1}
-START_WEBHOOK=${START_WEBHOOK:-auto}
+START_COMPOSE=${START_COMPOSE:-auto}
 START_FALLBACK_TIMER=${START_FALLBACK_TIMER:-1}
-START_SMART_HTTP=${START_SMART_HTTP:-1}
 INIT_MIRRORS=${INIT_MIRRORS:-1}
+BUILD_IMAGES=${BUILD_IMAGES:-1}
 
 usage() {
   cat <<'EOF'
@@ -25,18 +26,18 @@ Usage:
   sudo github-mirror/bin/install-controller.sh
 
 Environment:
-  INSTALL_ROOT   Installed package path. Default: 
/opt/trafficserver-ci/github-mirror
+  INSTALL_ROOT   Installed package/config path. Default: /opt/github-mirror
   MIRROR_ROOT    Mirror root. Default: /home/mirror
   MIRROR_USER    Mirror owner/service user. Default: gitdaemon
   MIRROR_GROUP   Mirror group. Default: nogroup
-  ENV_DIR        Secret/env directory. Default: 
/etc/trafficserver-github-mirror
+  CONFIG_DIR     Config directory. Default: $INSTALL_ROOT/config
+  ENV_FILE       Webhook env file. Default: 
$CONFIG_DIR/github-mirror-webhook.env
   APT_INSTALL    Install required apt packages when set to 1. Default: 1
   INIT_MIRRORS   Run init-mirrors.sh after install when set to 1. Default: 1
-  START_WEBHOOK  auto, 1, or 0. Default: auto
+  BUILD_IMAGES   Build docker-compose images when set to 1. Default: 1
+  START_COMPOSE  auto, 1, or 0. Default: auto
   START_FALLBACK_TIMER
                  Enable/start the systemd fallback timer when set to 1. 
Default: 1
-  START_SMART_HTTP
-                 Enable/start smart HTTP mirror service when set to 1. 
Default: 1
 EOF
 }
 
@@ -60,6 +61,23 @@ render_template() {
     "${src}" > "${dst}"
 }
 
+secret_is_configured() {
+  [ -f "${ENV_FILE}" ] &&
+    grep -q '^GITHUB_WEBHOOK_SECRET=' "${ENV_FILE}" &&
+    ! grep -q '^GITHUB_WEBHOOK_SECRET=CHANGE_ME' "${ENV_FILE}"
+}
+
+copy_if_present() {
+  local src=$1
+  local dst=$2
+
+  if [ -e "${src}" ] || [ -L "${src}" ]; then
+    cp -a "${src}" "${dst}"
+    return 0
+  fi
+  return 1
+}
+
 if [ $# -gt 0 ]; then
   case "$1" in
     -h|--help)
@@ -90,38 +108,68 @@ if ! id "${MIRROR_USER}" >/dev/null 2>&1; then
   useradd --system --home-dir /home/gitdaemon --shell /usr/sbin/nologin 
"${MIRROR_USER}"
 fi
 
+if ! getent group "${MIRROR_GROUP}" >/dev/null 2>&1; then
+  die "group ${MIRROR_GROUP} does not exist"
+fi
+
 install -d -o root -g root -m 0755 "$(dirname "${INSTALL_ROOT}")"
 rm -rf "${INSTALL_ROOT}.new"
 install -d -o root -g root -m 0755 "${INSTALL_ROOT}.new"
 cp -a "${PACKAGE_ROOT}/." "${INSTALL_ROOT}.new/"
 find "${INSTALL_ROOT}.new/bin" -type f -name '*.sh' -exec chmod 0755 {} +
 find "${INSTALL_ROOT}.new/bin" -type f -name '*.py' -exec chmod 0755 {} +
+install -d -o root -g root -m 0700 "${INSTALL_ROOT}.new/config"
+
+if ! copy_if_present "${ENV_FILE}" 
"${INSTALL_ROOT}.new/config/github-mirror-webhook.env"; then
+  # Migrate the pre-Compose secret location if this is an existing controller.
+  copy_if_present /etc/trafficserver-github-mirror/github-mirror-webhook.env \
+    "${INSTALL_ROOT}.new/config/github-mirror-webhook.env" || \
+    install -o root -g root -m 0600 \
+      "${INSTALL_ROOT}.new/config/github-mirror-webhook.env.example" \
+      "${INSTALL_ROOT}.new/config/github-mirror-webhook.env"
+  log "created ${INSTALL_ROOT}.new/config/github-mirror-webhook.env; set 
GITHUB_WEBHOOK_SECRET before starting webhook deliveries"
+fi
+
+if ! copy_if_present "${CONFIG_DIR}/git-daemon.default" 
"${INSTALL_ROOT}.new/config/git-daemon.default"; then
+  if [ -f /etc/default/git-daemon ] && [ ! -L /etc/default/git-daemon ]; then
+    copy_if_present /etc/default/git-daemon 
"${INSTALL_ROOT}.new/config/git-daemon.default" || true
+  fi
+fi
+
 rm -rf "${INSTALL_ROOT}"
 mv "${INSTALL_ROOT}.new" "${INSTALL_ROOT}"
 chown -R root:root "${INSTALL_ROOT}"
+chmod 0700 "${CONFIG_DIR}"
+chmod 0600 "${ENV_FILE}"
+
+mirror_uid=$(id -u "${MIRROR_USER}")
+mirror_gid=$(getent group "${MIRROR_GROUP}" | awk -F: '{ print $3 }')
+cat > "${COMPOSE_ENV_FILE}" <<EOF
+MIRROR_ROOT=${MIRROR_ROOT}
+MIRROR_UID=${mirror_uid}
+MIRROR_GID=${mirror_gid}
+EOF
+chmod 0644 "${COMPOSE_ENV_FILE}"
 
 install -d -o "${MIRROR_USER}" -g "${MIRROR_GROUP}" -m 0755 "${MIRROR_ROOT}"
-install -d -o root -g root -m 0700 "${ENV_DIR}"
-if [ ! -f "${ENV_FILE}" ]; then
-  install -o root -g root -m 0600 \
-    "${INSTALL_ROOT}/env/github-mirror-webhook.env.example" \
-    "${ENV_FILE}"
-  log "created ${ENV_FILE}; set GITHUB_WEBHOOK_SECRET before starting webhook 
deliveries"
-fi
+install -d -o "${MIRROR_USER}" -g "${MIRROR_GROUP}" -m 0755 
"${MIRROR_ROOT}/.locks"
+install -d -o root -g root -m 0755 /var/log/github-mirror-smart-http
+
+ln -sfn "${INSTALL_ROOT}/config/git-daemon.default" /etc/default/git-daemon
 
-install -o root -g root -m 0644 \
-  "${INSTALL_ROOT}/git-daemon/git-daemon.default" \
-  /etc/default/git-daemon
+# Remove legacy units from the pre-Compose implementation.
+systemctl disable --now github-mirror-webhook.service >/dev/null 2>&1 || true
+systemctl disable --now github-mirror-smart-http.service >/dev/null 2>&1 || 
true
+rm -f /etc/systemd/system/github-mirror-webhook.service
+rm -f /etc/systemd/system/github-mirror-smart-http.service
 
 tmp_unit=$(mktemp)
-render_template "${INSTALL_ROOT}/systemd/github-mirror-webhook.service" 
"${tmp_unit}"
-install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-webhook.service
+render_template "${INSTALL_ROOT}/systemd/github-mirror.service" "${tmp_unit}"
+install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror.service
 render_template "${INSTALL_ROOT}/systemd/github-mirror-fallback.service" 
"${tmp_unit}"
 install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-fallback.service
 render_template "${INSTALL_ROOT}/systemd/github-mirror-fallback.timer" 
"${tmp_unit}"
 install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-fallback.timer
-render_template "${INSTALL_ROOT}/systemd/github-mirror-smart-http.service" 
"${tmp_unit}"
-install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-smart-http.service
 rm -f "${tmp_unit}"
 
 systemctl daemon-reload
@@ -133,26 +181,23 @@ if [ "${INIT_MIRRORS}" = "1" ]; then
     "${INSTALL_ROOT}/bin/init-mirrors.sh"
 fi
 
-systemctl enable github-mirror-webhook.service
+if [ "${BUILD_IMAGES}" = "1" ]; then
+  (cd "${INSTALL_ROOT}" && docker-compose build github-mirror 
github-mirror-smart-http)
+fi
+
+systemctl enable github-mirror.service
 if [ "${START_FALLBACK_TIMER}" = "1" ]; then
   systemctl enable --now github-mirror-fallback.timer
 else
   systemctl disable --now github-mirror-fallback.timer >/dev/null 2>&1 || true
 fi
 
-if [ "${START_SMART_HTTP}" = "1" ]; then
-  systemctl enable --now github-mirror-smart-http.service
-else
-  systemctl disable --now github-mirror-smart-http.service >/dev/null 2>&1 || 
true
-fi
-
-if [ "${START_WEBHOOK}" = "1" ] ||
-   { [ "${START_WEBHOOK}" = "auto" ] && grep -q '^GITHUB_WEBHOOK_SECRET=' 
"${ENV_FILE}" &&
-     ! grep -q '^GITHUB_WEBHOOK_SECRET=CHANGE_ME' "${ENV_FILE}"; }; then
-  systemctl restart github-mirror-webhook.service
+if [ "${START_COMPOSE}" = "1" ] ||
+   { [ "${START_COMPOSE}" = "auto" ] && secret_is_configured; }; then
+  systemctl restart github-mirror.service
 else
-  log "webhook service installed but not started; configure ${ENV_FILE}, then 
run:"
-  log "  sudo systemctl restart github-mirror-webhook.service"
+  log "compose stack installed but not started; configure ${ENV_FILE}, then 
run:"
+  log "  sudo systemctl enable --now github-mirror.service"
 fi
 
 log "install complete"
diff --git a/github-mirror/git-daemon/git-daemon.default 
b/github-mirror/config/git-daemon.default
similarity index 100%
rename from github-mirror/git-daemon/git-daemon.default
rename to github-mirror/config/git-daemon.default
diff --git a/github-mirror/env/github-mirror-webhook.env.example 
b/github-mirror/config/github-mirror-webhook.env.example
similarity index 56%
rename from github-mirror/env/github-mirror-webhook.env.example
rename to github-mirror/config/github-mirror-webhook.env.example
index 17564f8..225e716 100644
--- a/github-mirror/env/github-mirror-webhook.env.example
+++ b/github-mirror/config/github-mirror-webhook.env.example
@@ -1,14 +1,14 @@
-# Copy to /etc/trafficserver-github-mirror/github-mirror-webhook.env.
+# Copy to /opt/github-mirror/config/github-mirror-webhook.env.
 # Keep the installed copy root-owned and mode 0600.
 
 GITHUB_WEBHOOK_SECRET=CHANGE_ME
 
-WEBHOOK_HOST=127.0.0.1
+WEBHOOK_HOST=0.0.0.0
 WEBHOOK_PORT=9419
 WEBHOOK_PATH=/github-mirror-webhook
 
 MIRROR_ROOT=/home/mirror
-UPDATE_MIRROR=/opt/trafficserver-ci/github-mirror/bin/update-mirror.sh
+UPDATE_MIRROR=/opt/github-mirror/bin/update-mirror.sh
 
 MAX_BODY_BYTES=1048576
 UPDATE_TIMEOUT_SECONDS=600
diff --git a/github-mirror/cron/github-mirror b/github-mirror/cron/github-mirror
deleted file mode 100644
index c14d77a..0000000
--- a/github-mirror/cron/github-mirror
+++ /dev/null
@@ -1,15 +0,0 @@
-# Temporary cron fallback for GitHub mirror updates before ASF webhooks are 
live.
-#
-# Install on the controller as:
-#   sudo install -o root -g root -m 0644 \
-#     /opt/trafficserver-ci/github-mirror/cron/github-mirror \
-#     /etc/cron.d/github-mirror
-#
-# Remove this file after the GitHub webhook has been validated and the
-# github-mirror-fallback.timer is the only periodic fallback updater.
-
-SHELL=/bin/bash
-PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-MIRROR_ROOT=/home/mirror
-
-* * * * * gitdaemon /opt/trafficserver-ci/github-mirror/bin/update-mirror.sh 
--all 2>&1 | /usr/bin/logger -t github-mirror-cron
diff --git a/github-mirror/docker-compose.yml b/github-mirror/docker-compose.yml
new file mode 100644
index 0000000..b08c7d8
--- /dev/null
+++ b/github-mirror/docker-compose.yml
@@ -0,0 +1,46 @@
+services:
+  github-mirror:
+    build:
+      context: .
+      dockerfile: webhook/Dockerfile
+    image: trafficserver/github-mirror-webhook:latest
+    container_name: github-mirror
+    restart: unless-stopped
+    user: "${MIRROR_UID}:${MIRROR_GID}"
+    ports:
+      - "127.0.0.1:9419:9419"
+    volumes:
+      - "${MIRROR_ROOT:-/home/mirror}:/home/mirror:rw"
+      - ".:/opt/github-mirror:ro"
+    env_file:
+      - ./config/github-mirror-webhook.env
+    environment:
+      WEBHOOK_HOST: "0.0.0.0"
+      WEBHOOK_PORT: "9419"
+      WEBHOOK_PATH: "/github-mirror-webhook"
+      MIRROR_ROOT: "/home/mirror"
+      UPDATE_MIRROR: "/opt/github-mirror/bin/update-mirror.sh"
+      HOME: "/tmp"
+
+  github-mirror-smart-http:
+    build:
+      context: ./httpd
+    image: trafficserver/github-mirror-smart-http:latest
+    container_name: github-mirror-smart-http
+    restart: unless-stopped
+    ports:
+      - "127.0.0.1:9417:80"
+    volumes:
+      - "${MIRROR_ROOT:-/home/mirror}:/usr/local/apache2/mirror:ro"
+      - /var/log/github-mirror-smart-http:/usr/local/apache2/logs
+
+  github-mirror-fallback:
+    image: trafficserver/github-mirror-webhook:latest
+    user: "${MIRROR_UID}:${MIRROR_GID}"
+    volumes:
+      - "${MIRROR_ROOT:-/home/mirror}:/home/mirror:rw"
+      - ".:/opt/github-mirror:ro"
+    environment:
+      MIRROR_ROOT: "/home/mirror"
+      HOME: "/tmp"
+    command: ["/opt/github-mirror/bin/update-mirror.sh", "--all"]
diff --git a/github-mirror/httpd/docker-compose.yml 
b/github-mirror/httpd/docker-compose.yml
deleted file mode 100644
index b0ec2da..0000000
--- a/github-mirror/httpd/docker-compose.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-version: '3'
-
-services:
-  github-mirror-smart-http:
-    build: .
-    image: trafficserver/github-mirror-smart-http:latest
-    container_name: github-mirror-smart-http
-    restart: unless-stopped
-    ports:
-      - "127.0.0.1:9417:80"
-    volumes:
-      - /home/mirror:/usr/local/apache2/mirror:ro
-      - /var/log/github-mirror-smart-http:/usr/local/apache2/logs
diff --git a/github-mirror/systemd/github-mirror-fallback.service 
b/github-mirror/systemd/github-mirror-fallback.service
index 36f21e5..83d40f2 100644
--- a/github-mirror/systemd/github-mirror-fallback.service
+++ b/github-mirror/systemd/github-mirror-fallback.service
@@ -6,9 +6,7 @@ Wants=network-online.target
 
 [Service]
 Type=oneshot
-User=@MIRROR_USER@
-Group=@MIRROR_GROUP@
-Environment=MIRROR_ROOT=@MIRROR_ROOT@
-ExecStart=@INSTALL_ROOT@/bin/update-mirror.sh --all
+WorkingDirectory=@INSTALL_ROOT@
+ExecStart=/usr/bin/docker-compose run --rm github-mirror-fallback
 TimeoutStartSec=30min
 Nice=5
diff --git a/github-mirror/systemd/github-mirror-smart-http.service 
b/github-mirror/systemd/github-mirror-smart-http.service
deleted file mode 100644
index e9c96f0..0000000
--- a/github-mirror/systemd/github-mirror-smart-http.service
+++ /dev/null
@@ -1,20 +0,0 @@
-[Unit]
-Description=ATS CI GitHub mirror smart HTTP service
-Documentation=file:@INSTALL_ROOT@/README.md
-After=network-online.target docker.service
-Wants=network-online.target
-Requires=docker.service
-
-[Service]
-Type=simple
-WorkingDirectory=@INSTALL_ROOT@/httpd
-ExecStartPre=/usr/bin/install -d -o root -g root -m 0755 
/var/log/github-mirror-smart-http
-ExecStartPre=/usr/bin/docker-compose build
-ExecStart=/usr/bin/docker-compose up --remove-orphans
-ExecStop=/usr/bin/docker-compose down
-Restart=on-failure
-RestartSec=5s
-TimeoutStartSec=10min
-
-[Install]
-WantedBy=multi-user.target
diff --git a/github-mirror/systemd/github-mirror-webhook.service 
b/github-mirror/systemd/github-mirror-webhook.service
deleted file mode 100644
index b93141b..0000000
--- a/github-mirror/systemd/github-mirror-webhook.service
+++ /dev/null
@@ -1,24 +0,0 @@
-[Unit]
-Description=ATS CI GitHub mirror webhook receiver
-Documentation=file:@INSTALL_ROOT@/README.md
-After=network-online.target
-Wants=network-online.target
-
-[Service]
-Type=simple
-User=@MIRROR_USER@
-Group=@MIRROR_GROUP@
-EnvironmentFile=/etc/trafficserver-github-mirror/github-mirror-webhook.env
-WorkingDirectory=@MIRROR_ROOT@
-ExecStart=@INSTALL_ROOT@/bin/github-mirror-webhook.py
-Restart=on-failure
-RestartSec=5s
-RestartPreventExitStatus=1
-NoNewPrivileges=true
-PrivateTmp=true
-ProtectSystem=full
-ProtectHome=false
-ReadWritePaths=@MIRROR_ROOT@
-
-[Install]
-WantedBy=multi-user.target
diff --git a/github-mirror/systemd/github-mirror.service 
b/github-mirror/systemd/github-mirror.service
new file mode 100644
index 0000000..c8edcee
--- /dev/null
+++ b/github-mirror/systemd/github-mirror.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=ATS CI GitHub mirror compose stack
+Documentation=file:@INSTALL_ROOT@/README.md
+After=network-online.target docker.service
+Wants=network-online.target
+Requires=docker.service
+
+[Service]
+Type=simple
+WorkingDirectory=@INSTALL_ROOT@
+ExecStartPre=/usr/bin/install -d -o root -g root -m 0755 
/var/log/github-mirror-smart-http
+ExecStartPre=/usr/bin/docker-compose build github-mirror 
github-mirror-smart-http
+ExecStart=/usr/bin/docker-compose up --remove-orphans github-mirror 
github-mirror-smart-http
+ExecStop=/usr/bin/docker-compose stop github-mirror github-mirror-smart-http
+ExecStopPost=/usr/bin/docker-compose rm -f github-mirror 
github-mirror-smart-http
+Restart=on-failure
+RestartSec=5s
+TimeoutStartSec=10min
+
+[Install]
+WantedBy=multi-user.target
diff --git a/github-mirror/webhook/Dockerfile b/github-mirror/webhook/Dockerfile
new file mode 100644
index 0000000..5b56693
--- /dev/null
+++ b/github-mirror/webhook/Dockerfile
@@ -0,0 +1,13 @@
+FROM python:3.11-slim
+
+RUN apt-get update \
+  && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends 
\
+    bash \
+    ca-certificates \
+    git \
+    util-linux \
+  && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /opt/github-mirror
+
+CMD ["/opt/github-mirror/bin/github-mirror-webhook.py"]


Reply via email to