From 8ba0288b99b8dfbc5528b0b67f6ef1acd3bbb5f2 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 09:22:37 -0600 Subject: [PATCH 1/6] K2GO-422 feat(dashboard): read-only Forgejo status endpoint Add GET /k2go-api/forgejo/status so the app can decide the post-install repos action without mutating anything. It returns { reachable, adminExists, adminAuthenticable, repos, manageable } from in-process HTTP to the forge API (no CLI, no fork), so it answers even when dash-node is off-proot (unlike the seed). manageable = we authenticate as the K2Go admin, OR the admin is absent so the seed can create it; a K2Go admin that exists but cannot be authenticated (password changed) is not manageable, so the app blocks instead of guessing. dash-node 1.3.5 -> 1.3.6 (package.json + CHANGELOG). --- static/dashboard/CHANGELOG.md | 1 + static/dashboard/package.json | 2 +- static/dashboard/routes.ts | 13 ++++ static/dashboard/sockets/forgejo.status.ts | 83 ++++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 static/dashboard/sockets/forgejo.status.ts diff --git a/static/dashboard/CHANGELOG.md b/static/dashboard/CHANGELOG.md index 8244ab58..08e0cb46 100644 --- a/static/dashboard/CHANGELOG.md +++ b/static/dashboard/CHANGELOG.md @@ -4,6 +4,7 @@ One line per version, newest first. Every REST-facing change bumps the version i (the app surfaces it via `/system/dashboard/update-check` and the "Update available" pill), so this file is the human record of what each bump enables. Keep entries short: `version - change (TICKET)`. +- **1.3.6** - Read-only Forgejo status for the post-install repos action (K2GO-422). New `GET /forgejo/status` returns `{reachable, adminExists, adminAuthenticable, repos, manageable}` from in-process HTTP to the forge API (no CLI, no fork, so it answers even when dash-node is off-proot, unlike the seed). The app uses it to offer "Install repos" (manageable and no repos yet), "Update repos" (repos present), or block (a K2Go admin exists that we cannot authenticate: password changed, so we must not guess or clobber). `manageable` = we authenticate as the admin OR the admin is absent (the seed can create it). Read-only, localhost-only. (K2GO-422) - **1.3.5** - Forgejo repo seed via dash-node (K2GO-417). New `POST /forgejo/seed {includeRepos}` runs the box orchestration (`seed_forgejo`) LIVE (box up, no runrole, no stop) through a detached wrapper (`tools/forgejo-seed.sh`), and `GET /forgejo/seed/status` returns `{state, lines}` for the app to poll. Seeds the admin + org always, plus the full example repo set when includeRepos (default true; false = admin/org only). The forge stays a conventional role (runrole); this only injects content, so it is also the base for live repo updates (K2GO-422). Localhost-only. (K2GO-417) - **1.3.4** - Forgejo admin auto-login (K2GO-212). New `/auth/forgejo/session` mints the box admin web session for the WebView, extending the auto-login family (Calibre-Web / Kolibri, 1.1.3) to the Git forge. Forgejo 15's login is CSRF-exempt: a POST of `user_name` + `password` returns 303 with a `session` cookie, verified by the redirect (a 200 means the form re-rendered, so wrong credentials). The returned cookie jar drops empty "clear" directives so a path-blind merge cannot inject the logged-out cookie. Localhost-only, like all of `/k2go-api`. (K2GO-212) - **1.3.3** - Base-map job takes a file id, composes the URL (K2GO-394). The `basemaps` runner now accepts a bare pmtiles file name per item (`POST /api/basemaps/download {ids:[.pmtiles]}`) and composes `https://iiab.switnet.org/maps/2/` itself, instead of taking a full URL. This matches the kiwix split: the app holds the catalog and sends a light id, the box owns the mirror host -- so the switnet host never enters the app. The id is validated as a plain `.pmtiles` file name (no path, no traversal); archives (search) are not delegated here. No other behavior changes (same aria2 flags, reconnect loop, cancel-cleanup). (K2GO-394) diff --git a/static/dashboard/package.json b/static/dashboard/package.json index ca50bca0..fa9d3694 100644 --- a/static/dashboard/package.json +++ b/static/dashboard/package.json @@ -1,6 +1,6 @@ { "name": "dashboard-console", - "version": "1.3.5", + "version": "1.3.6", "description": "", "main": "index.js", "scripts": { diff --git a/static/dashboard/routes.ts b/static/dashboard/routes.ts index 0cb8105a..9853ca1f 100644 --- a/static/dashboard/routes.ts +++ b/static/dashboard/routes.ts @@ -18,6 +18,7 @@ import { } from './sockets/kolibri.query'; import { checkReadiness, KolibriAuthError, KolibriApiError, login as kolibriLogin } from './sockets/kolibri.session'; import { login as forgejoLogin, ForgejoAuthError } from './sockets/forgejo.session'; +import { forgejoStatus } from './sockets/forgejo.status'; import { describeCredential, setCredential, clearCredential, isServiceName, } from './sockets/credentials'; @@ -363,6 +364,18 @@ apiRouter.get('/forgejo/seed/status', (_req: Request, res: Response): void => { res.json({ state, lines }); }); +// K2GO-422: read-only Forgejo status for the post-install repos action. All in-process HTTP to the +// forge (no fork), so it answers even if dash-node is off-proot. { reachable, adminExists, +// adminAuthenticable, repos, manageable }. +apiRouter.get('/forgejo/status', async (_req: Request, res: Response): Promise => { + res.set('Cache-Control', 'no-store'); + try { + res.json(await forgejoStatus()); + } catch (e: any) { + res.status(500).json({ error: e?.message || 'could not read forgejo status' }); + } +}); + // Trigger a rebuild. Fire-and-forget: launches the orchestrator DETACHED and returns 202 at once; // the app then polls /system/version + RestReadiness until the API is back on the new version. // ADFA-5339: an optional { site: true } also refreshes the served landing page in the same run. The diff --git a/static/dashboard/sockets/forgejo.status.ts b/static/dashboard/sockets/forgejo.status.ts new file mode 100644 index 00000000..a74c62a1 --- /dev/null +++ b/static/dashboard/sockets/forgejo.status.ts @@ -0,0 +1,83 @@ +// sockets/forgejo.status.ts -- K2GO-422 +// +// Read-only status of the box Forgejo, so the app can decide the post-install repos action +// (install repos / update repos / blocked) WITHOUT mutating anything. Every check is an in-process +// HTTP call to Forgejo's API (no CLI, no fork), so it works even when dash-node was relaunched +// off-proot (the fork-heavy seed path cannot -- ADR-5343). Talks to Forgejo DIRECTLY on :3300, like +// forgejo.session.ts (nginx strips the /forgejo prefix). Localhost-only, like all of /k2go-api. +import { getCredential } from './credentials'; +import { FORGEJO_BASE } from './forgejo.session'; + +const API = `${FORGEJO_BASE}/api/v1`; +// The org the seed creates (static/forgejo/orchestration FORGEJO_ORG). Overridable to match the box. +const ORG = process.env.K2GO_FORGEJO_ORG || 'AppDevForAll'; +const TIMEOUT_MS = 8000; + +export interface ForgejoStatus { + /** Forgejo answered at all. */ + reachable: boolean; + /** The K2Go admin user (k2goadmin) is present, even if we cannot authenticate as it. */ + adminExists: boolean; + /** We can authenticate as the K2Go admin with the credential we know (credentials.ts). */ + adminAuthenticable: boolean; + /** Repo names under the org (empty when none, or when we cannot read them). */ + repos: string[]; + /** + * K2Go can administer the forge: either we authenticate as the admin, or the admin does not + * exist yet and the seed can create it. Blocked only when the admin EXISTS but we cannot + * authenticate (its password was changed): we must not guess or touch a forge we do not control. + */ + manageable: boolean; +} + +function basicAuth(user: string, pass: string): string { + return 'Basic ' + Buffer.from(`${user}:${pass}`).toString('base64'); +} + +async function getJson(url: string, headers: Record = {}): Promise { + return fetch(url, { headers: { Accept: 'application/json', ...headers }, signal: AbortSignal.timeout(TIMEOUT_MS) }); +} + +/** Probe the box Forgejo read-only. Never throws: an unreachable box returns all-false/empty. */ +export async function forgejoStatus(): Promise { + const cred = getCredential('forgejo'); + const auth = basicAuth(cred.username, cred.password); + + let reachable = false; + let adminExists = false; + let adminAuthenticable = false; + let repos: string[] = []; + + // 1. Does the admin user exist? Public user endpoint, no auth: 200 = exists, 404 = absent. + try { + const r = await getJson(`${API}/users/${encodeURIComponent(cred.username)}`); + reachable = true; + adminExists = r.status === 200; + } catch { + return { reachable: false, adminExists: false, adminAuthenticable: false, repos: [], manageable: false }; + } + + // 2. Can we authenticate as the admin? An authed call to /user: 200 = yes, 401 = no. + try { + const r = await getJson(`${API}/user`, { Authorization: auth }); + adminAuthenticable = r.status === 200; + // A definite 200 also proves the admin exists, even if step 1 was blocked by REQUIRE_SIGNIN_VIEW. + if (adminAuthenticable) adminExists = true; + } catch { /* leave false */ } + + // 3. Repos under the org (only when we can read them as the admin). + if (adminAuthenticable) { + try { + const r = await getJson(`${API}/orgs/${encodeURIComponent(ORG)}/repos?limit=50`, { Authorization: auth }); + if (r.status === 200) { + const body = await r.json(); + if (Array.isArray(body)) { + repos = body.map((x: any) => (x && typeof x.name === 'string' ? x.name : '')).filter((n: string) => n.length > 0); + } + } + } catch { /* leave empty */ } + } + + const manageable = adminAuthenticable || !adminExists; + return { reachable, adminExists, adminAuthenticable, repos, manageable }; +} From db0d85585f69b2730e107d8a09010cdef9fbc129 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 09:38:24 -0600 Subject: [PATCH 2/6] K2GO-422 feat(forgejo): post-install "Install repos" button On an installed Forgejo, the module detail now offers "Install example repositories" when the box status (GET /forgejo/status) says the forge is manageable and has no repos yet. Tapping it enqueues the seed to the tasks index Get More style: bank the seed, start the foreground ForgejoSeedService, and open SetupProgressActivity, which tracks the running seed to completion via a new EXTRA_FORGEJO_SEED launch flag. - ForgejoStatusClient: reads the box status; canInstallRepos / hasRepos / blocked. - SetupProgressActivity: EXTRA_FORGEJO_SEED marks a post-install seed run, so the completion gate waits on it (seedPendingRun = ... && (moduleShown || postInstallSeed)) and the seed row shows. The stranded case (a banked seed leaking into an unrelated Get More flow) has neither signal, so it still does not block: no hang reintroduced. - ModuleDetailFragment: the installed branch queries the status off the main thread and shows "Install repos", nothing (repos present), or a blocked note (an admin exists that K2Go cannot authenticate). Update/refresh of existing repos (non-destructive) remains a later deliverable (K2GO-422 section A). New strings are parked for translation before merge. --- .../forgejo/data/ForgejoStatusClient.java | 99 +++++++++++++++++++ .../k2go/redesign/ModuleDetailFragment.java | 32 +++++- .../k2go/redesign/SetupProgressActivity.java | 16 ++- .../main/res/values/strings_untranslated.xml | 6 +- 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java diff --git a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java new file mode 100644 index 00000000..e55a9f99 --- /dev/null +++ b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java @@ -0,0 +1,99 @@ +/* + * ============================================================================ + * Name : ForgejoStatusClient.java + * Author : AppDevForAll + * Copyright : Copyright (c) 2026 AppDevForAll + * Description : K2GO-422. App-side client of the dash-node Forgejo status endpoint + * (static/dashboard GET /k2go-api/forgejo/status, dash-node 1.3.6). + * Read-only: it tells the module detail whether to offer "Install + * repos", show the repos as present, or block (an admin exists that + * K2Go cannot authenticate). The box does the work over its own API, + * so this is a plain HTTP GET the app runs on an IO thread. + * ============================================================================ + */ +package org.appdevforall.k2go.forgejo.data; + +import androidx.annotation.Nullable; + +import org.appdevforall.k2go.config.BoxEndpoints; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +/** Fetches the box Forgejo status. {@link #fetch()} BLOCKS (one HTTP GET), so callers run it off the + * main thread. Returns {@code null} when the box did not answer (unreachable / not ready). */ +public final class ForgejoStatusClient { + + /** Immutable snapshot of the box Forgejo, as the status endpoint reports it. */ + public static final class Status { + public final boolean reachable; + public final boolean adminExists; + public final boolean adminAuthenticable; + public final boolean manageable; + public final int repoCount; + + Status(boolean reachable, boolean adminExists, boolean adminAuthenticable, + boolean manageable, int repoCount) { + this.reachable = reachable; + this.adminExists = adminExists; + this.adminAuthenticable = adminAuthenticable; + this.manageable = manageable; + this.repoCount = repoCount; + } + + /** K2Go can administer the forge and it has no example repos yet -> offer "Install repos". */ + public boolean canInstallRepos() { return manageable && repoCount == 0; } + + /** The example repos are present -> nothing to install (refresh/update is K2GO-422 section A). */ + public boolean hasRepos() { return repoCount > 0; } + + /** An admin exists that K2Go cannot authenticate -> do not offer any repo action. */ + public boolean blocked() { return !manageable; } + } + + private static final String STATUS_URL = BoxEndpoints.API + "/forgejo/status"; + + /** GET the status; null on any read/parse error (treat as "cannot tell", offer nothing). */ + @Nullable + public Status fetch() { + try { + HttpURLConnection c = (HttpURLConnection) new URL(STATUS_URL).openConnection(); + c.setUseCaches(false); + c.setConnectTimeout(4000); + c.setReadTimeout(10000); // the box may do up to three short HTTP calls to the forge + c.setRequestMethod("GET"); + c.setRequestProperty("Accept", "application/json"); + int code = c.getResponseCode(); + boolean ok = code >= 200 && code < 300; + String text = readAll(ok ? c.getInputStream() : c.getErrorStream()); + c.disconnect(); + if (!ok) return null; + JSONObject j = new JSONObject(text.isEmpty() ? "{}" : text); + JSONArray repos = j.optJSONArray("repos"); + int repoCount = repos != null ? repos.length() : 0; + return new Status( + j.optBoolean("reachable", false), + j.optBoolean("adminExists", false), + j.optBoolean("adminAuthenticable", false), + j.optBoolean("manageable", false), + repoCount); + } catch (Exception e) { + return null; + } + } + + private static String readAll(@Nullable InputStream is) throws Exception { + if (is == null) return ""; + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + byte[] chunk = new byte[4096]; + int n; + while ((n = is.read(chunk)) != -1) buf.write(chunk, 0, n); + is.close(); + return buf.toString(StandardCharsets.UTF_8.name()); + } +} diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java index fff1dcff..c81c35dd 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java @@ -153,7 +153,37 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c } if (isInstalled) { addStatus(statusRow, K2GoStatusBadge.create(requireContext(), getString(R.string.k2go_mod_phase_done), R.color.k2go_leaf)); - return; // nothing to offer: a module cannot be uninstalled or reinstalled here + // K2GO-422: an installed Forgejo can still be missing its example repos (installed + // with the opt-in off, or a seed that never completed). Offer "Install repos" when + // the box status says the forge is manageable and has no repos yet. Read the status + // off the main thread; a null read (box not ready) or repos already present offers + // nothing, and an admin we cannot authenticate shows a blocked note. + if (isForgejo) { + org.appdevforall.k2go.util.AppExecutors.get().io().execute(() -> { + final org.appdevforall.k2go.forgejo.data.ForgejoStatusClient.Status st = + new org.appdevforall.k2go.forgejo.data.ForgejoStatusClient().fetch(); + root.post(() -> { + if (!isAdded() || st == null) return; + if (st.canInstallRepos()) { + installNowBtn.setText(R.string.k2go_forgejo_install_repos); + installNowBtn.setOnClickListener(v -> { + // Get More style: bank the seed, start the foreground service, and + // hand off to the tasks index (SetupProgressActivity), which tracks + // the running seed to completion (EXTRA_FORGEJO_SEED). + org.appdevforall.k2go.forgejo.data.ForgejoInstallPrefs.bankSeed(requireContext(), true); + org.appdevforall.k2go.forgejo.presentation.ForgejoSeedService.start(requireContext()); + startActivity(new android.content.Intent(requireContext(), SetupProgressActivity.class) + .putExtra(SetupProgressActivity.EXTRA_FORGEJO_SEED, true)); + }); + installNowBtn.setVisibility(View.VISIBLE); + } else if (st.blocked()) { + addStatus(statusRow, K2GoStatusBadge.create(requireContext(), + getString(R.string.k2go_forgejo_repos_blocked), R.color.k2go_amber_text)); + } + }); + }); + } + return; // a module cannot be uninstalled or reinstalled here (repos action aside) } // ADFA-4898: this module's runrole failed in the last finished batch — the SAME per-module // didFail(key) that colours the hub's "Couldn't install" pill, so only the module that diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java index be3e2a62..9a038b41 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java @@ -67,6 +67,9 @@ public class SetupProgressActivity extends AppCompatActivity implements org.appd /** ADFA-5011: this screen is driving a dash-node REST-core rebuild (not an install/content drain). * Latched so the screen stays on the animation and blocks leaving until the rebuild is SUCCESS/FAILED. */ public static final String EXTRA_REBUILD = "rebuild"; + /** K2GO-422: this run is a post-install Forgejo repo seed (the "Install repos" button), so the + * index tracks and waits on the seed even though there is no module install this run. */ + public static final String EXTRA_FORGEJO_SEED = "forgejoSeed"; private static final long READY_POLL_MS = 2000L; private static final long REDIRECT_MS = 3000L; @@ -116,6 +119,7 @@ public class SetupProgressActivity extends AppCompatActivity implements org.appd private boolean moduleStartFailed = false; private boolean moduleSeen = false; // latched once a non-maps proot batch is seen private boolean forgejoSeedSeen = false; // K2GO-423: latched once a Forgejo seed belongs to this session + private boolean postInstallSeed = false; // K2GO-422: this run was launched to seed repos post-install private int readyPolls = 0; // ADFA-4874: failed readiness polls so far (slow-start message) // ADFA-4842: a real module batch stops the server (pdsm stop) for its runroles. When the queue is // DONE, the index restarts the server and WAITS here — showing "Starting services…" — until the REST @@ -136,6 +140,8 @@ public class SetupProgressActivity extends AppCompatActivity implements org.appd protected void onCreate(@Nullable Bundle s) { super.onCreate(s); setContentView(R.layout.activity_k2go_setup_progress); + // K2GO-422: launched by the "Install repos" button, so this run tracks the post-install seed. + postInstallSeed = getIntent() != null && getIntent().getBooleanExtra(EXTRA_FORGEJO_SEED, false); dot = findViewById(R.id.k2go_sp_dot); statusText = findViewById(R.id.k2go_sp_status); @@ -709,7 +715,8 @@ private void render() { // once it is imminent (module server up) or already has a session, so it does not sit as // "Queued" through the whole runrole while the module row already tells that story. if (forgejoSeedInSession() - && (serverObservedUp() || org.appdevforall.k2go.forgejo.presentation.ForgejoSeedRepository.get().hasSession())) { + && (serverObservedUp() || postInstallSeed + || org.appdevforall.k2go.forgejo.presentation.ForgejoSeedRepository.get().hasSession())) { sections.addView(forgejoSeedRow()); } @@ -760,8 +767,11 @@ private void render() { // - not when the forgejo runrole FAILED -- the seed never starts then (it would clear the // marker), so waiting on it would hang; that run is already a failure (Finish + Retry); // - not when the server is slow/failed -- also already a failure. - boolean seedPendingRun = forgejoSeedActive() && !batchServerSlow && moduleShown - && !mq.didFail("forgejo"); + // K2GO-422: also wait in a post-install seed run (the "Install repos" button, no module this + // run). The stranded case (a banked seed leaking into an unrelated Get More flow) has neither + // moduleShown nor the launch extra, so it still does not block -- no hang reintroduced. + boolean seedPendingRun = forgejoSeedActive() && !batchServerSlow && !mq.didFail("forgejo") + && (moduleShown || postInstallSeed); if (noRest && prootShown) { // proot-only: complete when the queue is terminal — plus, for a module batch, once the server // is back (up) or the restart has failed (a dead home that wakes up seconds later is exactly diff --git a/controller/app/src/main/res/values/strings_untranslated.xml b/controller/app/src/main/res/values/strings_untranslated.xml index acde9a9d..11e23f7d 100644 --- a/controller/app/src/main/res/values/strings_untranslated.xml +++ b/controller/app/src/main/res/values/strings_untranslated.xml @@ -9,4 +9,8 @@ ONLY external string file. There are no per-feature string files; everything else lives in strings.xml. --> - + + + Install example repositories + This code host already has an admin that K2Go cannot manage. + From a77bec0567e1d781e5dc905e85dca85bf6446e44 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 10:22:54 -0600 Subject: [PATCH 3/6] K2GO-422 fix(forgejo): force a fresh seed for the post-install repos action Device test: the "Install repos" button returned "done" in ~6 ms without seeding. The box seed status is a single shared marker; the prior admin/org seed left it "done", and ForgejoSeedClient.drive short-circuits on a leftover "done" before it POSTs (finding D, deferred from the analysis, now hit). Add drive(includeRepos, force, listener): force ignores the leftover "done" and POSTs a fresh seed (the box POST rewrites the status to "running" synchronously, so the poll follows the fresh run); non-forced keeps the re-attach optimization for the install flow. ForgejoSeedService.start(ctx, force) threads it; the post-install button forces. Device-validated on arm64: the seed ran ~58 s and adopted the 3 example repos. --- .../k2go/forgejo/data/ForgejoSeedClient.java | 18 ++++++++++++++++-- .../presentation/ForgejoSeedService.java | 17 ++++++++++++----- .../k2go/redesign/ModuleDetailFragment.java | 4 +++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoSeedClient.java b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoSeedClient.java index 09a7f1db..b9452f7f 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoSeedClient.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoSeedClient.java @@ -66,10 +66,24 @@ public interface Listener { */ @NonNull public Result drive(boolean includeRepos, @Nullable Listener l) { + return drive(includeRepos, false, l); + } + + /** + * As {@link #drive(boolean, Listener)}, but {@code force} ignores a leftover "done" status and + * (re)starts a fresh seed. The status file is a single, shared marker, so a prior op's "done" + * would otherwise short-circuit an INTENTIONAL re-seed (K2GO-422 post-install repos) in a few ms + * without running anything. The box POST rewrites the status to "running" synchronously, so the + * poll then follows the fresh run. Non-forced keeps the re-attach optimization (an app that died + * mid-seed reads the finished box status instead of re-running the whole seed). + */ + @NonNull + public Result drive(boolean includeRepos, boolean force, @Nullable Listener l) { String state = readState(l); - if ("done".equals(state)) return Result.DONE; + if (!force && "done".equals(state)) return Result.DONE; if (!"running".equals(state)) { - // idle / error / missing -> (re)start it. A 409 "already running" is fine: fall through to poll. + // idle / error / missing (or forced past a stale done) -> (re)start it. A 409 "already + // running" is fine: fall through to poll. if (!postStart(includeRepos)) return Result.ERROR; } final long deadline = System.currentTimeMillis() + MAX_WAIT_MS; diff --git a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoSeedService.java b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoSeedService.java index 029fa6a2..78ce4a96 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoSeedService.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoSeedService.java @@ -55,13 +55,19 @@ public final class ForgejoSeedService extends Service { private static final int NOTIFICATION_ID = 8; public static final String ACTION_START = "org.iiab.controller.FORGEJO_SEED_START"; + /** K2GO-422: force a fresh seed past a leftover "done" status (an intentional post-install re-seed). */ + private static final String EXTRA_FORCE = "force"; private final Handler main = new Handler(Looper.getMainLooper()); /** Start (or re-attach to) the seed. Idempotent: a running session is left alone. */ - public static void start(Context ctx) { + public static void start(Context ctx) { start(ctx, false); } + + /** As {@link #start(Context)}, but {@code force} re-runs even if the box status is a stale "done" + * (K2GO-422: the post-install "Install repos" is an intentional re-seed). */ + public static void start(Context ctx, boolean force) { ContextCompat.startForegroundService(ctx, - new Intent(ctx, ForgejoSeedService.class).setAction(ACTION_START)); + new Intent(ctx, ForgejoSeedService.class).setAction(ACTION_START).putExtra(EXTRA_FORCE, force)); } /** Clear the finished session so a later install starts clean. */ @@ -96,8 +102,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } boolean includeRepos = ForgejoInstallPrefs.includeRepos(this); + boolean force = intent != null && intent.getBooleanExtra(EXTRA_FORCE, false); repo.startSession(includeRepos); - drive(includeRepos); + drive(includeRepos, force); return START_NOT_STICKY; } @@ -105,7 +112,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { private static final int MAX_ATTEMPTS = ForgejoInstallPrefs.MAX_ATTEMPTS; private static final long RETRY_DELAY_MS = 4000L; - private void drive(final boolean includeRepos) { + private void drive(final boolean includeRepos, final boolean force) { final Context app = getApplicationContext(); AppExecutors.get().io().execute(() -> { ForgejoSeedRepository repo = ForgejoSeedRepository.get(); @@ -127,7 +134,7 @@ private void drive(final boolean includeRepos) { for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { Log.i(TAG, "forgejo seed: driving (includeRepos=" + includeRepos + ", attempt " + attempt + "/" + MAX_ATTEMPTS + ")"); - r = new ForgejoSeedClient().drive(includeRepos, repo::appendLog); + r = new ForgejoSeedClient().drive(includeRepos, force, repo::appendLog); if (r == ForgejoSeedClient.Result.DONE) break; if (attempt < MAX_ATTEMPTS) { repo.appendLog("seed attempt " + attempt + " failed; retrying"); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java index c81c35dd..0b0fdd31 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleDetailFragment.java @@ -171,7 +171,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c // hand off to the tasks index (SetupProgressActivity), which tracks // the running seed to completion (EXTRA_FORGEJO_SEED). org.appdevforall.k2go.forgejo.data.ForgejoInstallPrefs.bankSeed(requireContext(), true); - org.appdevforall.k2go.forgejo.presentation.ForgejoSeedService.start(requireContext()); + // force: this is an intentional re-seed; do not let a prior seed's + // leftover "done" status short-circuit it (K2GO-422). + org.appdevforall.k2go.forgejo.presentation.ForgejoSeedService.start(requireContext(), true); startActivity(new android.content.Intent(requireContext(), SetupProgressActivity.class) .putExtra(SetupProgressActivity.EXTRA_FORGEJO_SEED, true)); }); From cff9850857a3e4299f9f290ca8649ac7fa92ea80 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 10:27:29 -0600 Subject: [PATCH 4/6] K2GO-422 fix(forgejo): harden the status query timeout and latency Two-pass review follow-ups on the status path: - ForgejoStatusClient: raise the read timeout to 30s. The box makes a couple of short forge calls before answering, and a 10s client timeout could expire on a warming forge, silently hiding the "Install repos" button. - forgejo.status.ts: run the independent probes (public users lookup + authed /user) with Promise.allSettled instead of sequentially, and lower the per-call timeout to 5s, so the worst-case latency is roughly halved. --- .../forgejo/data/ForgejoStatusClient.java | 5 +++- static/dashboard/sockets/forgejo.status.ts | 30 +++++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java index e55a9f99..7fc4c8b5 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/data/ForgejoStatusClient.java @@ -65,7 +65,10 @@ public Status fetch() { HttpURLConnection c = (HttpURLConnection) new URL(STATUS_URL).openConnection(); c.setUseCaches(false); c.setConnectTimeout(4000); - c.setReadTimeout(10000); // the box may do up to three short HTTP calls to the forge + // The box may do a couple of short HTTP calls to the forge before answering; keep the read + // timeout comfortably above their combined worst case so a warming forge does not make the + // status read time out (which would silently hide the button). + c.setReadTimeout(30000); c.setRequestMethod("GET"); c.setRequestProperty("Accept", "application/json"); int code = c.getResponseCode(); diff --git a/static/dashboard/sockets/forgejo.status.ts b/static/dashboard/sockets/forgejo.status.ts index a74c62a1..753d44e6 100644 --- a/static/dashboard/sockets/forgejo.status.ts +++ b/static/dashboard/sockets/forgejo.status.ts @@ -11,7 +11,7 @@ import { FORGEJO_BASE } from './forgejo.session'; const API = `${FORGEJO_BASE}/api/v1`; // The org the seed creates (static/forgejo/orchestration FORGEJO_ORG). Overridable to match the box. const ORG = process.env.K2GO_FORGEJO_ORG || 'AppDevForAll'; -const TIMEOUT_MS = 8000; +const TIMEOUT_MS = 5000; export interface ForgejoStatus { /** Forgejo answered at all. */ @@ -48,24 +48,24 @@ export async function forgejoStatus(): Promise { let adminAuthenticable = false; let repos: string[] = []; - // 1. Does the admin user exist? Public user endpoint, no auth: 200 = exists, 404 = absent. - try { - const r = await getJson(`${API}/users/${encodeURIComponent(cred.username)}`); + // The admin-exists probe (public GET /users/{admin}) and the authenticate probe (GET /user with + // the known credential) are independent, so run them together. allSettled never rejects. + const [usersRes, userRes] = await Promise.allSettled([ + getJson(`${API}/users/${encodeURIComponent(cred.username)}`), + getJson(`${API}/user`, { Authorization: auth }), + ]); + if (usersRes.status === 'fulfilled') { reachable = true; adminExists = usersRes.value.status === 200; } + if (userRes.status === 'fulfilled') { reachable = true; - adminExists = r.status === 200; - } catch { + adminAuthenticable = userRes.value.status === 200; + // A 200 here also proves the admin exists, even if the public probe was blocked (REQUIRE_SIGNIN_VIEW). + if (adminAuthenticable) adminExists = true; + } + if (!reachable) { return { reachable: false, adminExists: false, adminAuthenticable: false, repos: [], manageable: false }; } - // 2. Can we authenticate as the admin? An authed call to /user: 200 = yes, 401 = no. - try { - const r = await getJson(`${API}/user`, { Authorization: auth }); - adminAuthenticable = r.status === 200; - // A definite 200 also proves the admin exists, even if step 1 was blocked by REQUIRE_SIGNIN_VIEW. - if (adminAuthenticable) adminExists = true; - } catch { /* leave false */ } - - // 3. Repos under the org (only when we can read them as the admin). + // Repos under the org (only when we can read them as the admin; depends on the auth result). if (adminAuthenticable) { try { const r = await getJson(`${API}/orgs/${encodeURIComponent(ORG)}/repos?limit=50`, { Authorization: auth }); From 692af4215b10496adebedb2ccbb039949c949165 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 10:29:57 -0600 Subject: [PATCH 5/6] K2GO-422 chore(l10n): translate the post-install repos strings to all locales Migrate k2go_forgejo_install_repos and k2go_forgejo_repos_blocked from strings_untranslated.xml into strings.xml (English) and all 33 locale files. Machine translations, marked for human review. Tracker is empty again. --- controller/app/src/main/res/values-ar/strings.xml | 2 ++ controller/app/src/main/res/values-az/strings.xml | 2 ++ controller/app/src/main/res/values-bg/strings.xml | 2 ++ controller/app/src/main/res/values-bn/strings.xml | 2 ++ controller/app/src/main/res/values-cs/strings.xml | 2 ++ controller/app/src/main/res/values-de/strings.xml | 2 ++ controller/app/src/main/res/values-el/strings.xml | 2 ++ controller/app/src/main/res/values-es/strings.xml | 2 ++ controller/app/src/main/res/values-fa/strings.xml | 2 ++ controller/app/src/main/res/values-fr/strings.xml | 2 ++ controller/app/src/main/res/values-gu/strings.xml | 2 ++ controller/app/src/main/res/values-hi/strings.xml | 2 ++ controller/app/src/main/res/values-hu/strings.xml | 2 ++ controller/app/src/main/res/values-in/strings.xml | 2 ++ controller/app/src/main/res/values-it/strings.xml | 2 ++ controller/app/src/main/res/values-ja/strings.xml | 2 ++ controller/app/src/main/res/values-ko/strings.xml | 2 ++ controller/app/src/main/res/values-lt/strings.xml | 2 ++ controller/app/src/main/res/values-nl/strings.xml | 2 ++ controller/app/src/main/res/values-no/strings.xml | 2 ++ controller/app/src/main/res/values-pl/strings.xml | 2 ++ controller/app/src/main/res/values-pt/strings.xml | 2 ++ controller/app/src/main/res/values-ro/strings.xml | 2 ++ controller/app/src/main/res/values-ru-rRU/strings.xml | 2 ++ controller/app/src/main/res/values-sk/strings.xml | 2 ++ controller/app/src/main/res/values-sr/strings.xml | 2 ++ controller/app/src/main/res/values-sw/strings.xml | 2 ++ controller/app/src/main/res/values-ta/strings.xml | 2 ++ controller/app/src/main/res/values-tr/strings.xml | 2 ++ controller/app/src/main/res/values-uk/strings.xml | 2 ++ controller/app/src/main/res/values-vi/strings.xml | 2 ++ controller/app/src/main/res/values-yo/strings.xml | 2 ++ controller/app/src/main/res/values-zh-rCN/strings.xml | 2 ++ controller/app/src/main/res/values/strings.xml | 2 ++ controller/app/src/main/res/values/strings_untranslated.xml | 6 +----- 35 files changed, 69 insertions(+), 5 deletions(-) diff --git a/controller/app/src/main/res/values-ar/strings.xml b/controller/app/src/main/res/values-ar/strings.xml index 15d18ef7..38b9f971 100644 --- a/controller/app/src/main/res/values-ar/strings.xml +++ b/controller/app/src/main/res/values-ar/strings.xml @@ -1359,4 +1359,6 @@ تمت إضافة %1$d مستودعات يمكنك ترك هذا قيد التشغيل يستمر الإعداد في الخلفية. عُد في أي وقت للتحقق من التقدم. + تثبيت مستودعات نموذجية + يحتوي مضيف الشيفرة هذا بالفعل على مسؤول لا يستطيع K2Go إدارته. diff --git a/controller/app/src/main/res/values-az/strings.xml b/controller/app/src/main/res/values-az/strings.xml index 86813ee3..6d413ed3 100644 --- a/controller/app/src/main/res/values-az/strings.xml +++ b/controller/app/src/main/res/values-az/strings.xml @@ -1379,4 +1379,6 @@ %1$d anbar əlavə edildi Bunu işləməyə buraxa bilərsiniz Quraşdırma arxa planda davam edir. Gedişatı yoxlamaq üçün istənilən vaxt geri qayıdın. + Nümunə anbarlarını quraşdır + Bu kod hostunda artıq K2Go-nun idarə edə bilmədiyi bir admin var. diff --git a/controller/app/src/main/res/values-bg/strings.xml b/controller/app/src/main/res/values-bg/strings.xml index 252b4b39..c398430d 100644 --- a/controller/app/src/main/res/values-bg/strings.xml +++ b/controller/app/src/main/res/values-bg/strings.xml @@ -1366,4 +1366,6 @@ Добавени хранилища: %1$d Можете да оставите това да работи Настройката продължава на заден план. Върнете се по всяко време, за да проверите напредъка. + Инсталиране на примерни хранилища + Този хост за код вече има администратор, който K2Go не може да управлява. diff --git a/controller/app/src/main/res/values-bn/strings.xml b/controller/app/src/main/res/values-bn/strings.xml index 24238d02..26ef2ac0 100644 --- a/controller/app/src/main/res/values-bn/strings.xml +++ b/controller/app/src/main/res/values-bn/strings.xml @@ -1372,4 +1372,6 @@ %1$d রিপোজিটরি যোগ করা হয়েছে আপনি এটি চলতে রেখে দিতে পারেন সেটআপ পটভূমিতে চলতে থাকে। অগ্রগতি দেখতে যেকোনো সময় ফিরে আসুন। + উদাহরণ রিপোজিটরি ইনস্টল করুন + এই কোড হোস্টে ইতিমধ্যে একজন অ্যাডমিন আছে যা K2Go পরিচালনা করতে পারে না। diff --git a/controller/app/src/main/res/values-cs/strings.xml b/controller/app/src/main/res/values-cs/strings.xml index a2ae65a5..e86bddaf 100644 --- a/controller/app/src/main/res/values-cs/strings.xml +++ b/controller/app/src/main/res/values-cs/strings.xml @@ -1366,4 +1366,6 @@ Přidáno repozitářů: %1$d Můžete to nechat běžet Nastavení pokračuje na pozadí. Kdykoli se vraťte a zkontrolujte průběh. + Nainstalovat ukázkové repozitáře + Tento hostitel kódu už má správce, kterého K2Go nemůže spravovat. diff --git a/controller/app/src/main/res/values-de/strings.xml b/controller/app/src/main/res/values-de/strings.xml index c148b795..0ffc7268 100644 --- a/controller/app/src/main/res/values-de/strings.xml +++ b/controller/app/src/main/res/values-de/strings.xml @@ -1359,4 +1359,6 @@ %1$d Repositorys hinzugefügt Sie können dies laufen lassen Die Einrichtung wird im Hintergrund fortgesetzt. Kommen Sie jederzeit zurück, um den Fortschritt zu prüfen. + Beispiel-Repositorys installieren + Dieser Code-Host hat bereits einen Administrator, den K2Go nicht verwalten kann. diff --git a/controller/app/src/main/res/values-el/strings.xml b/controller/app/src/main/res/values-el/strings.xml index 56daf7ec..9b0a349b 100644 --- a/controller/app/src/main/res/values-el/strings.xml +++ b/controller/app/src/main/res/values-el/strings.xml @@ -1366,4 +1366,6 @@ Προστέθηκαν αποθετήρια: %1$d Μπορείτε να το αφήσετε να εκτελείται Η ρύθμιση συνεχίζεται στο παρασκήνιο. Επιστρέψτε οποτεδήποτε για να ελέγξετε την πρόοδο. + Εγκατάσταση δειγμάτων αποθετηρίων + Αυτός ο διακομιστής κώδικα έχει ήδη διαχειριστή που το K2Go δεν μπορεί να διαχειριστεί. diff --git a/controller/app/src/main/res/values-es/strings.xml b/controller/app/src/main/res/values-es/strings.xml index 2bc77935..69b9abea 100644 --- a/controller/app/src/main/res/values-es/strings.xml +++ b/controller/app/src/main/res/values-es/strings.xml @@ -1435,4 +1435,6 @@ %1$d repositorios agregados Puedes dejar esto en ejecución La configuración continúa en segundo plano. Vuelve cuando quieras para ver el progreso. + Instalar repositorios de ejemplo + Este host de código ya tiene un administrador que K2Go no puede gestionar. diff --git a/controller/app/src/main/res/values-fa/strings.xml b/controller/app/src/main/res/values-fa/strings.xml index 90143935..67c809a6 100644 --- a/controller/app/src/main/res/values-fa/strings.xml +++ b/controller/app/src/main/res/values-fa/strings.xml @@ -1359,4 +1359,6 @@ %1$d مخزن افزوده شد می‌توانید این را در حال اجرا رها کنید راه‌اندازی در پس‌زمینه ادامه می‌یابد. هر زمان خواستید برای بررسی پیشرفت بازگردید. + نصب مخازن نمونه + این میزبان کد از قبل مدیری دارد که K2Go نمی‌تواند آن را مدیریت کند. diff --git a/controller/app/src/main/res/values-fr/strings.xml b/controller/app/src/main/res/values-fr/strings.xml index 6519da9b..e0f1aaef 100644 --- a/controller/app/src/main/res/values-fr/strings.xml +++ b/controller/app/src/main/res/values-fr/strings.xml @@ -1446,4 +1446,6 @@ %1$d dépôts ajoutés Vous pouvez laisser cela s\'exécuter La configuration continue en arrière-plan. Revenez quand vous voulez pour voir la progression. + Installer les dépôts d\'exemple + Cet hôte de code a déjà un administrateur que K2Go ne peut pas gérer. diff --git a/controller/app/src/main/res/values-gu/strings.xml b/controller/app/src/main/res/values-gu/strings.xml index baebcd9a..4f83e27a 100644 --- a/controller/app/src/main/res/values-gu/strings.xml +++ b/controller/app/src/main/res/values-gu/strings.xml @@ -1372,4 +1372,6 @@ %1$d રિપોઝિટરી ઉમેરાઈ તમે આને ચાલુ રહેવા દઈ શકો છો સેટઅપ પૃષ્ઠભૂમિમાં ચાલુ રહે છે. પ્રગતિ તપાસવા માટે કોઈપણ સમયે પાછા આવો. + ઉદાહરણ રિપોઝિટરી ઇન્સ્ટૉલ કરો + આ કોડ હોસ્ટ પાસે પહેલેથી જ એક એડમિન છે જેને K2Go સંચાલિત કરી શકતું નથી. diff --git a/controller/app/src/main/res/values-hi/strings.xml b/controller/app/src/main/res/values-hi/strings.xml index 96e873f3..440fc62c 100644 --- a/controller/app/src/main/res/values-hi/strings.xml +++ b/controller/app/src/main/res/values-hi/strings.xml @@ -1436,4 +1436,6 @@ %1$d रिपॉज़िटरी जोड़ी गईं आप इसे चलता हुआ छोड़ सकते हैं सेटअप पृष्ठभूमि में जारी रहता है। प्रगति जांचने के लिए किसी भी समय वापस आएं। + उदाहरण रिपॉज़िटरी इंस्टॉल करें + इस कोड होस्ट में पहले से एक एडमिन है जिसे K2Go प्रबंधित नहीं कर सकता। diff --git a/controller/app/src/main/res/values-hu/strings.xml b/controller/app/src/main/res/values-hu/strings.xml index dc340d07..10d450a5 100644 --- a/controller/app/src/main/res/values-hu/strings.xml +++ b/controller/app/src/main/res/values-hu/strings.xml @@ -1359,4 +1359,6 @@ %1$d tároló hozzáadva Hagyhatod, hogy tovább fusson A beállítás a háttérben folytatódik. Térj vissza bármikor a folyamat ellenőrzéséhez. + Példatárolók telepítése + Ennek a kódtárhelynek már van egy rendszergazdája, akit a K2Go nem tud kezelni. diff --git a/controller/app/src/main/res/values-in/strings.xml b/controller/app/src/main/res/values-in/strings.xml index 9b674380..e6be5888 100644 --- a/controller/app/src/main/res/values-in/strings.xml +++ b/controller/app/src/main/res/values-in/strings.xml @@ -1366,4 +1366,6 @@ %1$d repositori ditambahkan Anda dapat membiarkannya berjalan Penyiapan berlanjut di latar belakang. Kembalilah kapan saja untuk memeriksa progres. + Instal repositori contoh + Host kode ini sudah memiliki admin yang tidak dapat dikelola K2Go. diff --git a/controller/app/src/main/res/values-it/strings.xml b/controller/app/src/main/res/values-it/strings.xml index b14cc685..9a425753 100644 --- a/controller/app/src/main/res/values-it/strings.xml +++ b/controller/app/src/main/res/values-it/strings.xml @@ -1359,4 +1359,6 @@ %1$d repository aggiunti Puoi lasciarlo in esecuzione La configurazione continua in background. Torna quando vuoi per controllare i progressi. + Installa i repository di esempio + Questo host di codice ha già un amministratore che K2Go non può gestire. diff --git a/controller/app/src/main/res/values-ja/strings.xml b/controller/app/src/main/res/values-ja/strings.xml index 2c492ee0..a23dbf68 100644 --- a/controller/app/src/main/res/values-ja/strings.xml +++ b/controller/app/src/main/res/values-ja/strings.xml @@ -1360,4 +1360,6 @@ %1$d 個のリポジトリを追加しました このまま実行し続けても構いません 設定はバックグラウンドで続行されます。いつでも戻って進行状況を確認できます。 + サンプルリポジトリをインストール + このコードホストには、K2Go が管理できない管理者がすでに存在します。 diff --git a/controller/app/src/main/res/values-ko/strings.xml b/controller/app/src/main/res/values-ko/strings.xml index 78d2f574..4c68a550 100644 --- a/controller/app/src/main/res/values-ko/strings.xml +++ b/controller/app/src/main/res/values-ko/strings.xml @@ -1360,4 +1360,6 @@ 저장소 %1$d개 추가됨 이 작업을 계속 실행되도록 둘 수 있습니다 설정은 백그라운드에서 계속됩니다. 언제든지 돌아와 진행 상황을 확인하세요. + 예제 저장소 설치 + 이 코드 호스트에는 K2Go가 관리할 수 없는 관리자가 이미 있습니다. diff --git a/controller/app/src/main/res/values-lt/strings.xml b/controller/app/src/main/res/values-lt/strings.xml index ef953e8b..6cce27a2 100644 --- a/controller/app/src/main/res/values-lt/strings.xml +++ b/controller/app/src/main/res/values-lt/strings.xml @@ -1376,4 +1376,6 @@ Pridėta saugyklų: %1$d Galite palikti tai veikti Konfigūravimas tęsiasi fone. Grįžkite bet kada, kad patikrintumėte eigą. + Įdiegti pavyzdines saugyklas + Šis kodo prieglobstis jau turi administratorių, kurio „K2Go“ negali valdyti. diff --git a/controller/app/src/main/res/values-nl/strings.xml b/controller/app/src/main/res/values-nl/strings.xml index 4af3d734..a00fa3df 100644 --- a/controller/app/src/main/res/values-nl/strings.xml +++ b/controller/app/src/main/res/values-nl/strings.xml @@ -1359,4 +1359,6 @@ %1$d repository\'s toegevoegd Je kunt dit laten doorlopen Het instellen gaat op de achtergrond verder. Kom op elk moment terug om de voortgang te bekijken. + Voorbeeldrepository\'s installeren + Deze code-host heeft al een beheerder die K2Go niet kan beheren. diff --git a/controller/app/src/main/res/values-no/strings.xml b/controller/app/src/main/res/values-no/strings.xml index 99d7c563..9f701ab3 100644 --- a/controller/app/src/main/res/values-no/strings.xml +++ b/controller/app/src/main/res/values-no/strings.xml @@ -1369,4 +1369,6 @@ %1$d repositorier lagt til Du kan la dette kjøre videre Konfigureringen fortsetter i bakgrunnen. Kom tilbake når som helst for å sjekke fremdriften. + Installer eksempelrepositorier + Denne kodeverten har allerede en administrator som K2Go ikke kan administrere. diff --git a/controller/app/src/main/res/values-pl/strings.xml b/controller/app/src/main/res/values-pl/strings.xml index bc4f4df8..a9f4ea26 100644 --- a/controller/app/src/main/res/values-pl/strings.xml +++ b/controller/app/src/main/res/values-pl/strings.xml @@ -1369,4 +1369,6 @@ Dodano repozytoria: %1$d Możesz zostawić to uruchomione Konfiguracja jest kontynuowana w tle. Wróć w dowolnym momencie, aby sprawdzić postęp. + Zainstaluj przykładowe repozytoria + Ten host kodu ma już administratora, którym K2Go nie może zarządzać. diff --git a/controller/app/src/main/res/values-pt/strings.xml b/controller/app/src/main/res/values-pt/strings.xml index 08af14a9..e05989ac 100644 --- a/controller/app/src/main/res/values-pt/strings.xml +++ b/controller/app/src/main/res/values-pt/strings.xml @@ -1438,4 +1438,6 @@ %1$d repositórios adicionados Você pode deixar isto em execução A configuração continua em segundo plano. Volte quando quiser para ver o progresso. + Instalar repositórios de exemplo + Este host de código já tem um administrador que o K2Go não pode gerenciar. diff --git a/controller/app/src/main/res/values-ro/strings.xml b/controller/app/src/main/res/values-ro/strings.xml index 9e7f17d5..9b120677 100644 --- a/controller/app/src/main/res/values-ro/strings.xml +++ b/controller/app/src/main/res/values-ro/strings.xml @@ -1359,4 +1359,6 @@ %1$d depozite adăugate Poți lăsa acest proces să ruleze Configurarea continuă în fundal. Revino oricând pentru a verifica progresul. + Instalează depozitele exemplu + Această gazdă de cod are deja un administrator pe care K2Go nu îl poate gestiona. diff --git a/controller/app/src/main/res/values-ru-rRU/strings.xml b/controller/app/src/main/res/values-ru-rRU/strings.xml index aaad2d58..50241215 100644 --- a/controller/app/src/main/res/values-ru-rRU/strings.xml +++ b/controller/app/src/main/res/values-ru-rRU/strings.xml @@ -1435,4 +1435,6 @@ Добавлено репозиториев: %1$d Вы можете оставить это выполняться Настройка продолжается в фоновом режиме. Возвращайтесь в любое время, чтобы проверить ход выполнения. + Установить примеры репозиториев + У этого хоста кода уже есть администратор, которым K2Go не может управлять. diff --git a/controller/app/src/main/res/values-sk/strings.xml b/controller/app/src/main/res/values-sk/strings.xml index ade036b5..175b1077 100644 --- a/controller/app/src/main/res/values-sk/strings.xml +++ b/controller/app/src/main/res/values-sk/strings.xml @@ -1366,4 +1366,6 @@ Pridané repozitáre: %1$d Môžete to nechať bežať Nastavenie pokračuje na pozadí. Kedykoľvek sa vráťte a skontrolujte priebeh. + Nainštalovať ukážkové repozitáre + Tento hostiteľ kódu už má správcu, ktorého K2Go nemôže spravovať. diff --git a/controller/app/src/main/res/values-sr/strings.xml b/controller/app/src/main/res/values-sr/strings.xml index db637069..85e6b81e 100644 --- a/controller/app/src/main/res/values-sr/strings.xml +++ b/controller/app/src/main/res/values-sr/strings.xml @@ -1372,4 +1372,6 @@ Додато репозиторијума: %1$d Можете оставити ово да ради Подешавање се наставља у позадини. Вратите се било када да проверите напредак. + Инсталирај примере репозиторијума + Овај хост кода већ има администратора којим K2Go не може да управља. diff --git a/controller/app/src/main/res/values-sw/strings.xml b/controller/app/src/main/res/values-sw/strings.xml index 5b0bd766..1ffd3bda 100644 --- a/controller/app/src/main/res/values-sw/strings.xml +++ b/controller/app/src/main/res/values-sw/strings.xml @@ -1379,4 +1379,6 @@ Hazina %1$d zimeongezwa Unaweza kuiacha ikiendelea Usanidi unaendelea chinichini. Rudi wakati wowote kuangalia maendeleo. + Sakinisha hazina za mfano + Seva hii ya msimbo tayari ina msimamizi ambaye K2Go haiwezi kumsimamia. diff --git a/controller/app/src/main/res/values-ta/strings.xml b/controller/app/src/main/res/values-ta/strings.xml index fd4c8679..063ce2ea 100644 --- a/controller/app/src/main/res/values-ta/strings.xml +++ b/controller/app/src/main/res/values-ta/strings.xml @@ -1379,4 +1379,6 @@ %1$d களஞ்சியங்கள் சேர்க்கப்பட்டன இதை இயங்கவிட்டு விடலாம் அமைப்பு பின்னணியில் தொடர்கிறது. முன்னேற்றத்தைச் சரிபார்க்க எப்போது வேண்டுமானாலும் திரும்பி வாருங்கள். + எடுத்துக்காட்டு களஞ்சியங்களை நிறுவு + இந்தக் குறியீட்டு ஹோஸ்ட்டில் K2Go நிர்வகிக்க முடியாத ஒரு நிர்வாகி ஏற்கனவே உள்ளார். diff --git a/controller/app/src/main/res/values-tr/strings.xml b/controller/app/src/main/res/values-tr/strings.xml index 36dbfbad..0137a8c2 100644 --- a/controller/app/src/main/res/values-tr/strings.xml +++ b/controller/app/src/main/res/values-tr/strings.xml @@ -1359,4 +1359,6 @@ %1$d depo eklendi Bunu çalışır durumda bırakabilirsiniz Kurulum arka planda devam eder. İlerlemeyi kontrol etmek için istediğiniz zaman geri dönün. + Örnek depoları yükle + Bu kod sunucusunda K2Go\'nun yönetemeyeceği bir yönetici zaten var. diff --git a/controller/app/src/main/res/values-uk/strings.xml b/controller/app/src/main/res/values-uk/strings.xml index e0f804da..25f44087 100644 --- a/controller/app/src/main/res/values-uk/strings.xml +++ b/controller/app/src/main/res/values-uk/strings.xml @@ -1359,4 +1359,6 @@ Додано репозиторіїв: %1$d Ви можете залишити це виконуватися Налаштування триває у фоновому режимі. Повертайтеся будь-коли, щоб перевірити перебіг. + Встановити приклади репозиторіїв + Цей хост коду вже має адміністратора, яким K2Go не може керувати. diff --git a/controller/app/src/main/res/values-vi/strings.xml b/controller/app/src/main/res/values-vi/strings.xml index 3cdd11ad..9254fe85 100644 --- a/controller/app/src/main/res/values-vi/strings.xml +++ b/controller/app/src/main/res/values-vi/strings.xml @@ -1359,4 +1359,6 @@ Đã thêm %1$d kho lưu trữ Bạn có thể để quá trình này tiếp tục chạy Quá trình thiết lập tiếp tục ở chế độ nền. Quay lại bất cứ lúc nào để kiểm tra tiến trình. + Cài đặt kho ví dụ + Máy chủ mã nguồn này đã có một quản trị viên mà K2Go không thể quản lý. diff --git a/controller/app/src/main/res/values-yo/strings.xml b/controller/app/src/main/res/values-yo/strings.xml index 26e71612..51d28b15 100644 --- a/controller/app/src/main/res/values-yo/strings.xml +++ b/controller/app/src/main/res/values-yo/strings.xml @@ -1379,4 +1379,6 @@ %1$d ibi ìtọ́jú ni a fi kún un O lè fi sílẹ̀ kí ó máa ṣiṣẹ́ Ìṣètò ń tẹ̀síwájú ní ẹ̀yìn. Padà nígbàkigbà láti ṣàyẹ̀wò ìlọsíwájú. + Fi àwọn ibi ìtọ́jú àpẹẹrẹ sórí ẹrọ + Olùgbàlejò koodu yìí ti ní alábòójútó tí K2Go kò lè ṣàkóso. diff --git a/controller/app/src/main/res/values-zh-rCN/strings.xml b/controller/app/src/main/res/values-zh-rCN/strings.xml index d927797d..262e8564 100644 --- a/controller/app/src/main/res/values-zh-rCN/strings.xml +++ b/controller/app/src/main/res/values-zh-rCN/strings.xml @@ -1360,4 +1360,6 @@ 已添加 %1$d 个仓库 您可以让它继续运行 设置将在后台继续。您可以随时返回查看进度。 + 安装示例仓库 + 此代码托管已有一个 K2Go 无法管理的管理员。 diff --git a/controller/app/src/main/res/values/strings.xml b/controller/app/src/main/res/values/strings.xml index e0c46e13..0f47e839 100644 --- a/controller/app/src/main/res/values/strings.xml +++ b/controller/app/src/main/res/values/strings.xml @@ -1545,4 +1545,6 @@ %1$d repositories added You can leave this running The setup continues in the background. Come back any time to check progress. + Install example repositories + This code host already has an admin that K2Go cannot manage. diff --git a/controller/app/src/main/res/values/strings_untranslated.xml b/controller/app/src/main/res/values/strings_untranslated.xml index 11e23f7d..acde9a9d 100644 --- a/controller/app/src/main/res/values/strings_untranslated.xml +++ b/controller/app/src/main/res/values/strings_untranslated.xml @@ -9,8 +9,4 @@ ONLY external string file. There are no per-feature string files; everything else lives in strings.xml. --> - - - Install example repositories - This code host already has an admin that K2Go cannot manage. - + From b627b808f00ebb552c21086ec0fa5c5f3a9dfbc1 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Fri, 25 Sep 2026 10:34:13 -0600 Subject: [PATCH 6/6] K2GO-422 fix(forgejo): scope the seed completion gate to the module flow Third-pass review: seedPendingRun released the completion gate on mq.didFail("forgejo") in the post-install flow too, where the module queue is a stale/unrelated verdict. Scope it to moduleShown so a post-install seed run waits on its own state. Behavior-preserving for the module-install and post-install flows; closes the fragile coupling. --- .../appdevforall/k2go/redesign/SetupProgressActivity.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java index 9a038b41..013daf97 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupProgressActivity.java @@ -770,8 +770,11 @@ private void render() { // K2GO-422: also wait in a post-install seed run (the "Install repos" button, no module this // run). The stranded case (a banked seed leaking into an unrelated Get More flow) has neither // moduleShown nor the launch extra, so it still does not block -- no hang reintroduced. - boolean seedPendingRun = forgejoSeedActive() && !batchServerSlow && !mq.didFail("forgejo") - && (moduleShown || postInstallSeed); + boolean seedPendingRun = forgejoSeedActive() && !batchServerSlow + && (moduleShown || postInstallSeed) + // The forgejo runrole failing releases the gate ONLY in a module-install flow; a + // post-install seed run (postInstallSeed) must not read a stale/unrelated queue verdict. + && !(moduleShown && mq.didFail("forgejo")); if (noRest && prootShown) { // proot-only: complete when the queue is terminal — plus, for a module batch, once the server // is back (up) or the restart has failed (a dead home that wakes up seconds later is exactly