Skip to content

Commit 11a0bf5

Browse files
committed
Add macOS code signing, private mirror push, and release signing management
- Add macOS code signing to release workflow with Apple Developer ID - Add `f push` command for syncing to private mirror remotes - Add `f release signing` subcommands (status/store/sync) for managing certs - Improve install script with configurable repos and GitHub auth support - Update readme with public install instructions and maintainer docs
1 parent a9b7bfc commit 11a0bf5

12 files changed

Lines changed: 1229 additions & 64 deletions

File tree

.github/workflows/release.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ jobs:
4848
fi
4949
cargo build --release --target ${{ matrix.target }}
5050
51+
- name: Import code-signing certificates (macOS)
52+
if: runner.os == 'macOS' && secrets.MACOS_SIGN_P12_B64 != ''
53+
uses: apple-actions/import-codesign-certs@v3
54+
with:
55+
p12-file-base64: ${{ secrets.MACOS_SIGN_P12_B64 }}
56+
p12-password: ${{ secrets.MACOS_SIGN_P12_PASSWORD }}
57+
58+
- name: Codesign (macOS)
59+
if: runner.os == 'macOS' && secrets.MACOS_SIGN_P12_B64 != '' && secrets.MACOS_SIGN_IDENTITY != ''
60+
env:
61+
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
62+
run: |
63+
BIN="target/${{ matrix.target }}/release/f"
64+
codesign --force --options runtime --timestamp --sign "$MACOS_SIGN_IDENTITY" "$BIN"
65+
codesign -vvv --strict "$BIN"
66+
5167
- name: Package
5268
run: |
5369
mkdir -p dist

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "flowd"
33
version = "0.1.0"
44
edition = "2024"
5+
repository = "https://github.com/nikivdev/flow"
56

67
[[bin]]
78
name = "f"

install.sh

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,52 @@ shasum_bin() {
6767
echo ""
6868
fi
6969
}
70+
71+
validate_repo() {
72+
repo="$1"
73+
if [ -z "${repo:-}" ]; then
74+
error "FLOW_UPGRADE_REPO is empty"
75+
fi
76+
77+
owner="${repo%/*}"
78+
name="${repo#*/}"
79+
if [ "$owner" = "$repo" ] || [ "$name" = "$repo" ]; then
80+
error "invalid repo '${repo}' (expected owner/repo)"
81+
fi
82+
case "$owner" in */*) error "invalid repo '${repo}' (expected owner/repo)" ;; esac
83+
case "$name" in */*) error "invalid repo '${repo}' (expected owner/repo)" ;; esac
84+
85+
case "$owner" in *[!A-Za-z0-9._-]*)
86+
error "invalid repo owner '${owner}' (allowed: A-Z a-z 0-9 . _ -)"
87+
;;
88+
esac
89+
case "$name" in *[!A-Za-z0-9._-]*)
90+
error "invalid repo name '${name}' (allowed: A-Z a-z 0-9 . _ -)"
91+
;;
92+
esac
93+
}
94+
95+
validate_token() {
96+
token="$1"
97+
case "$token" in
98+
*[!A-Za-z0-9._-]*)
99+
error "invalid GitHub token characters (refusing to use it)"
100+
;;
101+
esac
102+
}
103+
104+
validate_version() {
105+
version="$1"
106+
case "$version" in
107+
v*) tag="${version#v}" ;;
108+
*) tag="$version" ;;
109+
esac
110+
case "$tag" in
111+
""|*[!0-9A-Za-z._-]*)
112+
error "invalid release version '${version}'"
113+
;;
114+
esac
115+
}
70116
#endregion
71117

72118
#region download helpers
@@ -75,7 +121,11 @@ download_file() {
75121
file="$2"
76122
if command -v curl >/dev/null 2>&1; then
77123
debug ">" curl -fsSL -o "$file" "$url"
78-
curl -fsSL -o "$file" "$url"
124+
if [ "${FLOW_DEBUG-}" = "true" ] || [ "${FLOW_DEBUG-}" = "1" ]; then
125+
curl -fsSL -o "$file" "$url"
126+
else
127+
curl -fsSL -o "$file" "$url" 2>/dev/null
128+
fi
79129
elif command -v wget >/dev/null 2>&1; then
80130
debug ">" wget -qO "$file" "$url"
81131
wget -qO "$file" "$url"
@@ -87,7 +137,20 @@ download_file() {
87137
fetch_url() {
88138
url="$1"
89139
if command -v curl >/dev/null 2>&1; then
90-
curl -fsSL "$url"
140+
case "$url" in
141+
https://api.github.com/*)
142+
token="${GITHUB_TOKEN:-${GH_TOKEN:-${FLOW_GITHUB_TOKEN:-}}}"
143+
if [ -n "${token:-}" ]; then
144+
validate_token "$token"
145+
curl -fsSL -H "Authorization: Bearer $token" "$url"
146+
else
147+
curl -fsSL "$url"
148+
fi
149+
;;
150+
*)
151+
curl -fsSL "$url"
152+
;;
153+
esac
91154
elif command -v wget >/dev/null 2>&1; then
92155
wget -qO- "$url"
93156
else
@@ -96,17 +159,49 @@ fetch_url() {
96159
}
97160

98161
get_latest_version() {
99-
url="https://api.github.com/repos/nikitavoloboev/flow/releases/latest"
100-
fetch_url "$url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'
162+
repo="${FLOW_UPGRADE_REPO:-}"
163+
if [ -z "${repo:-}" ] && [ -n "${FLOW_GITHUB_OWNER:-}" ] && [ -n "${FLOW_GITHUB_REPO:-}" ]; then
164+
repo="${FLOW_GITHUB_OWNER}/${FLOW_GITHUB_REPO}"
165+
fi
166+
repo="${repo:-nikivdev/flow}"
167+
validate_repo "$repo"
168+
169+
url="https://api.github.com/repos/${repo}/releases/latest"
170+
version="$(fetch_url "$url" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')"
171+
validate_version "$version"
172+
echo "$version"
101173
}
102174

103175
get_checksum() {
104176
version="$1"
105177
target="$2"
106-
url="https://github.com/nikitavoloboev/flow/releases/download/${version}/checksums.txt"
178+
repo="${FLOW_UPGRADE_REPO:-}"
179+
if [ -z "${repo:-}" ] && [ -n "${FLOW_GITHUB_OWNER:-}" ] && [ -n "${FLOW_GITHUB_REPO:-}" ]; then
180+
repo="${FLOW_GITHUB_OWNER}/${FLOW_GITHUB_REPO}"
181+
fi
182+
repo="${repo:-nikivdev/flow}"
183+
validate_repo "$repo"
184+
185+
url="https://github.com/${repo}/releases/download/${version}/checksums.txt"
107186
checksums="$(fetch_url "$url" 2>/dev/null)" || return 1
108187
echo "$checksums" | grep "flow-${target}.tar.gz" | awk '{print $1}'
109188
}
189+
190+
get_checksum_for_file() {
191+
version="$1"
192+
file="$2"
193+
repo="${FLOW_UPGRADE_REPO:-}"
194+
if [ -z "${repo:-}" ] && [ -n "${FLOW_GITHUB_OWNER:-}" ] && [ -n "${FLOW_GITHUB_REPO:-}" ]; then
195+
repo="${FLOW_GITHUB_OWNER}/${FLOW_GITHUB_REPO}"
196+
fi
197+
repo="${repo:-nikivdev/flow}"
198+
validate_repo "$repo"
199+
200+
url="https://github.com/${repo}/releases/download/${version}/checksums.txt"
201+
checksums="$(fetch_url "$url" 2>/dev/null)" || return 1
202+
# checksums.txt format: "<sha256> <filename>"
203+
echo "$checksums" | awk -v f="$file" '$2==f {print $1}'
204+
}
110205
#endregion
111206

112207
install_flow() {
@@ -128,28 +223,61 @@ install_flow() {
128223
error "failed to fetch latest version"
129224
fi
130225
fi
226+
validate_version "$version"
131227
info "flow: version: $version"
132228

133229
# URLs - try CDN first, fallback to GitHub
134230
cdn_url="https://cdn.myflow.sh/${version}/flow-${target}.tar.gz"
135-
github_url="https://github.com/nikitavoloboev/flow/releases/download/${version}/flow-${target}.tar.gz"
231+
repo="${FLOW_UPGRADE_REPO:-}"
232+
if [ -z "${repo:-}" ] && [ -n "${FLOW_GITHUB_OWNER:-}" ] && [ -n "${FLOW_GITHUB_REPO:-}" ]; then
233+
repo="${FLOW_GITHUB_OWNER}/${FLOW_GITHUB_REPO}"
234+
fi
235+
repo="${repo:-nikivdev/flow}"
236+
validate_repo "$repo"
237+
github_url="https://github.com/${repo}/releases/download/${version}/flow-${target}.tar.gz"
136238

137239
download_dir="$(mktemp -d)"
138240
tarball="$download_dir/flow.tar.gz"
139241

242+
asset_file="flow-${target}.tar.gz"
243+
legacy_os="$os"
244+
if [ "$legacy_os" = "macos" ]; then
245+
legacy_os="darwin"
246+
fi
247+
legacy_arch="amd64"
248+
if [ "$arch" = "arm64" ]; then
249+
legacy_arch="arm64"
250+
fi
251+
legacy_file="flow_${version}_${legacy_os}_${legacy_arch}.tar.gz"
252+
legacy_url="https://github.com/${repo}/releases/download/${version}/${legacy_file}"
253+
140254
# Try CDN first (faster)
141255
info "flow: downloading..."
142256
if command -v curl >/dev/null 2>&1 && curl -fsSL -o "$tarball" "$cdn_url" 2>/dev/null; then
143257
debug "flow: downloaded from CDN"
144258
else
145259
debug "flow: trying GitHub..."
146-
download_file "$github_url" "$tarball" || error "download failed"
260+
if download_file "$github_url" "$tarball"; then
261+
asset_file="flow-${target}.tar.gz"
262+
elif download_file "$legacy_url" "$tarball"; then
263+
asset_file="$legacy_file"
264+
else
265+
error "download failed"
266+
fi
147267
fi
148268

149269
# Verify checksum if available
150270
shasum="$(shasum_bin)"
151271
if [ -n "$shasum" ]; then
152-
expected="$(get_checksum "$version" "$target" 2>/dev/null)" || true
272+
expected="$(get_checksum_for_file "$version" "$asset_file" 2>/dev/null)" || true
273+
if [ -z "${expected:-}" ]; then
274+
# Back-compat: allow checksums.txt to contain either naming scheme.
275+
if [ "$asset_file" = "$legacy_file" ]; then
276+
expected="$(get_checksum_for_file "$version" "flow-${target}.tar.gz" 2>/dev/null)" || true
277+
elif [ "$asset_file" = "flow-${target}.tar.gz" ]; then
278+
expected="$(get_checksum_for_file "$version" "$legacy_file" 2>/dev/null)" || true
279+
fi
280+
fi
153281
if [ -n "${expected:-}" ]; then
154282
debug "flow: verifying checksum..."
155283
actual="$($shasum "$tarball" | awk '{print $1}')"
@@ -158,6 +286,8 @@ install_flow() {
158286
error "checksum mismatch"
159287
fi
160288
info "flow: checksum verified"
289+
else
290+
info "flow: warning: checksum not verified (checksums.txt missing or entry not found)"
161291
fi
162292
fi
163293

readme.md

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,59 @@
22

33
> Everything you need to move your project faster
44
5-
<!-- todo: curl install, full automate -->
5+
## Install (Public)
66

7-
Install this CLI (currently by cloning repo and buliding rust binary).
7+
Install the latest release (macOS/Linux):
88

9-
## Local build (macOS, Flow + Jazz/Groove)
9+
```sh
10+
curl -fsSL https://myflow.sh/install.sh | sh
11+
# or:
12+
curl -fsSL https://raw.githubusercontent.com/nikivdev/flow/main/install.sh | sh
13+
```
14+
15+
Then run `f --version` and `f doctor`.
16+
17+
## Upgrade
18+
19+
Upgrade to the latest release:
20+
21+
```sh
22+
f upgrade
23+
```
24+
25+
If you fork Flow (or publish releases under a different repo), set:
26+
27+
- `FLOW_UPGRADE_REPO=owner/repo`
28+
- `FLOW_GITHUB_TOKEN` (or `GITHUB_TOKEN` / `GH_TOKEN`) to avoid GitHub API rate limits
29+
30+
## Supported Platforms
31+
32+
Release artifacts are built for:
33+
34+
- macOS: `arm64`, `x86_64`
35+
- Linux (glibc): `arm64`, `x86_64`
36+
37+
## Release Signing (Maintainers)
38+
39+
The GitHub Actions release workflow can code-sign macOS binaries if these repository
40+
secrets are configured:
41+
42+
- `MACOS_SIGN_P12_B64`: base64-encoded `.p12` certificate
43+
- `MACOS_SIGN_P12_PASSWORD`: password for the `.p12`
44+
- `MACOS_SIGN_IDENTITY`: signing identity (e.g. `Developer ID Application: ... (TEAMID)`)
45+
46+
Flow can store these values in the Flow personal env store and sync them to GitHub:
47+
48+
```sh
49+
f release signing status
50+
f release signing store --p12 /path/to/cert.p12 --p12-password '...' --identity 'Developer ID Application: ... (TEAMID)'
51+
f release signing sync
52+
```
53+
54+
Notarization is optional for a CLI distributed via `curl | sh` (downloads via `curl`
55+
typically do not set the quarantine attribute), but can be added later if desired.
56+
57+
## Local Build (macOS, Flow + Jazz/Groove)
1058

1159
If you want a local dev build that uses the Jazz/Groove crates from a local
1260
checkout, use the macOS installer script:
@@ -59,7 +107,7 @@ For available features, see [docs](docs) or feed `f --help` to AI.
59107

60108
## Examples
61109

62-
All projects of [Nikita](https://github.com/nikivdev) run on flow. Like [rust](https://github.com/nikivdev/rust) & flow itself.
110+
Projects run on flow, including flow itself.
63111

64112
## Contributing
65113

0 commit comments

Comments
 (0)