Adopt a8c-secrets for the demo app secrets - #824
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates the demo app’s secret materialization workflow from the legacy .configure/configure_apply flow to a8c-secrets, so internal contributors decrypt secrets via a8c-secrets while external contributors continue using the templated, gitignored secrets file.
Changes:
- Replace the build-phase secrets source with
a8c-secrets which+ versioned.a8c-secrets/Secrets.swift.agetracking, and avoid rewriting the derivedSecrets.swiftwhen content is unchanged. - Update contributor-facing documentation and Makefile targets to use
a8c-secrets. - Remove the legacy
.configuresetup and encrypted artifact, and add repo metadata/public keys fora8c-secrets.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| Sources/GravatarUI/GravatarUI.docc/GettingStarted.md | Updates internal contributor setup instructions to use a8c-secrets. |
| Scripts/build-phases/generate-secrets.xcfilelist | Tracks the versioned .age input so Xcode reruns the phase when secrets rotate. |
| Scripts/build-phases/generate-secrets.sh | Uses a8c-secrets which as primary source; adds “copy only if changed” behavior. |
| Makefile | Updates setup-secrets to run a8c-secrets decrypt. |
| Demo/Demo/Generated/README.md | Updates explanation of how Secrets.swift is generated for the demo target. |
| .gitattributes | Marks *.age as binary for Git attributes handling. |
| .configure-files/Secrets.swift.enc | Removes legacy encrypted secrets artifact. |
| .configure | Removes legacy .configure configuration file. |
| .a8c-secrets/Secrets.swift.age | Adds the versioned encrypted secrets source for a8c-secrets. |
| .a8c-secrets/repo-id | Adds a8c-secrets repo identifier metadata. |
| .a8c-secrets/keys.pub | Adds public recipient keys used for age encryption (dev/ci). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Note Blocked: this PR is waiting on Automattic/a8c-ci-toolkit-buildkite-plugin#218 (and the stacked #220) to merge and ship in a toolkit release. The Posted by Claude Code (Opus 4.8) on behalf of @mokagio with approval. Update: This has now been addressed. |
e93d28f to
04e0f6f
Compare
Resolve the decrypted `Secrets.swift` with `a8c-secrets which` instead of a hardcoded path. The location then follows `.a8c-secrets/repo-id`, and the docs no longer have to spell out where decrypted secrets sit on disk, which a reviewer flagged as worth avoiding in #822. Track the committed `.age` as the build phase input. It is the versioned source of the decrypted file, so Xcode can still skip the phase — a runtime-resolved path alone leaves nothing to declare, forcing the phase to run every build. Guard the copy on content because a checkout can bump the `.age` mtime without the secret changing, and an unconditional copy would recompile `Secrets.swift` each time. Call `a8c-secrets decrypt` directly rather than wrapping fastlane's `configure_apply`, so contributors drive the tool itself. Document the one-time `a8c-secrets keys import`, without which `decrypt` fails. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127eCAM2kNwzftdKuNHqXg6
Nothing reads these now that the demo secrets come from `a8c-secrets`. `.configure` described only the `Secrets.swift` copy, and code signing goes through `match` rather than the `configure` plugin, so dropping it leaves no other consumer behind. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0127eCAM2kNwzftdKuNHqXg6
The demo build needs `a8c-secrets` on `PATH` to decrypt `Secrets.swift`, and the agents don't ship it. `install_a8c-secrets_binary`, added in CI toolkit 6.2.0, pins the release and verifies its SHA-256 before putting anything on disk — so the two changes are coupled: the command does not exist before 6.2.0. The step's inline command moves to `.buildkite/commands/build-demo.sh` because Buildkite interpolates `$HOME` and `$PATH` in `pipeline.yml` at upload time. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.8 <noreply@anthropic.com>
04e0f6f to
3d0f5d2
Compare
|
| App Name | Gravatar Prototype Build | |
| Build Number | 2548 | |
| Version | PR #824 | |
| Bundle ID | com.automattic.gravatar-sdk-demo-uikit.prototype-build | |
| Commit | 8105a96 | |
| Installation URL | 53l6gfu0gq0c8 |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
The phase compiled whatever `a8c-secrets decrypt` last wrote into the decrypted tree, which can predate a rotation of the tracked `.age`. Xcode did re-run the phase on such a rotation, since the `.age` is a declared input, but the script then copied the superseded plaintext and the demo built against a secret that no longer applied. Dropping the `cmp` guard accepts a wasted copy when a checkout bumps the `.age` mtime without the secret changing. The guard avoided that by skipping the write, which left the output older than its input and so kept the phase permanently out of date. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VaF5AyEaPBpo5tarMErbtM
A `warning:` on a failed decrypt let the secrets from a previous run compile silently, which is the failure running `decrypt` in the phase was meant to prevent. For a contributor who has a8c-secrets installed, a decrypt that fails is an error, not something to paper over. `a8c-secrets which` failing now errors for the same reason, rather than falling through to the external-contributor file. That file stays a fallback rather than an override: it is reached only when a8c-secrets is not installed at all. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.8 <noreply@anthropic.com>
Should help avoid drift. Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
|
@AliSoftware @iangmaia I'd appreciate your thoughts on this. Having basically built it from scratch (only previous iteration #821) I think it's a good place where to iron out how to best integrate Thanks! |
|
|
||
| # Xcode build phases have access to a different PATH than the host. | ||
| # So, we need to explicitly add a8c-secrets' default install location to the PATH. | ||
| export PATH="$HOME/.local/bin:$PATH" |
There was a problem hiding this comment.
This was not needed for CI builds, but it was on my machine. Of course, I double checked that I could run a8c-secrets decrypt successfully on my system.
100% agree with the "cons" here. I think it's not worth it and also makes it too easy to do things wrong (local secrets, etc) so better avoid this. |
AliSoftware
left a comment
There was a problem hiding this comment.
Left a suggestion to make the script more scalable from when we'll start adopting it in more repos. But not a blocker for this repo in particular.
| # Keep the substitution in an assignment: inside `apply "$(...)"` its exit | ||
| # status would be discarded and a decrypt that produced nothing would fall | ||
| # through to the external-contributor branch. | ||
| SECRETS_FILE_NAME="Secrets.swift" |
There was a problem hiding this comment.
Thinking of scaling (especially when we'll be ready to adopt this technique for more repos): I wonder what would be the best way to handle multiple secret files.
- Maybe we should write this code as a bash array + for loop, even if in the case of Gravatar-SDK-iOS there will be only one item, just so it's scalable
- An alternative to hardcoding the list of files to decrypt could be to iterate of all the files in the directory returned by
a8c-secrets which, i.e. all the decrypted files. But I don't think that'd be a good idea, because that could risk copying files that are there but we don't need in the Xcode project, and thus copying too much. So I think hardcoding the list of files in thegenerate-secrets.shscript is still the right move, even if that means that we'll need to keep this script in sync every time we might need to add a new secret file to the Xcode project. But we'll need to add said secret file to the.xcodeprojanyway too, so I think that's fine.
So something like this, maybe? (not tested)
SECRETS_FILES_NAMES=(Secrets.swift)
if command -v a8c-secrets >/dev/null 2>&1; then
a8c-secrets decrypt --non-interactive >/dev/null \ || { echo "error: …"; exit 1; }
for secret_file_name in "${SECRETS_FILES_NAMES[@]}" do
secret_file=$(a8c-secrets which "${secret_file_name}") \ || { echo "error: …"; exit 1; }
apply "${secret_file}"
done
else
for secret_file_name in "${SECRETS_FILES_NAMES[@]}" do
basename = "${secret_file_name%.*}"
extension = "${secret_file_name##*.}"
external = ${SRCROOT}/Demo/${basename}.external-contributor.${extension}"
if [ -f "$external" ]; then
apply "${external}"
else
template = ${SRCROOT}/Demo/${basename}.template.${extension}"
echo "error: No secret found for file '${secret_file_name}'. Internal contributors: install a8c-secrets and follow its setup instructions; see https://github.com/Automattic/a8c-secrets. External contributors: copy '${template}' to '${external}' and fill in your own credentials."
exit 1
fi
done
fiThere was a problem hiding this comment.
Thinking of scaling (especially when we'll be ready to adopt this technique for more repos): I wonder what would be the best way to handle multiple secret files.
Good point. What do you think of passing the file names as arguments? That would allow us to have a single version of the script to either copy paste (i.e. keep in sync with the help of AI) or embed in a8c-secrets as a command. However, if we were to make it part of a8c-secrets we'd also need to both harden and make optional the external contributors worflow.
Regardless, using an array would definitely be a flexibility improvement. I'll keep it in mind through the adoption for when I'll run into a repo with more than one build-time secret to decrypt.
There was a problem hiding this comment.
Yep I like it. In fact, adding a dedicated a8c-secrets xcode-script-build-phase subcommand or the like to a8c-secrets — to DRY the logic of whatever script we end up using once we refined and set this up across our repos to find the best common denominator and logic that works well for all projects — was already in my list of ideas for the next stage of the project.
We're not totally there yet, but having the script be closer to being generic enough to become a single version of the script we can copy paste is definitively gonna get us closer to this 👍
| @@ -0,0 +1 @@ | |||
| *.age binary | |||
There was a problem hiding this comment.
I was surprised to see the Serets.swift.age file in this PR be in clear and was about to suggest we add exactly that… but I see that you covered this already; so I'm guessing it will not be taken into account by GitHub until after this is merged into trunk 🙃
There was a problem hiding this comment.
That's the conclusion I came to as well...
There was a problem hiding this comment.
Doubly strange: If I look at Secrets.swift.age now that the PR has been merged:
- Not only does GitHub not show it as "This file is binary" despite this line in the
.gitattributes - But also it shows its content as looking like Japanese text… while the binary content of the
.agefile is definitively not looking like that (even if it were misinterpreted with the wrong text encoding).- In fact, the
.agefile format specification shows that.agefiles start with a plain ASCII text header (age-encryption.org/v1as its first line, then a mix of plain text + Base64 data) before its actually-binary payload post-header. - So it's a mystery how even those first bytes of the file, which end up being regular ASCII text, end up making GitHub interpret those as Japanese characters… 🤷
- In fact, the
This mirrors the agreed implementation in Gravatar SDK iOS. See Automattic/Gravatar-SDK-iOS#824
Matches the shape agreed in Gravatar-SDK-iOS, whose build phase copies without comparing first: Automattic/Gravatar-SDK-iOS#824 The phase declares its inputs and output, so Xcode already skips it when nothing changed. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 <noreply@anthropic.com>

See https://linear.app/a8c/issue/AINFRA-1549
Switches from
.configuretoa8c-secrets, building on top of what already established in terms of extracting outside the repo in #822.In doing so, changes how the secrets decrypt build phase works a little bit. The rationale for the change came from @AliSoftware's comments in Automattic/pocket-casts-ios#4731, in particular the one about bypassing fastlane.
Here's what I propose:
a8c-secrets decryptto acquire the secrets it needs.a8c-secretsshould not prevent building the app1The build phase flow is:
a8c-secretsavailable, assume internal contributorFootnotes
We might want to refine the external contributor flow to "can build with placeholder credentials," meaning, an external contributor should only need to provide the credentials that are absolutely required for the app to work. Example: A valid Sentry DSN is not necessary because crash logging should not be a requirement for external contributors. The demo app here does not use Sentry, but the point stands. ↩
Alternative: Have a more generic "local override" file instead of an explicit "external" one and have local take precedence, maybe with an
echo "warning: Using local secrets override..."message. This would allow internal devs to override secrets if needed without having to jump through hoops to ignoreda8c-secrets. I'm not sure how likely that scenario is on a day to day basis, though, so not sure if it's worth implementing with the risk of local secrets remaining in use unintentionally given how a warning is easy to miss. On top of that, we want secrets to stay outside the repository, but adding a prioritized mechanism for internal contributors to track secrets in the repo seems like it would open a bigger door for possible leakages than what "you need to work arounda8c-secretsto make this happen" does. ↩High level instructions instead of specific commands such as "run
a8c-secrets keys importfollowed bya8c-secrets decrypt" to avoid the instructions drifting from the tool's API. Sending users to the tool's README keeps it as the single source of truth. ↩