summaryrefslogtreecommitdiff
path: root/bin/dot-request
blob: a34fe463a7848095d979ffcc6b7fd11a0029087f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/bin/bash

# Submit a change request from a machine that cannot push.
#
# A checkout cloned over https cannot publish anything: a change made on such a
# machine is lost at the next pull. So the change is written out, signed, and
# handed to whoever can apply it -- see "Can this machine push?" in CLAUDE.md.
#
#   dot-request --key ~/.config/dot/inbox-key < request.md
#   some-agent-writing-a-request | dot-request --key ~/.config/dot/inbox-key
#
# The request is markdown, and its first line is its title (`# ...`). Say which
# files change, give the diff or the exact replacement text rather than a
# description of it, why, and how to verify it.
#
# It posts to the request inbox, which is a public endpoint and named here on
# purpose -- see "Can this machine push?" in CLAUDE.md. Reaching it proves
# nothing on its own: a request is stored only if it carries a signature from a
# key authorised for that inbox. Set DOT_REQUEST_URL to point somewhere else
# when testing.
#
# Runs under macOS's /bin/bash 3.2.

set -euo pipefail

key=""
url="${DOT_REQUEST_URL:-https://req.soarez.net/inbox/dot}"

usage() {
    sed -n '3,25p' "$0" | sed 's/^# \{0,1\}//'
    exit "${1:-0}"
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        --key) key="${2:-}"; shift 2 ;;
        --key=*) key="${1#--key=}"; shift ;;
        -h|--help) usage 0 ;;
        *) >&2 echo "dot-request: unknown argument: $1"; usage 1 ;;
    esac
done

if [[ -z "${key}" ]]; then
    >&2 echo "dot-request: --key is required (the ssh key this machine signs with)"
    exit 1
fi
if [[ ! -r "${key}" ]]; then
    >&2 echo "dot-request: cannot read ${key}"
    exit 1
fi
if [[ -t 0 ]]; then
    >&2 echo "dot-request: the request is read from stdin"
    usage 1
fi

tmp="$(mktemp -d)"
trap 'rm -rf "${tmp}"' EXIT
cat > "${tmp}/request.md"

if [[ ! -s "${tmp}/request.md" ]]; then
    >&2 echo "dot-request: empty request"
    exit 1
fi
if ! head -1 "${tmp}/request.md" | grep -q '^# '; then
    >&2 echo "dot-request: the first line must be a markdown heading, '# something'"
    exit 1
fi

# The namespace names the inbox this request is for, and the store verifies
# against the same one. It is what stops a request signed here being replayed
# into a different inbox, and what stops this key being made to sign for
# anything else.
ssh-keygen -Y sign -q -f "${key}" -n sz-request:dot "${tmp}/request.md"
cat "${tmp}/request.md" "${tmp}/request.md.sig" > "${tmp}/signed.md"

code="$(curl -sS -o "${tmp}/reply" -w '%{http_code}' \
    -X POST -H 'Content-Type: text/markdown' \
    --data-binary "@${tmp}/signed.md" "${url}")"

if [[ "${code}" == "201" ]]; then
    echo "submitted: $(cat "${tmp}/reply")"
else
    >&2 echo "dot-request: rejected (HTTP ${code}): $(cat "${tmp}/reply")"
    exit 1
fi