diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bfe68a6..ad6a700 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -517,7 +517,7 @@ jobs:
if: github.ref == 'refs/heads/main'
env:
FORGE: https://git.dodotech.cloud
- REPO: DodoTech/DodoSSH
+ REPO: DodoTech-Public/DodoSSH
TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAGED: ${{ steps.nightly.outputs.staged }}
VERSION: ${{ steps.nightly.outputs.version }}
diff --git a/README.md b/README.md
index 7715182..1109369 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ not go. If the repository is private, every check answers `404`, and the honest
about that is say so. It is one line to check:
```bash
-curl -so /dev/null -w '%{http_code}\n' https://git.dodotech.cloud/api/v1/repos/DodoTech/DodoSSH
+curl -so /dev/null -w '%{http_code}\n' https://git.dodotech.cloud/api/v1/repos/DodoTech-Public/DodoSSH
```
`200` and updates work. `404` and they cannot. `/api/v1/version` answering `200` proves only that the forge
diff --git a/docs/adr/0013-desktop-distribution-and-updates.md b/docs/adr/0013-desktop-distribution-and-updates.md
index ed77c73..2f90ddc 100644
--- a/docs/adr/0013-desktop-distribution-and-updates.md
+++ b/docs/adr/0013-desktop-distribution-and-updates.md
@@ -118,7 +118,7 @@ than assumed:
- **The feed repository must be readable with no credentials, and that is a deployment precondition** rather
than a property of the code. It is checkable in one line, which is the only reason it is worth stating:
- `curl -so /dev/null -w '%{http_code}' https://git.dodotech.cloud/api/v1/repos/DodoTech/DodoSSH` answers
+ `curl -so /dev/null -w '%{http_code}' https://git.dodotech.cloud/api/v1/repos/DodoTech-Public/DodoSSH` answers
`200` when the design holds and `404` when it does not. `/api/v1/version` answering `200` proves only that
the forge is reachable, which is what made this look like nothing was wrong.
- **An unreachable channel must be distinguishable from a current build.** `IUpdateChannel.CheckAsync` used
diff --git a/docs/manual-checks.md b/docs/manual-checks.md
index ce5b60a..9672a93 100644
--- a/docs/manual-checks.md
+++ b/docs/manual-checks.md
@@ -1802,7 +1802,7 @@ Nothing else in this phase or the next means anything until this passes. Run it
nothing:
```bash
-curl -so /dev/null -w '%{http_code}\n' https://git.dodotech.cloud/api/v1/repos/DodoTech/DodoSSH
+curl -so /dev/null -w '%{http_code}\n' https://git.dodotech.cloud/api/v1/repos/DodoTech-Public/DodoSSH
```
**Pass:** `200`.
diff --git a/scripts/release-android.ps1 b/scripts/release-android.ps1
index 33ab73f..77b366e 100644
--- a/scripts/release-android.ps1
+++ b/scripts/release-android.ps1
@@ -50,7 +50,7 @@ $RepoRoot = Split-Path -Parent $PSScriptRoot
$Project = Join-Path $RepoRoot 'src/DodoSSH.Client.Android/DodoSSH.Client.Android.csproj'
$Staging = Join-Path $RepoRoot 'artifacts/android'
$Forge = 'https://git.dodotech.cloud'
-$Repo = 'DodoTech/DodoSSH'
+$Repo = 'DodoTech-Public/DodoSSH'
$Api = "$Forge/api/v1/repos/$Repo"
function Write-Step([string] $Message) {
diff --git a/scripts/release-windows.ps1 b/scripts/release-windows.ps1
index 3035328..38f3073 100644
--- a/scripts/release-windows.ps1
+++ b/scripts/release-windows.ps1
@@ -62,7 +62,11 @@ $PackAuthors = 'DodoTech'
# The project's own forge. Never a DodoSSH deployment — ADR 0011 rule 2. The same URL is a constant in
# VelopackUpdateChannel, and the two have to agree or the client polls somewhere nothing is published.
-$RepoUrl = 'https://git.dodotech.cloud/DodoTech/DodoSSH'
+#
+# The owner is part of it. When the repository moved organisations Gitea left a 301 at the old path, which
+# a GET follows and an upload does not — so a stale URL here fails at the one step that matters rather than
+# redirecting quietly.
+$RepoUrl = 'https://git.dodotech.cloud/DodoTech-Public/DodoSSH'
# A contract with VelopackUpdateChannel.ReleaseChannel. It is Velopack's Windows default, so leaving it
# unsaid on both sides would work too — but unsaid here and stated there is how a feed goes quiet with no
diff --git a/src/DodoSSH.Api/Dockerfile b/src/DodoSSH.Api/Dockerfile
index 4a583af..4118a0c 100644
--- a/src/DodoSSH.Api/Dockerfile
+++ b/src/DodoSSH.Api/Dockerfile
@@ -111,7 +111,7 @@ LABEL org.opencontainers.image.title="DodoSSH API" \
org.opencontainers.image.description="DodoSSH server: sync, identity, teams and relay authorization." \
org.opencontainers.image.vendor="DodoTech" \
org.opencontainers.image.licenses="MIT" \
- org.opencontainers.image.source="https://git.dodotech.cloud/DodoTech/DodoSSH" \
+ org.opencontainers.image.source="https://git.dodotech.cloud/DodoTech-Public/DodoSSH" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${REVISION}" \
org.opencontainers.image.created="${CREATED}"
diff --git a/src/DodoSSH.Client.Android/Platform/AndroidUpdateChannel.cs b/src/DodoSSH.Client.Android/Platform/AndroidUpdateChannel.cs
index cc4941e..32b8381 100644
--- a/src/DodoSSH.Client.Android/Platform/AndroidUpdateChannel.cs
+++ b/src/DodoSSH.Client.Android/Platform/AndroidUpdateChannel.cs
@@ -76,7 +76,14 @@ internal sealed partial class ForgeJsonContext : JsonSerializerContext;
internal sealed class AndroidUpdateChannel : IUpdateChannel
{
/// The project's own forge, and the one address in this file.
- private const string RepositoryApi = "https://git.dodotech.cloud/api/v1/repos/DodoTech/DodoSSH";
+ ///
+ /// ◆ The owner is DodoTech-Public and that half of the path matters. The repository was moved
+ /// between organisations, and Gitea leaves a 301 at the old one — so a client still naming it looks
+ /// fine, because HttpClient follows a redirect on a GET. What it buys is a dependency on a
+ /// redirect somebody can delete, and it does not extend to the release scripts, whose uploads are
+ /// POSTs. The live path is named here, in VelopackUpdateChannel and in both scripts.
+ ///
+ private const string RepositoryApi = "https://git.dodotech.cloud/api/v1/repos/DodoTech-Public/DodoSSH";
///
/// The session name the installer writes under, and it is reused rather than made unique.
diff --git a/src/DodoSSH.Client.App/Platform/VelopackUpdateChannel.cs b/src/DodoSSH.Client.App/Platform/VelopackUpdateChannel.cs
index 723306a..71d5ead 100644
--- a/src/DodoSSH.Client.App/Platform/VelopackUpdateChannel.cs
+++ b/src/DodoSSH.Client.App/Platform/VelopackUpdateChannel.cs
@@ -75,14 +75,24 @@ internal sealed class VelopackUpdateChannel : IUpdateChannel
///
///
/// This must never become a setting. ADR 0011 rule 2 says the deployment a client signs in to
+ ///
/// is never where the client comes from, and it says the same about the update check: an operator who
/// can answer "is there a newer version" can answer "no" forever, and pin a chosen user to a build
/// with a known hole without holding any key. A configurable feed URL is exactly the knob that would
/// hand them that, whether through a settings screen or through somebody editing the plaintext
/// settings.json by hand. A constant is that rule expressed structurally rather than as a convention
/// somebody has to keep.
+ ///
+ ///
+ /// ◆ DodoTech-Public, and the owner is part of the address rather than incidental. The
+ /// repository was moved between organisations, and Gitea leaves a 301 behind at the old path — which is
+ /// why a client pointing at the old one appears to work: HttpClient follows a redirect on a GET.
+ /// It does not follow one on a POST, so vpk upload against the stale URL fails rather than
+ /// redirecting, and a redirect is a thing an operator can remove. Both heads and both release scripts
+ /// name the live path.
+ ///
///
- private const string RepositoryUrl = "https://git.dodotech.cloud/DodoTech/DodoSSH";
+ private const string RepositoryUrl = "https://git.dodotech.cloud/DodoTech-Public/DodoSSH";
///
/// The release channel to read, and it is stated rather than left to the default.