#!/usr/bin/env bash # Host-guarded WRITE wrapper for the Arcodange Dolibarr SANDBOX API. # # THE GUARANTEE (ADR-0003): this wrapper REFUSES to run against anything that is # not the erp-sandbox host. It is the structural reason an AI agent driving this # skill can never mutate production — prod is a different host, and the guard # below rejects it before any request is sent. # # Usage: # dol-write.sh [json-body|@file] # dol-write.sh POST /thirdparties '{"name":"Acme","client":"1"}' # dol-write.sh POST /invoices @invoice.json # dol-write.sh PUT /thirdparties/42 '{"phone":"+33..."}' # dol-write.sh GET /thirdparties?limit=5 # reads are allowed too # # Reads DOLIBARR_SANDBOX_URL + DOLIBARR_SANDBOX_API_KEY from the sibling .env # (.claude/skills/dolibarr-sandbox-write/.env), mode 600, gitignored. # Prints the response body to stdout; exits non-zero on HTTP >= 400. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${SCRIPT_DIR}/../.env" if [[ ! -f "${ENV_FILE}" ]]; then echo "dol-write.sh: missing ${ENV_FILE}" >&2 echo " Create it with DOLIBARR_SANDBOX_URL + DOLIBARR_SANDBOX_API_KEY. See README.md." >&2 exit 2 fi # shellcheck disable=SC1090 set -a; source "${ENV_FILE}"; set +a : "${DOLIBARR_SANDBOX_URL:?dol-write.sh: DOLIBARR_SANDBOX_URL not set in .env}" : "${DOLIBARR_SANDBOX_API_KEY:?dol-write.sh: DOLIBARR_SANDBOX_API_KEY not set in .env}" # --------------------------------------------------------------------------- # HOST GUARD — the structural safety invariant. Only the sandbox host passes. # Override the allowed pattern only via DOLIBARR_SANDBOX_HOST_RE in .env if the # sandbox FQDN ever changes; never widen it to include a prod host. # --------------------------------------------------------------------------- ALLOWED_RE="${DOLIBARR_SANDBOX_HOST_RE:-^https://erp-sandbox\.arcodange\.lab(/|$)}" if [[ ! "${DOLIBARR_SANDBOX_URL}" =~ ${ALLOWED_RE} ]]; then echo "dol-write.sh: REFUSING to write — DOLIBARR_SANDBOX_URL='${DOLIBARR_SANDBOX_URL}'" >&2 echo " is not the erp-sandbox host (allowed: ${ALLOWED_RE})." >&2 echo " This skill only writes the sandbox. Promotion to prod is a separate, human-gated step." >&2 exit 3 fi if [[ $# -lt 2 ]]; then echo "dol-write.sh: usage: dol-write.sh [json-body|@file]" >&2 exit 2 fi METHOD="$1"; API_PATH="$2"; BODY="${3-}" case "${METHOD}" in GET|POST|PUT|DELETE|PATCH) ;; *) echo "dol-write.sh: unsupported method '${METHOD}'" >&2; exit 2 ;; esac CURL_ARGS=( -sS -X "${METHOD}" -H "DOLAPIKEY: ${DOLIBARR_SANDBOX_API_KEY}" -H "Accept: application/json" --max-time 30 ) if [[ -n "${BODY}" ]]; then # curl --data supports '@file' to read a JSON body from a file. CURL_ARGS+=( -H "Content-Type: application/json" --data "${BODY}" ) fi BODY_FILE="$(mktemp -t dolwrite.XXXXXX)" trap 'rm -f "${BODY_FILE}"' EXIT HTTP_CODE=$(curl "${CURL_ARGS[@]}" \ -o "${BODY_FILE}" -w "%{http_code}" \ "${DOLIBARR_SANDBOX_URL}/api/index.php${API_PATH}") cat "${BODY_FILE}" if [[ "${HTTP_CODE}" -ge 400 ]]; then echo "" >&2 echo "dol-write.sh: HTTP ${HTTP_CODE} on ${METHOD} ${API_PATH}" >&2 exit 1 fi