diff --git a/.gitignore b/.gitignore index 646d70e1..11a85457 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,7 @@ venv.bak/ # dotenv files .env* + +# nix build results +/result +/result-* diff --git a/README.rst b/README.rst index ad32f72b..f61526cb 100644 --- a/README.rst +++ b/README.rst @@ -158,6 +158,157 @@ The bot URL must be exposed on the internet through a reverse proxy and configured as a GitHub webhook, using the secret configured in ``GITHUB_SECRET``. +Getting started with nix +------------------------ + +The flake provides everything the bot shells out to -- python 3.12, redis, +git, rsync, pandoc and the commands from `maintainer-tools +`_ -- so nix with flakes enabled is +the only prerequisite. + +1. Create ``.env`` from `environment.sample <./environment.sample>`_. Five + variables have to be filled in before the bot can do anything: + + ``GITHUB_SECRET`` + the secret shared with the GitHub webhook + ``GITHUB_LOGIN`` + the login of the account the bot acts as + ``GITHUB_TOKEN`` + a token for that account + ``GIT_NAME`` and ``GIT_EMAIL`` + the author identity for the commits the bot pushes + + Add ``GITHUB_ORG`` to enable the scheduled tasks -- the nightly main + branch bot and the hourly ``ready to merge`` tagging. Without it the + scheduler runs nothing but its heartbeat. + +2. Warm the build. The first one compiles the python 3.12 dependencies + locally, as nixpkgs only caches the default interpreter's package set:: + + nix build .#stack + +3. Start the stack:: + + nix run . + + This brings up redis (``queue``), the webhook listener (``bot``, on + ``HTTP_PORT``, default 8080), the celery ``worker`` and the celery + scheduler (``beat``), the last three waiting for redis to answer a ping. + +4. Expose the listener, so that GitHub can reach it: a reverse proxy in + production, or a tunnel while developing:: + + ngrok http 8080 + +5. Configure each repository the bot should act on, as described in + `Setting up a repository for the bot`_. + +The same command drives a stack that is already running, locating it by a +socket path derived from the checkout:: + + nix run . -- process list + nix run . -- attach + nix run . -- down + +State lives in ``./data``, where the docker composition's bind mounts put it, +so both ways of running share the git clone cache: ``data/queue`` holds the +redis append-only file, ``data/cache`` the bare clone the bot keeps of each +repository, ``data/simple-index`` the locally published wheels, and +``data/logs`` the process-compose log. + +The stack reads the same ``.env`` as the docker composition, and rewrites the +two settings that only make sense inside a container: a ``BROKER_URI`` of +``redis://queue`` becomes the local redis, and a ``SIMPLE_INDEX_ROOT`` under +``/app/run`` becomes ``./data/simple-index``. Set ``OCABOT_REDIS_PORT`` if +6379 is already taken. + +``nix develop`` gives a shell with the bot's dependencies, the test +dependencies, the ``oca-gen-*`` commands and the stack itself as +``oca-github-bot-stack``, so ``pytest`` and ``pre-commit run --all-files`` +work directly. The maintainer tools are a flake input, pinned to the revision +the ``Dockerfile`` installs; to work against a local checkout of them:: + + nix run . --override-input maintainer-tools path:../maintainer-tools + +Setting up a repository for the bot +----------------------------------- + +The bot account +~~~~~~~~~~~~~~~ + +``GITHUB_TOKEN`` must belong to an account with write access to the +repository. The bot comments on pull requests, adds labels, creates labels +and milestones, pushes generated files to main branches, pushes to the target +branch when merging, and deletes merged branches. With a classic token that +is the ``repo`` scope; with a fine-grained token, read and write on contents, +issues and pull requests. + +The webhook +~~~~~~~~~~~ + +Add a webhook on the repository -- or on the organisation, to cover all of +them at once -- pointing at the bot: + +* **Payload URL** -- the public URL of the bot, which serves the webhook at ``/`` +* **Content type** -- ``application/json`` +* **Secret** -- the same value as ``GITHUB_SECRET`` +* **Events** -- *Pull requests*, *Pull request reviews*, *Issue comments*, + *Pushes*, *Statuses*, *Check runs* and *Check suites* + +No other event is handled. Issue comments are what carry the ``/ocabot`` +commands, so leaving them out makes every command silently do nothing. + +Branches +~~~~~~~~ + +The main branch operations only run on branches named after an Odoo series, +``x.y``, from ``MAIN_BRANCH_BOT_MIN_VERSION`` (default ``11.0``) upwards. +From ``GEN_PYPROJECT_MIN_VERSION`` (default ``17.0``) the bot generates +``pyproject.toml`` rather than ``setup.py``. Branches named ``master``, +``main`` or ``x.y`` are never deleted by the bot. + +What ``/ocabot merge`` needs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The merge bot does not use the GitHub merge button. It pushes a temporary +branch named ``-ocabot-merge-pr--by--bump-`` to the +repository, waits for the CI to go green on that branch, then fast-forwards +the target branch onto it and deletes the temporary branch. Two consequences +for the repository: + +* the CI must run on pushed branches, not only on pull requests, or the bot + waits for a status that never arrives; +* the bot account must be able to push to the target branch. A protected + branch needs the bot allowed to bypass the restriction, otherwise the final + push is refused after the CI has already run. + +``GITHUB_STATUS_IGNORED`` and ``GITHUB_CHECK_SUITES_IGNORED`` list the +statuses and check suites that do not count towards green. + +Who may invoke commands +~~~~~~~~~~~~~~~~~~~~~~~ + +A ``/ocabot`` command is honoured when the commenter has push access to the +repository, or is declared in the ``maintainers`` key of every addon the pull +request modifies. ``MAINTAINER_CHECK_ODOO_RELEASES`` lists the branches +searched for that declaration. + +Labels and milestones +~~~~~~~~~~~~~~~~~~~~~ + +Nothing has to be created by hand. The bot adds ``needs review`` when the CI +goes green, ``approved`` once a pull request has ``APPROVALS_REQUIRED`` +approving reviews (default 2), ``ready to merge`` once it is also +``MIN_PR_AGE`` days old (default 5), and ``bot is merging ⏳`` then +``merged 🎉`` while merging. It also creates one label per modified addon at +repository level, coloured with ``MODULE_LABEL_COLOR``. ``/ocabot migration`` +creates the milestone named after the target branch, and the "Migration to +version x.y" issue, if they do not exist yet. + +One label is read rather than written: ``work in progress`` on a pull request +suppresses ``needs review``, as does a title starting with ``wip:`` or +``[wip]``. + Development =========== diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..2a57872b --- /dev/null +++ b/flake.lock @@ -0,0 +1,45 @@ +{ + "nodes": { + "maintainer-tools": { + "flake": false, + "locked": { + "lastModified": 1758451543, + "narHash": "sha256-oye26Il1d2V6Wu+KY0/QFRgFjlldRnt3EBdlSyCrA+I=", + "owner": "OCA", + "repo": "maintainer-tools", + "rev": "f70373fca6830e6536c3967ba5794311602b4bd4", + "type": "github" + }, + "original": { + "owner": "OCA", + "repo": "maintainer-tools", + "rev": "f70373fca6830e6536c3967ba5794311602b4bd4", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1789885152, + "narHash": "sha256-mDNo95riUw8k0MA2Btzj/OuiMcfLKofqIliLYsLbrSY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "6d663c0533ff269008fb84e45930151e37c99db9", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "maintainer-tools": "maintainer-tools", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..330b206a --- /dev/null +++ b/flake.nix @@ -0,0 +1,161 @@ +{ + description = "OCA GitHub bot - development stack (process-compose)"; + + inputs = { + # Pinned to 26.05: nixpkgs-unstable ships setuptools 83, which dropped + # pkg_resources and breaks setuptools-odoo 3.3.2 at import time. + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; + + # Same revision the Dockerfile installs into its /ocamt virtualenv. + # Point it at a checkout to hack on both at once: + # nix run . --override-input maintainer-tools path:../maintainer-tools + maintainer-tools = { + url = "github:OCA/maintainer-tools/f70373fca6830e6536c3967ba5794311602b4bd4"; + flake = false; + }; + }; + + outputs = + { + self, + nixpkgs, + maintainer-tools, + }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + forAllSystems = + f: + nixpkgs.lib.genAttrs systems ( + system: + f ( + import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + } + ) + ); + in + { + overlays.default = final: prev: { + # pythonPackagesExtensions rather than overriding python312Packages: + # only the former is picked up by python312.withPackages. + pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [ + (pyfinal: pyprev: { + odoorpc = pyfinal.callPackage ./nix/odoorpc.nix { }; + oca-github-bot = pyfinal.callPackage ./nix/oca-github-bot.nix { }; + + # The bot runs on 3.12 (as the Dockerfile does), and nixpkgs only + # caches builds of the default interpreter's package set, so the + # first build compiles these locally. Their test suites pull in + # cassandra-driver, pymongo and friends, which costs far more than + # the packages themselves. + celery = pyprev.celery.overridePythonAttrs { doCheck = false; }; + kombu = pyprev.kombu.overridePythonAttrs { doCheck = false; }; + + # setup.py caps this at <2 ("we use an old version of self-hosted + # Sentry"), so honour the cap rather than the nixpkgs version. + sentry-sdk = pyprev.sentry-sdk.overridePythonAttrs (old: rec { + version = "1.45.1"; + src = pyfinal.fetchPypi { + pname = "sentry_sdk"; + inherit version; + hash = "sha256-oWyZfA9OPfY8D8XkIHzLGrN5AEM+D3L++IMV0xeCmiY="; + }; + doCheck = false; + # The 2.x expression's optional-dependency wiring does not apply. + dontCheckRuntimeDeps = true; + }); + }) + ]; + + oca-maintainer-tools = final.callPackage ./nix/maintainer-tools.nix { + src = maintainer-tools; + version = "0-unstable-${builtins.substring 0 8 (maintainer-tools.lastModifiedDate or "19700101")}"; + }; + }; + + packages = forAllSystems ( + pkgs: + let + inherit (pkgs) python312Packages; + + pythonEnv = pkgs.python312.withPackages (ps: [ + ps.oca-github-bot + ps.pip # build_wheels.py provisions a venv at runtime + ]); + in + rec { + default = stack; + + stack = pkgs.callPackage ./nix/stack.nix { + inherit pythonEnv; + maintainer-tools = pkgs.oca-maintainer-tools; + }; + + inherit pythonEnv; + oca-github-bot = python312Packages.oca-github-bot; + oca-maintainer-tools = pkgs.oca-maintainer-tools; + } + ); + + apps = forAllSystems (pkgs: { + default = { + type = "app"; + program = nixpkgs.lib.getExe self.packages.${pkgs.stdenv.hostPlatform.system}.stack; + }; + }); + + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = [ + (pkgs.python312.withPackages ( + ps: + ps.oca-github-bot.dependencies + ++ [ + ps.pip + ps.pytest + ps.pytest-asyncio + ps.pytest-cov + ps.pytest-mock + ps.pytest-vcr + ] + )) + pkgs.oca-maintainer-tools + self.packages.${pkgs.stdenv.hostPlatform.system}.stack + ] + ++ (with pkgs; [ + git + openssh + pandoc + pre-commit + process-compose + redis + rsync + ]); + + shellHook = '' + # Work against the checkout rather than an installed copy. + # + # This *overwrites* PYTHONPATH rather than prepending to it: python + # packages in a shell (pre-commit, here) export their dependency + # closure onto PYTHONPATH, and that closure is built for the + # default interpreter. Left in place it leaks python 3.13 + # site-packages into every child process -- including the venv + # build_wheels.py provisions, where it breaks `pip check`. The + # console scripts carry their own wrapping and are unaffected. + export PYTHONPATH="$PWD/src" + export SSL_CERT_FILE="''${SSL_CERT_FILE:-${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt}" + echo "oca-github-bot devShell: 'oca-github-bot-stack' runs redis + bot + worker + beat" + ''; + }; + }); + + formatter = forAllSystems (pkgs: pkgs.nixfmt-tree); + }; +} diff --git a/nix/maintainer-tools.nix b/nix/maintainer-tools.nix new file mode 100644 index 00000000..acffe74e --- /dev/null +++ b/nix/maintainer-tools.nix @@ -0,0 +1,85 @@ +# OCA maintainer-tools. The bot shells out to these commands by name (see +# src/oca_github_bot/tasks/main_branch_bot.py), so they only need to be on +# PATH -- keeping them in their own derivation mirrors the Dockerfile's +# isolation between the bot's dependencies and the tools' dependencies. +# +# The source comes from the `maintainer-tools` flake input, pinned by default +# to the revision the Dockerfile installs. To build a local checkout instead: +# +# nix run . --override-input maintainer-tools path:../maintainer-tools +{ + lib, + python312Packages, + pandoc, + src, + version, +}: + +python312Packages.buildPythonApplication { + pname = "oca-maintainers-tools"; + inherit src version; + pyproject = true; + + # hatch-vcs derives the version from git metadata, which the source closure + # does not carry. + env.SETUPTOOLS_SCM_PRETEND_VERSION = "0.0.0"; + + build-system = with python312Packages; [ + hatchling + hatch-vcs + ]; + + dependencies = with python312Packages; [ + appdirs + click + docutils + freezegun + github3-py + jinja2 + manifestoo-core + polib + pygments + pypandoc + pyyaml + requests + setuptools-odoo + toml + towncrier + twine + wheel + whool + ]; + + # erppeek, selenium and pyproject-dependencies are declared dependencies of + # the distribution but are only imported by tools the bot never invokes + # (odoo_login, publish_modules). erppeek is not in nixpkgs, so drop them + # rather than package a dependency nothing here uses. + dontCheckRuntimeDeps = true; + + # pypandoc.ensure_pandoc_installed() downloads a pandoc release unless one is + # already on PATH; oca-gen-addon-readme calls it for every markdown fragment. + makeWrapperArgs = [ + "--prefix PATH : ${lib.makeBinPath [ pandoc ]}" + ]; + + # pythonImportsCheck is inherited from dependencies via setup hooks here, which + # drags in setuptools-odoo test fixtures; check the console scripts instead. + dontUsePythonImportsCheck = true; + + postInstallCheck = '' + $out/bin/oca-gen-addons-table --help > /dev/null + $out/bin/oca-gen-addon-readme --help > /dev/null + $out/bin/oca-gen-addon-icon --help > /dev/null + $out/bin/oca-gen-metapackage --help > /dev/null + $out/bin/oca-towncrier --help > /dev/null + ''; + + # No test suite is shipped in the wheel. + doCheck = false; + + meta = { + description = "Set of tools to help managing Odoo Community projects"; + homepage = "https://github.com/OCA/maintainer-tools"; + license = lib.licenses.agpl3Only; + }; +} diff --git a/nix/oca-github-bot.nix b/nix/oca-github-bot.nix new file mode 100644 index 00000000..a841591d --- /dev/null +++ b/nix/oca-github-bot.nix @@ -0,0 +1,76 @@ +{ + lib, + buildPythonPackage, + setuptools, + setuptools-scm, + aiohttp, + appdirs, + celery, + flower, + gidgethub, + github3-py, + lxml, + odoorpc, + packaging, + redis, + sentry-sdk, + setuptools-odoo, + twine, + whool, + version ? "20240706", +}: + +buildPythonPackage { + pname = "oca-github-bot"; + inherit version; + pyproject = true; + + src = lib.fileset.toSource { + root = ../.; + fileset = lib.fileset.unions [ + ../setup.py + ../setup.cfg + ../README.rst + ../src + ]; + }; + + # setup.py uses setuptools_scm and there is no .git in the source closure. + env.SETUPTOOLS_SCM_PRETEND_VERSION = version; + + build-system = [ + setuptools + setuptools-scm + ]; + + dependencies = [ + aiohttp + appdirs + celery + flower + gidgethub + github3-py + lxml + odoorpc + packaging + redis # celery[redis] extra + sentry-sdk + setuptools # build_wheels.py and setuptools-odoo need it at runtime + setuptools-odoo + twine + whool + ]; + + pythonImportsCheck = [ "oca_github_bot" ]; + + # Tests live outside the package and need the checkout; run them from the + # devShell (pytest) instead. + doCheck = false; + + meta = { + description = "GitHub bot for the Odoo Community Association"; + homepage = "https://github.com/OCA/oca-github-bot"; + license = lib.licenses.mit; + mainProgram = "oca-github-bot"; + }; +} diff --git a/nix/odoorpc.nix b/nix/odoorpc.nix new file mode 100644 index 00000000..71f51a7b --- /dev/null +++ b/nix/odoorpc.nix @@ -0,0 +1,33 @@ +# odoorpc is not packaged in nixpkgs; it is a small pure-python XML-RPC/JSON-RPC +# client with no runtime dependencies outside the stdlib. +{ + lib, + buildPythonPackage, + fetchPypi, + setuptools, +}: + +buildPythonPackage rec { + pname = "odoorpc"; + version = "0.10.1"; + pyproject = true; + + src = fetchPypi { + pname = "OdooRPC"; + inherit version; + hash = "sha256-0LxSTFuWB4EWVXW62cE9Ay1vlow8CSdicQRd27tIOqU="; + }; + + build-system = [ setuptools ]; + + # The test suite talks to a live Odoo server. + doCheck = false; + + pythonImportsCheck = [ "odoorpc" ]; + + meta = { + description = "Python module providing an easy way to pilot Odoo servers through RPC"; + homepage = "https://github.com/OCA/odoorpc"; + license = lib.licenses.lgpl3Plus; + }; +} diff --git a/nix/stack.nix b/nix/stack.nix new file mode 100644 index 00000000..8b02f550 --- /dev/null +++ b/nix/stack.nix @@ -0,0 +1,197 @@ +# The dev stack: the four docker-compose services, run by process-compose. +{ + lib, + writeShellApplication, + formats, + cacert, + git, + openssh, + pandoc, + process-compose, + redis, + rsync, + pythonEnv, + maintainer-tools, +}: + +let + celery = "${pythonEnv}/bin/celery --app=oca_github_bot.queue.app"; + + # Everything the bot shells out to at runtime: git/rsync/openssh from + # github.py and pypi.py, the oca-gen-* commands from main_branch_bot.py. + toolchain = lib.makeBinPath [ + pythonEnv + maintainer-tools + git + openssh + pandoc + redis + rsync + ]; + + dependsOnQueue = { + queue.condition = "process_healthy"; + }; + + restartOnFailure = { + restart = "on_failure"; + backoff_seconds = 2; + max_restarts = 10; + }; + + config = { + version = "0.5"; + + processes = { + # docker-compose: queue (redis:4-alpine) + queue = { + command = lib.concatStringsSep " " [ + "redis-server" + "--appendonly yes" + "--auto-aof-rewrite-min-size 64mb" + "--auto-aof-rewrite-percentage 10" + "--dir \"$OCABOT_DATA/queue\"" + "--bind 127.0.0.1" + "--port \"$OCABOT_REDIS_PORT\"" + "--save ''" + ]; + readiness_probe = { + exec.command = "redis-cli -p \"$OCABOT_REDIS_PORT\" ping"; + initial_delay_seconds = 1; + period_seconds = 2; + timeout_seconds = 2; + failure_threshold = 15; + }; + availability = restartOnFailure; + }; + + # docker-compose: bot -- the aiohttp webhook listener + bot = { + command = "${pythonEnv}/bin/python -m oca_github_bot"; + depends_on = dependsOnQueue; + availability = restartOnFailure; + }; + + # docker-compose: worker + worker = { + command = "${celery} worker --concurrency=2 --loglevel=INFO"; + depends_on = dependsOnQueue; + availability = restartOnFailure; + }; + + # docker-compose: beat -- scheduled tasks from src/oca_github_bot/cron.py + beat = { + # --schedule keeps celerybeat-schedule out of the working directory. + command = "${celery} beat --loglevel=INFO --schedule \"$OCABOT_DATA/celerybeat-schedule\""; + depends_on = dependsOnQueue; + availability = restartOnFailure; + }; + }; + }; + + configFile = (formats.yaml { }).generate "process-compose.yaml" config; +in +writeShellApplication { + name = "oca-github-bot-stack"; + + runtimeInputs = [ process-compose ]; + + text = '' + # Run from the checkout unless told otherwise; state lives in ./data, + # exactly where the docker composition's bind mounts put it. + root=''${OCABOT_ROOT:-$PWD} + + if [ ! -f "$root/.env" ]; then + echo "oca-github-bot: no .env in $root" >&2 + echo " cp environment.sample .env # then fill in GITHUB_* and GIT_*" >&2 + exit 1 + fi + + # Parse .env the way docker's env_file does -- KEY=VALUE taken literally -- + # rather than sourcing it: values such as GIT_NAME are not shell-quoted, + # and .env should not be able to run code. + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + "" | \#*) continue ;; + *=*) ;; + *) continue ;; + esac + key=''${line%%=*} + value=''${line#*=} + case "$key" in + *[!A-Za-z0-9_]*) continue ;; + esac + # docker compose strips one layer of matching quotes + case "$value" in + \"*\") value=''${value#\"}; value=''${value%\"} ;; + \'*\') value=''${value#\'}; value=''${value%\'} ;; + esac + export "$key=$value" + done < "$root/.env" + + OCABOT_DATA="$root/data" + OCABOT_REDIS_PORT=''${OCABOT_REDIS_PORT:-6379} + export OCABOT_DATA OCABOT_REDIS_PORT + + # docker-compose runs the containers as "''${UID}:''${GID}", which expands to + # ":" in any shell that does not export UID (fish, among others) -- the + # containers then run as root and leave root-owned files in ./data. Say so + # plainly instead of failing later with a bare EACCES from redis. + for dir in queue cache simple-index logs; do + if ! mkdir -p "$OCABOT_DATA/$dir" 2>/dev/null || [ ! -w "$OCABOT_DATA/$dir" ]; then + echo "oca-github-bot: $OCABOT_DATA/$dir is not writable by $(id -un)" >&2 + echo " owner: $(stat -c '%U:%G' "$OCABOT_DATA/$dir" 2>/dev/null || echo "could not create it")" >&2 + echo " fix: sudo chown -R \"$(id -u):$(id -g)\" \"$OCABOT_DATA\"" >&2 + exit 1 + fi + done + + # The docker composition points these at paths inside the image; rewrite + # them for a local run so a stock .env works unchanged. + case "''${BROKER_URI:-redis://queue}" in + redis://queue*) BROKER_URI="redis://127.0.0.1:$OCABOT_REDIS_PORT/0" ;; + esac + case "''${SIMPLE_INDEX_ROOT:-}" in + /app/run/*) SIMPLE_INDEX_ROOT="$OCABOT_DATA/simple-index" ;; + esac + export BROKER_URI SIMPLE_INDEX_ROOT + + # docker-compose published the webhook port as 127.0.0.1:8080 only; without + # a HTTP_HOST aiohttp would listen on every interface. + export HTTP_HOST="''${HTTP_HOST:-127.0.0.1}" + + # github.py caches bare clones under appdirs.user_cache_dir("oca-mqt"); + # in the container that resolved to /app/run/.cache -> ./data/cache. + export XDG_CACHE_HOME="$OCABOT_DATA/cache" + + # The python env below is self-contained; an inherited PYTHONPATH (from a + # nix shell, say) would only leak foreign site-packages into the bot and + # into the venv build_wheels.py provisions. + unset PYTHONPATH + + export PATH="${toolchain}:$PATH" + export SSL_CERT_FILE="''${SSL_CERT_FILE:-${cacert}/etc/ssl/certs/ca-bundle.crt}" + export GIT_SSL_CAINFO="$SSL_CERT_FILE" + + # process-compose's own API listens on 8080 by default, which is the port + # the bot wants; keep it off TCP altogether. The socket lives in TMPDIR + # because a unix socket path is limited to ~108 bytes, which a checkout + # nested a few directories deep would blow past. Deriving it from $root + # means the client subcommands below find the server without being told. + PC_SOCKET_PATH="''${TMPDIR:-/tmp}/oca-github-bot-$(printf '%s' "$root" | cksum | cut -d' ' -f1).sock" + export PC_SOCKET_PATH + + # --config and --log-file belong to `up` alone, while --use-uds and + # --unix-socket are global. Passing the first two to a client subcommand + # ("down", "process list", "attach") fails with `unknown flag: --config`, + # so configure `up` through its environment variables instead. + case "''${1:-}" in + "" | -* | up) + export PC_CONFIG_FILES="${configFile}" + export PC_LOG_FILE="$OCABOT_DATA/logs/process-compose.log" + ;; + esac + + exec process-compose --use-uds "$@" + ''; +}