feat(images): rename android-flutter → android-flutter-ios and add CocoaPods #58

Merged
to merged 11 commits from dev into main 2026-05-29 16:13:32 +02:00
Owner

Summary

Two changes bundled:

  1. Rename images/fedora/android-flutterimages/fedora/android-flutter-ios. The flavor's scope expanded from "Android-only Flutter" to "Android + iOS Flutter (with Mac handoff for the actual ipa build)". config.ini tag follows: android-flutter-fedoraandroid-flutter-ios-fedora.

  2. Bundle CocoaPods. pod install is pure Ruby and runs fine on linux/amd64 — it resolves Podfile.lock and fetches Pods/ without needing Xcode. The actual flutter build ipa / xcodebuild archive step still requires macOS, but lockfile generation and pod fetching now happen in-container.

Bumper

New rubygem:NAME source handler. Queries rubygems.org's /api/v1/versions/<gem>/latest.json — server-side filtered for stable releases, so the response works directly with constraint=latest. Wired into the cocoapods row via source=rubygem:cocoapods.

Image tag migration

Old tag scc:android-flutter-fedora-latest stays on the registry but won't be updated. New tag is scc:android-flutter-ios-fedora-latest and scc:<version>-android-flutter-ios-fedora. Existing .scc/config.ini files pointing at the old tag should update — old tag drifts.

Test plan

  • ./bump-deps.sh --dry-run -i fedora/android-flutter-ios resolves all six bumpable rows with zero errors; rubygem handler returns cocoapods 1.16.2
  • bats 61/61 pass
  • CI test job
  • After build: docker run --rm --platform=linux/amd64 --entrypoint pod scc:android-flutter-ios-fedora-latest --version returns 1.16.2

Bonus

This branch also pulls in main's daily-bump (Flutter 3.27.1 → 3.44.0, Android cmdline-tools 13114758 → 14742923, Temurin 21.0.5+11 → 21.0.11+10) via the merge commit that landed earlier.

## Summary Two changes bundled: 1. **Rename** `images/fedora/android-flutter` → `images/fedora/android-flutter-ios`. The flavor's scope expanded from "Android-only Flutter" to "Android + iOS Flutter (with Mac handoff for the actual ipa build)". config.ini `tag` follows: `android-flutter-fedora` → `android-flutter-ios-fedora`. 2. **Bundle CocoaPods**. `pod install` is pure Ruby and runs fine on linux/amd64 — it resolves Podfile.lock and fetches Pods/ without needing Xcode. The actual `flutter build ipa` / `xcodebuild archive` step still requires macOS, but lockfile generation and pod fetching now happen in-container. ## Bumper New `rubygem:NAME` source handler. Queries rubygems.org's `/api/v1/versions/<gem>/latest.json` — server-side filtered for stable releases, so the response works directly with `constraint=latest`. Wired into the cocoapods row via `source=rubygem:cocoapods`. ## Image tag migration Old tag `scc:android-flutter-fedora-latest` stays on the registry but won't be updated. New tag is `scc:android-flutter-ios-fedora-latest` and `scc:<version>-android-flutter-ios-fedora`. Existing `.scc/config.ini` files pointing at the old tag should update — old tag drifts. ## Test plan - `./bump-deps.sh --dry-run -i fedora/android-flutter-ios` resolves all six bumpable rows with zero errors; rubygem handler returns cocoapods 1.16.2 - bats 61/61 pass - CI test job - After build: `docker run --rm --platform=linux/amd64 --entrypoint pod scc:android-flutter-ios-fedora-latest --version` returns 1.16.2 ## Bonus This branch also pulls in main's daily-bump (Flutter 3.27.1 → 3.44.0, Android cmdline-tools 13114758 → 14742923, Temurin 21.0.5+11 → 21.0.11+10) via the merge commit that landed earlier.
to added 9 commits 2026-05-29 15:49:25 +02:00
feat(launcher): daily Claude Code update check (1.1.2 → 1.2.0)
All checks were successful
build-and-push / test-1 (push) Successful in 26s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 24s
build-and-push / build (push) Successful in 3m46s
test / test (pull_request) Successful in 25s
bad4785181
Until now scc only installed Claude once, then served the cached binary
from the scc-claude volume forever — no version check, no nudge when
Anthropic shipped a fix. The only refresh path was the user knowing to
set SCC_FORCE_REINSTALL=1.

This adds a once-per-day check mirroring the existing scc.sh self-update
flow: scc.sh GETs @anthropic-ai/claude-code/latest from npm (~1 KB,
1 s timeout), compares it to the version cached in the volume, and warns
+ prompts when upstream is newer. Decisions are remembered per upstream
version so users don't get re-prompted for the same release.

Plumbing:

  - scc-entrypoint.sh writes `claude --version` into both
    /opt/scc-claude/version (canonical, lives with the binary) and
    /scc-meta/claude_version (bind-mounted from ~/.scc/volume-meta/, so
    the host launcher can read it without a docker-exec roundtrip).
    Re-captured after every reinstall; idempotent on the fast path.
  - scc.sh adds the /scc-meta mount alongside the existing volume mount.
    Older launchers don't mount /scc-meta; the entrypoint silently skips
    the mirror in that case, so a new image works with an old launcher.
  - New check_for_claude_update() in scc.sh: 24h-cached fetch, semver
    comparison via sort -V, per-version ack file at ~/.scc/claude-update-check.
    Fail-closed: non-TTY runs note and continue, never auto-update.

Three new env vars:

  - SCC_AUTO_UPDATE_CLAUDE=1       skip the prompt, auto-set
                                   SCC_FORCE_REINSTALL=1 on detection.
  - SCC_NO_CLAUDE_UPDATE_CHECK=1   skip the daily probe entirely.
  - (existing SCC_FORCE_REINSTALL=1 still works; suppresses the check
    when set, since reinstall is already happening.)

Tests:

  - tests/feature/helpers.bash exports SCC_NO_CLAUDE_UPDATE_CHECK=1
    alongside SCC_NO_UPDATE_CHECK=1 so the daily curl never fires under
    test. All 56 existing bats tests pass; no new tests added (the new
    function is HTTP-bound and matches the established pattern).

README "Claude binary handling" section gains a "Daily Claude update
check" subsection documenting the prompt, the env vars, and where the
metadata lives. scc.sh.version bumped 1.1.2 → 1.2.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(launcher): SCC_PLATFORM + .scc/config.ini platform= override (1.2.0 → 1.3.0)
All checks were successful
build-and-push / test-1 (push) Successful in 28s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 24s
test / test (pull_request) Successful in 24s
build-and-push / build (push) Successful in 3m39s
58b13f1b57
Adds a third configurable field next to image and claude_args: a docker
--platform pin. Use case: running the new fedora/android-flutter image
(amd64-only, since Google ships no arm64 Android SDK) on Apple Silicon
under Rosetta-for-Linux. Without --platform, docker warns about arch
mismatch and Rosetta's behavior is implicit; with --platform=linux/amd64
the launcher tells docker exactly what to pull + run.

Resolution order matches the existing image plumbing:

  SCC_PLATFORM env > .scc/config.ini platform= > (omit, docker chooses)

Wired through both `docker pull` and `docker run` via a shared
platform_args array. The status line prints "(--platform linux/amd64)"
when set so you can tell at a glance whether emulation is in play.

Validation is minimal — `^[a-z0-9]+/[a-z0-9/.-]+$` catches the obvious
typo (`amd64` without the `linux/` prefix) but otherwise trusts docker
to error on garbage. Anything that looks like os/arch passes through.

Trust gate extended:

  - require_project_trust() takes a 4th argument (platform).
  - Trust file format gains a `platform=` line. Old trust files without
    the line keep working because ini_get returns "" for missing keys
    and ""="" compares equal when the project also doesn't pin platform.
    Re-prompt only fires when a project NEWLY pins a platform that
    wasn't in the old trust file.
  - SCC_PLATFORM env is user-explicit and does NOT trigger the trust
    prompt, matching the SCC_IMAGE behavior.

Tests: 3 new bats cases in project_config.bats:

  - platform = … in config.ini reaches docker run argv as
    `--platform linux/amd64`
  - SCC_PLATFORM env overrides the config.ini value
  - Malformed platform (`amd64`, no `linux/` prefix) rejected with rc=2

All 59/59 tests pass (was 56 before).

README's "Schema" + "Trust prompt" sections updated to document the new
field, the resolution order, and the bypass env vars. scc.sh.version
bumped 1.2.0 → 1.3.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(launcher): per-project Claude volume via .scc/config.ini volume= (1.3.0 → 1.4.0)
All checks were successful
build-and-push / test-1 (push) Successful in 25s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 26s
build-and-push / build (push) Successful in 3m48s
test / test (pull_request) Successful in 25s
68233c9c41
The named volume `scc-claude` is shared across all image flavors — first
image to install Claude wins, every subsequent flavor exec's that same
binary. When two flavors have substantially different runtime layouts
(e.g. default Fedora-with-Node vs android-flutter without it, or amd64
under Rosetta vs native arm64), the cached binary from flavor A can fail
to exec under flavor B's environment.

Add a project-pinnable override so each project can opt into its own
Claude volume, matching the existing image= / claude_args= / platform=
pattern:

  [scc]
  image  = forge.stacktop.network/openstacktop/scc:android-flutter-fedora-latest
  volume = scc-claude-android-flutter

Resolution: SCC_CLAUDE_VOLUME env > .scc/config.ini volume= > default
"scc-claude". CLAUDE_VOLUME loses `readonly` so it can be reassigned
after project-config parsing; the three downstream usages (status label,
state label, bind mount) are unchanged.

Not part of the trust gate: a project pointing at a different cached
binary is no greater risk than image= itself (which already gates), and
the existing SCC_CLAUDE_VOLUME env path doesn't gate either — adding it
to the trust hash now would be asymmetric. Existing trust files keep
working unchanged.

Tests:

  - volume = … in config.ini → docker run argv contains
    `my-project-claude:/opt/scc-claude` and NOT `scc-claude:/opt/scc-claude`
  - SCC_CLAUDE_VOLUME env overrides the config.ini value

All 61/61 bats tests pass (was 59 before).

README schema + resolution-priority table updated; help text mentions the
new field and the env precedence. scc.sh.version bumped 1.3.0 → 1.4.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore: trigger forgejo PR recompute (#55)
All checks were successful
build-and-push / test-1 (push) Successful in 24s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 25s
test / test (pull_request) Successful in 25s
build-and-push / build (push) Successful in 3m38s
a5e818d56a
Merge branch 'main' into dev
All checks were successful
build-and-push / test-1 (push) Successful in 24s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 24s
test / test (pull_request) Successful in 25s
build-and-push / build (push) Successful in 3m34s
40a6208e86
Resolves the squash-vs-individual-commits conflict pattern. Main contains
the squashed PR #54 (claude update check + platform override) as one
commit; dev contained the same changes as three separate commits plus
the new volume= work on top. Git's 3-way merge couldn't auto-resolve
since both sides modified the same regions, but dev's content is a
strict superset of main's (it has main's changes via the 3-commit
sequence plus the volume= additions), so all four conflicting files
resolved by taking dev's side:

  README.md
  scc.sh
  scc.sh.version
  tests/feature/project_config.bats

61/61 bats tests pass post-resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(entrypoint): locate Claude binary by path, not via command -v (1.4.0 → 1.4.1)
All checks were successful
build-and-push / test-1 (push) Successful in 25s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 25s
test / test (pull_request) Successful in 24s
build-and-push / build (push) Successful in 3m46s
a94cd6fc7f
Claude Code 2.1+ ships as a single native binary and the install.sh
drops it at ~/.local/bin/claude WITHOUT adding that directory to PATH
(the installer just prints "echo 'export PATH=...' >> your shell config"
as a manual setup hint). The entrypoint was using `command -v claude`
to find what the installer just dropped, which returned empty and
aborted the install with "claude not on PATH after install — installer
layout changed?" — even though the binary was successfully written.

Fix: check the known native-install locations directly
(/root/.local/bin/claude, $HOME/.local/bin/claude) before falling back
to a PATH-widened `command -v` for older or relocated layouts. The
fallback keeps backward compatibility with the previous installer that
did expose claude on PATH; the explicit-path checks handle the 2.1+
native-install flow.

Surfaced by:
  - fresh `scc-claude-android-flutter` volume + `scc` against the
    android-flutter image. Install succeeded ("Claude Code successfully
    installed! Version: 2.1.150 Location: ~/.local/bin/claude") but the
    next entrypoint step bailed out with the missing-on-PATH error.

scc.sh.version bumped 1.4.0 → 1.4.1; 61/61 bats tests still pass (the
entrypoint runs inside the container, not on the bats host, so coverage
is unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merge branch 'main' into dev
All checks were successful
build-and-push / test-1 (push) Successful in 26s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 25s
test / test (pull_request) Successful in 24s
build-and-push / build (push) Successful in 3m41s
efac92a816
Same squash-vs-individual-commits conflict pattern as the previous merge
back (PR #55). Main has my volume= change as a squash commit; dev has
the same change as its original commit plus the 1.4.1 entrypoint patch
on top. Conflicts on scc.sh (version line) and scc.sh.version (only),
both resolved by taking dev's side — dev contains main's 1.4.0 plus the
1.4.1 patch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(images): rename android-flutter → android-flutter-ios and add CocoaPods
Some checks failed
build-and-push / test-1 (push) Successful in 29s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 26s
build-and-push / build (push) Failing after 4m12s
test / test (pull_request) Successful in 29s
543d632c8f
The flavor's scope grew from "Android-only Flutter dev" to "Android +
iOS Flutter dev that hands off to a Mac for the final build" once you
realised pod install is just Ruby and works on linux/amd64. Rename the
directory + the config.ini `tag` to match the broader scope, and bundle
CocoaPods so iOS dependency resolution happens in the container.

What CocoaPods does here:

  - Resolves Podfile.lock and fetches Pods/ — pure Ruby work, no Xcode
    needed. Generated lockfile + workspace are valid for the Mac to
    pick up and sign.
  - The actual `flutter build ipa` / `xcodebuild archive` step still
    runs on macOS + Xcode on the host. This image stays Android-build-
    capable + iOS-resolve-capable.
  - Edge cases (Firebase / GoogleMaps pods with post-install hooks that
    shell out to xcrun) will fail loudly with a clear error and have to
    run on the Mac; most Flutter plugin pods don't trip this.

Image changes:

  - Dockerfile installs ruby + ruby-devel + rubygems + gcc/g++/make
    (gcc/g++/make are required for native gem extensions like json).
  - New ARG COCOAPODS_VERSION + `gem install cocoapods -v $VER` step.
  - Header comment expanded with the iOS scope + Mac-handoff note.

Bumper:

  - New `rubygem:NAME` source handler. Queries rubygems.org's
    /api/v1/versions/<gem>/latest.json — yanked/pre-release versions
    are filtered server-side, so the response is a clean semver string
    suitable for `constraint=latest` without extra filtering.
  - cocoapods row in config.ini wires it up:
    kind=arg; source=rubygem:cocoapods; arg=COCOAPODS_VERSION; ...

Image tag changes:

  - config.ini `tag = android-flutter-fedora` → `android-flutter-ios-fedora`
  - Next build publishes scc:android-flutter-ios-fedora-latest and
    scc:<version>-android-flutter-ios-fedora. The old tag stays on the
    registry but won't be updated; users of .scc/config.ini with
    image=…android-flutter-fedora-latest should switch over.

README arch table updated.

Includes the merge of main's daily-bump (Flutter 3.27.1 → 3.44.0,
Android cmdline-tools 13114758 → 14742923, Temurin 21.0.5+11 → 21.0.11+10).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(android-flutter-ios): correct Fedora 44 ruby/cocoapods build deps
All checks were successful
build-and-push / test-1 (push) Successful in 25s
build-and-push / test (push) Successful in 0s
test / test (push) Successful in 25s
test / test (pull_request) Successful in 24s
build-and-push / build (push) Successful in 16m19s
326e8f5ce6
The dev validation build failed on `gem install cocoapods`. Two known
issues with the first cut:

1. `rubygems` was listed as a separate dnf package. Since Fedora 30 it's
   bundled directly in `ruby`, and the standalone name was retired. dnf
   would error out at the install step.

2. `libffi-devel` was missing. CocoaPods doesn't depend on `ffi`
   directly, but `typhoeus` → `ethon` → `ffi` is in its dep graph, and
   `ffi`'s C extension needs libffi headers to compile. Without it,
   `gem install cocoapods` fails inside the ffi extconf step.

Also adding `redhat-rpm-config` — Fedora's gem build relies on rpm
build flags for hardened compilation defaults; missing it can produce
"cannot find ruby header" or similar fc44-specific extconf errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated by .forgejo/workflows/build.yml on
2026-05-29T14:11:37Z.
to merged commit 64bc52520a into main 2026-05-29 16:13:32 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
openstacktop/scc!58
No description provided.