Skip to content

Commit c388416

Browse files
chore: refactor
1 parent 8e60f9e commit c388416

4 files changed

Lines changed: 120 additions & 9 deletions

File tree

APPLE_TODO.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Apple Notarization TODO
2+
3+
To distribute the CreateOS CLI on macOS without Gatekeeper warnings, complete the following steps.
4+
5+
## Prerequisites
6+
7+
- [ ] Enroll in the Apple Developer Program at https://developer.apple.com — $99/year
8+
- [ ] Generate a **Developer ID Application** certificate in Xcode or at developer.apple.com/account
9+
- [ ] Export the certificate as a `.p12` file and note the password
10+
11+
## Steps
12+
13+
### 1. Install GoReleaser
14+
15+
```bash
16+
brew install goreleaser
17+
```
18+
19+
### 2. Create `.goreleaser.yaml`
20+
21+
```yaml
22+
builds:
23+
- env: [CGO_ENABLED=0]
24+
goos: [darwin, linux, windows]
25+
goarch: [amd64, arm64]
26+
ldflags:
27+
- -s -w -X github.com/NodeOps-app/createos-cli/internal/pkg/version.Version={{.Version}}
28+
29+
archives:
30+
- format: tar.gz
31+
name_template: "createos_{{ .Os }}_{{ .Arch }}"
32+
format_overrides:
33+
- goos: windows
34+
format: zip
35+
36+
signs:
37+
- cmd: codesign
38+
args:
39+
- "--sign"
40+
- "Developer ID Application: Your Name (TEAMID)"
41+
- "--options"
42+
- "runtime"
43+
- "--timestamp"
44+
- "${artifact}"
45+
artifacts: macos
46+
47+
notarize:
48+
macos:
49+
- enabled: true
50+
sign:
51+
certificate: "{{.Env.APPLE_CERT_BASE64}}"
52+
password: "{{.Env.APPLE_CERT_PASSWORD}}"
53+
notarize:
54+
issuer_id: "{{.Env.APPLE_ISSUER_ID}}"
55+
key_id: "{{.Env.APPLE_KEY_ID}}"
56+
key: "{{.Env.APPLE_KEY}}"
57+
```
58+
59+
### 3. Set environment variables (CI or local)
60+
61+
| Variable | Description |
62+
|----------|-------------|
63+
| `APPLE_CERT_BASE64` | Base64-encoded `.p12` certificate (`base64 -i cert.p12`) |
64+
| `APPLE_CERT_PASSWORD` | Password for the `.p12` file |
65+
| `APPLE_ISSUER_ID` | Found in App Store Connect → Keys → Issuer ID |
66+
| `APPLE_KEY_ID` | Found in App Store Connect → Keys |
67+
| `APPLE_KEY` | Contents of the `.p8` API key file |
68+
69+
### 4. Manual signing (without GoReleaser)
70+
71+
```bash
72+
# Sign
73+
codesign --sign "Developer ID Application: Your Name (TEAMID)" \
74+
--options runtime \
75+
--timestamp \
76+
createos
77+
78+
# Zip for notarization submission
79+
zip createos.zip createos
80+
81+
# Submit to Apple
82+
xcrun notarytool submit createos.zip \
83+
--apple-id "you@email.com" \
84+
--team-id "YOURTEAMID" \
85+
--password "app-specific-password" \
86+
--wait
87+
88+
# Staple the notarization ticket to the binary
89+
xcrun stapler staple createos
90+
```
91+
92+
### 5. Verify
93+
94+
```bash
95+
# Check signature
96+
codesign --verify --verbose createos
97+
98+
# Check notarization
99+
spctl --assess --verbose createos
100+
# Expected: createos: accepted
101+
```
102+
103+
## Notes
104+
105+
- `--options runtime` (hardened runtime) is required for notarization
106+
- `--timestamp` uses Apple's secure timestamp server — required for notarization
107+
- Stapling embeds the ticket so the binary works offline without calling Apple
108+
- Without notarization, users see "Apple cannot verify this app" and must manually allow it in System Settings → Privacy & Security
109+
- ARM (Apple Silicon) and AMD64 builds must both be signed separately

cmd/auth/login.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
)
1212

1313
const (
14-
oauthClientID = "fbcaaa58-1e30-43fe-8fba-34382ba4fe7f"
15-
oauthIssuerURL = "https://id.nodeops.network"
1614
oauthCallbackPort = 65341
1715
oauthCallbackURI = "http://localhost:65341/callback"
1816
)
@@ -81,7 +79,7 @@ func loginWithBrowser() error {
8179

8280
// 2. Fetch OAuth server metadata
8381
pterm.Info.Println("Fetching authorization server info...")
84-
meta, err := internaloauth.FetchServerMetadata(oauthIssuerURL)
82+
meta, err := internaloauth.FetchServerMetadata(config.OAuthIssuerURL)
8583
if err != nil {
8684
return fmt.Errorf("could not reach authorization server: %w", err)
8785
}
@@ -101,7 +99,7 @@ func loginWithBrowser() error {
10199
// 5. Build authorization URL
102100
authURL := internaloauth.BuildAuthURL(
103101
meta.AuthorizationEndpoint,
104-
oauthClientID,
102+
config.OAuthClientID,
105103
redirectURI,
106104
state,
107105
pkce.Challenge,
@@ -135,7 +133,7 @@ func loginWithBrowser() error {
135133
pterm.Info.Println("Completing sign in...")
136134
tokenResp, err := internaloauth.ExchangeCode(
137135
meta.TokenEndpoint,
138-
oauthClientID,
136+
config.OAuthClientID,
139137
code,
140138
redirectURI,
141139
pkce.Verifier,

cmd/root/root.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import (
1818
"github.com/urfave/cli/v2"
1919
)
2020

21-
const oauthClientID = "fbcaaa58-1e30-43fe-8fba-34382ba4fe7f"
22-
2321
// NewApp creates and configures the root CLI application
2422
func NewApp() *cli.App {
2523
app := &cli.App{
@@ -57,11 +55,11 @@ func NewApp() *cli.App {
5755
if config.IsTokenExpired(session) {
5856
tokenEndpoint := session.TokenEndpoint
5957
if tokenEndpoint == "" {
60-
tokenEndpoint = "https://id.nodeops.network/oauth2/token"
58+
tokenEndpoint = config.OAuthIssuerURL + "/oauth2/token"
6159
}
6260
refreshed, err := internaloauth.RefreshTokens(
6361
tokenEndpoint,
64-
oauthClientID,
62+
config.OAuthClientID,
6563
session.RefreshToken,
6664
)
6765
if err != nil {

internal/config/oauth.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ import (
1010

1111
const oauthFile = ".oauth"
1212

13+
// OAuthClientID is the pre-registered public OAuth client ID for the CreateOS CLI
14+
const OAuthClientID = "fbcaaa58-1e30-43fe-8fba-34382ba4fe7f"
15+
16+
// OAuthIssuerURL is the OAuth identity server base URL
17+
const OAuthIssuerURL = "https://id.nodeops.network"
18+
1319
// OAuthSession holds the OAuth tokens
1420
type OAuthSession struct {
1521
AccessToken string `json:"access_token"`

0 commit comments

Comments
 (0)