@arsenalc2/cli
v0.9.0
Published
Publish ATAK plugins to an Arsenal C2 registry. Metadata is read off the APK, so a push is the artifact and a channel.
Maintainers
Readme
@arsenalc2/cli
arsenal publishes ATAK plugins to an Arsenal C2 registry.
You describe nothing. The registry reads the package name, versionCode, versionName, target ATAK version, CIV/MIL flavor, native ABIs and the signing certificate off the APK itself, so a push is the artifact plus a channel. There is no metadata form to fill in and nothing that can drift from the thing you actually built.
npm i -g @arsenalc2/cliNode 20 or newer. No runtime dependencies.
Authenticate
Create a token on your profile page, then put it in the environment:
export ARSENAL_TOKEN=arc2_xxxThat is the only configuration. ARSENAL_URL overrides the registry host if you run your
own; leave it unset for the public one.
Publish a build
arsenal push app/build/outputs/apk/civ/release/plugin.apk \
--channel beta \
--release-name 1.4.0 \
--notes "Fix range ring snap"Channels are alpha, beta, rc and release. Every new release needs a semver
release name (below).
Each push is a new release. A channel's latest release, what devices on it are offered, is the newest release on it that is not yanked, as on Google Play (below).
A release that ships more than one build - CIV and MIL, two ATAK targets, an ABI split -
is one push per build. A build joins an existing release when it has the same
versionCode and the same versionName apart from the ATAK template's - [5.6.0]
suffix, so a matrix is a loop in your CI job and needs no flag:
for apk in build/outputs/**/*.apk; do arsenal push "$apk" --channel beta --release-name 1.4.0; doneIf your builds' names or versionCodes differ per variant (a versionCode that folds in
the ATAK target, say), name the release they join: arsenal push <apk> --release 10204.
Pushing the same build twice is what is refused.
A release is addressed by its versionCode, or <versionCode>-<n> when an earlier release
already carried that code. A tak.gov build zipped without .git reports versionCode 1 on
every release, and that works: each one is its own release (1, 1-2, 1-3).
Which release is latest
Nothing is pointed by hand (#349): a channel's latest release is computed. It is the newest release on the channel by semver precedence of the release name (releases published before names were required sort below every named one, by versionCode) that is not yanked and still has a build that is not. A device that cannot run it is offered the newest release below it that it can.
- Push a newer release and it is the latest from the next catalog read.
- Push an older one, a vendor backfilling a catalog or a 1.2.1 fix after 1.3.0, and it is stored, listed and installable from history below the latest release, which stays.
- Stage a release by pushing it to a lower channel and promoting it when it is ready.
- Roll a channel back by yanking its latest release,
arsenal yank <package> <release>: the channel falls back to the newest release below it, and--undorestores it.
arsenal make-current and --no-make-current are gone; the registry refuses the
makeCurrent form field an older CLI sends, so a script fails rather than publishing.
Promoting a release
promote moves a release up to a higher channel without uploading it again, where it
becomes the latest release, the way Google Play promotes a release from one track to the
next:
arsenal promote com.example.plugin 42 --channel release
# com.example.plugin: latest on release is now release 42 (1.3.0), was 41 (1.2.0); 42 is on beta, releaseThe build that ships is the exact file that was tested: same address, versionCode, digest
and name. It stays on every channel it was on, so testers granted only beta keep it.
Promotion goes up only (alpha, beta, rc, release), skipping a level is fine, and
it is refused (promotion_not_newest) when the release is not newer than the latest on
the target: to go back, yank the latest release instead. Onto rc
or beta needs what a push needs; onto release needs an admin or owner and a "full"
token. Running it again once done is not an error. It warns, as a push does, when no
build of the release loads on official ATAK, when a build's versionCode is below what the
target channel already served, and when a pre-release name goes onto release. A MIL
build is refused onto a channel granted to everyone.
Two ways of naming pre-releases both work, and which one is yours to choose:
- Rebuild per stage:
1.3.0-beta.1,1.3.0-beta.2onbeta, then a new build named1.3.0pushed torelease. Nothing is promoted. - Build once and promote: name builds for the version they will become (
1.3.0), push them tobeta, and promote the one that holds up. Promotion never renames, so a build named1.3.0-beta.2ships onreleaseunder that name.
A release none of whose builds loads on official ATAK (unsigned, or signed by a key
official ATAK refuses) becoming the latest on release is allowed, and both the push and
promote print a wont_load_official warning saying what devices on official ATAK will
see, and which yank falls back to a release that loads there.
Naming the release
Every new release needs a semver release name (1.4.0, 1.4.0-beta.2, 1.4.0+ci.7),
on every plugin. It is what the release is listed under and what the registry orders
releases by: semver precedence, so 2.5.0-rc.9 is older than 2.5.0. A push that creates
a release without one is refused with release_name_required, and one with a name that
is not semver with release_name_not_semver. Give it one of three ways:
- push from a semver tag. With no flag, the CLI reads
GITHUB_REF_NAMEwhenGITHUB_REF_TYPE=tag(GitHub Actions) orCI_COMMIT_TAG(GitLab), strips one leadingv(v1.4.0becomes1.4.0), and uses it if the rest is semver. Never a branch. - set
ARSENAL_RELEASE_NAMEin the job, which beats a tag. - pass
--release-name, which beats both.
The CLI prints which one it used: release name 1.4.0 (from GITHUB_REF_NAME). A CLI from
before this rule cannot read the name from CI, so upgrade it:
npm i -g @arsenalc2/cli@latest.
for apk in build/outputs/**/*.apk; do
arsenal push "$apk" --channel release --release-name 2.4.0
doneWrite prerelease counters with a dot: beta.10, not beta10. Semver compares beta10 as
text, so it sorts below beta9; the registry says so when it sees one.
The name is the release. A push carrying a name joins the release already declared
under exactly that name, whatever its versionCode (so a 5.4.0 build at versionCode 1 and a
5.6.0 build at a commit timestamp are one release when both say 1.1.42), and otherwise
creates that release. A push with no name can only add a build to an existing release:
the one whose versionCode and versionName (apart from - [5.6.0]) it matches; when it
matches several that lack this build, it is refused (release_ambiguous) until you name
the release. With --release, the named release is joined, and a
--release-name that disagrees with its name is refused; a name inferred from CI is not
sent with --release, so a tag pipeline can still add a build to a release published
before names were required.
Releases published before names were required have no name, and every release
published before this rule is one of them. A named push of one of their versions (pushing
1.3.0 when an unnamed release already is 1.3.0) is refused with
release_name_matches_legacy, because it would otherwise create a second 1.3.0 and make
it the latest. Add the build to the existing release with --release <ref>, which the
refusal names, and no --release-name. Each build keeps the versionName it carries, and
the registry warns when that names a different version than the release (only the
MAJOR.MINOR.PATCH is compared, so a 2.5.0-beta.1 release shipping an APK that says
2.5.0 is fine).
A release name must be unique within the plugin, and so must its precedence: 2.5.0+a
and 2.5.0+b are one version. Either is refused with release_name_taken before
anything is uploaded. Yanked releases keep their names.
The registry lints every build as it lands, and reports what it found. The ones worth knowing about describe failures that are otherwise silent on a tester's device:
- versionCode reused: this release carries the same versionCode as another. Android
still installs it as an update, but an MDM keyed on versionCode will not see a new
build. Include
.gitin your tak.gov submission zip, or setversionCodeinbuild.gradleyourself. - will not load on official ATAK builds, because the APK carries a non-TPC signer. A warning: it is correct for a build meant for sideloading onto dev devices.
You find out here rather than from a device that quietly never shows the plugin.
A plugin keeps the name it was first published under
The plugin's listed name is its android:label, read off the artifact like everything
else, and the first published build sets it. A later push whose label differs is refused,
on every channel:
display_name_changed
{"published":"Plugin Template","pushed":"ATAK Reactive Example"}The reason is the devices. The package name has not changed, so everyone who installed the old build is offered this one as a routine update, under a name they never chose - and a publisher correcting a template leftover looks identical, from the registry's side, to one quietly becoming "TAK Core". Worse, the name ATAK shows in its own plugin menu is read off the APK rather than from us, so there is nothing we could annotate to explain it after the fact. Declining to distribute the build is the only thing that actually protects the device.
Restore android:label to the published name and push again.
Case and whitespace are not a rename: ATAK Reactive example to ATAK Reactive Example,
or a stray trailing space, goes through and updates the stored name.
There is deliberately no flag to override this. If you need a genuine rename - a rebrand, a template leftover, a transferred namespace - it wants a deliberate operation that also requires your next build's label to match the new name, so that the listing and the device can never disagree. That is not built yet; ask, rather than working around it by republishing under a new package name, which would orphan every install.
Other commands
arsenal promote com.example.plugin 42 --channel release # move a release up a channel
arsenal yank com.example.plugin 42 # withdraw a release; --undo restores it
arsenal delete com.example.plugin 42 # remove a release, every build of it
arsenal delete com.example.plugin 42 --artifact atak-apk-civ-5.6.0-arm64
arsenal grant com.example.plugin --channel beta --team testers
arsenal revoke com.example.plugin --channel beta --team testers
arsenal grants com.example.plugin # who holds which channel
arsenal --helpPrefer yank to delete: it keeps the record and can be undone. delete is for a
release that was never one, such as a pull request build replaced by the next push
(--notes-prefix), or a build that leaked a secret. Yanking, deleting and the grant
commands need an admin or owner and a "full" token. yank, grant and revoke are
safe to repeat, so a CI job can run them unconditionally. revoke --user takes any
verified address of the person's account, the same as grant.
Reading the registry
These print to stdout, and take --json. A "read" token is enough:
arsenal list # the namespace's plugins
arsenal versions com.example.plugin --channel beta # releases and their builds
arsenal download com.example.plugin 42 --output plugin.apk # fetch a build, sha256 verifieddownload checks the bytes against the sha256 the registry holds and removes the file if
they differ. A release with several builds needs --artifact <id>, and download lists
the ids when it is missing. Downloading needs the owner, admin or publisher role: anyone
else installs through the Arsenal C2 client.
In CI
The token is the whole setup. A restricted token infers its organization, so --org is
usually unnecessary:
# On a tag push (v1.4.0): the release is named 1.4.0 from the tag, no flag needed.
- run: npm i -g @arsenalc2/cli@latest
- run: arsenal push build/plugin.apk --channel release --notes "$GITHUB_REF_NAME"
env:
ARSENAL_TOKEN: ${{ secrets.ARSENAL_TOKEN }}Not building from a tag? Set ARSENAL_RELEASE_NAME (for example
1.4.0-beta.${{ github.run_number }}) or pass --release-name.
Full documentation is at arsenalc2.com/docs.
License
Apache-2.0
