aboutsummaryrefslogtreecommitdiff
path: root/deploy/webpaket.sh
blob: ba47ea5377cc6a8013cede77556e732091525a73 (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
203
204
205
#!/bin/sh
# SPDX-FileCopyrightText: 2026 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
# SPDX-License-Identifier: AGPL-3.0-or-later

# Macht aus dem Übersetzerergebnis ein auslieferbares Verzeichnis. Läuft in der Phase
# „package“ des Profils -Pwasm, unmittelbar nach native-image.
#
#   1. Der Textzwischenschritt aendggner.js.wat (mehrere hundert Megabyte) wird entfernt.
#   2. Das Modul geht durch wasm-opt -Oz, sofern Binaryen zur Hand ist.
#   3. Der Quelltext der gebauten Fassung wird als Tarball beigelegt — AGPLv3 §13 verlangt
#      beim Netzwerkbetrieb einen Quellcode-Zugang für die Nutzer:innen, und der Footer der
#      Startseite verweist darauf.
#   4. Die Startseite bekommt unter dem Titel das Datum des gebauten Commits.
#   5. Nur die vom Bau erzeugten Dateien bleiben stehen; alles hier Liegende wird
#      hochgeladen.
#   6. Auf Wunsch (VORKOMPRIMIEREN=1) werden die großen Dateien vorkomprimiert.
#
# Zielplattform ist Cloudflare Workers: dort gilt eine Grenze von 25 MiB je Datei
# (unkomprimiert), und komprimiert wird beim Ausliefern ohnehin. Deshalb ist die
# Vorkompression abgeschaltet und die Größe jeder Datei wird geprüft. Wer stattdessen
# selbst mit nginx ausliefert (deploy/nginx-aendggner.conf, gzip_static/brotli_static),
# ruft das Skript mit VORKOMPRIMIEREN=1 auf.
#
# Aufruf: deploy/webpaket.sh <ausgabeverzeichnis>   (Standard: target/web)

set -eu

ziel="${1:-target/web}"
quelle="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"

# Cloudflare Workers nimmt keine Datei über 25 MiB an.
grenze_datei=26214400
# Der Quelltext misst ein Viertel Megabyte. Wächst das Archiv über diese Schranke, sind
# wieder Massendaten ins Repository geraten, die dort nicht hingehören (siehe
# .gitattributes) — dann lieber der Bau ab als ein unbrauchbares Paket.
grenze_archiv=8388608

if [ ! -d "$ziel" ]; then
  echo "webpaket: $ziel gibt es nicht — erst -Pwasm package laufen lassen." >&2
  exit 1
fi

groesse() {
  wc -c <"$1" | tr -d ' '
}

# 1. Zwischenschritt fort.
rm -f "$ziel/aendggner.js.wat"

# 2. Nachoptimierung. -Oz zieht die Codegröße noch einmal spürbar zusammen.
#
# Die Merkmale sind einzeln aufgezählt, nicht als --all-features: Binaryen darf ausgeben,
# was es darf, und mit --all-features nutzt es auch Vorschläge, die noch kein Browser
# annimmt — das Modul scheitert dann erst beim Instanziieren („invalid heap type 'exact'“
# aus den custom descriptors, „invalid import kind 127“ aus der kompakten
# Importsektion). Aufgeführt ist deshalb nur, was ausgeliefert in den Browsern steht und
# was Web Image braucht: WasmGC samt Referenztypen, das Ausnahmen-Proposal, endständige
# Aufrufe. Fehlt wasm-opt, bleibt das Modul, wie es ist — der Bau soll daran nicht
# scheitern.
wasm_merkmale="--enable-gc --enable-reference-types --enable-exception-handling
               --enable-tail-call --enable-bulk-memory --enable-bulk-memory-opt
               --enable-nontrapping-float-to-int --enable-sign-ext
               --enable-mutable-globals --enable-multivalue --enable-extended-const
               --enable-simd --enable-call-indirect-overlong"

if [ -f "$ziel/aendggner.js.wasm" ]; then
  vorher="$(groesse "$ziel/aendggner.js.wasm")"
  if command -v wasm-opt >/dev/null 2>&1; then
    # shellcheck disable=SC2086  # die Merkmalsliste soll in Wörter zerfallen
    wasm-opt $wasm_merkmale -Oz \
      -o "$ziel/aendggner.js.wasm.neu" "$ziel/aendggner.js.wasm"
    mv "$ziel/aendggner.js.wasm.neu" "$ziel/aendggner.js.wasm"
    echo "webpaket: wasm-opt: $vorher -> $(groesse "$ziel/aendggner.js.wasm") Bytes."
  else
    echo "webpaket: wasm-opt nicht gefunden — Modul bleibt unnachoptimiert" \
         "($vorher Bytes)." >&2
  fi
fi

# 3. Quelltext der gebauten Fassung.
#
# Ein veränderter Arbeitsbaum bricht den Bau ab: Der angebotene Quelltext muss der
# ausgelieferten Fassung entsprechen, sonst ist die Auflage aus §13 verfehlt. Wer nur
# ausprobieren will, setzt QUELLTEXT_UNGEPRUEFT=1.
if ! git -C "$quelle" rev-parse --git-dir >/dev/null 2>&1; then
  echo "webpaket: $quelle ist kein Git-Arbeitsbaum; ohne ihn lässt sich der Quelltext" >&2
  echo "          der gebauten Fassung nicht beilegen (AGPLv3 §13)." >&2
  exit 1
fi

if [ -n "$(git -C "$quelle" status --porcelain)" ] && [ "${QUELLTEXT_UNGEPRUEFT:-}" != 1 ]; then
  echo "webpaket: Der Arbeitsbaum trägt uneingecheckte Änderungen. Der beigelegte Quelltext" >&2
  echo "          wäre dann nicht der gebaute. Erst einchecken — oder für einen Probelauf" >&2
  echo "          QUELLTEXT_UNGEPRUEFT=1 setzen (dann nicht ausliefern)." >&2
  exit 1
fi

fassung="$(git -C "$quelle" rev-parse HEAD)"
archiv="$(CDPATH='' cd -- "$ziel" && pwd)/aendggner-quelltext.tar.gz"
git -C "$quelle" archive --format=tar.gz --prefix=aendggner/ HEAD -o "$archiv"

archivgroesse="$(groesse "$archiv")"
if [ "$archivgroesse" -gt "$grenze_archiv" ]; then
  echo "webpaket: Das Quelltextarchiv misst $archivgroesse Bytes und überschreitet damit" >&2
  echo "          die Schranke von $grenze_archiv. Vermutlich sind Massendaten ins" >&2
  echo "          Repository geraten; sie gehören in .gitattributes (export-ignore)." >&2
  exit 1
fi

{
  echo "ÄndGgner — Quelltext der ausgelieferten Fassung"
  echo
  echo "Commit:  $fassung"
  echo "Fassung: $(git -C "$quelle" log -1 --format=%cs HEAD)"
  echo "Gebaut:  $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
  echo
  echo "Vollständig in aendggner-quelltext.tar.gz; Bauanleitung darin in README.md."
  echo "Fortlaufend: https://git.benkard.de/mulk/aendggner"
  echo
  echo "Nicht im Archiv liegt der Beispielkorpus (src/test/resources/sampledata): Gesetzes-"
  echo "und Drucksachentexte fremder Urheberschaft, an denen die Tests messen. Gebaut wird"
  echo "ohne ihn; nur die Tests verlangen danach. Wer sie laufen lassen will, holt das"
  echo "Repository von der oben genannten Adresse, wo der Korpus vollständig liegt."
} > "$ziel/quelltext-fassung.txt"

# 4. Fassungsdatum der Startseite.
#
# Unter dem Titel steht „in der Fassung vom …“. Im Quelltext trägt die Marke das Datum des
# letzten Handanlegens; ausgeliefert wird das Datum des gebauten Commits, damit die Angabe
# nicht stillschweigend veraltet. Das Datum kommt aus %cs (JJJJ-MM-TT); date(1) bleibt
# außen vor, weil BSD und GNU sich über die Schalter nicht einig sind.
fassungsdatum="$(git -C "$quelle" log -1 --format=%cs HEAD)"
jahr="${fassungsdatum%%-*}"
rest="${fassungsdatum#*-}"
monat="${rest%%-*}"
tag="${rest#*-}"

case "$monat" in
  01) monatsname="Januar" ;;   02) monatsname="Februar" ;;   03) monatsname="März" ;;
  04) monatsname="April" ;;    05) monatsname="Mai" ;;       06) monatsname="Juni" ;;
  07) monatsname="Juli" ;;     08) monatsname="August" ;;    09) monatsname="September" ;;
  10) monatsname="Oktober" ;;  11) monatsname="November" ;;  12) monatsname="Dezember" ;;
  *) echo "webpaket: unlesbares Commit-Datum „$fassungsdatum“." >&2; exit 1 ;;
esac

# Führende Null im Tag ist im deutschen Fließtext unüblich.
tag="${tag#0}"
fassungstext="$tag. $monatsname $jahr"

startseite="$ziel/index.html"
if [ ! -f "$startseite" ]; then
  echo "webpaket: $startseite fehlt — die statischen Seiten sind nicht mitkopiert." >&2
  exit 1
fi

sed -e "s|<time datetime=\"[0-9-]*\">[^<]*</time>|<time datetime=\"$fassungsdatum\">$fassungstext</time>|" \
  "$startseite" > "$startseite.neu"

# Greift die Ersetzung nicht, ist die Marke im Vordruck umgebaut worden. Dann lieber der
# Bau ab als eine falsch datierte Fassung.
if ! grep -q "<time datetime=\"$fassungsdatum\">$fassungstext</time>" "$startseite.neu"; then
  rm -f "$startseite.neu"
  echo "webpaket: In index.html ist keine <time>-Marke unter dem Titel zu finden; das" >&2
  echo "          Fassungsdatum lässt sich nicht einsetzen." >&2
  exit 1
fi
mv "$startseite.neu" "$startseite"
echo "webpaket: Startseite trägt die Fassung vom $fassungstext."

# 5. Nur das Gebaute bleibt liegen — was hier steht, geht hoch.
rm -f "$ziel/.DS_Store"

# 6. Vorkompression, nur auf Anforderung (siehe Kopf). Der Tarball ist bereits gepackt und
# bleibt außen vor.
if [ "${VORKOMPRIMIEREN:-}" = 1 ]; then
  for datei in "$ziel"/*.wasm "$ziel"/*.js "$ziel"/*.css "$ziel"/*.html "$ziel"/*.svg \
               "$ziel"/*.txt; do
    [ -f "$datei" ] || continue
    gzip -9 -f -k -- "$datei"
    if command -v brotli >/dev/null 2>&1; then
      brotli -f -- "$datei"
    fi
  done

  if ! command -v brotli >/dev/null 2>&1; then
    echo "webpaket: brotli nicht gefunden — nur .gz angelegt." >&2
  fi
fi

# Was zu groß ist, nimmt Cloudflare nicht an. Lieber hier auffallen als beim Hochladen.
zugross=""
for datei in "$ziel"/*; do
  [ -f "$datei" ] || continue
  if [ "$(groesse "$datei")" -gt "$grenze_datei" ]; then
    zugross="$zugross  $(basename "$datei") ($(groesse "$datei") Bytes)
"
  fi
done
if [ -n "$zugross" ]; then
  echo "webpaket: Diese Dateien überschreiten die 25-MiB-Grenze von Cloudflare Workers:" >&2
  printf '%s' "$zugross" >&2
  exit 1
fi

echo "webpaket: $ziel ist auslieferbar (Fassung $(git -C "$quelle" rev-parse --short HEAD))."