v0.1.1: monochrome UI, spaced settings tabs, keychain-safe publishing
The accent is ink rather than blue: primary buttons, the floating Add button, the empty-state shield, the active settings tab, the switch, the countdown ring and the initials tile. Light mode uses a deep near-black with a whisper of cool hue, dark mode a near-white — the Daito / Beautiful UI treatment. Status colours and the platform dots keep their hue because they carry meaning. The input focus glow stays, now a neutral 4px halo with a mid-grey edge. The per-issuer hue helper in the web UI is gone; the rendered chat tile still uses its issuer colour, where it helps tell accounts apart. Settings tabs get an 8px gap: each pill carries a 1px hairline ring outside its box, so 4px read as the pills touching. build-and-publish-unraid.sh writes registry auth into its private Docker config instead of `docker login`/`docker logout`. With an empty config the CLI falls back to docker-credential-osxkeychain, which shares its keychain item with git's helper: login fails with -25299 and logout deletes the credential git uses. PUBLISHING.md documents the trap and the release flow for this version. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
ad3720edbf
commit
3c83cd9928
7 files changed
+146
-86
No files matched your search
Generated
+6
-6
@@ -1210,7 +1210,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openmfa"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -1225,7 +1225,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openmfa-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"base32",
|
||||
@@ -1250,7 +1250,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openmfa-delivery"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"openmfa-core",
|
||||
@@ -1268,7 +1268,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openmfa-http-api"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64 0.22.1",
|
||||
@@ -1290,7 +1290,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openmfa-render"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"resvg",
|
||||
@@ -1302,7 +1302,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openmfa-webui"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.90"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -314,7 +314,9 @@ and the binary is genuinely standalone.
|
||||
|
||||
The UI follows [Beautiful UI](https://www.beautifului.dev/) — OKLCH colour
|
||||
ramps, hairline-ring shadows, inset fields against raised buttons, Inter and
|
||||
JetBrains Mono. Light and dark both ship.
|
||||
JetBrains Mono. The accent is monochrome in the vein of [Daito](https://daito.io):
|
||||
ink in light mode, near-white in dark, so the only colour on screen is status
|
||||
(live, expiring, error) and the three platform dots. Light and dark both ship.
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -341,6 +343,16 @@ renames refresh without waiting for a rollover.
|
||||
have it on screen.
|
||||
- Email → chat forwarding for services that send codes by email.
|
||||
|
||||
## Changelog
|
||||
|
||||
- **v0.1.1** — Monochrome UI. The accent is ink rather than blue across the
|
||||
primary buttons, the floating **Add an account** button, the empty-state
|
||||
shield, the settings tabs and the focus glow on inputs; the settings tabs
|
||||
are spaced so their hairline rings no longer touch.
|
||||
- **v0.1** — First release: live TOTP tiles to Discord, Slack or Telegram, a
|
||||
three-way provider picker at setup, Google Authenticator export import,
|
||||
QR upload / camera / paste, and the Unraid template.
|
||||
|
||||
## Licence
|
||||
|
||||
MIT OR Apache-2.0. JetBrains Mono is bundled under the SIL Open Font License
|
||||
|
||||
+41
-24
@@ -34,10 +34,19 @@
|
||||
--stripe: oklch(40.5% 0 0/.075);
|
||||
--stripe-bg: oklch(97% 0 0);
|
||||
|
||||
--accent: oklch(62.6% .205 254.947);
|
||||
--accent-ink: oklch(55.6% .187 255.617);
|
||||
--accent-tint: oklch(96% .019 252.878);
|
||||
/* Monochrome accent. The primary colour is ink, not blue: a deep
|
||||
near-black with a whisper of cool hue in light mode (the Daito /
|
||||
Beautiful UI treatment), inverted to near-white in dark mode. Status
|
||||
colours below stay saturated because they carry meaning. */
|
||||
--accent: oklch(21% .012 262);
|
||||
--accent-ink: oklch(31% .012 262);
|
||||
--accent-tint: oklch(0% 0 0 / .07);
|
||||
--accent-edge: oklch(10% .012 262);
|
||||
--on-accent: oklch(100% 0 0);
|
||||
--focus-edge: oklch(50.6% .01 264.477);
|
||||
--glow: oklch(0% 0 0 / .1);
|
||||
--selection: oklch(0% 0 0 / .14);
|
||||
--shadow-fab: 0 6px 20px oklch(0% 0 0 / .18), 0 2px 6px oklch(0% 0 0 / .12);
|
||||
|
||||
--green: oklch(60.3% .155 150.883);
|
||||
--green-tint: oklch(60.3% .155 150.883/.12);
|
||||
@@ -55,7 +64,7 @@
|
||||
--shadow-raised: 0 0 0 1px oklch(91.2% .005 258.326), 0 2px 10px #0000000f;
|
||||
--shadow-overlay: 0 0 0 1px oklch(91.2% .005 258.326), 0 16px 48px #00000021;
|
||||
--shadow-inset-field: inset 0 1px 2px oklch(0% 0 0/.12);
|
||||
--ring: 0 0 0 3px var(--accent-tint);
|
||||
--ring: 0 0 0 4px var(--glow);
|
||||
|
||||
--radius: 8px;
|
||||
--radius-lg: 12px;
|
||||
@@ -92,10 +101,15 @@
|
||||
--stripe: oklch(100% 0 0/.055);
|
||||
--stripe-bg: oklch(22.6% .004 264.485);
|
||||
|
||||
--accent: oklch(68% .173 253.301);
|
||||
--accent-ink: oklch(78.8% .113 248.33);
|
||||
--accent-tint: oklch(68% .173 253.301/.16);
|
||||
--on-accent: oklch(20% .01 253);
|
||||
--accent: oklch(96.4% .002 247.839);
|
||||
--accent-ink: oklch(89% .003 248);
|
||||
--accent-tint: oklch(100% 0 0 / .1);
|
||||
--accent-edge: oklch(100% 0 0 / .22);
|
||||
--on-accent: oklch(20.9% .004 264.477);
|
||||
--focus-edge: oklch(73.1% .008 260.731);
|
||||
--glow: oklch(100% 0 0 / .15);
|
||||
--selection: oklch(100% 0 0 / .2);
|
||||
--shadow-fab: 0 6px 20px oklch(0% 0 0 / .5), 0 2px 6px oklch(0% 0 0 / .35);
|
||||
|
||||
--green: oklch(70.5% .154 153.814);
|
||||
--green-tint: oklch(70.5% .154 153.814/.14);
|
||||
@@ -134,10 +148,11 @@ h2 { font-size: 17px; }
|
||||
h3 { font-size: 14px; }
|
||||
p { margin: 0; }
|
||||
|
||||
a { color: var(--accent-ink); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
a { color: var(--ink); text-decoration: underline;
|
||||
text-decoration-color: var(--ink-3); text-underline-offset: 2px; }
|
||||
a:hover { text-decoration-color: var(--ink); }
|
||||
|
||||
::selection { background: var(--accent-tint); }
|
||||
::selection { background: var(--selection); }
|
||||
|
||||
:focus-visible {
|
||||
outline: none;
|
||||
@@ -238,7 +253,7 @@ a:hover { text-decoration: underline; }
|
||||
.search input::placeholder { color: var(--ink-3); }
|
||||
.search input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
border-color: var(--focus-edge);
|
||||
box-shadow: var(--shadow-inset-field), var(--ring);
|
||||
}
|
||||
.search .clear {
|
||||
@@ -273,8 +288,7 @@ a:hover { text-decoration: underline; }
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent); color: var(--on-accent);
|
||||
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 70%, black),
|
||||
0 1px 2px #00000026;
|
||||
box-shadow: 0 0 0 1px var(--accent-edge), 0 1px 2px oklch(0% 0 0 / .15);
|
||||
}
|
||||
.btn-primary:hover { background: var(--accent-ink); }
|
||||
|
||||
@@ -347,7 +361,7 @@ textarea.input { height: auto; min-height: 76px; padding: 9px 11px;
|
||||
line-height: 1.6; }
|
||||
.input::placeholder { color: var(--ink-3); }
|
||||
.input:focus, .select:focus, textarea.input:focus {
|
||||
outline: none; border-color: var(--accent);
|
||||
outline: none; border-color: var(--focus-edge);
|
||||
box-shadow: var(--shadow-inset-field), var(--ring);
|
||||
}
|
||||
.input[aria-invalid="true"] { border-color: var(--red); }
|
||||
@@ -404,14 +418,17 @@ textarea.input { height: auto; min-height: 76px; padding: 9px 11px;
|
||||
}
|
||||
.acct:hover { background: var(--hover); }
|
||||
|
||||
.acct .avatar {
|
||||
/* Initials tile. Shared by the account list and the import preview. Neutral
|
||||
rather than a per-issuer hue so the list stays monochrome; the rendered
|
||||
chat tile keeps its issuer colour, where it helps tell accounts apart. */
|
||||
.avatar {
|
||||
display: grid; place-items: center;
|
||||
width: 38px; height: 38px;
|
||||
border-radius: 10px;
|
||||
font-size: 15px; font-weight: 600;
|
||||
color: var(--on-accent);
|
||||
background: var(--accent);
|
||||
box-shadow: inset 0 0 0 1px #0000001a;
|
||||
color: var(--ink);
|
||||
background: var(--hover-2);
|
||||
box-shadow: inset 0 0 0 1px var(--line-strong);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@@ -517,9 +534,7 @@ textarea.input { height: auto; min-height: 76px; padding: 9px 11px;
|
||||
color: var(--on-accent);
|
||||
background: var(--accent);
|
||||
border: 0; border-radius: 999px;
|
||||
box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 70%, black),
|
||||
0 6px 20px color-mix(in oklab, var(--accent) 40%, transparent),
|
||||
0 2px 6px #0000001f;
|
||||
box-shadow: 0 0 0 1px var(--accent-edge), var(--shadow-fab);
|
||||
cursor: pointer;
|
||||
transition: transform .16s var(--ease), background .16s var(--ease);
|
||||
}
|
||||
@@ -606,7 +621,7 @@ textarea.input { height: auto; min-height: 76px; padding: 9px 11px;
|
||||
transition: border-color .16s var(--ease), background .16s var(--ease);
|
||||
}
|
||||
.dropzone:hover, .dropzone.over {
|
||||
border-color: var(--accent);
|
||||
border-color: var(--focus-edge);
|
||||
background: var(--accent-tint);
|
||||
}
|
||||
.dropzone svg { width: 26px; height: 26px; color: var(--ink-3); margin-bottom: 8px; }
|
||||
@@ -784,7 +799,9 @@ textarea.input { height: auto; min-height: 76px; padding: 9px 11px;
|
||||
|
||||
/* ── Settings ─────────────────────────────────────────────────────────── */
|
||||
|
||||
.settings-nav { display: flex; gap: 4px; margin-bottom: 18px; flex-wrap: wrap; }
|
||||
/* 8px, not 4: each pill carries a 1px hairline ring outside its box, so a
|
||||
smaller gap reads as the pills touching. */
|
||||
.settings-nav { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
|
||||
|
||||
.kv { display: flex; justify-content: space-between; gap: 16px;
|
||||
padding: 9px 0; font-size: 13px; }
|
||||
|
||||
@@ -162,15 +162,6 @@ function initials(str) {
|
||||
return (s[0] || '?').toUpperCase();
|
||||
}
|
||||
|
||||
/** Deterministic hue per issuer so each account keeps a stable avatar
|
||||
* colour across reloads without storing one. */
|
||||
function avatarStyle(seed) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < seed.length; i++) hash = (hash * 31 + seed.charCodeAt(i)) | 0;
|
||||
const hue = Math.abs(hash) % 360;
|
||||
return `background: oklch(62% 0.17 ${hue}); color: oklch(100% 0 0);`;
|
||||
}
|
||||
|
||||
async function copyText(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@@ -599,7 +590,6 @@ function acctRow(a) {
|
||||
ring.dataset.ring = a.id;
|
||||
|
||||
const avatar = h('div.avatar', { text: initials(a.issuer || a.label) });
|
||||
avatar.setAttribute('style', avatarStyle(a.issuer || a.label || a.id));
|
||||
|
||||
const pushDot = h('span.push-dot', {
|
||||
title: pushTitle(a),
|
||||
@@ -1110,7 +1100,6 @@ function openAddSheet() {
|
||||
|
||||
const list = h('div.import-list', {}, rows.map((r, i) => {
|
||||
const av = h('div.avatar', { text: initials(r.acct.issuer || r.acct.label) });
|
||||
av.setAttribute('style', avatarStyle(r.acct.issuer || r.acct.label || String(i)));
|
||||
return h('label.import-row', {}, [
|
||||
boxes[i],
|
||||
av,
|
||||
|
||||
+63
-31
@@ -1,73 +1,105 @@
|
||||
# Publishing to Gitea
|
||||
|
||||
Everything here needs a Gitea **personal access token**, which is why it isn't
|
||||
automated: the token should only ever exist in your own shell.
|
||||
Releases are cut by hand. Each one needs a Gitea **personal access token**
|
||||
with the `write:repository` and `write:package` scopes, which is why none of
|
||||
this is automated: the token should only ever exist in your own shell or
|
||||
keychain.
|
||||
|
||||
Create one at **Gitea → Settings → Applications → Generate New Token** with
|
||||
scopes:
|
||||
|
||||
- `write:repository` — to create the repo and push
|
||||
- `write:package` — to push the container image
|
||||
|
||||
Export it once per shell session:
|
||||
On a Mac that has pushed before, git already holds the token in the login
|
||||
keychain and nothing needs exporting. Elsewhere, create one at **Gitea →
|
||||
Settings → Applications → Generate New Token** and export it once per shell
|
||||
session:
|
||||
|
||||
```bash
|
||||
export GITEA_TOKEN='<paste-your-token>'
|
||||
```
|
||||
|
||||
## 1. Create the repository
|
||||
## 1. Cut the version
|
||||
|
||||
Bump `version` in the workspace `Cargo.toml`, update the `IMAGE_TAG` and
|
||||
`GIT_REF` defaults in `scripts/build-and-publish-unraid.sh`, add a line to the
|
||||
README changelog, and commit. Then tag:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "https://gitea.milesward.dev/api/v1/user/repos" -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" -d '{"name":"OpenMFA","description":"Self-hosted TOTP authenticator with live delivery to Discord, Slack or Telegram. Container-native Rust.","private":false,"auto_init":false}'
|
||||
git tag -a v0.1.1 -m "OpenMFA v0.1.1"
|
||||
```
|
||||
|
||||
The repo is public so Unraid can pull the image anonymously without registry
|
||||
credentials; the Gitea instance itself is not exposed beyond the LAN.
|
||||
Tags are `vMAJOR.MINOR[.PATCH]` — `v0.1`, `v0.1.1`, … — and the image carries
|
||||
the same number without the `v` (`0.1`, `0.1.1`) plus a moving `latest`.
|
||||
|
||||
## 2. Push the code and the tag
|
||||
|
||||
```bash
|
||||
cd "/Users/casperadmin/OpenMFA" && git -c "http.extraHeader=Authorization: token $GITEA_TOKEN" push -u origin main --follow-tags
|
||||
git push origin main --follow-tags
|
||||
```
|
||||
|
||||
That pushes `main` and the `v0.1` tag together.
|
||||
With a token in the environment rather than the keychain, prefix the command
|
||||
with `git -c "http.extraHeader=Authorization: token $GITEA_TOKEN"`.
|
||||
|
||||
## 3. Push the container image
|
||||
## 3. Build the image
|
||||
|
||||
The image is already built and tagged locally as
|
||||
`gitea.milesward.dev/mward4/openmfa:0.1` and `:latest`.
|
||||
The Dockerfile cross-compiles to `x86_64-unknown-linux-musl` from whatever
|
||||
architecture the builder runs on, so this is fast on Apple Silicon — no QEMU.
|
||||
`--provenance=false --sbom=false` matters: Unraid's Docker cannot load the
|
||||
attestation manifests BuildKit attaches by default.
|
||||
|
||||
```bash
|
||||
printf '%s' "$GITEA_TOKEN" | docker login gitea.milesward.dev --username mward4 --password-stdin && docker push gitea.milesward.dev/mward4/openmfa:0.1 && docker push gitea.milesward.dev/mward4/openmfa:latest && docker logout gitea.milesward.dev
|
||||
docker build --pull --platform linux/amd64 --provenance=false --sbom=false -f deploy/docker/Dockerfile -t gitea.milesward.dev/mward4/openmfa:0.1.1 -t gitea.milesward.dev/mward4/openmfa:latest .
|
||||
```
|
||||
|
||||
If the image isn't built (a fresh machine, say), `scripts/build-and-publish-unraid.sh`
|
||||
does the clone, build, login and push in one pass:
|
||||
## 4. Push the image
|
||||
|
||||
**Never run `docker login` or `docker logout` for `gitea.milesward.dev` on a
|
||||
Mac where git keeps the token in the keychain.** Docker's `osxkeychain`
|
||||
credential helper maps to the same keychain item as git's: `login` fails with
|
||||
"The specified item already exists in the keychain (-25299)", and `logout`
|
||||
silently deletes the credential git was using, so the next `git push` fails
|
||||
with a bare 401. Write the auth into a throwaway config instead — a config
|
||||
that already contains an `auths` entry makes the CLI skip the helper:
|
||||
|
||||
```bash
|
||||
export DOCKER_CONFIG="$(mktemp -d)" && printf '{"auths":{"gitea.milesward.dev":{"auth":"%s"}}}' "$(printf 'mward4:%s' "$GITEA_TOKEN" | base64 | tr -d '\n')" > "$DOCKER_CONFIG/config.json" && docker push gitea.milesward.dev/mward4/openmfa:0.1.1 && docker push gitea.milesward.dev/mward4/openmfa:latest; rm -rf "$DOCKER_CONFIG"; unset DOCKER_CONFIG
|
||||
```
|
||||
|
||||
On a Colima host, `export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"`
|
||||
first: an isolated `DOCKER_CONFIG` has no `currentContext`, and the CLI would
|
||||
otherwise look for `/var/run/docker.sock`.
|
||||
|
||||
On a fresh machine, `scripts/build-and-publish-unraid.sh` does the clone,
|
||||
build and push in one pass using that same private-config approach:
|
||||
|
||||
```bash
|
||||
GITEA_TOKEN="$GITEA_TOKEN" ./scripts/build-and-publish-unraid.sh
|
||||
```
|
||||
|
||||
## 4. Deploy on Unraid
|
||||
## 5. Verify the pull
|
||||
|
||||
The repo is public, so this needs no credentials — which is exactly how Unraid
|
||||
will fetch it:
|
||||
|
||||
```bash
|
||||
docker pull gitea.milesward.dev/mward4/openmfa:0.1.1
|
||||
```
|
||||
|
||||
## 6. Deploy on Unraid
|
||||
|
||||
Add a container with Repository set to:
|
||||
|
||||
```
|
||||
gitea.milesward.dev/mward4/openmfa:0.1
|
||||
gitea.milesward.dev/mward4/openmfa:0.1.1
|
||||
```
|
||||
|
||||
Since the package is in a private registry, add the Gitea credentials under
|
||||
Unraid's Docker → Registries first, or the pull will 401.
|
||||
|
||||
Map `/var/lib/openmfa` to `/mnt/user/appdata/openmfa`,
|
||||
publish port `8720`, and leave networking on bridge.
|
||||
|
||||
Or import `deploy/unraid/openmfa.xml` into
|
||||
(or `:latest`). Map `/var/lib/openmfa` to `/mnt/user/appdata/openmfa`,
|
||||
publish port `8720`, and leave networking on bridge. Or import
|
||||
`deploy/unraid/openmfa.xml` into
|
||||
`/boot/config/plugins/dockerMan/templates-user/` to get all of that prefilled.
|
||||
|
||||
To upgrade an existing container, change the tag and apply. The vault, key,
|
||||
settings and admin account all live in the appdata mount and carry over.
|
||||
|
||||
## Afterwards
|
||||
|
||||
Confirm the token isn't left lying around in your shell history:
|
||||
If a token was exported, make sure it is not left in the shell:
|
||||
|
||||
```bash
|
||||
unset GITEA_TOKEN
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# GITEA_OWNER default: mward4
|
||||
# GITEA_REPO default: OpenMFA
|
||||
# IMAGE_NAME default: openmfa
|
||||
# IMAGE_TAG default: 0.1.0 (also pushed as :latest)
|
||||
# IMAGE_TAG default: 0.1.1 (also pushed as :latest)
|
||||
# PLATFORM default: linux/amd64 (Unraid is x86_64)
|
||||
# GIT_REF default: v0.1 (tag/branch to build)
|
||||
# GIT_REF default: v0.1.1 (tag/branch to build)
|
||||
# WORKDIR default: /tmp/openmfa-build (removed on exit)
|
||||
#
|
||||
# After this, set the Unraid template's Repository to:
|
||||
@@ -27,9 +27,9 @@ GITEA_HOST=${GITEA_HOST:-gitea.milesward.dev}
|
||||
GITEA_OWNER=${GITEA_OWNER:-mward4}
|
||||
GITEA_REPO=${GITEA_REPO:-OpenMFA}
|
||||
IMAGE_NAME=${IMAGE_NAME:-openmfa}
|
||||
IMAGE_TAG=${IMAGE_TAG:-0.1.0}
|
||||
IMAGE_TAG=${IMAGE_TAG:-0.1.1}
|
||||
PLATFORM=${PLATFORM:-linux/amd64}
|
||||
GIT_REF=${GIT_REF:-v0.1}
|
||||
GIT_REF=${GIT_REF:-v0.1.1}
|
||||
WORKDIR=${WORKDIR:-/tmp/openmfa-build}
|
||||
|
||||
# OCI distribution rejects uppercase repository paths, so the image name is
|
||||
@@ -51,10 +51,23 @@ done
|
||||
|
||||
# A private docker config so the token never lands in ~/.docker/config.json.
|
||||
# The trap wipes it (and the checkout) on any exit path.
|
||||
#
|
||||
# The auth is written into the file directly rather than via `docker login`.
|
||||
# With an empty config the CLI falls back to the platform credential helper,
|
||||
# and on macOS `docker-credential-osxkeychain` shares its keychain item with
|
||||
# git's `osxkeychain` helper: `login` then fails with -25299 if git already
|
||||
# stored the token, and `logout` deletes the credential git was using. A
|
||||
# config that already contains an `auths` entry skips the helper entirely.
|
||||
DOCKER_CONFIG="$(mktemp -d)"
|
||||
export DOCKER_CONFIG
|
||||
trap 'rm -rf "$DOCKER_CONFIG" "$WORKDIR"' EXIT
|
||||
|
||||
# Colima keeps its socket outside /var/run; an isolated DOCKER_CONFIG has no
|
||||
# `currentContext`, so point at the daemon explicitly when one is not set.
|
||||
if [[ -z "${DOCKER_HOST:-}" && -S "$HOME/.colima/default/docker.sock" ]]; then
|
||||
export DOCKER_HOST="unix://$HOME/.colima/default/docker.sock"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==> Workspace : $WORKDIR"
|
||||
echo "==> Gitea host : $GITEA_HOST"
|
||||
@@ -84,18 +97,15 @@ docker build \
|
||||
--tag "$LATEST_TAG" \
|
||||
.
|
||||
|
||||
# Step 3: log in. --password-stdin keeps the token out of `ps` and shell history.
|
||||
printf '%s' "$GITEA_TOKEN" | docker login \
|
||||
--username "$GITEA_OWNER" \
|
||||
--password-stdin \
|
||||
"$GITEA_HOST"
|
||||
# Step 3: registry auth, written straight into the private config (see above).
|
||||
printf '{"auths":{"%s":{"auth":"%s"}}}' "$GITEA_HOST" \
|
||||
"$(printf '%s:%s' "$GITEA_OWNER" "$GITEA_TOKEN" | base64 | tr -d '\n')" \
|
||||
> "$DOCKER_CONFIG/config.json"
|
||||
|
||||
# Step 4: push both tags.
|
||||
# Step 4: push both tags. The trap removes the config, so there is no logout.
|
||||
docker push "$FULL_TAG"
|
||||
docker push "$LATEST_TAG"
|
||||
|
||||
docker logout "$GITEA_HOST" || true
|
||||
|
||||
echo
|
||||
echo "==> Done. Pull from an Unraid template with:"
|
||||
echo " $FULL_TAG"
|
||||
|
||||
Reference in new issue
Block a user