aboutsummaryrefslogtreecommitdiff
path: root/contrib/guix/guix-attest
blob: 8b4746caf9a1eff03a7fb3ec76bb1aa9d9b01f38 (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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env bash
export LC_ALL=C
set -e -o pipefail

# Source the common prelude, which:
#   1. Checks if we're at the top directory of the Bitcoin Core repository
#   2. Defines a few common functions and variables
#
# shellcheck source=libexec/prelude.bash
source "$(dirname "${BASH_SOURCE[0]}")/libexec/prelude.bash"


###################
## Sanity Checks ##
###################

################
# Required non-builtin commands should be invokable
################

check_tools cat env basename mkdir xargs find
if [ -z "$NO_SIGN" ]; then
    check_tools gpg
fi

################
# Required env vars should be non-empty
################

cmd_usage() {
cat <<EOF
Synopsis:

    env GUIX_SIGS_REPO=<path/to/guix.sigs> \\
        SIGNER=GPG_KEY_NAME[=SIGNER_NAME] \\
        [ NO_SIGN=1 ]
      ./contrib/guix/guix-attest

Example w/o overriding signing name:

    env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
        SIGNER=achow101 \\
      ./contrib/guix/guix-attest

Example overriding signing name:

    env GUIX_SIGS_REPO=/home/dongcarl/guix.sigs \\
        SIGNER=0x96AB007F1A7ED999=dongcarl \\
      ./contrib/guix/guix-attest

Example w/o signing, just creating SHA256SUMS:

    env GUIX_SIGS_REPO=/home/achow101/guix.sigs \\
        SIGNER=achow101 \\
        NO_SIGN=1 \\
      ./contrib/guix/guix-attest

EOF
}

if [ -z "$GUIX_SIGS_REPO" ] || [ -z "$SIGNER" ]; then
    cmd_usage
    exit 1
fi

################
# GUIX_SIGS_REPO should exist as a directory
################

if [ ! -d "$GUIX_SIGS_REPO" ]; then
cat << EOF
ERR: The specified GUIX_SIGS_REPO is not an existent directory:

    '$GUIX_SIGS_REPO'

Hint: Please clone the guix.sigs repository and point to it with the
      GUIX_SIGS_REPO environment variable.

EOF
cmd_usage
exit 1
fi

################
# The key specified in SIGNER should be usable
################

IFS='=' read -r gpg_key_name signer_name <<< "$SIGNER"
if [ -z "${signer_name}" ]; then
    signer_name="$gpg_key_name"
fi

if [ -z "$NO_SIGN" ] && ! gpg --dry-run --list-secret-keys "${gpg_key_name}" >/dev/null 2>&1; then
    echo "ERR: GPG can't seem to find any key named '${gpg_key_name}'"
    exit 1
fi

################
# We should be able to find at least one output
################

echo "Looking for build output directories in ${OUTDIR_BASE}"

shopt -s nullglob
OUTDIRS=( "${OUTDIR_BASE}"/* ) # This expands to an array of directories...
shopt -u nullglob

if (( ${#OUTDIRS[@]} )); then
    echo "Found build output directories:"
    for outdir in "${OUTDIRS[@]}"; do
        echo "    '$outdir'"
    done
    echo
else
    echo "ERR: Could not find any build output directories in ${OUTDIR_BASE}"
    exit 1
fi


##############
##  Attest  ##
##############

# Usage: out_name $outdir
#
#   HOST: The output directory being attested
#
out_name() {
    basename "$1"
}

# Usage: out_sig_dir $outdir
#
#   outdir: The output directory being attested
#
out_sig_dir() {
    echo "$GUIX_SIGS_REPO/$VERSION/$(out_name "$1")/$signer_name"
}

# Accumulate a list of signature directories that already exist...
outdirs_already_attested_to=()

echo "Attesting to build outputs for version: '${VERSION}'"
echo ""

# MAIN LOGIC: Loop through each output for VERSION and attest to output in
#             GUIX_SIGS_REPO as SIGNER, if attestation does not exist
for outdir in "${OUTDIRS[@]}"; do
    if [ -e "${outdir}/SKIPATTEST.TAG" ]; then
        echo "${outname}: SKIPPING: Output directory marked with SKIPATTEST.TAG file"
        continue
    fi
    outname="$(out_name "$outdir")"
    outsigdir="$(out_sig_dir "$outdir")"
    if [ -e "$outsigdir" ]; then
        echo "${outname}: SKIPPING: Signature directory already exists in the specified guix.sigs repository"
        outdirs_already_attested_to+=("$outdir")
    else
        mkdir -p "$outsigdir"

        (
            cd "$outdir"

            if [ -e inputs.SHA256SUMS ]; then
                echo "${outname}: Including existent input SHA256SUMS"
                cat inputs.SHA256SUMS >> "$outsigdir"/SHA256SUMS
            fi

            echo "${outname}: Hashing build outputs to produce SHA256SUMS"
            files="$(find -L . -type f ! -iname '*.SHA256SUMS')"
            if [ -n "$files" ]; then
                cut -c3- <<< "$files" | env LC_ALL=C sort | xargs sha256sum >> "$outsigdir"/SHA256SUMS
            else
                echo "ERR: ${outname}: No outputs found in '${outdir}'"
                exit 1
            fi
        )
        if [ -z "$NO_SIGN" ]; then
            echo "${outname}: Signing SHA256SUMS to produce SHA256SUMS.asc"
            gpg --detach-sign --local-user "$gpg_key_name" --armor --output "$outsigdir"/SHA256SUMS.asc "$outsigdir"/SHA256SUMS
        else
            echo "${outname}: Not signing SHA256SUMS as \$NO_SIGN is not empty"
        fi
        echo ""
    fi
done

if (( ${#outdirs_already_attested_to[@]} )); then
# ...so that we can print them out nicely in a warning message
cat << EOF

WARN: Signature directories from '$signer_name' already exist in the specified
      guix.sigs repository for the following output directories and were
      skipped:

EOF
for outdir in "${outdirs_already_attested_to[@]}"; do
    echo "    '${outdir}'"
    echo "    Corresponds to: '$(out_sig_dir "$outdir")'"
    echo ""
done
fi