#!/bin/sh
# Installs flower. Served publicly, with the downloads Worker's origin and the channel written in, so
# a person runs `curl -fsSL
| sh`. It holds nothing secret: it shows a sign-in link (Enter
# opens it), the browser hands the person's Cloudflare Access token to a loopback port this script
# listens on, it downloads the channel's release with that token, checks its SHA-256 against its
# manifest and hands it to the manager it carries, which checks the signature, keeps the token and
# installs. On a terminal it draws its progress; anywhere else it prints one plain line per step.
# Its body is one function called on the last line, so a download cut short runs nothing.
set -eu
install_flower() {
base='https://flower-worker-installs-downloads.webflow-productivity.workers.dev' channel=stable
case $channel in
stable) product=Flower command=flower ;;
*) product='Flower Next' command=flower-next ;;
esac
drawn=
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-dumb}" != dumb ]; then drawn=1; fi
esc=$(printf '\033')
if [ -n "$drawn" ]; then
pink="${esc}[38;5;211m" green="${esc}[38;5;114m" red="${esc}[38;5;203m" dim="${esc}[2m" bold="${esc}[1m" reset="${esc}[0m"
else
pink='' green='' red='' dim='' bold='' reset=''
fi
spinner='' reader='' tty_saved=''
cleanup() {
[ -n "$spinner" ] && kill "$spinner" 2>/dev/null
[ -n "$reader" ] && kill "$reader" 2>/dev/null
[ -n "$tty_saved" ] && stty "$tty_saved" /dev/null
[ -n "$drawn" ] && printf '%s[?25h' "$esc"
rm -rf "$work"
}
frame_at() {
case $(($1 % 10)) in
0) printf '⠋' ;;
1) printf '⠙' ;;
2) printf '⠹' ;;
3) printf '⠸' ;;
4) printf '⠼' ;;
5) printf '⠴' ;;
6) printf '⠦' ;;
7) printf '⠧' ;;
8) printf '⠇' ;;
*) printf '⠏' ;;
esac
}
line() { printf '\r%s[2K' "$esc"; }
done_step() {
if [ -n "$drawn" ]; then
line
printf ' %s✓%s %s\n' "$green" "$reset" "$*"
else echo "flower: $*"; fi
}
spin() {
[ -n "$drawn" ] || {
echo "flower: $*"
return
}
unspin
(
i=0
while :; do
line
printf ' %s%s%s %s' "$pink" "$(frame_at $i)" "$reset" "$*"
i=$((i + 1))
sleep 0.08
done
) &
spinner=$!
}
unspin() {
if [ -n "$spinner" ]; then
kill "$spinner" 2>/dev/null || true
wait "$spinner" 2>/dev/null || true
spinner=''
line
fi
}
fail() {
unspin
if [ -n "$drawn" ]; then
line
printf ' %s✗%s %s\n\n' "$red" "$reset" "$*" >&2
else echo "flower: $*" >&2; fi
exit 1
}
tilde() { printf '%s' "$1" | sed "s#^$HOME#~#; s# $HOME# ~#g"; }
work=$(mktemp -d)
trap cleanup EXIT
trap 'exit 130' INT TERM
[ "$(uname -s)" = Darwin ] || fail "flower runs on macOS only"
[ "$(sysctl -n hw.optional.arm64 2>/dev/null)" = 1 ] || fail "flower runs on Apple silicon Macs only"
if [ -n "$drawn" ]; then
printf '%s[?25l\n' "$esc"
for mark in '·' '∘' '❀' '✿'; do
printf '\r %s%s%s' "$pink" "$mark" "$reset"
sleep 0.07
done
printf ' '
word="flower"
while [ -n "$word" ]; do
rest=${word#?}
printf '%s%s%s' "$bold" "${word%"$rest"}" "$reset"
word=$rest
sleep 0.03
done
[ "$channel" = stable ] || printf ' %s· next%s' "$dim" "$reset"
printf '\n\n'
fi
state=$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')
port=$(awk 'BEGIN { srand(); print 49152 + int(rand() * 16000) }')
signed_in='Signed in to flower✿You are signed in to flower
Close this tab and return to your terminal.
'
answer=$(printf 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: %s\r\nConnection: close\r\n\r\n%s' "$(printf '%s' "$signed_in" | wc -c | tr -d ' ')" "$signed_in")
sign_in="$base/api/sign-in?port=$port&state=$state"
if [ -n "$drawn" ]; then
printf ' %sSign in with your Webflow account%s\n' "$bold" "$reset"
printf ' %s%s]8;;%s%s\\%s%s]8;;%s\\%s\n' "$pink" "$esc" "$sign_in" "$esc" "$sign_in" "$esc" "$esc" "$reset"
if [ -r /dev/tty ] && tty_saved=$(stty -g /dev/null); then
printf ' %sPress Enter to open it in your browser%s\n\n' "$dim" "$reset"
stty -echo /dev/null
(read -r _ /dev/null 2>&1 &
reader=$!
else
tty_saved=''
printf '\n'
fi
else
echo "flower: sign in at $sign_in"
fi
spin "Waiting for your browser"
token=''
while [ -z "$token" ]; do
printf '%s' "$answer" | nc -l 127.0.0.1 "$port" >"$work/handed" || fail "could not listen on 127.0.0.1:$port for the sign-in"
asked=$(sed -n '1s/^GET \/?\([^ ]*\) HTTP.*/\1/p' "$work/handed" | tr '&' '\n')
if printf '%s\n' "$asked" | grep -qx "state=$state"; then
token=$(printf '%s\n' "$asked" | sed -n 's/^token=//p')
fi
done
unspin
[ -n "$reader" ] && {
kill "$reader" 2>/dev/null || true
reader=''
}
[ -n "$tty_saved" ] && {
stty "$tty_saved" /dev/null || true
tty_saved=''
}
claims=$(printf '%s' "$token" | cut -d. -f2 | tr '_-' '/+')
while [ $((${#claims} % 4)) -ne 0 ]; do claims="$claims="; done
email=$(printf '%s' "$claims" | base64 -D 2>/dev/null | plutil -extract email raw -o - - 2>/dev/null || true)
done_step "Signed in${email:+ as $email}"
spin "Finding the latest $product"
pointed=$(curl -sS --connect-timeout 10 --retry 3 -H "cf-access-token: $token" -o "$work/pointer.json" -w '%{http_code}' "$base/api/channels/$channel") || fail "could not reach $base"
case $pointed in
200) ;;
404) fail "nothing is published to $channel yet" ;;
*) fail "$base answered $pointed for the $channel channel" ;;
esac
release=$(plutil -extract release raw -o - "$work/pointer.json")
curl -fsSL --connect-timeout 10 --retry 3 -H "cf-access-token: $token" -o "$work/manifest.json" "$base/api/releases/$release/manifest" ||
fail "could not read the manifest of $release"
plutil -extract manifest raw -o - "$work/manifest.json" >"$work/release.json"
expected=$(plutil -extract sha256 raw -o - "$work/release.json")
bytes=$(plutil -extract bytes raw -o - "$work/release.json")
megabytes=$(awk -v b="$bytes" 'BEGIN { printf "%.0f", b / 1048576 }')
unspin
done_step "Found $product $release"
curl -fsSL --connect-timeout 10 --retry 3 --speed-limit 1024 --speed-time 30 -H "cf-access-token: $token" \
-o "$work/archive.tar.xz" "$base/api/releases/$release/archive" 2>"$work/download.err" &
download=$!
if [ -n "$drawn" ]; then
i=0 width=28
while kill -0 "$download" 2>/dev/null; do
have=$(stat -f %z "$work/archive.tar.xz" 2>/dev/null || echo 0)
filled=$((have * width / bytes))
[ "$filled" -gt "$width" ] && filled=$width
bar='' n=0
while [ "$n" -lt "$width" ]; do
if [ "$n" -lt "$filled" ]; then bar="$bar━"; else bar="$bar$dim─$reset"; fi
n=$((n + 1))
done
line
printf ' %s%s%s Downloading %s%s%s %s%s / %s MB%s' "$pink" "$(frame_at $i)" "$reset" "$pink" "$bar" "$reset" \
"$dim" "$(awk -v b="$have" 'BEGIN { printf "%.0f", b / 1048576 }')" "$megabytes" "$reset"
i=$((i + 1))
sleep 0.08
done
else
echo "flower: downloading $release ($megabytes MB)"
fi
if wait "$download"; then :; else fail "the download of $release stopped: $(tail -1 "$work/download.err")"; fi
[ "$(shasum -a 256 "$work/archive.tar.xz" | awk '{print $1}')" = "$expected" ] ||
fail "the download of $release does not match its manifest"
done_step "Downloaded $megabytes MB and checked it"
tar -xf "$work/archive.tar.xz" -C "$work" app/manager/manager.cjs app/node/bin/node
FLOWER_ACCESS_TOKEN=$token "$work/app/node/bin/node" "$work/app/manager/manager.cjs" self install \
--channel "$channel" --base "$base" --release "$release" --archive "$work/archive.tar.xz" >"$work/manager.out" 2>&1 &
manager=$!
said=0 doing="Installing $release" i=0
shown() {
total=$(wc -l <"$work/manager.out" | tr -d ' ')
while [ "$said" -lt "$total" ]; do
said=$((said + 1))
told=$(sed -n "${said}p" "$work/manager.out")
told=${told#flower: }
case $told in
"checking "*) doing="Checking that ${told#checking } runs on this Mac" ;;
"installed $release in "*) done_step "Installed in $(tilde "${told#installed "$release" in }")" ;;
"added "*" to PATH in "*)
path_file=${told#* to PATH in }
done_step "Added $command to your PATH in $(tilde "${path_file%%;*}")"
;;
"installed $product "* | "replaced $product "* | "$product "*" is in place") done_step "$product is in your Applications folder" ;;
*) [ -n "$told" ] && done_step "$(tilde "$told")" ;;
esac
done
}
while kill -0 "$manager" 2>/dev/null; do
shown
if [ -n "$drawn" ]; then
line
printf ' %s%s%s %s' "$pink" "$(frame_at $i)" "$reset" "$doing"
i=$((i + 1))
fi
sleep 0.08
done
if wait "$manager"; then installed=0; else installed=$?; fi
shown
[ "$installed" -eq 0 ] || fail "installing $release did not finish"
if [ -n "$drawn" ]; then
line
printf '\n %s✿%s %s%s is ready.%s\n\n' "$pink" "$reset" "$bold" "$product" "$reset"
printf ' Open a new terminal and run %s%s%s, or open %s from Applications.\n\n' "$pink" "$command" "$reset" "$product"
else
echo "flower: $product $release is ready; open a new terminal and run $command"
fi
}
install_flower