11 Commits
Author SHA1 Message Date
jcoffey-dev 0191f7e10c Merge pull request 'Write the name in lowercase where people see it' (#11) from fix/brand-lowercase into main
ci / build (push) Successful in 1m23s
ci / publish (push) Skipped
2026-09-22 22:21:02 +00:00
jcoffey-dev 7e3418079c Write the name in lowercase where people see it
ci / build (pull_request) Successful in 1m1s
ci / publish (pull_request) Skipped
The brand is lowercase inbuxa. This changes the setup wizard's welcome and
completion text, the logo's accessible name, the version line in the top bar,
the document title, the legacy-protocols screens and banner, the protocol
table's JMAP row, one help text, and the issuer new authenticator enrollments
are labeled with.

Keys are ids here, not English text, so nothing moved: each catalog value and
its inline fallback changed together. This console ships English only, so
there is no other catalog to follow. Comments, identifiers and env var names
are untouched.
2026-09-22 15:19:25 -07:00
jcoffey-dev 0ea86b6be0 Merge pull request 'Point links at git.coffeylabs.org after the move from GitHub' (#10) from fix/links-after-move into main
ci / build (push) Successful in 1m4s
ci / publish (push) Skipped
2026-09-22 16:11:23 +00:00
jcoffey-dev 3e7bb80f73 Point links at git.coffeylabs.org after the move from GitHub
ci / build (pull_request) Successful in 1m6s
ci / publish (pull_request) Skipped
GitHub took the organization's repos and GHCR offline on 2026-09-20. Repo,
release, raw-file and clone links now go to Gitea at git.coffeylabs.org,
container images to registry.coffeylabs.org, and GitLab-style /-/blob paths
to Gitea's /src/branch form. Go module paths are identifiers and stay as
they are; links to GitHub issues and pull requests are left as history.
2026-09-22 09:08:22 -07:00
jcoffey-dev 4eb838042c Merge pull request 'ci: run socket-free jobs on the light label' (#9) from ci/light-runner into main
ci / build (push) Successful in 1m6s
ci / publish (push) Skipped
2026-09-22 13:54:26 +00:00
jcoffey-dev 14ccb05e52 ci: run socket-free jobs on the light label
ci / build (pull_request) Successful in 1m55s
ci / publish (pull_request) Skipped
Both runners carry `light` (host1, and host2 over the wg-hosts link), so
these jobs run on whichever host is free. Jobs that mount the docker socket
keep `runs-on: docker`, which only host1 has.
2026-09-22 06:45:44 -07:00
jcoffey-dev 7d5f2273ae Merge branch 'ci/gitea-actions' into 'main'
ci / build (push) Successful in 4m23s
ci / publish (push) Skipped
ci: add Gitea Actions workflow

See merge request inbuxa/inbuxa-admin!8
2026-09-22 00:00:36 -07:00
jcoffey-dev de58de765a ci: fail clearly when PACKAGE_TOKEN is missing
ci / build (pull_request) Successful in 6m2s
ci / publish (pull_request) Skipped
ci / build (push) Successful in 3m14s
ci / publish (push) Failing after 46s
2026-09-21 23:20:50 -07:00
jcoffey-dev 1be7b5c60b ci: add Gitea Actions workflows ported from .gitlab-ci.yml 2026-09-21 22:52:11 -07:00
jcoffey-dev 2cd73c6705 Merge branch 'ci/registry-token-host' into 'main'
Fetch the registry token from the public address, not the runner's

See merge request inbuxa/inbuxa-admin!7
2026-09-21 18:20:30 -07:00
jcoffey-dev 7a455e7b6a Fetch the registry token from the public address, not the runner's
The builder on the host's network (the last change here) didn't help: the
next publish failed exactly as before. Looking on the host showed why.
Both builders resolve git.coffeylabs.org publicly; the token isn't fetched
by the builder at all. buildx fetches registry tokens on the client side,
in the job container, and on ci-net the name git.coffeylabs.org belongs to
the gitlab container itself (172.30.0.2) -- which is how the runner clones
over plain HTTP, and which has nothing on 443. So every push asked
https://git.coffeylabs.org/jwt/auth for a token and was refused. The login
before it worked because the host's daemon does the login, and the host
resolves the name publicly.

For the publish job only, the name now points at its public address in the
job's /etc/hosts, looked up from a public resolver, as the host sees it.
/etc/hosts wins over Docker's DNS, and nothing else in the job is affected:
the checkout is done, and image layers go to the registry's own DNS-only
name, not this one. The builder goes back to the shared ci-builder; its
network was never the problem.

The lookup and the /etc/hosts write were tried in the job's own image
(docker:28-cli, same digest): it picks the first public IPv4 address and
getent then returns it.
2026-09-21 16:48:43 -07:00
17 changed files with 303 additions and 37 deletions
+127
View File
@@ -0,0 +1,127 @@
# CI on the self-hosted Gitea, ported from .gitlab-ci.yml during the move off
# GitLab (2026-09-22). Gitea reads .gitea/workflows and ignores .github/ once
# this directory exists; .github/workflows stays as it was for GitHub.
#
# Every job runs in an image pinned by digest (tag in the trailing comment),
# and the only action used is coffey-labs/actions/checkout pinned by SHA. The
# instance resolves short `uses:` against itself, never GitHub, so nothing
# unreviewed can be pulled in. Read the comment for the version; the digest is
# what runs.
#
# Jobs run on the runner's `ci-net` network and clone from Gitea's internal
# address, never through the Cloudflare-proxied public name, which caps
# request bodies at 100 MB. Images go to the registry's own DNS-only name
# (vars.REGISTRY, an org variable).
#
# The weekly release is its own workflow, weekly-release.yml.
#
# Not ported:
# * cleanup.yml pruned GHCR with dataaxiom/ghcr-cleanup-action; on Gitea
# that belongs in the package cleanup rules (owner settings -> Packages),
# not in a workflow.
name: ci
on:
push:
branches: [main]
# Only date tags publish (v2026.9.21, v2026.9.21.2). The repository still
# carries the inherited v1.0.x tags, and a tag of any other shape pushed
# by hand is not a release.
tags:
- 'v[0-9][0-9][0-9][0-9].[0-9]+.[0-9]+'
- 'v[0-9][0-9][0-9][0-9].[0-9]+.[0-9]+.[0-9]+'
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# A release tag is built and tested again before its image is published.
build:
runs-on: light
container:
image: node:22-bookworm-slim@sha256:48e4b67d85f87bd551df43704e24d252f56cc5f8e9718841aace50f19948f0f9 # 22-bookworm-slim
env:
NPM_CONFIG_CACHE: ${{ github.workspace }}/.npm
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
- run: npm ci --ignore-scripts
- run: npm run typecheck
- run: npm run lint
- run: npm test
- run: npm run build
# ----------------------------------------------------------- publish ------
# Port of publish.yml, to the owner's own registry now that GHCR went with
# the GitHub account: <REGISTRY>/inbuxa/inbuxa-admin, the same path the
# GitLab registry used.
#
# Tag-driven. A release cut with the job's own token raises no event on
# Gitea (as on GitHub), so weekly-release.yml creates its release with
# RELEASE_TOKEN; the tag that makes is an ordinary push, and starts this.
#
# The tag must agree with inbuxa-version.json at the commit it names -- the
# property release.yml was built around: the tree a tag points at reports
# the version the tag claims. A tag placed beside an unbumped file fails
# here rather than publishing an image that reports the wrong version.
#
# Both architectures build under QEMU on this amd64 host, where publish.yml
# had a native arm64 runner. That is slow -- tens of minutes for npm ci and
# the Vite build through instruction translation -- and tolerable for a
# weekly tag, which is why this is tag-only. If arm64 starts timing out, the
# fix is an arm64 runner, not dropping the platform.
#
# The push logs in with PACKAGE_TOKEN (jcoffey-dev, write:package): Gitea's
# per-job token is refused by the container registry. The registry hands out
# its push tokens from its own name, so unlike on GitLab nothing here has to
# be pointed at a public address.
publish:
if: ${{ startsWith(github.ref, 'refs/tags/') }}
needs: [build]
runs-on: docker
container:
image: docker:28-cli@sha256:625d9431a9f54c5a2bc90f24f0e1c3d55b1349fd857dd85035f98c2c9acbdd4d # 28-cli
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env:
DOCKER_BUILDKIT: "1"
REGISTRY: ${{ vars.REGISTRY }}
IMAGE: ${{ vars.REGISTRY }}/${{ github.repository }}
PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
- run: |
set -eu
apk add --no-cache -q jq curl
VERSION="$(jq -er .version inbuxa-version.json)"
if [ "$GITHUB_REF_NAME" != "v$VERSION" ]; then
echo "Tag $GITHUB_REF_NAME names a commit whose inbuxa-version.json says $VERSION." >&2
echo "Refusing to publish an image that would report the wrong version." >&2
exit 1
fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
- run: |
test -n "$REGISTRY"
test -n "$PACKAGE_TOKEN" || { echo "PACKAGE_TOKEN secret is not set on this repository" >&2; exit 1; }
echo "$PACKAGE_TOKEN" | docker login -u jcoffey-dev --password-stdin "$REGISTRY"
docker run --privileged --rm tonistiigi/binfmt --install arm64
docker buildx create --use --name gitea-builder --driver docker-container || docker buildx use gitea-builder
# Attestations are off, as they were in publish.yml: they add manifests
# of their own to the index.
- run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--provenance=false --sbom=false \
--tag "$IMAGE:$VERSION" \
--tag "$IMAGE:latest" \
--push .
docker buildx imagetools inspect "$IMAGE:$VERSION"
# Gitea keeps a container package on its owner; linking it shows it on
# the repository's Packages tab. Idempotent.
- run: |
curl -fsS -o /dev/null -X POST -H "Authorization: token $PACKAGE_TOKEN" \
"$CI_SERVER_INTERNAL/api/v1/packages/${GITHUB_REPOSITORY%%/*}/container/${GITHUB_REPOSITORY#*/}/-/link/${GITHUB_REPOSITORY#*/}" \
|| echo "package already linked (or link refused); not fatal"
- if: always()
run: docker logout "$REGISTRY" || true
+135
View File
@@ -0,0 +1,135 @@
# Weekly release, ported from the weekly-release job in .gitlab-ci.yml (itself
# a port of release.yml): cut a release once a week, but only if there is
# something in it. The decision is unchanged -- count the commits on main
# since the newest published release, and skip the week if there are none. A
# release with nothing in it moves :latest to an identical build, spends a
# version number, and notifies everybody about nothing.
#
# The version is the date, YYYY.M.D unpadded, with a .N suffix from 2 for a
# second release on one day. It is committed to main in inbuxa-version.json
# and the tag names that commit, so the commit is the release.
#
# Mondays 09:37 UTC, as release.yml did. Run it by hand from the Actions tab
# (workflow_dispatch); dry_run defaults to true, so a manual run shows the
# decision and stops unless you untick it.
#
# SIDE-BY-SIDE PERIOD: until the GitLab cutover, GitLab's own schedule is
# still live and still cuts the real release, and its bump commit and tag
# reach this copy through the sync. Two releasers would race to write the same
# version, so this workflow only ever dry-runs unless the variable
# RELEASE_LIVE is '1'. Set RELEASE_LIVE=1 (repo or org Actions variable) at
# cutover, when GitLab's schedule is switched off -- not before.
#
# Reads use the job's own token. Everything that writes uses RELEASE_TOKEN
# (jcoffey-dev, write:repository), because a tag Gitea creates for the job
# token raises no event (checked 2026-09-22) and the tag must start ci.yml's
# publish job:
# * the bump is committed through the contents API. Gitea's API has no
# "only if the branch is still at X" guard like GitLab's last_commit_id,
# so the job checks main's head immediately before writing and refuses if
# it moved since the commit it counted from; run it again. Otherwise the
# notes and the count would describe a different commit from the one
# released. (The API does refuse if the file itself changed, via its blob
# sha.)
# * the release -- and with it the tag -- is created through the releases
# API. A tag made that way is an ordinary push, so it starts ci.yml and
# `publish` builds the image.
# The token's owner must be allowed to push to main.
name: weekly-release
on:
schedule:
- cron: '37 9 * * 1'
workflow_dispatch:
inputs:
dry_run:
description: Show the decision and stop
type: boolean
default: true
# One at a time: two overlapping runs would race to write the same version and
# create the same tag.
concurrency:
group: weekly-release
cancel-in-progress: false
jobs:
weekly-release:
runs-on: light
container:
image: node:22-bookworm-slim@sha256:48e4b67d85f87bd551df43704e24d252f56cc5f8e9718841aace50f19948f0f9 # 22-bookworm-slim
env:
READ_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
# Live only with RELEASE_LIVE=1 AND either the schedule or a manual run
# with dry_run unticked.
DRY_RUN: ${{ (vars.RELEASE_LIVE == '1' && (github.event_name == 'schedule' || inputs.dry_run == false || inputs.dry_run == 'false')) && '0' || '1' }}
steps:
- uses: coffey-labs/actions/checkout@fab0c4d45e0162963965f1555df27b7bed5e20ec
with:
fetch-depth: 0
- run: apt-get update -qq && apt-get install -y -qq --no-install-recommends curl jq ca-certificates >/dev/null
- shell: bash
run: |
set -euo pipefail
API="${CI_SERVER_INTERNAL}/api/v1/repos/${GITHUB_REPOSITORY}"
sha="$(git rev-parse HEAD)"
# The newest published release, or empty on a project that has never
# had one -- in which case everything counts as new.
previous="$(curl -fsS -H "Authorization: token ${READ_TOKEN}" "${API}/releases?draft=false&pre-release=false&limit=1" | jq -r '.[0].tag_name // ""')"
# A release can outlive its tag; falling back to the whole history
# over-counts, which cuts a release that was due anyway. Tag lookups
# use show-ref, which matches an exact ref: rev-parse --verify on this
# git can read some tag names as describe output and "find" a tag
# that isn't there (see ihasmail's port).
if [ -n "$previous" ] && git show-ref --verify --quiet "refs/tags/${previous}"; then
count="$(git rev-list --count "${previous}..HEAD")"; range="${previous}..HEAD"
else
count="$(git rev-list --count HEAD)"; range="HEAD"
fi
if [ "$count" -eq 0 ]; then
echo "Nothing to release: no commits since ${previous}."; exit 0
fi
today="$(date -u +%Y.%-m.%-d)"
version="$today"; n=2
while git show-ref --verify --quiet "refs/tags/v${version}"; do
version="${today}.${n}"; n=$((n + 1))
done
tag="v${version}"
echo "Releasing ${tag} -- ${count} commit(s) since ${previous:-the beginning}, from ${sha}."
if [ "$DRY_RUN" = "1" ]; then echo "Dry run (RELEASE_LIVE='${{ vars.RELEASE_LIVE }}'): stopping here."; exit 0; fi
auth=(-H "Authorization: token ${RELEASE_TOKEN}")
# The bump, written with a JSON parser rather than sed: a version put
# into JSON by string substitution is one stray quote from a file
# nothing can read.
VERSION="$version" node -e '
const fs = require("fs");
const f = "inbuxa-version.json";
const j = JSON.parse(fs.readFileSync(f, "utf8"));
j.version = process.env.VERSION;
fs.writeFileSync(f, JSON.stringify(j, null, 2) + "\n");
'
head="$(curl -fsS "${auth[@]}" "${API}/branches/main" | jq -er .commit.id)"
if [ "$head" != "$sha" ]; then
echo "main moved from ${sha} to ${head} since this run counted; run it again." >&2
exit 1
fi
blob="$(curl -fsS "${auth[@]}" "${API}/contents/inbuxa-version.json?ref=${sha}" | jq -er .sha)"
jq -n --arg msg "Version ${version}" --arg blob "$blob" \
--arg content "$(base64 -w0 inbuxa-version.json)" \
'{branch:"main", message:$msg, sha:$blob, content:$content}' > commit.json
bump="$(curl -fsS "${auth[@]}" -X PUT -H "Content-Type: application/json" \
--data @commit.json "${API}/contents/inbuxa-version.json" | jq -er .commit.sha)"
echo "committed the bump as ${bump}"
# Notes bounded to what is new: one line per change on main's
# first-parent history, which is what GitHub's generated notes listed.
notes="$(git log --first-parent --format='- %s' "$range")"
jq -n --arg tag "$tag" --arg ref "$bump" --arg name "INBUXA Admin ${version}" \
--arg body "$(printf '%s commit(s) since %s.\n\n%s' "$count" "${previous:-the beginning}" "$notes")" \
'{tag_name:$tag, target_commitish:$ref, name:$name, body:$body}' > release.json
curl -fsS "${auth[@]}" -H "Content-Type: application/json" \
--data @release.json "${API}/releases" | jq -r '"created release " + .tag_name'
+14 -10
View File
@@ -87,16 +87,20 @@ publish:
echo "VERSION=$VERSION" > version.env echo "VERSION=$VERSION" > version.env
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
- docker run --privileged --rm tonistiigi/binfmt --install arm64 - docker run --privileged --rm tonistiigi/binfmt --install arm64
# The builder runs as a container on the host's daemon and does the push # The registry hands out push tokens from https://git.coffeylabs.org/jwt/auth,
# itself, including fetching a registry token from git.coffeylabs.org. # and buildx fetches them here, in the job, not in its builder. On ci-net
# On the runner's network that name resolves to an internal address # that name is the gitlab container itself (172.30.0.2), which serves
# (172.30.0.2) with nothing on 443, so the token request was refused and # plain HTTP to the runner and nothing on 443, so every push failed at the
# every push failed at the last step -- here and in ihasmail alike. On the # last step with "connection refused". The login above works because the
# host's network the name resolves as it does for `docker login` above. # host's daemon does it, and the host resolves the name publicly. So, for
# Only the token request uses it; layers still go to the registry's own # this job only, point the name at its public address the same way. Only
# DNS-only name. A new name, because `ci-builder` is a long-lived container # the token request uses it; layers go to the registry's own DNS-only name.
# shared between jobs and would keep whatever network it was created on. - |
- docker buildx create --use --name ci-builder-host --driver docker-container --driver-opt network=host || docker buildx use ci-builder-host public="$(nslookup "$CI_SERVER_HOST" 1.1.1.1 2>/dev/null | awk '/^Address: / && $2 !~ /:/ { print $2; exit }')"
if [ -z "$public" ]; then echo "Could not resolve $CI_SERVER_HOST publicly" >&2; exit 1; fi
echo "$public $CI_SERVER_HOST" >> /etc/hosts
echo "$CI_SERVER_HOST -> $public for the registry token"
- docker buildx create --use --name ci-builder --driver docker-container || docker buildx use ci-builder
script: script:
- . ./version.env - . ./version.env
# Attestations are off, as they were in publish.yml: they add manifests of # Attestations are off, as they were in publish.yml: they add manifests of
+1 -1
View File
@@ -32,6 +32,6 @@ session.
You'll get an acknowledgement within a few days. A report that turns out to You'll get an acknowledgement within a few days. A report that turns out to
affect the mail server rather than this interface will be moved to affect the mail server rather than this interface will be moved to
[inbuxa-server](https://github.com/inbuxa/inbuxa-server), and one that affects [inbuxa-server](https://git.coffeylabs.org/inbuxa/inbuxa-server), and one that affects
upstream Stalwart's web interface will be passed to Stalwart Labs with credit upstream Stalwart's web interface will be passed to Stalwart Labs with credit
to you. to you.
+3 -3
View File
@@ -336,7 +336,7 @@ export function BootstrapWizard() {
<WizardShell> <WizardShell>
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h2 className="text-2xl font-semibold tracking-tight">{t('bootstrap.welcome', 'Welcome to INBUXA')}</h2> <h2 className="text-2xl font-semibold tracking-tight">{t('bootstrap.welcome', 'Welcome to inbuxa')}</h2>
<p className="text-sm text-muted-foreground mt-1"> <p className="text-sm text-muted-foreground mt-1">
{t('bootstrap.welcomeSubtitle', "Let's get your server set up.")} {t('bootstrap.welcomeSubtitle', "Let's get your server set up.")}
</p> </p>
@@ -471,7 +471,7 @@ function SuccessScreen({
'bootstrap.credentialsCreated', 'bootstrap.credentialsCreated',
'Your administrator account has been created. Write these down now: the password will not be shown again.', 'Your administrator account has been created. Write these down now: the password will not be shown again.',
) )
: t('bootstrap.configuredSuccessfully', 'INBUXA has been configured successfully.')} : t('bootstrap.configuredSuccessfully', 'inbuxa has been configured successfully.')}
</p> </p>
</div> </div>
</div> </div>
@@ -503,7 +503,7 @@ function SuccessScreen({
<span className="font-medium">{t('bootstrap.nextStepLabel', 'Next step:')}</span>{' '} <span className="font-medium">{t('bootstrap.nextStepLabel', 'Next step:')}</span>{' '}
{t( {t(
'bootstrap.nextStepBody', 'bootstrap.nextStepBody',
'restart INBUXA for the new configuration to take effect. Once restarted, sign in with the credentials above to continue administering your server.', 'restart inbuxa for the new configuration to take effect. Once restarted, sign in with the credentials above to continue administering your server.',
)} )}
</p> </p>
</div> </div>
+1 -1
View File
@@ -20,7 +20,7 @@ export function DefaultLogo() {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="165 35 616 130" viewBox="165 35 616 130"
aria-label={t('logo.inbuxaAlt', 'INBUXA')} aria-label={t('logo.inbuxaAlt', 'inbuxa')}
className="h-7 w-auto max-w-[320px]" className="h-7 w-auto max-w-[320px]"
> >
<image x="165.85" y="35.00" width="109.39" height="130.00" href={inbuxaMark} /> <image x="165.85" y="35.00" width="109.39" height="130.00" href={inbuxaMark} />
+1 -1
View File
@@ -38,7 +38,7 @@ function buildOtpAuthUrl(totp: OTPAuth.TOTP): string {
function generateTotp(): { totp: OTPAuth.TOTP; url: string } { function generateTotp(): { totp: OTPAuth.TOTP; url: string } {
const totp = new OTPAuth.TOTP({ const totp = new OTPAuth.TOTP({
issuer: 'INBUXA', issuer: 'inbuxa',
label: 'account', label: 'account',
algorithm: 'SHA1', algorithm: 'SHA1',
digits: 6, digits: 6,
+1 -1
View File
@@ -104,7 +104,7 @@ export function TopBar() {
</Link> </Link>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="bottom"> <TooltipContent side="bottom">
{t('version.label', 'INBUXA Admin {{version}}', { version: __APP_VERSION__ })} {t('version.label', 'inbuxa Admin {{version}}', { version: __APP_VERSION__ })}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -62,10 +62,10 @@ export function LegacyProtocolsBanner() {
{t('legacyProtocols.bannerLead', 'Legacy mail protocols are')}{' '} {t('legacyProtocols.bannerLead', 'Legacy mail protocols are')}{' '}
<strong>{t('legacyProtocols.bannerOff', 'off')}</strong>{' '} <strong>{t('legacyProtocols.bannerOff', 'off')}</strong>{' '}
{off === 'server' {off === 'server'
? t('legacyProtocols.bannerTail', 'on this server. Only INBUXA webmail and JMAP apps can sign in.') ? t('legacyProtocols.bannerTail', 'on this server. Only inbuxa webmail and JMAP apps can sign in.')
: t( : t(
'legacyProtocols.bannerTailTenant', 'legacyProtocols.bannerTailTenant',
'for your organization. Only INBUXA webmail and JMAP apps can sign in.', 'for your organization. Only inbuxa webmail and JMAP apps can sign in.',
)} )}
</span> </span>
{off === 'server' && ( {off === 'server' && (
@@ -115,7 +115,7 @@ export function LegacyProtocolsPage() {
<p className="text-muted-foreground"> <p className="text-muted-foreground">
{t( {t(
'legacyProtocols.subtitle', 'legacyProtocols.subtitle',
'Turn off IMAP, POP3, ManageSieve and sending from mail apps, so that only INBUXA webmail and JMAP apps can reach this server.', 'Turn off IMAP, POP3, ManageSieve and sending from mail apps, so that only inbuxa webmail and JMAP apps can reach this server.',
)} )}
</p> </p>
</header> </header>
@@ -206,7 +206,7 @@ function StatusCard({
</p> </p>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{off {off
? t('legacyProtocols.statusOffBody', 'Only INBUXA webmail and JMAP apps can sign in.') ? t('legacyProtocols.statusOffBody', 'Only inbuxa webmail and JMAP apps can sign in.')
: t('legacyProtocols.statusOnBody', 'Mail apps can use IMAP, POP3 and ManageSieve.')} : t('legacyProtocols.statusOnBody', 'Mail apps can use IMAP, POP3 and ManageSieve.')}
{policy.changedAt !== null && ( {policy.changedAt !== null && (
<> <>
@@ -276,7 +276,7 @@ function ProtocolTable({ rows, off }: { rows: ProtocolRow[]; off: boolean }) {
<p className="border-t bg-muted/30 px-4 py-2 text-xs text-muted-foreground"> <p className="border-t bg-muted/30 px-4 py-2 text-xs text-muted-foreground">
{t( {t(
'legacyProtocols.lockNote', 'legacyProtocols.lockNote',
'Incoming mail (SMTP) and INBUXA webmail (JMAP) are locked open: closing them would stop mail arriving and lock everyone out, including you.', 'Incoming mail (SMTP) and inbuxa webmail (JMAP) are locked open: closing them would stop mail arriving and lock everyone out, including you.',
)} )}
</p> </p>
</div> </div>
@@ -102,7 +102,7 @@ export function TenantLegacyProtocols({ tenantId }: { tenantId: string }) {
{off {off
? t( ? t(
'legacyProtocols.tenantOff', 'legacyProtocols.tenantOff',
'Off for {{organization}}. Only INBUXA webmail and JMAP apps can sign in to its domains.', 'Off for {{organization}}. Only inbuxa webmail and JMAP apps can sign in to its domains.',
{ organization: name }, { organization: name },
) )
: t( : t(
+5 -5
View File
@@ -128,7 +128,7 @@ export function Statement({ scope }: { scope: StatementScope }) {
return ( return (
<section className="space-y-3 rounded-xl border border-amber-500/40 bg-amber-500/5 p-5 text-sm leading-relaxed"> <section className="space-y-3 rounded-xl border border-amber-500/40 bg-amber-500/5 p-5 text-sm leading-relaxed">
<p className="text-base font-semibold"> <p className="text-base font-semibold">
{t('legacyProtocols.statementTitle', 'Only INBUXA webmail and JMAP apps will work.')} {t('legacyProtocols.statementTitle', 'Only inbuxa webmail and JMAP apps will work.')}
</p> </p>
<p> <p>
{scope.kind === 'server' {scope.kind === 'server'
@@ -152,7 +152,7 @@ export function Statement({ scope }: { scope: StatementScope }) {
<li> <li>
{t( {t(
'legacyProtocols.statementFilters', 'legacyProtocols.statementFilters',
'Filters managed from a mail app (ManageSieve) will stop working. Filters set in INBUXA webmail keep working.', 'Filters managed from a mail app (ManageSieve) will stop working. Filters set in inbuxa webmail keep working.',
)} )}
</li> </li>
<li> <li>
@@ -164,7 +164,7 @@ export function Statement({ scope }: { scope: StatementScope }) {
<li> <li>
{t( {t(
'legacyProtocols.statementWebmail', 'legacyProtocols.statementWebmail',
'People keep full access through INBUXA webmail, which can be installed as an app on phones and computers.', 'People keep full access through inbuxa webmail, which can be installed as an app on phones and computers.',
)} )}
</li> </li>
</ul> </ul>
@@ -183,7 +183,7 @@ export function Statement({ scope }: { scope: StatementScope }) {
<p> <p>
{t( {t(
'legacyProtocols.statementSubmission', 'legacyProtocols.statementSubmission',
'Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and INBUXA webmail (JMAP) are not affected and cannot be turned off here.', 'Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and inbuxa webmail (JMAP) are not affected and cannot be turned off here.',
)} )}
</p> </p>
{scope.kind === 'server' && ( {scope.kind === 'server' && (
@@ -191,7 +191,7 @@ export function Statement({ scope }: { scope: StatementScope }) {
<strong>{t('legacyProtocols.firewallLead', 'This does not change your firewall or port forwarding.')}</strong>{' '} <strong>{t('legacyProtocols.firewallLead', 'This does not change your firewall or port forwarding.')}</strong>{' '}
{t( {t(
'legacyProtocols.firewallBody', 'legacyProtocols.firewallBody',
'INBUXA stops answering on these ports; anything that still routes them to this server — firewall rules, NAT port-forwards, a load balancer or proxy — is yours to reconcile.', 'inbuxa stops answering on these ports; anything that still routes them to this server — firewall rules, NAT port-forwards, a load balancer or proxy — is yours to reconcile.',
)} )}
</p> </p>
)} )}
+1 -1
View File
@@ -244,7 +244,7 @@ export function protocolRows(policy: ProtocolPolicy, listeners: PolicyListener[]
}, },
// Incoming mail and JMAP are never the switch's to close (LP-3, "Not affected, ever"). // Incoming mail and JMAP are never the switch's to close (LP-3, "Not affected, ever").
{ key: 'smtp', label: 'SMTP (incoming mail)', state: 'locked', ports: [] }, { key: 'smtp', label: 'SMTP (incoming mail)', state: 'locked', ports: [] },
{ key: 'jmap', label: 'JMAP (INBUXA webmail)', state: 'locked', ports: [] }, { key: 'jmap', label: 'JMAP (inbuxa webmail)', state: 'locked', ports: [] },
]; ];
} }
+1 -1
View File
@@ -104,7 +104,7 @@ export const PAGE_HELP: Record<string, PageHelp> = {
about: 'Named sets of permissions. Give a role to a person to let them do more, or less.', about: 'Named sets of permissions. Give a role to a person to let them do more, or less.',
}, },
'x:OAuthClient': { 'x:OAuthClient': {
about: 'Apps allowed to sign people in through this server, like INBUXA webmail and INBUXA Admin.', about: 'Apps allowed to sign people in through this server, like inbuxa webmail and inbuxa Admin.',
}, },
'x:DkimSignature': { 'x:DkimSignature': {
about: 'The keys that sign outgoing mail so receivers can check it really came from you.', about: 'The keys that sign outgoing mail so receivers can check it really came from you.',
+1 -1
View File
@@ -9,7 +9,7 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
const APP_NAME = 'INBUXA Admin'; const APP_NAME = 'inbuxa Admin';
export function useDocumentTitle(title?: string | null) { export function useDocumentTitle(title?: string | null) {
useEffect(() => { useEffect(() => {
+5 -5
View File
@@ -15,7 +15,7 @@
"bootstrap": { "bootstrap": {
"clipboardBlocked": "Your browser blocked clipboard access.", "clipboardBlocked": "Your browser blocked clipboard access.",
"complete": "Setup complete", "complete": "Setup complete",
"configuredSuccessfully": "INBUXA has been configured successfully.", "configuredSuccessfully": "inbuxa has been configured successfully.",
"copyFailed": "Copy failed", "copyFailed": "Copy failed",
"credentialsCreated": "Your administrator account has been created. Write these down now: the password will not be shown again.", "credentialsCreated": "Your administrator account has been created. Write these down now: the password will not be shown again.",
"emptyForm": "Setup form is empty. The server did not return any bootstrap fields.", "emptyForm": "Setup form is empty. The server did not return any bootstrap fields.",
@@ -23,11 +23,11 @@
"failedToLoad": "Failed to load bootstrap state.", "failedToLoad": "Failed to load bootstrap state.",
"finishSetup": "Finish setup", "finishSetup": "Finish setup",
"loadingSetup": "Loading setup...", "loadingSetup": "Loading setup...",
"nextStepBody": "restart INBUXA for the new configuration to take effect. Once restarted, sign in with the credentials above to continue administering your server.", "nextStepBody": "restart inbuxa for the new configuration to take effect. Once restarted, sign in with the credentials above to continue administering your server.",
"nextStepLabel": "Next step:", "nextStepLabel": "Next step:",
"noConfirm": "The server did not confirm the update.", "noConfirm": "The server did not confirm the update.",
"stepOf": "Step {{current}} of {{total}}", "stepOf": "Step {{current}} of {{total}}",
"welcome": "Welcome to INBUXA", "welcome": "Welcome to inbuxa",
"welcomeSubtitle": "Let's get your server set up." "welcomeSubtitle": "Let's get your server set up."
}, },
"common": { "common": {
@@ -295,11 +295,11 @@
}, },
"logo": { "logo": {
"alt": "Logo", "alt": "Logo",
"inbuxaAlt": "INBUXA" "inbuxaAlt": "inbuxa"
}, },
"logout": "Logout", "logout": "Logout",
"version": { "version": {
"label": "INBUXA Admin {{version}}" "label": "inbuxa Admin {{version}}"
}, },
"nav": { "nav": {
"layoutLegacy": "Legacy", "layoutLegacy": "Legacy",
+1 -1
View File
@@ -11,4 +11,4 @@
* this fork. The version shown beside the link names the build, which is what * this fork. The version shown beside the link names the build, which is what
* makes the offer something a person can act on. * makes the offer something a person can act on.
*/ */
export const SOURCE_URL = 'https://github.com/inbuxa/inbuxa-admin'; export const SOURCE_URL = 'https://git.coffeylabs.org/inbuxa/inbuxa-admin';