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 b9452f7f..41f4b108 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
@@ -39,8 +39,8 @@
*/
public final class ForgejoSeedClient {
- /** Terminal verdict of a drive. */
- public enum Result { DONE, ERROR }
+ /** Terminal verdict of a drive. CANCELLED only applies to a refresh the user stopped. */
+ public enum Result { DONE, ERROR, CANCELLED }
/** Streamed status-tail lines, for a log/notification. Optional (pass null to ignore). */
public interface Listener {
@@ -49,6 +49,10 @@ public interface Listener {
private static final String SEED_URL = BoxEndpoints.API + "/forgejo/seed";
private static final String STATUS_URL = BoxEndpoints.API + "/forgejo/seed/status";
+ // K2GO-422: the repo refresh reuses this same POST-then-poll shape against its own endpoints.
+ private static final String REFRESH_URL = BoxEndpoints.API + "/forgejo/refresh";
+ private static final String REFRESH_STATUS_URL = BoxEndpoints.API + "/forgejo/refresh/status";
+ private static final String REFRESH_CANCEL_URL = BoxEndpoints.API + "/forgejo/refresh/cancel";
private static final long POLL_MS = 2000L;
private static final int MAX_POLL_ERRORS = 15; // ~30s of transient blips before giving up
// A seed (repo clones ~113 MB) can run for minutes; cap the wait so a wedged box job cannot
@@ -57,6 +61,15 @@ public interface Listener {
/** The last status-tail line handed to the listener, so a poll that did not advance stays quiet. */
private String lastEmitted;
+ // K2GO-422: last per-repo outcome counts reported by a refresh status (-1 = not reported / unknown).
+ private int lastChanged = -1, lastProblems = -1, lastTotal = -1;
+
+ /** Repos that advanced in the last refresh (fast-forward or merge); -1 if the box did not report it. */
+ public int lastChanged() { return lastChanged; }
+ /** Repos the last refresh could not update (conflict or a fetch/push failure); -1 if not reported. */
+ public int lastProblems() { return lastProblems; }
+ /** Repos the last refresh attempted; -1 if the box did not report it. */
+ public int lastTotal() { return lastTotal; }
/**
* Start the seed if it is not already running, then poll to a terminal state.
@@ -79,35 +92,73 @@ public Result drive(boolean includeRepos, @Nullable Listener l) {
*/
@NonNull
public Result drive(boolean includeRepos, boolean force, @Nullable Listener l) {
- String state = readState(l);
+ String state = readState(STATUS_URL, l);
if (!force && "done".equals(state)) return Result.DONE;
if (!"running".equals(state)) {
// 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;
+ if (!postSeed(includeRepos)) return Result.ERROR;
+ }
+ return poll(STATUS_URL, l);
+ }
+
+ /**
+ * K2GO-422: drive a repo REFRESH to a terminal state. Same POST-then-poll shape as the seed, on the
+ * refresh endpoints. Each refresh is intentional, so there is NO "done" short-circuit: unless one is
+ * already running (re-attach), it POSTs a fresh run. The box refresh is non-destructive (fast-forward
+ * or clean merge, skip on conflict), so re-running is safe. Returns DONE when the box refresh
+ * finished, ERROR on an unreachable box or timeout.
+ */
+ @NonNull
+ public Result refresh(@Nullable Listener l) {
+ String state = readState(REFRESH_STATUS_URL, l);
+ if (!"running".equals(state)) {
+ if (!post(REFRESH_URL, null)) return Result.ERROR;
}
+ return poll(REFRESH_STATUS_URL, l);
+ }
+
+ /** Poll one status endpoint to a terminal state, streaming new tail lines through the listener. */
+ @NonNull
+ private Result poll(@NonNull String statusUrl, @Nullable Listener l) {
final long deadline = System.currentTimeMillis() + MAX_WAIT_MS;
int pollErrors = 0;
while (System.currentTimeMillis() < deadline) {
try { Thread.sleep(POLL_MS); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return Result.ERROR; }
- String s = readState(l);
+ String s = readState(statusUrl, l);
if (s == null) { if (++pollErrors > MAX_POLL_ERRORS) return Result.ERROR; continue; }
pollErrors = 0;
if ("done".equals(s)) return Result.DONE;
if ("error".equals(s)) return Result.ERROR;
+ if ("cancelled".equals(s)) return Result.CANCELLED; // K2GO-422: user stopped a refresh
// "running" (or an unknown transient) -> keep polling.
}
return Result.ERROR; // timed out; the box job may still finish, a later drive re-checks
}
- /** POST /forgejo/seed; true if the box accepted the start (2xx) or it is already running (409). */
- private boolean postStart(boolean includeRepos) {
+ /** K2GO-422: ask the box to stop a running refresh (best-effort; a poll then reads "cancelled"). */
+ public void cancelRefresh() {
+ post(REFRESH_CANCEL_URL, null);
+ }
+
+ /** POST /forgejo/seed with the includeRepos body; true on 2xx or 409 (already running). */
+ private boolean postSeed(boolean includeRepos) {
try {
- JSONObject body = new JSONObject().put("includeRepos", includeRepos);
- HttpURLConnection c = open("POST", SEED_URL);
- c.setDoOutput(true);
- c.setRequestProperty("Content-Type", "application/json");
- try (OutputStream os = c.getOutputStream()) { os.write(body.toString().getBytes(StandardCharsets.UTF_8)); }
+ return post(SEED_URL, new JSONObject().put("includeRepos", includeRepos));
+ } catch (org.json.JSONException e) {
+ return false;
+ }
+ }
+
+ /** POST a start endpoint (optional JSON body); true if accepted (2xx) or already running (409). */
+ private boolean post(@NonNull String url, @Nullable JSONObject body) {
+ try {
+ HttpURLConnection c = open("POST", url);
+ if (body != null) {
+ c.setDoOutput(true);
+ c.setRequestProperty("Content-Type", "application/json");
+ try (OutputStream os = c.getOutputStream()) { os.write(body.toString().getBytes(StandardCharsets.UTF_8)); }
+ }
int code = c.getResponseCode();
c.disconnect();
return (code >= 200 && code < 300) || code == 409;
@@ -116,17 +167,21 @@ private boolean postStart(boolean includeRepos) {
}
}
- /** GET /forgejo/seed/status -> the state string, streaming any new tail lines; null on a read error. */
+ /** GET a status endpoint -> the state string, streaming any new tail lines; null on a read error. */
@Nullable
- private String readState(@Nullable Listener l) {
+ private String readState(@NonNull String statusUrl, @Nullable Listener l) {
try {
- HttpURLConnection c = open("GET", STATUS_URL);
+ HttpURLConnection c = open("GET", statusUrl);
int code = c.getResponseCode();
boolean ok = code >= 200 && code < 400;
String text = readAll(ok ? c.getInputStream() : c.getErrorStream());
c.disconnect();
if (!ok) return null;
JSONObject j = new JSONObject(text.isEmpty() ? "{}" : text);
+ // K2GO-422: capture the refresh outcome counts when present (the seed status omits them).
+ if (j.has("changed")) lastChanged = j.optInt("changed", lastChanged);
+ if (j.has("problems")) lastProblems = j.optInt("problems", lastProblems);
+ if (j.has("total")) lastTotal = j.optInt("total", lastTotal);
if (l != null) {
JSONArray lines = j.optJSONArray("lines");
if (lines != null && lines.length() > 0) {
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 7fc4c8b5..2dd5505a 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
@@ -52,6 +52,11 @@ public static final class Status {
/** The example repos are present -> nothing to install (refresh/update is K2GO-422 section A). */
public boolean hasRepos() { return repoCount > 0; }
+ /** K2Go can administer the forge AND example repos exist -> offer "Update repos". Gated on
+ * manageable so a forge we cannot authenticate (changed admin password) never offers a refresh
+ * that would 401; a blocked forge shows the blocked note instead. */
+ public boolean canUpdateRepos() { return manageable && repoCount > 0; }
+
/** An admin exists that K2Go cannot authenticate -> do not offer any repo action. */
public boolean blocked() { return !manageable; }
}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoRepoRefresh.java b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoRepoRefresh.java
new file mode 100644
index 00000000..5a7f6a4d
--- /dev/null
+++ b/controller/app/src/main/java/org/appdevforall/k2go/forgejo/presentation/ForgejoRepoRefresh.java
@@ -0,0 +1,160 @@
+/*
+ * ============================================================================
+ * Name : ForgejoRepoRefresh.java
+ * Author : AppDevForAll
+ * Copyright : Copyright (c) 2026 AppDevForAll
+ * Description : K2GO-422. The single "Update repos" flow, shared by the module detail button and the
+ * module action sheet row so neither duplicates it. It gates like the dashboard update
+ * (needs internet, then metered consent), shows minimal inline progress (a description, a
+ * live one-line output tail, an indeterminate bar and a Cancel) injected right after the
+ * trigger view, runs the box refresh on an IO thread, and reports the outcome in a snackbar.
+ *
+ * Lifecycle: there is NO persistent app-side state. The box refresh job is detached
+ * (setsid), so a host that goes away mid-run (the sheet dismissed, the fragment detached)
+ * just drops the UI updates (guarded by View.isAttachedToWindow()); the box finishes on its
+ * own and the next /forgejo/status read reflects reality. The only state is the box's own
+ * status/pid files, which the box manages. So there is nothing here to leak or to clear.
+ * ============================================================================
+ */
+package org.appdevforall.k2go.forgejo.presentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.progressindicator.LinearProgressIndicator;
+
+import org.appdevforall.k2go.R;
+import org.appdevforall.k2go.forgejo.data.ForgejoSeedClient;
+import org.appdevforall.k2go.util.AppExecutors;
+import org.appdevforall.k2go.util.Snackbars;
+
+public final class ForgejoRepoRefresh {
+
+ private ForgejoRepoRefresh() {}
+
+ /**
+ * Gate (internet, then metered consent) then run the refresh with progress injected right after
+ * {@code trigger}. The trigger stays in place (only disabled) as a visible anchor for the snackbar.
+ */
+ public static void start(@NonNull Activity act, @NonNull View trigger) {
+ if (!org.appdevforall.k2go.networkpolicy.data.AndroidNetworkClassifier.hasInternet(act)) {
+ Snackbars.make(trigger, act.getString(R.string.k2go_dash_needs_internet)).show();
+ return;
+ }
+ // Same metered gate as the dashboard update: the box git-fetches over the device network.
+ org.appdevforall.k2go.networkpolicy.presentation.NetworkPolicyGate.guardHeavyStart(act, () -> run(trigger));
+ }
+
+ private static void run(@NonNull View trigger) {
+ final ViewGroup parent = (ViewGroup) trigger.getParent();
+ if (parent == null || !trigger.isAttachedToWindow()) return; // host went away during the gate
+ final Context ctx = trigger.getContext();
+ final Handler main = new Handler(Looper.getMainLooper());
+ final float d = ctx.getResources().getDisplayMetrics().density;
+ final int side = Math.round(20 * d);
+
+ final LinearLayout progress = new LinearLayout(ctx);
+ progress.setOrientation(LinearLayout.VERTICAL);
+ LinearLayout.LayoutParams plp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ plp.leftMargin = side; plp.rightMargin = side; plp.topMargin = Math.round(8 * d);
+ progress.setLayoutParams(plp);
+
+ // Description of what is happening (static), then a live line that tails the box output one line at
+ // a time (the per-repo outcomes advance like the proot rows: it advances, not a log box).
+ final TextView label = new TextView(ctx);
+ label.setText(R.string.k2go_forgejo_updating);
+ label.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
+ label.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_muted));
+ progress.addView(label);
+
+ final TextView liveLine = new TextView(ctx);
+ liveLine.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodySmall);
+ liveLine.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_muted));
+ liveLine.setMaxLines(1);
+ liveLine.setEllipsize(TextUtils.TruncateAt.END);
+ LinearLayout.LayoutParams llp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ llp.topMargin = Math.round(2 * d);
+ liveLine.setLayoutParams(llp);
+ progress.addView(liveLine);
+
+ // The bar and Cancel share one line: the bar takes the width, Cancel sits beside it.
+ final LinearLayout barLine = new LinearLayout(ctx);
+ barLine.setOrientation(LinearLayout.HORIZONTAL);
+ barLine.setGravity(Gravity.CENTER_VERTICAL);
+ LinearLayout.LayoutParams barLineLp = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ barLineLp.topMargin = Math.round(4 * d);
+ barLine.setLayoutParams(barLineLp);
+
+ final LinearProgressIndicator bar = new LinearProgressIndicator(ctx);
+ bar.setIndeterminate(true);
+ LinearLayout.LayoutParams blp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f);
+ bar.setLayoutParams(blp);
+ barLine.addView(bar);
+
+ final TextView cancel = new TextView(ctx);
+ cancel.setText(R.string.k2go_dash_cancel);
+ cancel.setAllCaps(true);
+ cancel.setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_LabelLarge);
+ cancel.setTextColor(ContextCompat.getColor(ctx, R.color.k2go_teal));
+ int hp = Math.round(12 * d), vp = Math.round(6 * d);
+ cancel.setPadding(hp, vp, hp, vp);
+ barLine.addView(cancel);
+ progress.addView(barLine);
+
+ parent.addView(progress, parent.indexOfChild(trigger) + 1);
+ trigger.setEnabled(false); // stays in place as an anchor; re-enabled when the refresh settles
+
+ cancel.setOnClickListener(cv -> {
+ cancel.setEnabled(false);
+ label.setText(R.string.k2go_forgejo_update_cancelling);
+ AppExecutors.get().io().execute(() -> new ForgejoSeedClient().cancelRefresh());
+ });
+
+ AppExecutors.get().io().execute(() -> {
+ final ForgejoSeedClient client = new ForgejoSeedClient();
+ final ForgejoSeedClient.Result r = client.refresh(rawLine -> {
+ // Tail one line at a time, trimmed and without the org prefix, so it reads cleanly.
+ final String shown = rawLine.trim().replace("AppDevForAll/", "");
+ main.post(() -> { if (liveLine.isAttachedToWindow()) liveLine.setText(shown); });
+ });
+ // The box reports per-repo outcome counts on the refresh status (advanced / could-not / total).
+ final int changed = client.lastChanged();
+ final int problems = client.lastProblems();
+ final int total = client.lastTotal();
+ main.post(() -> {
+ if (!trigger.isAttachedToWindow()) return;
+ parent.removeView(progress);
+ trigger.setEnabled(true);
+ Snackbars.make(trigger, ctx.getString(messageFor(r, changed, problems, total))).show();
+ });
+ });
+ }
+
+ /** Map the refresh outcome to a user message covering every state. */
+ private static int messageFor(ForgejoSeedClient.Result r, int changed, int problems, int total) {
+ if (r == ForgejoSeedClient.Result.CANCELLED) return R.string.k2go_forgejo_update_cancelled;
+ if (r != ForgejoSeedClient.Result.DONE) return R.string.k2go_forgejo_update_failed; // box unreachable
+ if (problems > 0) {
+ // some repos could not be updated (conflict or a fetch/push failure); reconcile in the web UI
+ return (total - problems > 0) ? R.string.k2go_forgejo_update_some_failed
+ : R.string.k2go_forgejo_update_all_failed;
+ }
+ if (changed > 0) return R.string.k2go_forgejo_update_done; // at least one repo advanced
+ if (changed == 0) return R.string.k2go_forgejo_update_none; // nothing to update
+ return R.string.k2go_forgejo_update_done; // unknown counts (older box)
+ }
+}
diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
index 7668453c..59887d34 100644
--- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
+++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleActionSheet.java
@@ -202,6 +202,27 @@ public static void show(Activity act, String endpoint, String title, int iconRes
content.addView(row(ctx, R.drawable.ic_arrow_right, act.getString(R.string.k2go_sheet_open),
Emphasis.ACCENT, null, false, v -> { dlg.dismiss(); openContent(act, endpoint); }));
content.addView(about);
+ // K2GO-422: an installed forgejo with the example repos present -> offer "Update repos"
+ // here (the module action menu). Gated by the SAME status the detail uses (hasRepos), read
+ // off the main thread; the row is inserted after About when the box answers, if the sheet
+ // is still up. Tapping runs the shared refresh IN the sheet (it does NOT dismiss).
+ if ("forgejo".equals(key)) {
+ final LinearLayout host = content;
+ 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();
+ // Gate on canUpdateRepos (manageable AND repos present): a null read (cannot tell),
+ // no repos, or an admin we cannot authenticate (blocked) shows no Update row.
+ if (st == null || !st.canUpdateRepos()) return;
+ act.runOnUiThread(() -> {
+ if (!dlg.isShowing()) return;
+ View urow = row(ctx, R.drawable.ic_refresh,
+ act.getString(R.string.k2go_forgejo_update_repos), Emphasis.ACCENT, null, false,
+ v -> org.appdevforall.k2go.forgejo.presentation.ForgejoRepoRefresh.start(act, v));
+ host.addView(urow, host.indexOfChild(about) + 1);
+ });
+ });
+ }
break;
case SCHEDULED: {
content.addView(about);
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 0b0fdd31..4389aba7 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
@@ -181,6 +181,18 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
} else if (st.blocked()) {
addStatus(statusRow, K2GoStatusBadge.create(requireContext(),
getString(R.string.k2go_forgejo_repos_blocked), R.color.k2go_amber_text));
+ } else if (st.canUpdateRepos()) {
+ // K2GO-422: manageable + repos present -> offer a non-destructive refresh. Minimal
+ // inline progress (a label + an indeterminate bar) reusing the dashboard
+ // update idiom; no service/index because the box refresh is fast and
+ // detached (leaving mid-refresh is safe: the box finishes on its own).
+ installNowBtn.setText(R.string.k2go_forgejo_update_repos);
+ // Shared flow (gates + inline progress + result), also used by the
+ // module action sheet, so the refresh lives in one place.
+ installNowBtn.setOnClickListener(v ->
+ org.appdevforall.k2go.forgejo.presentation.ForgejoRepoRefresh.start(
+ requireActivity(), installNowBtn));
+ installNowBtn.setVisibility(View.VISIBLE);
}
});
});
diff --git a/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml b/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml
index 215f583b..0bb79ad3 100644
--- a/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml
+++ b/controller/app/src/main/res/layout/fragment_k2go_module_detail.xml
@@ -4,6 +4,7 @@
secondary "Install now" schedules it and proceeds to the install index immediately. No content
selector (that's maps-only, later) — the description replaces it. -->
يستمر الإعداد في الخلفية. عُد في أي وقت للتحقق من التقدم.
تثبيت مستودعات نموذجية
يحتوي مضيف الشيفرة هذا بالفعل على مسؤول لا يستطيع K2Go إدارته.
+ تحديث المستودعات
+ جارٍ تحديث المستودعات...
+ تم تحديث المستودعات
+ جميع المستودعات محدّثة بالفعل
+ تعذّر تحديث بعض المستودعات. راجع وصف المستودع.
+ تعذّر تحديث المستودعات. راجع وصف المستودع.
+ تعذّر تحديث المستودعات
+ جارٍ الإلغاء...
+ تم إلغاء التحديث
diff --git a/controller/app/src/main/res/values-az/strings.xml b/controller/app/src/main/res/values-az/strings.xml
index 6d413ed3..6e2c3085 100644
--- a/controller/app/src/main/res/values-az/strings.xml
+++ b/controller/app/src/main/res/values-az/strings.xml
@@ -1381,4 +1381,13 @@
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.
+ Anbarlari yenile
+ Anbarlar yenilenir...
+ Anbarlar yenilendi
+ Butun anbarlar artiq gunceldir
+ Bezi anbarlar yenilene bilmedi. Anbar tesvirine baxin.
+ Anbarlar yenilene bilmedi. Anbar tesvirine baxin.
+ Anbarlar yenilene bilmedi
+ Legv edilir...
+ Yenileme legv edildi
diff --git a/controller/app/src/main/res/values-bg/strings.xml b/controller/app/src/main/res/values-bg/strings.xml
index c398430d..7ad9f8bc 100644
--- a/controller/app/src/main/res/values-bg/strings.xml
+++ b/controller/app/src/main/res/values-bg/strings.xml
@@ -1368,4 +1368,13 @@
Настройката продължава на заден план. Върнете се по всяко време, за да проверите напредъка.
Инсталиране на примерни хранилища
Този хост за код вече има администратор, който K2Go не може да управлява.
+ Обнови хранилищата
+ Обновяване на хранилищата...
+ Хранилищата са обновени
+ Всички хранилища вече са актуални
+ Някои хранилища не можаха да бъдат обновени. Вижте описанието на хранилището.
+ Хранилищата не можаха да бъдат обновени. Вижте описанието на хранилището.
+ Хранилищата не можаха да бъдат обновени
+ Отказване...
+ Обновяването е отказано
diff --git a/controller/app/src/main/res/values-bn/strings.xml b/controller/app/src/main/res/values-bn/strings.xml
index 26ef2ac0..a9bae6a1 100644
--- a/controller/app/src/main/res/values-bn/strings.xml
+++ b/controller/app/src/main/res/values-bn/strings.xml
@@ -1374,4 +1374,13 @@
সেটআপ পটভূমিতে চলতে থাকে। অগ্রগতি দেখতে যেকোনো সময় ফিরে আসুন।
উদাহরণ রিপোজিটরি ইনস্টল করুন
এই কোড হোস্টে ইতিমধ্যে একজন অ্যাডমিন আছে যা K2Go পরিচালনা করতে পারে না।
+ রিপোজিটরি আপডেট করুন
+ রিপোজিটরি আপডেট হচ্ছে...
+ রিপোজিটরি আপডেট হয়েছে
+ সব রিপোজিটরি ইতিমধ্যে হালনাগাদ আছে
+ কিছু রিপোজিটরি আপডেট করা যায়নি। রিপো বিবরণ দেখুন।
+ রিপোজিটরি আপডেট করা যায়নি। রিপো বিবরণ দেখুন।
+ রিপোজিটরি আপডেট করা যায়নি
+ বাতিল করা হচ্ছে...
+ আপডেট বাতিল করা হয়েছে
diff --git a/controller/app/src/main/res/values-cs/strings.xml b/controller/app/src/main/res/values-cs/strings.xml
index e86bddaf..84b4e8e0 100644
--- a/controller/app/src/main/res/values-cs/strings.xml
+++ b/controller/app/src/main/res/values-cs/strings.xml
@@ -1368,4 +1368,13 @@
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.
+ Aktualizovat repozitare
+ Aktualizace repozitaru...
+ Repozitare aktualizovany
+ Vsechny repozitare jsou jiz aktualni
+ Nektere repozitare se nepodarilo aktualizovat. Viz popis repozitare.
+ Repozitare se nepodarilo aktualizovat. Viz popis repozitare.
+ Repozitare se nepodarilo aktualizovat
+ Ruseni...
+ Aktualizace zrusena
diff --git a/controller/app/src/main/res/values-de/strings.xml b/controller/app/src/main/res/values-de/strings.xml
index 0ffc7268..91be962c 100644
--- a/controller/app/src/main/res/values-de/strings.xml
+++ b/controller/app/src/main/res/values-de/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Repos aktualisieren
+ Repositorys werden aktualisiert...
+ Repositorys aktualisiert
+ Alle Repositorys sind bereits aktuell
+ Einige Repositorys konnten nicht aktualisiert werden. Siehe Repo-Beschreibung.
+ Repositorys konnten nicht aktualisiert werden. Siehe Repo-Beschreibung.
+ Repositorys konnten nicht aktualisiert werden
+ Wird abgebrochen...
+ Aktualisierung abgebrochen
diff --git a/controller/app/src/main/res/values-el/strings.xml b/controller/app/src/main/res/values-el/strings.xml
index 9b0a349b..8a39f79a 100644
--- a/controller/app/src/main/res/values-el/strings.xml
+++ b/controller/app/src/main/res/values-el/strings.xml
@@ -1368,4 +1368,13 @@
Η ρύθμιση συνεχίζεται στο παρασκήνιο. Επιστρέψτε οποτεδήποτε για να ελέγξετε την πρόοδο.
Εγκατάσταση δειγμάτων αποθετηρίων
Αυτός ο διακομιστής κώδικα έχει ήδη διαχειριστή που το K2Go δεν μπορεί να διαχειριστεί.
+ Ενημέρωση αποθετηρίων
+ Ενημέρωση αποθετηρίων...
+ Τα αποθετήρια ενημερώθηκαν
+ Όλα τα αποθετήρια είναι ήδη ενημερωμένα
+ Ορισμένα αποθετήρια δεν ήταν δυνατό να ενημερωθούν. Δείτε την περιγραφή του αποθετηρίου.
+ Δεν ήταν δυνατή η ενημέρωση των αποθετηρίων. Δείτε την περιγραφή του αποθετηρίου.
+ Δεν ήταν δυνατή η ενημέρωση των αποθετηρίων
+ Ακύρωση...
+ Η ενημέρωση ακυρώθηκε
diff --git a/controller/app/src/main/res/values-es/strings.xml b/controller/app/src/main/res/values-es/strings.xml
index 69b9abea..ecf2e945 100644
--- a/controller/app/src/main/res/values-es/strings.xml
+++ b/controller/app/src/main/res/values-es/strings.xml
@@ -1437,4 +1437,13 @@
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.
+ Actualizar repos
+ Actualizando repositorios...
+ Repositorios actualizados
+ Todos los repositorios ya estan al dia
+ Algunos repositorios no se pudieron actualizar. Consulta la descripcion del repo.
+ No se pudieron actualizar los repositorios. Consulta la descripcion del repo.
+ No se pudieron actualizar los repositorios
+ Cancelando...
+ Actualizacion cancelada
diff --git a/controller/app/src/main/res/values-fa/strings.xml b/controller/app/src/main/res/values-fa/strings.xml
index 67c809a6..c32b195d 100644
--- a/controller/app/src/main/res/values-fa/strings.xml
+++ b/controller/app/src/main/res/values-fa/strings.xml
@@ -1361,4 +1361,13 @@
راهاندازی در پسزمینه ادامه مییابد. هر زمان خواستید برای بررسی پیشرفت بازگردید.
نصب مخازن نمونه
این میزبان کد از قبل مدیری دارد که K2Go نمیتواند آن را مدیریت کند.
+ بهروزرسانی مخازن
+ در حال بهروزرسانی مخازن...
+ مخازن بهروزرسانی شدند
+ همه مخازن از قبل بهروز هستند
+ برخی مخازن بهروزرسانی نشدند. توضیحات مخزن را ببینید.
+ بهروزرسانی مخازن ممکن نشد. توضیحات مخزن را ببینید.
+ بهروزرسانی مخازن ممکن نشد
+ در حال لغو...
+ بهروزرسانی لغو شد
diff --git a/controller/app/src/main/res/values-fr/strings.xml b/controller/app/src/main/res/values-fr/strings.xml
index e0f1aaef..4fc4b9b9 100644
--- a/controller/app/src/main/res/values-fr/strings.xml
+++ b/controller/app/src/main/res/values-fr/strings.xml
@@ -1448,4 +1448,13 @@
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.
+ Mettre a jour les depots
+ Mise a jour des depots...
+ Depots mis a jour
+ Tous les depots sont deja a jour
+ Certains depots n\'ont pas pu etre mis a jour. Voir la description du depot.
+ Impossible de mettre a jour les depots. Voir la description du depot.
+ Impossible de mettre a jour les depots
+ Annulation...
+ Mise a jour annulee
diff --git a/controller/app/src/main/res/values-gu/strings.xml b/controller/app/src/main/res/values-gu/strings.xml
index 4f83e27a..6b88e8f9 100644
--- a/controller/app/src/main/res/values-gu/strings.xml
+++ b/controller/app/src/main/res/values-gu/strings.xml
@@ -1374,4 +1374,13 @@
સેટઅપ પૃષ્ઠભૂમિમાં ચાલુ રહે છે. પ્રગતિ તપાસવા માટે કોઈપણ સમયે પાછા આવો.
ઉદાહરણ રિપોઝિટરી ઇન્સ્ટૉલ કરો
આ કોડ હોસ્ટ પાસે પહેલેથી જ એક એડમિન છે જેને K2Go સંચાલિત કરી શકતું નથી.
+ રિપોઝિટરી અપડેટ કરો
+ રિપોઝિટરી અપડેટ થઈ રહી છે...
+ રિપોઝિટરી અપડેટ થઈ
+ બધી રિપોઝિટરી પહેલેથી જ અદ્યતન છે
+ કેટલીક રિપોઝિટરી અપડેટ થઈ શકી નથી. રિપો વર્ણન જુઓ.
+ રિપોઝિટરી અપડેટ થઈ શકી નથી. રિપો વર્ણન જુઓ.
+ રિપોઝિટરી અપડેટ થઈ શકી નથી
+ રદ કરી રહ્યાં છીએ...
+ અપડેટ રદ કરાયું
diff --git a/controller/app/src/main/res/values-hi/strings.xml b/controller/app/src/main/res/values-hi/strings.xml
index 440fc62c..fc34978d 100644
--- a/controller/app/src/main/res/values-hi/strings.xml
+++ b/controller/app/src/main/res/values-hi/strings.xml
@@ -1438,4 +1438,13 @@
सेटअप पृष्ठभूमि में जारी रहता है। प्रगति जांचने के लिए किसी भी समय वापस आएं।
उदाहरण रिपॉज़िटरी इंस्टॉल करें
इस कोड होस्ट में पहले से एक एडमिन है जिसे K2Go प्रबंधित नहीं कर सकता।
+ रिपॉज़िटरी अपडेट करें
+ रिपॉज़िटरी अपडेट हो रही हैं...
+ रिपॉज़िटरी अपडेट हो गईं
+ सभी रिपॉज़िटरी पहले से अप-टू-डेट हैं
+ कुछ रिपॉज़िटरी अपडेट नहीं हो सकीं. रिपॉज़िटरी विवरण देखें.
+ रिपॉज़िटरी अपडेट नहीं हो सकीं. रिपॉज़िटरी विवरण देखें.
+ रिपॉज़िटरी अपडेट नहीं हो सकीं
+ रद्द किया जा रहा है...
+ अपडेट रद्द किया गया
diff --git a/controller/app/src/main/res/values-hu/strings.xml b/controller/app/src/main/res/values-hu/strings.xml
index 10d450a5..21f5c57a 100644
--- a/controller/app/src/main/res/values-hu/strings.xml
+++ b/controller/app/src/main/res/values-hu/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Adattarak frissitese
+ Adattarak frissitese...
+ Adattarak frissitve
+ Minden adattar mar naprakesz
+ Nehany adattarat nem sikerult frissiteni. Lasd az adattar leirasat.
+ Nem sikerult frissiteni az adattarakat. Lasd az adattar leirasat.
+ Nem sikerult frissiteni az adattarakat
+ Megszakitas...
+ Frissites megszakitva
diff --git a/controller/app/src/main/res/values-in/strings.xml b/controller/app/src/main/res/values-in/strings.xml
index e6be5888..6cd5d9fb 100644
--- a/controller/app/src/main/res/values-in/strings.xml
+++ b/controller/app/src/main/res/values-in/strings.xml
@@ -1368,4 +1368,13 @@
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.
+ Perbarui repo
+ Memperbarui repositori...
+ Repositori diperbarui
+ Semua repositori sudah yang terbaru
+ Beberapa repositori tidak dapat diperbarui. Lihat deskripsi repo.
+ Tidak dapat memperbarui repositori. Lihat deskripsi repo.
+ Tidak dapat memperbarui repositori
+ Membatalkan...
+ Pembaruan dibatalkan
diff --git a/controller/app/src/main/res/values-it/strings.xml b/controller/app/src/main/res/values-it/strings.xml
index 9a425753..576ecf3d 100644
--- a/controller/app/src/main/res/values-it/strings.xml
+++ b/controller/app/src/main/res/values-it/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Aggiorna i repository
+ Aggiornamento dei repository...
+ Repository aggiornati
+ Tutti i repository sono gia aggiornati
+ Alcuni repository non e stato possibile aggiornarli. Vedi la descrizione del repo.
+ Impossibile aggiornare i repository. Vedi la descrizione del repo.
+ Impossibile aggiornare i repository
+ Annullamento...
+ Aggiornamento annullato
diff --git a/controller/app/src/main/res/values-ja/strings.xml b/controller/app/src/main/res/values-ja/strings.xml
index a23dbf68..a30291b2 100644
--- a/controller/app/src/main/res/values-ja/strings.xml
+++ b/controller/app/src/main/res/values-ja/strings.xml
@@ -1362,4 +1362,13 @@
設定はバックグラウンドで続行されます。いつでも戻って進行状況を確認できます。
サンプルリポジトリをインストール
このコードホストには、K2Go が管理できない管理者がすでに存在します。
+ リポジトリを更新
+ リポジトリを更新しています...
+ リポジトリを更新しました
+ すべてのリポジトリは既に最新です
+ 一部のリポジトリを更新できませんでした。リポジトリの説明を確認してください。
+ リポジトリを更新できませんでした。リポジトリの説明を確認してください。
+ リポジトリを更新できませんでした
+ キャンセルしています...
+ 更新をキャンセルしました
diff --git a/controller/app/src/main/res/values-ko/strings.xml b/controller/app/src/main/res/values-ko/strings.xml
index 4c68a550..dd23ffab 100644
--- a/controller/app/src/main/res/values-ko/strings.xml
+++ b/controller/app/src/main/res/values-ko/strings.xml
@@ -1362,4 +1362,13 @@
설정은 백그라운드에서 계속됩니다. 언제든지 돌아와 진행 상황을 확인하세요.
예제 저장소 설치
이 코드 호스트에는 K2Go가 관리할 수 없는 관리자가 이미 있습니다.
+ 저장소 업데이트
+ 저장소 업데이트 중...
+ 저장소가 업데이트됨
+ 모든 저장소가 이미 최신 상태입니다
+ 일부 저장소를 업데이트할 수 없습니다. 저장소 설명을 확인하세요.
+ 저장소를 업데이트할 수 없습니다. 저장소 설명을 확인하세요.
+ 저장소를 업데이트할 수 없습니다
+ 취소 중...
+ 업데이트 취소됨
diff --git a/controller/app/src/main/res/values-lt/strings.xml b/controller/app/src/main/res/values-lt/strings.xml
index 6cce27a2..75d71627 100644
--- a/controller/app/src/main/res/values-lt/strings.xml
+++ b/controller/app/src/main/res/values-lt/strings.xml
@@ -1378,4 +1378,13 @@
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.
+ Atnaujinti saugyklas
+ Atnaujinamos saugyklos...
+ Saugyklos atnaujintos
+ Visos saugyklos jau atnaujintos
+ Kai kuriu saugyklu nepavyko atnaujinti. Ziurekite saugyklos aprasa.
+ Nepavyko atnaujinti saugyklu. Ziurekite saugyklos aprasa.
+ Nepavyko atnaujinti saugyklu
+ Atsaukiama...
+ Atnaujinimas atsauktas
diff --git a/controller/app/src/main/res/values-nl/strings.xml b/controller/app/src/main/res/values-nl/strings.xml
index a00fa3df..277ce699 100644
--- a/controller/app/src/main/res/values-nl/strings.xml
+++ b/controller/app/src/main/res/values-nl/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Repo\'s bijwerken
+ Repository\'s bijwerken...
+ Repository\'s bijgewerkt
+ Alle repository\'s zijn al up-to-date
+ Sommige repository\'s konden niet worden bijgewerkt. Zie de repobeschrijving.
+ Kan de repository\'s niet bijwerken. Zie de repobeschrijving.
+ Kan de repository\'s niet bijwerken
+ Annuleren...
+ Bijwerken geannuleerd
diff --git a/controller/app/src/main/res/values-no/strings.xml b/controller/app/src/main/res/values-no/strings.xml
index 9f701ab3..ff481e03 100644
--- a/controller/app/src/main/res/values-no/strings.xml
+++ b/controller/app/src/main/res/values-no/strings.xml
@@ -1371,4 +1371,13 @@
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.
+ Oppdater repoer
+ Oppdaterer repositorier...
+ Repositorier oppdatert
+ Alle repositorier er allerede oppdatert
+ Noen repositorier kunne ikke oppdateres. Se repo-beskrivelsen.
+ Kunne ikke oppdatere repositoriene. Se repo-beskrivelsen.
+ Kunne ikke oppdatere repositoriene
+ Avbryter...
+ Oppdatering avbrutt
diff --git a/controller/app/src/main/res/values-pl/strings.xml b/controller/app/src/main/res/values-pl/strings.xml
index a9f4ea26..50f41afc 100644
--- a/controller/app/src/main/res/values-pl/strings.xml
+++ b/controller/app/src/main/res/values-pl/strings.xml
@@ -1371,4 +1371,13 @@
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ć.
+ Aktualizuj repozytoria
+ Aktualizowanie repozytoriow...
+ Repozytoria zaktualizowane
+ Wszystkie repozytoria sa juz aktualne
+ Nie udalo sie zaktualizowac niektorych repozytoriow. Zobacz opis repozytorium.
+ Nie udalo sie zaktualizowac repozytoriow. Zobacz opis repozytorium.
+ Nie udalo sie zaktualizowac repozytoriow
+ Anulowanie...
+ Aktualizacja anulowana
diff --git a/controller/app/src/main/res/values-pt/strings.xml b/controller/app/src/main/res/values-pt/strings.xml
index e05989ac..9dea3c8e 100644
--- a/controller/app/src/main/res/values-pt/strings.xml
+++ b/controller/app/src/main/res/values-pt/strings.xml
@@ -1440,4 +1440,13 @@
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.
+ Atualizar repos
+ Atualizando repositorios...
+ Repositorios atualizados
+ Todos os repositorios ja estao atualizados
+ Nao foi possivel atualizar alguns repositorios. Veja a descricao do repo.
+ Nao foi possivel atualizar os repositorios. Veja a descricao do repo.
+ Nao foi possivel atualizar os repositorios
+ Cancelando...
+ Atualizacao cancelada
diff --git a/controller/app/src/main/res/values-ro/strings.xml b/controller/app/src/main/res/values-ro/strings.xml
index 9b120677..1ca176c5 100644
--- a/controller/app/src/main/res/values-ro/strings.xml
+++ b/controller/app/src/main/res/values-ro/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Actualizeaza depozitele
+ Se actualizeaza depozitele...
+ Depozite actualizate
+ Toate depozitele sunt deja actualizate
+ Unele depozite nu au putut fi actualizate. Vezi descrierea depozitului.
+ Depozitele nu au putut fi actualizate. Vezi descrierea depozitului.
+ Depozitele nu au putut fi actualizate
+ Se anuleaza...
+ Actualizare anulata
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 50241215..0c0e7b3d 100644
--- a/controller/app/src/main/res/values-ru-rRU/strings.xml
+++ b/controller/app/src/main/res/values-ru-rRU/strings.xml
@@ -1437,4 +1437,13 @@
Настройка продолжается в фоновом режиме. Возвращайтесь в любое время, чтобы проверить ход выполнения.
Установить примеры репозиториев
У этого хоста кода уже есть администратор, которым K2Go не может управлять.
+ Обновить репозитории
+ Обновление репозиториев...
+ Репозитории обновлены
+ Все репозитории уже обновлены
+ Некоторые репозитории не удалось обновить. См. описание репозитория.
+ Не удалось обновить репозитории. См. описание репозитория.
+ Не удалось обновить репозитории
+ Отмена...
+ Обновление отменено
diff --git a/controller/app/src/main/res/values-sk/strings.xml b/controller/app/src/main/res/values-sk/strings.xml
index 175b1077..93ee0626 100644
--- a/controller/app/src/main/res/values-sk/strings.xml
+++ b/controller/app/src/main/res/values-sk/strings.xml
@@ -1368,4 +1368,13 @@
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ť.
+ Aktualizovat repozitare
+ Aktualizacia repozitarov...
+ Repozitare aktualizovane
+ Vsetky repozitare su uz aktualne
+ Niektore repozitare sa nepodarilo aktualizovat. Pozri popis repozitara.
+ Repozitare sa nepodarilo aktualizovat. Pozri popis repozitara.
+ Repozitare sa nepodarilo aktualizovat
+ Rusi sa...
+ Aktualizacia zrusena
diff --git a/controller/app/src/main/res/values-sr/strings.xml b/controller/app/src/main/res/values-sr/strings.xml
index 85e6b81e..08bea847 100644
--- a/controller/app/src/main/res/values-sr/strings.xml
+++ b/controller/app/src/main/res/values-sr/strings.xml
@@ -1374,4 +1374,13 @@
Подешавање се наставља у позадини. Вратите се било када да проверите напредак.
Инсталирај примере репозиторијума
Овај хост кода већ има администратора којим K2Go не може да управља.
+ Ажурирај репозиторијуме
+ Ажурирање репозиторијума...
+ Репозиторијуми ажурирани
+ Сви репозиторијуми су већ ажурни
+ Неке репозиторијуме није било могуће ажурирати. Погледајте опис репозиторијума.
+ Није могуће ажурирати репозиторијуме. Погледајте опис репозиторијума.
+ Није могуће ажурирати репозиторијуме
+ Отказивање...
+ Ажурирање отказано
diff --git a/controller/app/src/main/res/values-sw/strings.xml b/controller/app/src/main/res/values-sw/strings.xml
index 1ffd3bda..d790c4dd 100644
--- a/controller/app/src/main/res/values-sw/strings.xml
+++ b/controller/app/src/main/res/values-sw/strings.xml
@@ -1381,4 +1381,13 @@
Usanidi unaendelea chinichini. Rudi wakati wowote kuangalia maendeleo.
Sakinisha hazina za mfano
Seva hii ya msimbo tayari ina msimamizi ambaye K2Go haiwezi kumsimamia.
+ Sasisha hazina
+ Inasasisha hazina...
+ Hazina zimesasishwa
+ Hazina zote tayari zimesasishwa
+ Baadhi ya hazina hazikuweza kusasishwa. Angalia maelezo ya hazina.
+ Imeshindwa kusasisha hazina. Angalia maelezo ya hazina.
+ Imeshindwa kusasisha hazina
+ Inaghairi...
+ Usasishaji umeghairiwa
diff --git a/controller/app/src/main/res/values-ta/strings.xml b/controller/app/src/main/res/values-ta/strings.xml
index 063ce2ea..6ad9b7de 100644
--- a/controller/app/src/main/res/values-ta/strings.xml
+++ b/controller/app/src/main/res/values-ta/strings.xml
@@ -1381,4 +1381,13 @@
அமைப்பு பின்னணியில் தொடர்கிறது. முன்னேற்றத்தைச் சரிபார்க்க எப்போது வேண்டுமானாலும் திரும்பி வாருங்கள்.
எடுத்துக்காட்டு களஞ்சியங்களை நிறுவு
இந்தக் குறியீட்டு ஹோஸ்ட்டில் K2Go நிர்வகிக்க முடியாத ஒரு நிர்வாகி ஏற்கனவே உள்ளார்.
+ களஞ்சியங்களைப் புதுப்பி
+ களஞ்சியங்கள் புதுப்பிக்கப்படுகின்றன...
+ களஞ்சியங்கள் புதுப்பிக்கப்பட்டன
+ அனைத்து களஞ்சியங்களும் ஏற்கனவே புதுப்பித்த நிலையில் உள்ளன
+ சில களஞ்சியங்களைப் புதுப்பிக்க முடியவில்லை. களஞ்சிய விளக்கத்தைப் பார்க்கவும்.
+ களஞ்சியங்களைப் புதுப்பிக்க முடியவில்லை. களஞ்சிய விளக்கத்தைப் பார்க்கவும்.
+ களஞ்சியங்களைப் புதுப்பிக்க முடியவில்லை
+ ரத்து செய்யப்படுகிறது...
+ புதுப்பிப்பு ரத்து செய்யப்பட்டது
diff --git a/controller/app/src/main/res/values-tr/strings.xml b/controller/app/src/main/res/values-tr/strings.xml
index 0137a8c2..6617d577 100644
--- a/controller/app/src/main/res/values-tr/strings.xml
+++ b/controller/app/src/main/res/values-tr/strings.xml
@@ -1361,4 +1361,13 @@
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.
+ Depolari guncelle
+ Depolar guncelleniyor...
+ Depolar guncellendi
+ Tum depolar zaten guncel
+ Bazi depolar guncellenemedi. Depo aciklamasina bakin.
+ Depolar guncellenemedi. Depo aciklamasina bakin.
+ Depolar guncellenemedi
+ Iptal ediliyor...
+ Guncelleme iptal edildi
diff --git a/controller/app/src/main/res/values-uk/strings.xml b/controller/app/src/main/res/values-uk/strings.xml
index 25f44087..65361e3e 100644
--- a/controller/app/src/main/res/values-uk/strings.xml
+++ b/controller/app/src/main/res/values-uk/strings.xml
@@ -1361,4 +1361,13 @@
Налаштування триває у фоновому режимі. Повертайтеся будь-коли, щоб перевірити перебіг.
Встановити приклади репозиторіїв
Цей хост коду вже має адміністратора, яким K2Go не може керувати.
+ Оновити репозиторії
+ Оновлення репозиторіїв...
+ Репозиторії оновлено
+ Усі репозиторії вже оновлені
+ Деякі репозиторії не вдалося оновити. Див. опис репозиторію.
+ Не вдалося оновити репозиторії. Див. опис репозиторію.
+ Не вдалося оновити репозиторії
+ Скасування...
+ Оновлення скасовано
diff --git a/controller/app/src/main/res/values-vi/strings.xml b/controller/app/src/main/res/values-vi/strings.xml
index 9254fe85..87efacda 100644
--- a/controller/app/src/main/res/values-vi/strings.xml
+++ b/controller/app/src/main/res/values-vi/strings.xml
@@ -1361,4 +1361,13 @@
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ý.
+ Cap nhat kho
+ Dang cap nhat kho...
+ Da cap nhat kho
+ Tat ca kho da duoc cap nhat
+ Mot so kho khong the cap nhat. Xem mo ta kho.
+ Khong the cap nhat kho. Xem mo ta kho.
+ Khong the cap nhat kho
+ Dang huy...
+ Da huy cap nhat
diff --git a/controller/app/src/main/res/values-yo/strings.xml b/controller/app/src/main/res/values-yo/strings.xml
index 51d28b15..77664618 100644
--- a/controller/app/src/main/res/values-yo/strings.xml
+++ b/controller/app/src/main/res/values-yo/strings.xml
@@ -1381,4 +1381,13 @@
Ìṣè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.
+ Se imudojuiwon awon ibi itoju
+ Nse imudojuiwon awon ibi itoju...
+ A ti se imudojuiwon awon ibi itoju
+ Gbogbo awon ibi itoju ti wa ni tuntun
+ Awon ibi itoju kan ko seese lati se imudojuiwon. Wo apejuwe ibi itoju.
+ Ko seese lati se imudojuiwon awon ibi itoju. Wo apejuwe ibi itoju.
+ Ko seese lati se imudojuiwon awon ibi itoju
+ Nfagile...
+ A ti fagile imudojuiwon
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 262e8564..32962d0c 100644
--- a/controller/app/src/main/res/values-zh-rCN/strings.xml
+++ b/controller/app/src/main/res/values-zh-rCN/strings.xml
@@ -1362,4 +1362,13 @@
设置将在后台继续。您可以随时返回查看进度。
安装示例仓库
此代码托管已有一个 K2Go 无法管理的管理员。
+ 更新仓库
+ 正在更新仓库...
+ 仓库已更新
+ 所有仓库均已是最新
+ 部分仓库无法更新。请查看仓库描述。
+ 无法更新仓库。请查看仓库描述。
+ 无法更新仓库
+ 正在取消...
+ 更新已取消
diff --git a/controller/app/src/main/res/values/strings.xml b/controller/app/src/main/res/values/strings.xml
index 0f47e839..df6c87c3 100644
--- a/controller/app/src/main/res/values/strings.xml
+++ b/controller/app/src/main/res/values/strings.xml
@@ -1547,4 +1547,13 @@
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.
+ Update repos
+ Updating repositories...
+ Repositories updated
+ All repositories are already up to date
+ Some repositories could not be updated. See the repo description.
+ Could not update the repositories. See the repo description.
+ Could not update the repositories
+ Cancelling...
+ Update cancelled
diff --git a/static/dashboard/CHANGELOG.md b/static/dashboard/CHANGELOG.md
index 08e0cb46..b74de345 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.7** - Non-destructive live repo refresh for Forgejo (K2GO-422). New `POST /forgejo/refresh` runs the box orchestration (`refresh_forgejo`) LIVE (box up, no runrole, no stop) through a detached wrapper (`tools/forgejo-refresh.sh`), and `GET /forgejo/refresh/status` returns `{state, lines}` to poll, and `POST /forgejo/refresh/cancel` stops a running refresh (SIGKILL to the wrapper process group; safe because the refresh only ever fast-forwards or writes a side ref). Per seeded example repo it fast-forwards when pristine, does a clean 3-way merge (`git merge-tree`, no worktree) when the owner committed on the served branch, and skips on conflict; the branch only moves forward (every push is a fast-forward through the forge hooks), so a refresh never drops a commit. It also writes a human status board into each repo description (Last Update / Last Attempt / Status), derived from git state. Localhost-only. (K2GO-422)
- **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)
diff --git a/static/dashboard/package.json b/static/dashboard/package.json
index fa9d3694..e64843a2 100644
--- a/static/dashboard/package.json
+++ b/static/dashboard/package.json
@@ -1,6 +1,6 @@
{
"name": "dashboard-console",
- "version": "1.3.6",
+ "version": "1.3.7",
"description": "",
"main": "index.js",
"scripts": {
diff --git a/static/dashboard/routes.ts b/static/dashboard/routes.ts
index 9853ca1f..da167b07 100644
--- a/static/dashboard/routes.ts
+++ b/static/dashboard/routes.ts
@@ -376,6 +376,77 @@ apiRouter.get('/forgejo/status', async (_req: Request, res: Response): Promise {
+ let running = false;
+ try { running = fs.readFileSync(FORGEJO_REFRESH_STATUS, 'utf8').trim() === 'running'; } catch { /* none */ }
+ if (running) { res.status(409).json({ error: 'a forgejo refresh is already running' }); return; }
+ if (!fs.existsSync(FORGEJO_REFRESH_SCRIPT)) { res.status(500).json({ error: 'forgejo refresh script not found' }); return; }
+ try {
+ // setsid => own session, so a pdsm restart of dash-node cannot kill the refresh mid-run.
+ const child = spawn('setsid', ['bash', FORGEJO_REFRESH_SCRIPT], {
+ detached: true, stdio: 'ignore', env: { ...process.env },
+ });
+ child.unref();
+ // Mark running synchronously before answering, so an immediate poll cannot read a stale state.
+ try { fs.writeFileSync(FORGEJO_REFRESH_STATUS, 'running'); } catch { /* best effort */ }
+ res.status(202).json({ ok: true, state: 'running' });
+ } catch (e: any) {
+ res.status(500).json({ error: e?.message || 'could not start forgejo refresh' });
+ }
+});
+
+apiRouter.get('/forgejo/refresh/status', (_req: Request, res: Response): void => {
+ res.set('Cache-Control', 'no-store');
+ let state = 'idle';
+ try { state = fs.readFileSync(FORGEJO_REFRESH_STATUS, 'utf8').trim() || 'idle'; } catch { /* no file yet */ }
+ let lines: string[] = [];
+ try {
+ const all = fs.readFileSync(FORGEJO_REFRESH_LOG, 'utf8').split('\n');
+ if (all.length && all[all.length - 1] === '') all.pop();
+ lines = all.slice(-FORGEJO_SEED_LOG_TAIL);
+ } catch { /* no log yet */ }
+ // K2GO-422: per-repo outcome counts, so the app can distinguish "updated" / "already up to date" /
+ // "some could not be updated" / "could not update any". Derived from the box's own refresh log (the
+ // only place that knows each repo's outcome). changed = advanced; problems = conflict or a fetch/push
+ // failure (the user reconciles those in the repo web UI); total = repos actually attempted.
+ const changed = lines.filter(l =>
+ l.includes('refresh fast-forward') || l.includes('refresh merged upstream')).length;
+ const problems = lines.filter(l =>
+ l.includes('refresh conflict') || l.includes('refresh fetch failed')
+ || l.includes('refresh ff push failed') || l.includes('refresh merge push failed')).length;
+ const okNoChange = lines.filter(l =>
+ l.includes('refresh up-to-date') || l.includes('refresh already ahead')).length;
+ res.json({ state, lines, changed, problems, total: changed + okNoChange + problems });
+});
+
+// Cancel a running refresh. The wrapper runs under setsid (its own process group), so a SIGKILL to the
+// group (kill -pid) stops the shell and the git child it is on. Safe: the refresh only ever fast-forwards
+// or writes to a side ref, so a mid-run kill leaves the served branches untouched. No-op if not running.
+apiRouter.post('/forgejo/refresh/cancel', (_req: Request, res: Response): void => {
+ let running = false;
+ try { running = fs.readFileSync(FORGEJO_REFRESH_STATUS, 'utf8').trim() === 'running'; } catch { /* none */ }
+ if (!running) { res.json({ ok: true, state: 'idle' }); return; }
+ let pid = 0;
+ try { pid = parseInt(fs.readFileSync(FORGEJO_REFRESH_PID, 'utf8').trim(), 10); } catch { /* no pid */ }
+ if (pid > 0) {
+ try { process.kill(-pid, 'SIGKILL'); } catch { /* already gone */ }
+ }
+ // Mark terminal ourselves: the killed wrapper cannot write its own status.
+ try { fs.writeFileSync(FORGEJO_REFRESH_STATUS, 'cancelled'); } catch { /* best effort */ }
+ res.json({ ok: true, state: 'cancelled' });
+});
+
// 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/forgejo/orchestration b/static/forgejo/orchestration
index 260801e0..58d5cea8 100644
--- a/static/forgejo/orchestration
+++ b/static/forgejo/orchestration
@@ -159,6 +159,9 @@ _fj_migrate() {
# Method B: shallow snapshot -> adopt (recommended; full current tree, compilable, no history).
# K2GO-212: keep the failure output for debugging.
+# K2GO-422: clone at depth 20 (not 1). ~0 extra size on these repos, but it keeps a short recent
+# history so the web UI shows the last changes and so a later refresh has a merge base to compare
+# against (git ancestry is the single source; no extra on-disk marker is written).
_fj_shallow_adopt() {
local owner="$1" name="$2" addr="$3" branch="$4" out
# Forgejo derives the on-disk path as //.git (RepoPath lowercases owner and
@@ -169,7 +172,7 @@ _fj_shallow_adopt() {
local br=""; [ -n "$branch" ] && br="--branch $branch"
mkdir -p "$ldir"
# shellcheck disable=SC2086
- out="$(git clone --bare --depth 1 --single-branch $br "$addr" "$dir" 2>&1)" \
+ out="$(git clone --bare --depth 20 --single-branch $br "$addr" "$dir" 2>&1)" \
|| { warn "clone $owner/$name failed: $out"; return 1; }
chown -R "$FORGEJO_RUN_USER:$FORGEJO_RUN_USER" "$ldir"
out="$(_fj_api -X POST "$FORGEJO_API/admin/unadopted/$owner/$name" 2>&1)" \
@@ -199,3 +202,116 @@ seed_forgejo() {
done
ok "forgejo seeding done"
}
+
+# ===== K2GO-422: live repo refresh (non-destructive) =======================
+# Refresh the seeded example repos from their upstreams WITHOUT a box stop and
+# WITHOUT ever losing a local commit. Per repo:
+# up to date : the served branch equals upstream -> nothing to do.
+# pristine : the served branch is an ancestor of upstream -> fast-forward.
+# diverged : the owner committed on the served branch. Try a real 3-way merge
+# with git merge-tree (no worktree is needed on a bare repo):
+# clean -> commit the merge and advance (keeps their commits);
+# conflict -> leave the branch AS IS and report "blocked" (the
+# owner reconciles in the forge web UI, not here).
+# pristine vs diverged is DERIVED from git ancestry, so no extra on-disk marker is
+# kept (git history is the single source). The served branch only ever moves
+# FORWARD (every push is a fast-forward), so a refresh can never drop a commit.
+# Ref moves go THROUGH forgejo (an authenticated push) so its hooks fire and the
+# forge metadata stays in sync, unlike a raw update-ref.
+
+# Advance a branch to by pushing THROUGH forgejo (fires its hooks). The
+# push is always a fast-forward ( has the old tip as an ancestor), so it
+# can never be destructive; forgejo rejects a non-ff, which we treat as a skip.
+_fj_push_branch() {
+ local owner="$1" name="$2" commit="$3" br="$4"
+ local dir="$FORGEJO_REPO_ROOT/$(_fj_lc "$owner")/$(_fj_lc "$name").git"
+ local auth; auth="$(printf '%s:%s' "$FORGEJO_ADMIN_USER" "$FORGEJO_ADMIN_PASS" | base64 | tr -d '\n')"
+ git -C "$dir" -c "http.extraHeader=Authorization: Basic $auth" \
+ push "http://127.0.0.1:3300/$owner/$name.git" "$commit:refs/heads/$br" >/dev/null 2>&1
+}
+
+# Write the repo description as a human status board. It is a PROJECTION: rebuilt
+# every run from git state + the two stored timestamps, never read back as
+# authority. State is derived from the served branch vs the last-fetched upstream
+# side ref; the two timestamps live in the repo git config (their only home).
+# NOTE: the "--[ ... ]--" section markers are a DELIBERATE exception to the no-"--"
+# rule, allowed only because this string never enters an XML resource. ASCII-only;
+# do NOT copy this template into strings.xml (-- is illegal in an XML comment).
+_fj_write_desc() {
+ local owner="$1" name="$2" dir br cur up state lu la d
+ dir="$FORGEJO_REPO_ROOT/$(_fj_lc "$owner")/$(_fj_lc "$name").git"
+ br="$(git -C "$dir" symbolic-ref --short HEAD 2>/dev/null)"
+ cur="$(git -C "$dir" rev-parse "refs/heads/$br" 2>/dev/null)"
+ up="$(git -C "$dir" rev-parse "refs/k2go/upstream/$br" 2>/dev/null)"
+ if [ -z "$up" ] || [ "$cur" = "$up" ]; then state="updatable"
+ elif git -C "$dir" merge-base --is-ancestor "$cur" "$up" 2>/dev/null; then state="updatable"
+ elif git -C "$dir" merge-base --is-ancestor "$up" "$cur" 2>/dev/null; then state="diverged, updatable"
+ else state="diverged, blocked"; fi
+ lu="$(git -C "$dir" config --get k2go.lastupdate 2>/dev/null)"; [ -n "$lu" ] || lu="n/a"
+ la="$(git -C "$dir" config --get k2go.lastattempt 2>/dev/null)"; [ -n "$la" ] || la="n/a"
+ d="$name example repo. To keep it evergreen, please fork it and apply your changes to your own fork; avoid committing to this branch so we can keep it in sync later on. --[ Last Update: $lu ]-- --[ Last Attempt: $la ]-- --[ Status: $state ]--"
+ _fj_api -X PATCH "$FORGEJO_API/repos/$owner/$name" -H 'Content-Type: application/json' \
+ -d "{\"description\":\"$d\"}" >/dev/null 2>&1 || warn "desc update failed $owner/$name"
+}
+
+# Refresh one repo (guard -> fetch to a side ref -> classify -> ff/merge/skip -> desc).
+_fj_refresh_one() {
+ local owner="$1" name="$2" addr="$3" branch="$4"
+ local dir="$FORGEJO_REPO_ROOT/$(_fj_lc "$owner")/$(_fj_lc "$name").git"
+ local now; now="$(date -u '+%Y-%m-%d %H:%M UTC')"
+ if ! _fj_repo_ready "$owner" "$name"; then log "refresh skip $owner/$name (not seeded)"; return 0; fi
+ [ -d "$dir" ] || { warn "refresh skip $owner/$name (no on-disk dir)"; return 0; }
+ local br="$branch"
+ [ -n "$br" ] || br="$(git -C "$dir" symbolic-ref --short HEAD 2>/dev/null)"
+ [ -n "$br" ] || { warn "refresh skip $owner/$name (no branch)"; return 0; }
+ git -C "$dir" config k2go.lastattempt "$now" # attempt recorded regardless of outcome
+ local uref="refs/k2go/upstream/$br"
+ if ! git -C "$dir" fetch --depth 20 "$addr" "+$br:$uref" >/dev/null 2>&1; then
+ warn "refresh fetch failed $owner/$name (offline?)"; _fj_write_desc "$owner" "$name"; return 0
+ fi
+ local cur up; cur="$(git -C "$dir" rev-parse "refs/heads/$br")"; up="$(git -C "$dir" rev-parse "$uref")"
+ # If neither tip is an ancestor of the other, the link may just be cut by the shallow boundary (a repo
+ # that fell more than ~20 commits behind upstream). Deepen once and re-read upstream BEFORE deciding it
+ # is diverged, so a far-behind PRISTINE repo fast-forwards instead of being falsely reported "blocked".
+ if [ -n "$cur" ] && [ -n "$up" ] && [ "$cur" != "$up" ] \
+ && ! git -C "$dir" merge-base --is-ancestor "$cur" "$up" 2>/dev/null \
+ && ! git -C "$dir" merge-base --is-ancestor "$up" "$cur" 2>/dev/null; then
+ git -C "$dir" fetch --deepen 200 "$addr" "+$br:$uref" >/dev/null 2>&1 || true
+ up="$(git -C "$dir" rev-parse "$uref")"
+ fi
+ if [ "$cur" = "$up" ]; then
+ git -C "$dir" config k2go.lastupdate "$now"; log "refresh up-to-date $owner/$name"
+ elif git -C "$dir" merge-base --is-ancestor "$cur" "$up"; then
+ if _fj_push_branch "$owner" "$name" "$up" "$br"; then
+ git -C "$dir" config k2go.lastupdate "$now"; ok "refresh fast-forward $owner/$name"
+ else warn "refresh ff push failed $owner/$name"; fi
+ elif git -C "$dir" merge-base --is-ancestor "$up" "$cur"; then
+ # the served branch already contains all of upstream (local commits sit on top)
+ # -> nothing to pull. A no-op, so a diverged-but-merged repo does not pile a
+ # redundant merge commit on every refresh.
+ git -C "$dir" config k2go.lastupdate "$now"; log "refresh already ahead $owner/$name"
+ else
+ local tree merged
+ if tree="$(git -C "$dir" merge-tree --write-tree "$cur" "$up" 2>/dev/null)"; then
+ merged="$(GIT_AUTHOR_NAME=K2Go GIT_AUTHOR_EMAIL=k2go@box.lan GIT_COMMITTER_NAME=K2Go GIT_COMMITTER_EMAIL=k2go@box.lan \
+ git -C "$dir" commit-tree "$tree" -p "$cur" -p "$up" -m "Merge upstream into $br (K2Go refresh)" 2>/dev/null)"
+ if [ -n "$merged" ] && _fj_push_branch "$owner" "$name" "$merged" "$br"; then
+ git -C "$dir" config k2go.lastupdate "$now"; ok "refresh merged upstream into $owner/$name"
+ else warn "refresh merge push failed $owner/$name"; fi
+ else
+ warn "refresh conflict $owner/$name (left as is; reconcile in the web UI)"
+ fi
+ fi
+ _fj_write_desc "$owner" "$name"
+}
+
+# ===== entry point: refresh (what the dash-node refresh endpoint calls) =====
+refresh_forgejo() {
+ _fj_wait_200 || return 1
+ _fj_resolve_admin_creds # same single-source admin credential the seed uses
+ printf '%s\n' "$FORGEJO_REPOS" | while IFS='|' read -r owner name addr branch; do
+ [ -n "$owner" ] && [ -n "$addr" ] || continue
+ _fj_refresh_one "$owner" "$name" "$addr" "$branch" || true # one repo must not abort the rest
+ done
+ ok "forgejo refresh done"
+}
diff --git a/tools/forgejo-refresh.sh b/tools/forgejo-refresh.sh
new file mode 100644
index 00000000..5c70de50
--- /dev/null
+++ b/tools/forgejo-refresh.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# ============================================================================
+# Name : tools/forgejo-refresh.sh
+# Author : AppDevForAll
+# Copyright : Copyright (c) 2026 AppDevForAll
+# Description : K2GO-422. dash-node-driven Forgejo repo refresh. Runs the box
+# orchestration (static/forgejo/orchestration -> refresh_forgejo)
+# LIVE, while the box is up (no runrole, no box stop), and writes
+# a status file + a log that the dash-node endpoint polls.
+# Mirrors tools/forgejo-seed.sh (status + detached run).
+#
+# Non-destructive: each seeded example repo is fast-forwarded when pristine, or
+# merged when the owner committed locally and the merge is clean; a conflict is
+# left AS IS and reported. The served branch only moves forward. The refresh
+# operates on whatever is already seeded (it skips repos that are absent).
+# ============================================================================
+set -u
+STATUS=/var/run/forgejo-refresh.status
+LOG=/var/log/forgejo-refresh.log
+PID=/var/run/forgejo-refresh.pid
+ORCH=/opt/iiab-android/static/forgejo/orchestration
+
+: > "$LOG" 2>/dev/null || true
+echo running > "$STATUS" 2>/dev/null || true
+# K2GO-422: dash-node spawns this with setsid, so this shell is the session/process-group leader.
+# Record its PID so POST /forgejo/refresh/cancel can stop the whole group (kill -PID) mid-run.
+echo $$ > "$PID" 2>/dev/null || true
+# Remove the pid file on a normal exit so a finished run leaves no stale pid for cancel to kill (a reused
+# pid). A SIGKILL from cancel skips this, but cancel sets status to 'cancelled', which already guards it.
+trap 'rm -f "$PID" 2>/dev/null' EXIT
+
+{
+ if [ ! -f "$ORCH" ]; then
+ echo "forgejo-refresh: orchestration not found at $ORCH"
+ echo error > "$STATUS" 2>/dev/null || true
+ exit 1
+ fi
+ # shellcheck disable=SC1090
+ . "$ORCH"
+ # Refresh the full example set; _fj_refresh_one skips any repo that is not seeded.
+ export FORGEJO_REPOS="$FORGEJO_REPOS_FULL"
+ if refresh_forgejo; then
+ echo done > "$STATUS" 2>/dev/null || true
+ else
+ echo error > "$STATUS" 2>/dev/null || true
+ fi
+} >> "$LOG" 2>&1