From 46bf0a587e9b97843d876755a91d7553c450a798 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Wed, 23 Sep 2026 19:30:07 -0600 Subject: [PATCH 1/9] K2GO-212 chore(proot): carry the upstream Forgejo role (iiab#4505) Stage the consolidated iiab/iiab#4505 diff as an upstream-patch carry so the bake gets the forgejo role until it merges upstream. The applier self-skips it once #4505 ships in the pinned iiab commit. --- .../patches/0003-forgejo-add-role-4505.patch | 1102 +++++++++++++++++ 1 file changed, 1102 insertions(+) create mode 100644 tools/upstream-patches/patches/0003-forgejo-add-role-4505.patch diff --git a/tools/upstream-patches/patches/0003-forgejo-add-role-4505.patch b/tools/upstream-patches/patches/0003-forgejo-add-role-4505.patch new file mode 100644 index 000000000..d134bfba0 --- /dev/null +++ b/tools/upstream-patches/patches/0003-forgejo-add-role-4505.patch @@ -0,0 +1,1102 @@ +Upstream-PR: https://github.com/iiab/iiab/pull/4505 +Upstream-Status: open (carried until it ships in the pinned iiab commit) +Applies-to: roles/forgejo/* (new role); roles/0-init/tasks/{validate_*,disallow_*} (forgejo asserts); roles/6-generic-apps/tasks/main.yml (role include); roles/proot_services (pdsm forgejo service + mariadb responsiveness wrapper); roles/matomo/defaults + vars/default_vars.yml (mariadb_log_dir move); vars/local_vars_android_{small,medium,large}.yml + vars/local_vars_large.yml (forgejo section, install/enabled False) +Summary: Add the Forgejo role (offline self-hosted Git forge) for K2GO-212. Serves at http://box/forgejo (nginx to 127.0.0.1:3300); sqlite by default; HTTP only under proot. Needs the K2GO-411 proot futex_time64 fix to run 32-bit on pre-5.1 kernels. + +Mirrors upstream iiab/iiab#4505. This is the CONSOLIDATED net diff of the PR, not +its 13-commit mbox: the applier uses "patch -p1" with a reverse-dry-run +idempotency check, so a single net diff reverse-applies cleanly and is skipped +once #4505 ships in the pinned iiab commit. Text only, so it takes the "patch -p1" +path (no binary hunks). The role ships "forgejo_install/enabled: False" in every +vars file; K2Go's bake flips them True per tier in "iiab-android". Remove this +patch once #4505 merges into the pinned iiab commit. + +diff --git a/roles/0-init/tasks/disallow_install_false_with_enabled_true.yml b/roles/0-init/tasks/disallow_install_false_with_enabled_true.yml +index e638f72c71..94b99ab055 100644 +--- a/roles/0-init/tasks/disallow_install_false_with_enabled_true.yml ++++ b/roles/0-init/tasks/disallow_install_false_with_enabled_true.yml +@@ -76,6 +76,12 @@ + fail_msg: "DISALLOWED: 'azuracast_install: False' WITH 'azuracast_enabled: True' -- IIAB DOES NOT SUPPORT UNINSTALLS -- please verify those 2 variable values e.g. in /etc/iiab/local_vars.yml, and other places variables are defined?" + quiet: yes + ++- name: "DISALLOW 'forgejo_install: False' WITH 'forgejo_enabled: True'" ++ assert: ++ that: forgejo_install or not forgejo_enabled ++ fail_msg: "DISALLOWED: 'forgejo_install: False' WITH 'forgejo_enabled: True' -- IIAB DOES NOT SUPPORT UNINSTALLS -- please verify those 2 variable values e.g. in /etc/iiab/local_vars.yml, and other places variables are defined?" ++ quiet: yes ++ + - name: "DISALLOW 'gitea_install: False' WITH 'gitea_enabled: True'" + assert: + that: gitea_install or not gitea_enabled +diff --git a/roles/0-init/tasks/disallow_install_false_with_installed_defined.yml b/roles/0-init/tasks/disallow_install_false_with_installed_defined.yml +index 6a1d19375d..173d58cfab 100644 +--- a/roles/0-init/tasks/disallow_install_false_with_installed_defined.yml ++++ b/roles/0-init/tasks/disallow_install_false_with_installed_defined.yml +@@ -76,6 +76,12 @@ + fail_msg: "DISALLOWED: 'azuracast_install: False' (e.g. in /etc/iiab/local_vars.yml) WHEN 'azuracast_installed' is defined (e.g. in /etc/iiab/iiab_state.yml) -- IIAB DOES NOT SUPPORT UNINSTALLS -- please verify those 2 files especially, and other places variables are defined?" + quiet: yes + ++- name: "DISALLOW 'forgejo_install: False' WHEN forgejo_installed is defined" ++ assert: ++ that: forgejo_install or forgejo_installed is undefined ++ fail_msg: "DISALLOWED: 'forgejo_install: False' (e.g. in /etc/iiab/local_vars.yml) WHEN 'forgejo_installed' is defined (e.g. in /etc/iiab/iiab_state.yml) -- IIAB DOES NOT SUPPORT UNINSTALLS -- please verify those 2 files especially, and other places variables are defined?" ++ quiet: yes ++ + - name: "DISALLOW 'gitea_install: False' WHEN gitea_installed is defined" + assert: + that: gitea_install or gitea_installed is undefined +diff --git a/roles/0-init/tasks/validate_enabled_vars_boolean.yml b/roles/0-init/tasks/validate_enabled_vars_boolean.yml +index 8b3e21cc7a..4e34355143 100644 +--- a/roles/0-init/tasks/validate_enabled_vars_boolean.yml ++++ b/roles/0-init/tasks/validate_enabled_vars_boolean.yml +@@ -76,6 +76,12 @@ + fail_msg: "VARIABLE MUST BE BOOLEAN: 'azuracast_enabled' now has type '{{ azuracast_enabled | type_debug }}' and value '{{ azuracast_enabled }}' -- PLEASE SET A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" + quiet: yes + ++- name: Assert that var forgejo_enabled is type boolean (NOT type string, which can invert logic!) ++ assert: ++ that: forgejo_enabled | type_debug == 'bool' ++ fail_msg: "VARIABLE MUST BE BOOLEAN: 'forgejo_enabled' now has type '{{ forgejo_enabled | type_debug }}' and value '{{ forgejo_enabled }}' -- PLEASE SET A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ + - name: Assert that var gitea_enabled is type boolean (NOT type string, which can invert logic!) + assert: + that: gitea_enabled | type_debug == 'bool' +diff --git a/roles/0-init/tasks/validate_enabled_vars_defined.yml b/roles/0-init/tasks/validate_enabled_vars_defined.yml +index 0af5f95a70..829374d395 100644 +--- a/roles/0-init/tasks/validate_enabled_vars_defined.yml ++++ b/roles/0-init/tasks/validate_enabled_vars_defined.yml +@@ -76,6 +76,12 @@ + fail_msg: "VARIABLE MUST BE DEFINED: 'azuracast_enabled' NEEDS A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" + quiet: yes + ++- name: Assert that var forgejo_enabled is defined ++ assert: ++ that: forgejo_enabled is defined ++ fail_msg: "VARIABLE MUST BE DEFINED: 'forgejo_enabled' NEEDS A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ + - name: Assert that var gitea_enabled is defined + assert: + that: gitea_enabled is defined +diff --git a/roles/0-init/tasks/validate_install_vars_boolean.yml b/roles/0-init/tasks/validate_install_vars_boolean.yml +index 35cde63642..a7d20baf7f 100644 +--- a/roles/0-init/tasks/validate_install_vars_boolean.yml ++++ b/roles/0-init/tasks/validate_install_vars_boolean.yml +@@ -76,6 +76,12 @@ + fail_msg: "VARIABLE MUST BE BOOLEAN: 'azuracast_install' now has type '{{ lookup('vars', 'azuracast_install') | type_debug }}' and value '{{ lookup('vars', 'azuracast_install') }}' -- PLEASE SET A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" + quiet: yes + ++- name: Assert that var forgejo_install is type boolean (NOT type string, which can invert logic!) ++ assert: ++ that: forgejo_install | type_debug == 'bool' ++ fail_msg: "VARIABLE MUST BE BOOLEAN: 'forgejo_install' now has type '{{ lookup('vars', 'forgejo_install') | type_debug }}' and value '{{ lookup('vars', 'forgejo_install') }}' -- PLEASE SET A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ + - name: Assert that var gitea_install is type boolean (NOT type string, which can invert logic!) + assert: + that: gitea_install | type_debug == 'bool' +diff --git a/roles/0-init/tasks/validate_install_vars_defined.yml b/roles/0-init/tasks/validate_install_vars_defined.yml +index 31f772ecdc..c9932bff94 100644 +--- a/roles/0-init/tasks/validate_install_vars_defined.yml ++++ b/roles/0-init/tasks/validate_install_vars_defined.yml +@@ -76,6 +76,12 @@ + fail_msg: "VARIABLE MUST BE DEFINED: 'azuracast_install' NEEDS A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" + quiet: yes + ++- name: Assert that var forgejo_install is defined ++ assert: ++ that: forgejo_install is defined ++ fail_msg: "VARIABLE MUST BE DEFINED: 'forgejo_install' NEEDS A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ + - name: Assert that var gitea_install is defined + assert: + that: gitea_install is defined +diff --git a/roles/6-generic-apps/tasks/main.yml b/roles/6-generic-apps/tasks/main.yml +index 67054777ae..c450f3286e 100644 +--- a/roles/6-generic-apps/tasks/main.yml ++++ b/roles/6-generic-apps/tasks/main.yml +@@ -26,6 +26,12 @@ + name: gitea + when: gitea_install and not is_proot + ++# Forgejo supports proot (via pdsm), so it is NOT guarded by 'not is_proot' ++- name: FORGEJO ++ include_role: ++ name: forgejo ++ when: forgejo_install ++ + - name: JUPYTERHUB + include_role: + name: jupyterhub +diff --git a/roles/forgejo/defaults/main.yml b/roles/forgejo/defaults/main.yml +new file mode 100644 +index 0000000000..89c5018c38 +--- /dev/null ++++ b/roles/forgejo/defaults/main.yml +@@ -0,0 +1,113 @@ ++# forgejo_install: True ++# forgejo_enabled: True ++ ++# Sibling of the 'gitea' role. Forgejo is a community fork of Gitea, so its ++# app.ini and CLI are compatible. ++ ++# --- Version and download --------------------------------------------------- ++ ++# Forgejo has no "latest patch of a branch" URL like dl.gitea.com, so install ++# pins the full LTS version below for a deterministic, offline-reproducible build. ++# TO UPDATE: bump forgejo_version here (or in local_vars), then re-run the role with ++# 'runrole --reinstall forgejo'. On a reinstall the role backs up (forgejo dump) ++# before swapping the binary, then restarts so the DB schema migrations run - it does ++# NOT wipe /library/forgejo/data (repos, DB), so user work is preserved. ++forgejo_lts_major: 15 # current LTS line (v15.x); change when the LTS major moves ++forgejo_version: "15.0.9" # pinned patch; bump + 'runrole --reinstall forgejo' to update ++ ++# forgejo's [git.timeout] MIGRATE (seconds). The forgejo default is 600s, which is ++# too low for large repos with ~1GB it takes around ~15min; raise it so seeding big ++# repos does not time out. Baked into app.ini.j2. ++forgejo_migrate_timeout: 7200 ++ ++# uname -m -> Forgejo asset suffix. Forgejo ships amd64, arm64 and arm-6 only. ++# 32-bit ARM (arm-6) IS supported; there is NO 32-bit x86 (i386) build, so i386 ++# is left out on purpose and resolves to 'unknown' -> the install fails clearly. ++iset_suffixes: ++ x86_64: amd64 ++ aarch64: arm64 ++ armv7l: arm-6 ++ armv6l: arm-6 ++ armv8l: arm-6 # 32-bit userland on a 64-bit ARM kernel (e.g. 32-bit app on ++ # an arm64 phone) reports armv8l via the PER_LINUX32 personality ++ ++forgejo_iset_suffix: "{{ iset_suffixes[ansible_facts.machine] | default('unknown') }}" ++ ++forgejo_download_base: "https://codeberg.org/forgejo/forgejo/releases/download/v{{ forgejo_version }}" ++forgejo_download_url: "{{ forgejo_download_base }}/forgejo-{{ forgejo_version }}-linux-{{ forgejo_iset_suffix }}" ++forgejo_asc_url: "{{ forgejo_download_url }}.asc" ++forgejo_sha256_url: "{{ forgejo_download_url }}.sha256" ++ ++# Forgejo release signing key (keys.openpgp.org). Upstream rotates it regularly ++# and recommends re-fetching each time; GPG verify is best-effort (non-fatal), ++# same approach as the gitea role. ++forgejo_gpg_key: EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 ++ ++# --- Filesystem layout ------------------------------------------------------ ++ ++forgejo_root_directory: "{{ content_base }}/forgejo" # /library/forgejo ++forgejo_subdirectories: ++ - bin ++ - custom ++ - data ++ - data/repositories # forgejo creates this lazily on the 1st repo; pre-create it so ++ # 'forgejo dump' (the pre-upgrade backup) works on a repo-less install ++ - log ++ - backups ++ ++forgejo_install_path: "{{ forgejo_root_directory }}/bin/forgejo-{{ forgejo_version }}" ++forgejo_asc_path: "{{ forgejo_root_directory }}/bin/forgejo-{{ forgejo_version }}.asc" ++forgejo_link_path: "{{ forgejo_root_directory }}/forgejo" # stable symlink -> versioned binary ++ ++# Data locations (git repos and LFS live on disk, NOT in the database) ++forgejo_repo_root: "{{ forgejo_root_directory }}/data/repositories" ++forgejo_lfs_root: "{{ forgejo_root_directory }}/data/lfs" ++forgejo_db_path: "{{ forgejo_root_directory }}/data/forgejo.db" # sqlite only ++forgejo_log_root: "{{ forgejo_root_directory }}/log" ++forgejo_backups_root: "{{ forgejo_root_directory }}/backups" # forgejo dump target (in-place upgrade backup) ++ ++# --- Config (/etc/forgejo) -------------------------------------------------- ++ ++forgejo_config_dir: /etc/forgejo ++forgejo_config_path: "{{ forgejo_config_dir }}/app.ini" ++ ++# Secrets are generated once into these files and referenced by *_URI in app.ini ++# (better than gitea's hard-coded keys). See tasks/install.yml (guarded by creates). ++forgejo_secret_key_path: "{{ forgejo_config_dir }}/secret_key" ++forgejo_internal_token_path: "{{ forgejo_config_dir }}/internal_token" ++forgejo_jwt_secret_path: "{{ forgejo_config_dir }}/jwt_secret" ++forgejo_lfs_jwt_secret_path: "{{ forgejo_config_dir }}/lfs_jwt_secret" ++ ++forgejo_display_name: Forgejo ++forgejo_run_mode: prod # gitea uses 'dev' ++forgejo_install_lock: true # skip the web install screen; first user becomes admin ++ ++# --- Database --------------------------------------------------------------- ++ ++# 'sqlite' (default, zero extra services, required under proot) or 'mariadb'. ++# The DB only stores metadata, so repo size does not drive this choice; write ++# concurrency does. This only sets up a FRESH DB for the chosen engine. ++# Switching engines on an EXISTING install is refused by the role (no auto-migration). ++# To move data yourself: `forgejo dump`, load it into the new DB (`forgejo restore` / ++# your DB tools), then set forgejo_db_engine to match and re-run to keep updating. ++forgejo_db_engine: sqlite ++forgejo_db_name: forgejo ++forgejo_db_user: forgejo ++forgejo_db_password: changeme # only when forgejo_db_engine == mariadb; override in local_vars.yml ++ ++# --- Runtime user and network ---------------------------------------------- ++ ++forgejo_user: forgejo ++forgejo_home: "/home/{{ forgejo_user }}" ++forgejo_run_directory: "{{ forgejo_root_directory }}" ++ ++forgejo_url: /forgejo ++forgejo_port: 3300 # avoids the kiwix 3000 clash under proot ++ ++# Git LFS on by default (helps with large IDE binaries; objects live on disk) ++forgejo_lfs: true ++ ++# Built-in SSH server: on for systemd (x86_64/arm64) ++forgejo_enable_ssh: "{{ not is_proot }}" ++forgejo_ssh_port: 2222 ++ +diff --git a/roles/forgejo/tasks/enable-or-disable.yml b/roles/forgejo/tasks/enable-or-disable.yml +new file mode 100644 +index 0000000000..c1891a0353 +--- /dev/null ++++ b/roles/forgejo/tasks/enable-or-disable.yml +@@ -0,0 +1,74 @@ ++# --- systemd platforms (x86_64/arm64) --- ++ ++- name: Enable & Restart 'forgejo' systemd service, if forgejo_enabled (not proot) ++ systemd: ++ name: forgejo ++ daemon_reload: yes ++ enabled: yes ++ state: restarted ++ when: ++ - forgejo_enabled ++ - not is_proot ++ ++- name: Disable & Stop 'forgejo' systemd service, if not forgejo_enabled (not proot) ++ systemd: ++ name: forgejo ++ enabled: no ++ state: stopped ++ when: ++ - not forgejo_enabled ++ - not is_proot ++ ++ ++# --- proot platform (pdsm) --- ++ ++- name: Enable & Start 'forgejo' via pdsm (proot), if forgejo_enabled ++ when: ++ - forgejo_enabled ++ - is_proot ++ block: ++ - name: Enable 'forgejo' via pdsm (proot) ++ command: pdsm enable forgejo ++ register: pdsm_enable ++ changed_when: "'already enabled' not in pdsm_enable.stdout" ++ ++ - name: Restart 'forgejo' via pdsm (proot) ++ command: pdsm restart forgejo ++ ++- name: Disable & Stop 'forgejo' via pdsm (proot), if not forgejo_enabled ++ when: ++ - not forgejo_enabled ++ - is_proot ++ block: ++ - name: Stop 'forgejo' via pdsm (proot) ++ command: pdsm stop forgejo ++ register: pdsm_stop ++ changed_when: "'already stopped' not in pdsm_stop.stdout" ++ ++ - name: Disable 'forgejo' via pdsm (proot) ++ command: pdsm disable forgejo ++ ++ ++# --- NGINX reverse proxy (both platforms) --- ++ ++- name: Enable http://box{{ forgejo_url }} via NGINX, by installing {{ nginx_conf_dir }}/forgejo-nginx.conf from template ++ template: ++ src: forgejo-nginx.conf.j2 ++ dest: "{{ nginx_conf_dir }}/forgejo-nginx.conf" # /etc/nginx/conf.d ++ when: forgejo_enabled ++ ++- name: Disable http://box{{ forgejo_url }} via NGINX, by removing {{ nginx_conf_dir }}/forgejo-nginx.conf ++ file: ++ path: "{{ nginx_conf_dir }}/forgejo-nginx.conf" # /etc/nginx/conf.d ++ state: absent ++ when: not forgejo_enabled ++ ++- name: Reload 'nginx' systemd service (not proot) ++ systemd: ++ name: nginx ++ state: restarted ++ when: not is_proot ++ ++- name: Restart 'nginx' via pdsm (proot) ++ command: pdsm restart nginx ++ when: is_proot +diff --git a/roles/forgejo/tasks/install.yml b/roles/forgejo/tasks/install.yml +new file mode 100644 +index 0000000000..d04418b0cb +--- /dev/null ++++ b/roles/forgejo/tasks/install.yml +@@ -0,0 +1,269 @@ ++- name: Record (initial) disk space used ++ shell: df -B1 --output=used / | tail -1 ++ register: df1 ++ ++ ++# 1. Prepare to install Forgejo: stop any running instance, create user and dirs ++ ++- name: Stop existing Forgejo systemd service (if reinstalling, not proot) ++ systemd: ++ name: forgejo ++ state: stopped ++ when: not is_proot ++ ignore_errors: yes ++ ++- name: Stop existing Forgejo via pdsm (if reinstalling, proot) ++ command: pdsm stop forgejo ++ when: is_proot ++ ignore_errors: yes ++ ++- name: Ensure group 'forgejo' exists ++ group: ++ name: "{{ forgejo_user }}" ++ state: present ++ ++- name: Create user 'forgejo' ++ user: ++ name: "{{ forgejo_user }}" ++ comment: Forgejo daemon account ++ group: "{{ forgejo_user }}" ++ home: "{{ forgejo_home }}" # /home/forgejo ++ ++- name: Create {{ forgejo_root_directory }} directory structure ++ file: ++ path: "{{ forgejo_root_directory }}/{{ item }}" # /library/forgejo ++ state: directory ++ owner: "{{ forgejo_user }}" ++ group: "{{ forgejo_user }}" ++ with_items: "{{ forgejo_subdirectories }}" ++ ++- name: Make directories data, log, backups writable (0750) ++ file: ++ path: "{{ forgejo_root_directory }}/{{ item }}" ++ mode: '0750' ++ with_items: ++ - data ++ - log ++ - backups ++ ++ ++# 1b. In-place upgrade safety. install.yml only runs when 'forgejo_installed' is ++# undefined, i.e. on a fresh install OR on 'runrole --reinstall forgejo' (which ++# clears that flag). This is how a reinstall doubles as the UPDATE path. When an ++# install already exists (app.ini present), back up the DB + repos with 'forgejo ++# dump' BEFORE swapping the binary, so a bad schema migration on the next restart ++# can be recovered. /library/forgejo/data (repos, DB) is never wiped, so user work ++# is preserved. Skipped on a fresh install (nothing to back up). ++ ++- name: Check whether Forgejo is already installed (app.ini present) ++ stat: ++ path: "{{ forgejo_config_path }}" ++ register: forgejo_existing_config ++ ++# The role sets up a FRESH DB for the chosen engine but never migrates data between ++# engines. Refuse a sqlite<->mariadb switch on an existing install, else we'd silently ++# start on an empty DB and orphan the old data. Manual migration path: see defaults/main.yml. ++- name: Detect the existing install's DB engine (engine-change guard) ++ when: forgejo_existing_config.stat.exists ++ command: "grep -oE '^DB_TYPE[[:space:]]*=[[:space:]]*[A-Za-z0-9]+' {{ forgejo_config_path }}" ++ register: forgejo_existing_db_type ++ changed_when: false ++ failed_when: false ++ ++- name: Refuse an unsupported DB engine change on an existing install ++ vars: ++ _cur: "{{ forgejo_existing_db_type.stdout | default('') }}" ++ fail: ++ msg: >- ++ Existing Forgejo install uses '{{ _cur }}' but forgejo_db_engine='{{ forgejo_db_engine }}'. ++ The role does not migrate data between engines. Migrate manually (see ++ roles/forgejo/defaults/main.yml), then set forgejo_db_engine to match and re-run. ++ when: ++ - forgejo_existing_config.stat.exists ++ - (forgejo_db_engine == 'mariadb' and 'sqlite' in _cur) or ++ (forgejo_db_engine == 'sqlite' and 'mysql' in _cur) ++ ++- name: Back up Forgejo before an in-place upgrade ++ when: forgejo_existing_config.stat.exists ++ block: ++ # ansible_date_time is NOT populated under the proot fact subset ++ # (all,!hardware,!network), so derive the timestamp from 'date' instead. ++ - name: Compute a backup timestamp (proot-safe, no fact dependency) ++ command: date -u +%Y%m%dT%H%M%SZ ++ register: forgejo_backup_ts ++ changed_when: false ++ ++ - name: "Back up Forgejo (forgejo dump) to {{ forgejo_backups_root }} before swapping the binary" ++ command: > ++ {{ forgejo_link_path }} dump ++ --config {{ forgejo_config_path }} ++ --file {{ forgejo_backups_root }}/forgejo-dump-{{ forgejo_backup_ts.stdout }}.zip ++ args: ++ chdir: "{{ forgejo_backups_root }}" ++ become: yes ++ become_user: "{{ forgejo_user }}" ++ ++ ++# 2. Download, verify, and link the Forgejo binary ++ ++- name: Fail if we detect an unsupported CPU architecture ++ fail: ++ msg: "No Forgejo binary for CPU architecture \"{{ ansible_facts.machine }}\" (Forgejo ships amd64, arm64, arm-6 only; 32-bit x86 is unsupported)" ++ when: forgejo_iset_suffix == "unknown" ++ ++- name: Download Forgejo binary {{ forgejo_download_url }} to {{ forgejo_install_path }} (0775, ~100 MB, SLOW) ++ get_url: ++ url: "{{ forgejo_download_url }}" ++ dest: "{{ forgejo_install_path }}" # /library/forgejo/bin/forgejo- ++ mode: "0775" ++ timeout: "{{ download_timeout }}" ++ ++- name: Download Forgejo GPG signature {{ forgejo_asc_url }} to {{ forgejo_asc_path }} ++ get_curl: ++ url: "{{ forgejo_asc_url }}" ++ dest: "{{ forgejo_asc_path }}" ++ timeout: "{{ download_timeout }}" ++ ++- name: Verify Forgejo binary with GPG signature (best-effort, non-fatal like the gitea role) ++ shell: | ++ gpg --keyserver keys.openpgp.org --recv {{ forgejo_gpg_key }} ++ gpg --verify {{ forgejo_asc_path }} {{ forgejo_install_path }} ++ ignore_errors: yes ++ ++- name: Symlink {{ forgejo_link_path }} -> {{ forgejo_install_path }} ++ file: ++ src: "{{ forgejo_install_path }}" ++ path: "{{ forgejo_link_path }}" # /library/forgejo/forgejo ++ owner: "{{ forgejo_user }}" ++ group: "{{ forgejo_user }}" ++ state: link ++ force: yes ++ ++# An update (runrole --reinstall forgejo) downloads a new bin/forgejo- and repoints ++# the symlink above; the superseded binary (~110 MB each) would otherwise pile up in bin/. ++# Keep only the active version and its .asc, so disk growth per update stays bounded. ++- name: Find installed Forgejo binaries in {{ forgejo_install_path | dirname }} ++ find: ++ paths: "{{ forgejo_install_path | dirname }}" # /library/forgejo/bin ++ patterns: "forgejo-*" ++ register: forgejo_bin_files ++ ++- name: Remove superseded Forgejo binaries (keep only {{ forgejo_version }}) ++ file: ++ path: "{{ item.path }}" ++ state: absent ++ loop: "{{ forgejo_bin_files.files }}" ++ loop_control: ++ label: "{{ item.path | basename }}" ++ when: ++ - forgejo_version | string | length > 0 # never prune when the version is unset ++ - (item.path | basename) != ('forgejo-' ~ forgejo_version) ++ - (item.path | basename) != ('forgejo-' ~ forgejo_version ~ '.asc') ++ ++ ++# 3. Configure Forgejo (/etc/forgejo) ++ ++- name: mkdir {{ forgejo_config_dir }} (0770 root:forgejo) ++ file: ++ state: directory ++ path: "{{ forgejo_config_dir }}" ++ owner: root ++ group: "{{ forgejo_user }}" ++ mode: "0770" ++ ++- name: Generate Forgejo secrets once (guarded by 'creates'), referenced by *_URI in app.ini ++ shell: "umask 027; {{ forgejo_link_path }} generate secret {{ item.kind }} > {{ item.path }}" ++ args: ++ creates: "{{ item.path }}" ++ with_items: ++ - { kind: SECRET_KEY, path: "{{ forgejo_secret_key_path }}" } ++ - { kind: INTERNAL_TOKEN, path: "{{ forgejo_internal_token_path }}" } ++ - { kind: JWT_SECRET, path: "{{ forgejo_jwt_secret_path }}" } ++ - { kind: LFS_JWT_SECRET, path: "{{ forgejo_lfs_jwt_secret_path }}" } ++ ++- name: Secure the generated secret files (0640 root:forgejo) ++ file: ++ path: "{{ item }}" ++ owner: root ++ group: "{{ forgejo_user }}" ++ mode: "0640" ++ with_items: ++ - "{{ forgejo_secret_key_path }}" ++ - "{{ forgejo_internal_token_path }}" ++ - "{{ forgejo_jwt_secret_path }}" ++ - "{{ forgejo_lfs_jwt_secret_path }}" ++ ++ ++# 3b. MariaDB backend (wired option; default engine is sqlite). Engine migration ++# from an existing sqlite DB is a later PR - this only sets up a fresh DB. ++ ++- name: Set up MariaDB backend for Forgejo ++ when: forgejo_db_engine == 'mariadb' ++ block: ++ - name: "Set 'mysql_install: True' and 'mysql_enabled: True'" ++ set_fact: ++ mysql_install: True ++ mysql_enabled: True ++ ++ - name: MYSQL - run 'mysql' role ++ include_role: ++ name: mysql ++ ++ - name: FAIL (STOP THE INSTALL) IF 'mysql_installed is undefined' ++ fail: ++ msg: "Forgejo needs MariaDB but the 'mysql' role did not install it." ++ when: mysql_installed is undefined ++ ++ - name: "Create MySQL database for Forgejo: {{ forgejo_db_name }}" ++ ansible.mariadb.mariadb_db: ++ name: "{{ forgejo_db_name }}" ++ ++ - name: Create MySQL username ({{ forgejo_db_user }}) with password, for above database ++ ansible.mariadb.mariadb_user: ++ name: "{{ forgejo_db_user }}" ++ password: "{{ forgejo_db_password }}" ++ priv: "{{ forgejo_db_name }}.*:ALL,GRANT" ++ ++- name: Install {{ forgejo_config_path }} from template (0664 root:forgejo) ++ template: ++ src: app.ini.j2 ++ dest: "{{ forgejo_config_path }}" ++ owner: root ++ group: "{{ forgejo_user }}" ++ mode: "0664" ++ ++ ++# 4. Service unit (systemd platforms only) ++# Under proot the pdsm wrapper is provided by the 'proot_services' role ++# (roles/proot_services/files/pdsm/usr_local_pdsm/services-available/forgejo, ++# listed in its 'pdsm_installed_services'); this role only enables/starts it. ++ ++- name: "Install systemd unit /etc/systemd/system/forgejo.service (not proot)" ++ template: ++ src: forgejo.service.j2 ++ dest: /etc/systemd/system/forgejo.service ++ when: not is_proot ++ ++ ++# 5. RECORD Forgejo AS INSTALLED ++ ++- name: Record (final) disk space used ++ shell: df -B1 --output=used / | tail -1 ++ register: df2 ++ ++- name: Add 'forgejo_disk_usage = {{ df2.stdout | int - df1.stdout | int }}' to {{ iiab_ini_file }} ++ ini_file: ++ path: "{{ iiab_ini_file }}" # /etc/iiab/iiab.ini ++ section: forgejo ++ option: forgejo_disk_usage ++ value: "{{ df2.stdout | int - df1.stdout | int }}" ++ ++- name: "Set 'forgejo_installed: True'" ++ set_fact: ++ forgejo_installed: True ++ ++- name: "Add 'forgejo_installed: True' to {{ iiab_state_file }}" ++ lineinfile: ++ path: "{{ iiab_state_file }}" # /etc/iiab/iiab_state.yml ++ regexp: '^forgejo_installed' ++ line: 'forgejo_installed: True' +diff --git a/roles/forgejo/tasks/main.yml b/roles/forgejo/tasks/main.yml +new file mode 100644 +index 0000000000..6939487bf1 +--- /dev/null ++++ b/roles/forgejo/tasks/main.yml +@@ -0,0 +1,48 @@ ++- name: Assert that "forgejo_install is sameas true" (boolean not string etc) ++ assert: ++ that: forgejo_install is sameas true ++ fail_msg: "PLEASE SET 'forgejo_install: True' e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ ++- name: Assert that "forgejo_enabled | type_debug == 'bool'" (boolean not string etc) ++ assert: ++ that: forgejo_enabled | type_debug == 'bool' ++ fail_msg: "PLEASE GIVE VARIABLE 'forgejo_enabled' A PROPER (UNQUOTED) ANSIBLE BOOLEAN VALUE e.g. IN: /etc/iiab/local_vars.yml" ++ quiet: yes ++ ++ ++- block: ++ ++ - name: Install Forgejo if 'forgejo_installed' not defined, e.g. in {{ iiab_state_file }} # /etc/iiab/iiab_state.yml ++ include_tasks: install.yml ++ when: forgejo_installed is undefined ++ ++ - name: Enable/Disable Forgejo (systemd or pdsm) and NGINX ++ include_tasks: enable-or-disable.yml ++ ++ - name: Add 'forgejo' variable values to {{ iiab_ini_file }} ++ ini_file: ++ path: "{{ iiab_ini_file }}" # /etc/iiab/iiab.ini ++ section: forgejo ++ option: "{{ item.option }}" ++ value: "{{ item.value | string }}" ++ with_items: ++ - option: name ++ value: Forgejo ++ - option: description ++ value: 'Forgejo, self-hosted Git service.' ++ - option: forgejo_install ++ value: "{{ forgejo_install }}" ++ - option: forgejo_enabled ++ value: "{{ forgejo_enabled }}" ++ - option: forgejo_url ++ value: "{{ forgejo_url }}" ++ - option: forgejo_port ++ value: "{{ forgejo_port }}" ++ ++ rescue: ++ ++ - name: 'SEE ERROR ABOVE (skip_role_on_error: {{ skip_role_on_error }})' ++ fail: ++ msg: "" ++ when: not skip_role_on_error +diff --git a/roles/forgejo/templates/app.ini.j2 b/roles/forgejo/templates/app.ini.j2 +new file mode 100644 +index 0000000000..d936adb9aa +--- /dev/null ++++ b/roles/forgejo/templates/app.ini.j2 +@@ -0,0 +1,83 @@ ++; Managed by IIAB Ansible (roles/forgejo). Do not edit by hand. ++; Config reference: https://forgejo.org/docs/latest/admin/config-cheat-sheet/ ++; This is a lean config on purpose (unlike gitea's full cheat-sheet template); ++; only the settings IIAB actually needs are set here. ++ ++APP_NAME = {{ forgejo_display_name }} ++RUN_USER = {{ forgejo_user }} ++RUN_MODE = {{ forgejo_run_mode }} ++WORK_PATH = {{ forgejo_run_directory }} ++ ++[repository] ++ROOT = {{ forgejo_repo_root }} ++ ++[git.timeout] ++; forgejo default MIGRATE is 600s, too low for large repos. ++; See forgejo_migrate_timeout in defaults/main.yml. ++MIGRATE = {{ forgejo_migrate_timeout }} ++ ++[server] ++PROTOCOL = http ++DOMAIN = {{ iiab_hostname }}.{{ iiab_domain }} ++; Served behind nginx on the {{ forgejo_url }} sub-path (see forgejo-nginx.conf). ++ROOT_URL = http://%(DOMAIN)s{{ forgejo_url }}/ ++HTTP_ADDR = 127.0.0.1 ++HTTP_PORT = {{ forgejo_port }} ++OFFLINE_MODE = true ++LFS_START_SERVER = {{ forgejo_lfs | lower }} ++{% if forgejo_lfs %} ++; Secret is generated once and referenced by file (see tasks/install.yml). ++; NOTE: confirm LFS_JWT_SECRET_URI is honored by the pinned Forgejo version. ++LFS_JWT_SECRET_URI = file:{{ forgejo_lfs_jwt_secret_path }} ++{% endif %} ++{% if forgejo_enable_ssh %} ++; Built-in SSH server on a high port (systemd platforms; not the host sshd). ++DISABLE_SSH = false ++START_SSH_SERVER = true ++SSH_DOMAIN = %(DOMAIN)s ++SSH_PORT = {{ forgejo_ssh_port }} ++SSH_LISTEN_PORT = {{ forgejo_ssh_port }} ++{% else %} ++; proot v1: HTTP-only, no SSH. ++DISABLE_SSH = true ++START_SSH_SERVER = false ++{% endif %} ++ ++[lfs] ++{% if forgejo_lfs %} ++PATH = {{ forgejo_lfs_root }} ++{% endif %} ++ ++[database] ++{% if forgejo_db_engine == 'mariadb' %} ++DB_TYPE = mysql ++HOST = 127.0.0.1:3306 ++NAME = {{ forgejo_db_name }} ++USER = {{ forgejo_db_user }} ++PASSWD = {{ forgejo_db_password }} ++{% else %} ++DB_TYPE = sqlite3 ++PATH = {{ forgejo_db_path }} ++{% endif %} ++ ++[security] ++; First registered user becomes admin (same as the gitea role). ++INSTALL_LOCK = {{ forgejo_install_lock | lower }} ++SECRET_KEY_URI = file:{{ forgejo_secret_key_path }} ++INTERNAL_TOKEN_URI = file:{{ forgejo_internal_token_path }} ++ ++[oauth2] ++JWT_SECRET_URI = file:{{ forgejo_jwt_secret_path }} ++ ++[service] ++DISABLE_REGISTRATION = false ++REQUIRE_SIGNIN_VIEW = false ++REGISTER_EMAIL_CONFIRM = false ++ ++[mailer] ++ENABLED = false ++ ++[log] ++ROOT_PATH = {{ forgejo_log_root }} ++MODE = console ++LEVEL = Info +diff --git a/roles/forgejo/templates/forgejo-nginx.conf.j2 b/roles/forgejo/templates/forgejo-nginx.conf.j2 +new file mode 100644 +index 0000000000..b9bbd031a2 +--- /dev/null ++++ b/roles/forgejo/templates/forgejo-nginx.conf.j2 +@@ -0,0 +1,24 @@ ++# Reverse proxy for Forgejo at http://box{{ forgejo_url }} ++# Installed by roles/forgejo (tasks/enable-or-disable.yml) into nginx conf.d. ++ ++location = {{ forgejo_url }} { ++ return 302 {{ forgejo_url }}/; ++} ++ ++location {{ forgejo_url }}/ { ++ proxy_pass http://127.0.0.1:{{ forgejo_port }}/; ++ proxy_set_header Host $host; ++ proxy_set_header X-Real-IP $remote_addr; ++ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ++ proxy_set_header X-Forwarded-Proto $scheme; ++ ++ # Large-repo tuning: allow big clone/push (e.g. CodeOnTheGo ~1.2 GB). ++ # nginx default client_max_body_size is 1m, which would fail large pushes. ++ client_max_body_size 0; ++ proxy_request_buffering off; ++ # Keep the proxy timeout >= Forgejo's [git.timeout] MIGRATE, else nginx drops a ++ # long migrate/clone mid-flight and Forgejo rolls the partial repo back. The +300s ++ # margin keeps Forgejo's own timeout authoritative (clean abort, not an nginx 504). ++ proxy_read_timeout {{ forgejo_migrate_timeout + 300 }}s; ++ proxy_send_timeout {{ forgejo_migrate_timeout + 300 }}s; ++} +diff --git a/roles/forgejo/templates/forgejo.service.j2 b/roles/forgejo/templates/forgejo.service.j2 +new file mode 100644 +index 0000000000..290a0076af +--- /dev/null ++++ b/roles/forgejo/templates/forgejo.service.j2 +@@ -0,0 +1,22 @@ ++[Unit] ++Description=Forgejo (Internet-in-a-Box Git service) ++After=network.target ++{% if forgejo_db_engine == 'mariadb' %} ++After=mariadb.service ++Wants=mariadb.service ++{% endif %} ++ ++[Service] ++Type=simple ++User={{ forgejo_user }} ++Group={{ forgejo_user }} ++WorkingDirectory={{ forgejo_run_directory }} ++; ExecStart uses the stable symlink, so an update (runrole --reinstall forgejo) just ++; repoints it to the new binary and restarts, without rewriting this unit. ++ExecStart={{ forgejo_link_path }} web --config {{ forgejo_config_path }} ++Restart=always ++RestartSec=2s ++Environment=USER={{ forgejo_user }} HOME={{ forgejo_home }} FORGEJO_WORK_DIR={{ forgejo_run_directory }} ++ ++[Install] ++WantedBy=multi-user.target +diff --git a/roles/matomo/defaults/main.yml b/roles/matomo/defaults/main.yml +index c0f9093adf..2349f90055 100644 +--- a/roles/matomo/defaults/main.yml ++++ b/roles/matomo/defaults/main.yml +@@ -35,4 +35,3 @@ proot_nginx_timeout: 180 + tcp_host: "127.0.0.1" + tcp_port: 3306 + mariadb_run_dir: "/run/mysqld" +-mariadb_log_dir: "/var/log/mysql" +diff --git a/roles/proot_services/defaults/main.yml b/roles/proot_services/defaults/main.yml +index c809d71175..97eeb74c58 100644 +--- a/roles/proot_services/defaults/main.yml ++++ b/roles/proot_services/defaults/main.yml +@@ -8,6 +8,7 @@ pdsm_installed_services: + - kolibri + - kiwix + - calibre-web ++ - forgejo + + # OTOH, not all services should be enabled by default due device constrains, + # users can enable any of the installed services in their own setup. +diff --git a/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/forgejo b/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/forgejo +new file mode 100644 +index 0000000000..70318319de +--- /dev/null ++++ b/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/forgejo +@@ -0,0 +1,97 @@ ++#!/bin/sh ++# /usr/local/pdsm/services-available/forgejo ++# Simple PDSM service wrapper for Forgejo (IIAB, proot). ++# Installed by roles/proot_services (listed in pdsm_installed_services); ++# the forgejo role only enables/starts it (tasks/enable-or-disable.yml). ++ ++PDSM_COMMON="/usr/local/pdsm/lib/pdsm-common.sh" ++[ -r "$PDSM_COMMON" ] && . "$PDSM_COMMON" ++ ++SERVICE_NAME="forgejo" ++ ++FORGEJO_USER="forgejo" ++FORGEJO_HOME="/home/forgejo" ++FORGEJO_BIN="/library/forgejo/forgejo" # stable symlink -> versioned binary ++FORGEJO_CONFIG="/etc/forgejo/app.ini" ++FORGEJO_WORK_DIR="/library/forgejo" ++FORGEJO_LOG="/library/forgejo/log/forgejo.log" ++ ++# Forgejo refuses to run as root. Under proot the login shell is root, so we drop ++# to the unprivileged 'forgejo' user (created by the forgejo role) to start it. ++# The process is tracked by its command line (pgrep); no pidfile needed. ++ ++running() { ++ pgrep -f "$FORGEJO_BIN web" >/dev/null 2>&1 ++} ++ ++start() { ++ if running; then ++ echo "[pdsm:$SERVICE_NAME] already running" ++ return 0 ++ fi ++ ++ # nginx is the reverse proxy in front of Forgejo ++ require_service "nginx" 2>/dev/null || true ++ ++ echo "[pdsm:$SERVICE_NAME] starting..." ++ ++ # Drop from root to the 'forgejo' user (Forgejo won't run as root). ++ setsid su -s /bin/sh "$FORGEJO_USER" -c \ ++ "HOME=$FORGEJO_HOME FORGEJO_WORK_DIR=$FORGEJO_WORK_DIR exec $FORGEJO_BIN web --config $FORGEJO_CONFIG" \ ++ >>"$FORGEJO_LOG" 2>&1 & ++ ++ for i in $(seq 1 "${PDSM_START_TIMEOUT:-15}"); do ++ if running; then ++ echo "[pdsm:$SERVICE_NAME] started" ++ return 0 ++ fi ++ sleep 1 ++ done ++ ++ echo "[pdsm:$SERVICE_NAME] failed to start (check $FORGEJO_LOG)" >&2 ++ return 1 ++} ++ ++stop() { ++ if ! running; then ++ echo "[pdsm:$SERVICE_NAME] already stopped" ++ return 0 ++ fi ++ ++ echo "[pdsm:$SERVICE_NAME] stopping..." ++ pkill -f "$FORGEJO_BIN web" >/dev/null 2>&1 || true ++ ++ for i in $(seq 1 "${PDSM_START_TIMEOUT:-15}"); do ++ if ! running; then ++ echo "[pdsm:$SERVICE_NAME] stopped" ++ return 0 ++ fi ++ sleep 1 ++ done ++ ++ echo "[pdsm:$SERVICE_NAME] did not fully stop" >&2 ++ return 1 ++} ++ ++status() { ++ if running; then ++ echo "[pdsm:$SERVICE_NAME] running" ++ return 0 ++ fi ++ echo "[pdsm:$SERVICE_NAME] not running" ++ return 3 ++} ++ ++case "$1" in ++ start|stop|status) ++ "$1" ++ ;; ++ restart) ++ stop ++ start ++ ;; ++ *) ++ echo "Usage: $0 {start|stop|restart|status}" >&2 ++ exit 1 ++ ;; ++esac +diff --git a/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/mariadb b/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/mariadb +index 3aa5181917..ab52d07a2c 100644 +--- a/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/mariadb ++++ b/roles/proot_services/files/pdsm/usr_local_pdsm/services-available/mariadb +@@ -23,12 +23,26 @@ running() { + kill -0 "$pid" 2>/dev/null + } + ++responds() { ++ # This proves the server actually accepts a client connection. ++ mysql --protocol=socket --socket="$MARIADB_SOCKET" -N -e 'SELECT 1' >/dev/null 2>&1 ++} ++ + start() { +- if running; then ++ # Treat the service as up only if it is alive AND actually answers. ++ if running && responds; then + echo "[pdsm:$SERVICE_NAME] already running" + return 0 + fi + ++ # Alive but not answering: a stale orphan. Stop it (and clear the stale pidfile) so we ++ # never launch a second mariadbd on the same datadir. ++ if running; then ++ echo "[pdsm:$SERVICE_NAME] stale instance (alive but not responding) - cleaning up" ++ stop ++ fi ++ rm -f "$MARIADB_PIDFILE" ++ + echo "[pdsm:$SERVICE_NAME] starting..." + + # Ensure run dir exists with proper owner +@@ -47,9 +61,10 @@ start() { + --log-error="$MARIADB_LOG" \ + >>"$MARIADB_LOG" 2>&1 & + +- # Small wait loop so 'start' only returns when it's actually alive ++ # Wait loop returns only when MariaDB actually ANSWERS (not merely PID-alive), so ++ # callers that run DB ops right after 'start' don't race a still-initialising server. + for i in $(seq 1 "$PDSM_START_TIMEOUT"); do +- if running; then ++ if responds; then + echo "[pdsm:$SERVICE_NAME] started" + return 0 + fi +@@ -91,7 +106,9 @@ stop() { + } + + status() { +- if running; then ++ # Report running only if it is alive AND actually answers, so a broken orphan is not ++ # misreported as healthy (and so pdsm's require_service restarts a stale dependency). ++ if running && responds; then + echo "[pdsm:$SERVICE_NAME] running" + return 0 + fi +diff --git a/vars/default_vars.yml b/vars/default_vars.yml +index b36bc18b61..12b4843cd0 100644 +--- a/vars/default_vars.yml ++++ b/vars/default_vars.yml +@@ -272,6 +272,7 @@ pi_swap_file_size: 1536 + mysql_install: False + mysql_enabled: False + mysql_service: mariadb ++mariadb_log_dir: /var/log/mysql # moved from roles/matomo, used by roles/mysql + + # 2020-09-24: NGINX is MANDATORY but still evolving - please see: + # https://github.com/iiab/iiab/blob/master/roles/nginx/README.md +@@ -413,6 +414,12 @@ gitea_enabled: False + gitea_url: /gitea + gitea_port: 61734 + ++# Forgejo (community fork of Gitea) ++forgejo_install: False ++forgejo_enabled: False ++forgejo_url: /forgejo ++forgejo_port: 3300 ++ + # JupyterHub programming environment with student Notebooks + jupyterhub_install: False + jupyterhub_enabled: False +diff --git a/vars/local_vars_android_large.yml b/vars/local_vars_android_large.yml +index a374192077..98905fc03d 100644 +--- a/vars/local_vars_android_large.yml ++++ b/vars/local_vars_android_large.yml +@@ -14,6 +14,7 @@ is_proot: True + # 4 Kolibri localhost:8085/kolibri + # 5 Matomo localhost:8085/matomo + # 6 Code on the Go localhost:8085/code ++# 7 Forgejo localhost:8085/forgejo + ############################## + + ############################## +@@ -81,6 +82,12 @@ code_install: True + code_enabled: True + ############################## + ++############################## ++# Forgejo (community fork of Gitea) ++forgejo_install: False ++forgejo_enabled: False ++############################## ++ + ############################## + # Disable sshd as it is not directly used on proot-distro + sshd_install: False +diff --git a/vars/local_vars_android_medium.yml b/vars/local_vars_android_medium.yml +index 55336dad69..174379fa2d 100644 +--- a/vars/local_vars_android_medium.yml ++++ b/vars/local_vars_android_medium.yml +@@ -14,6 +14,7 @@ is_proot: True + # 4 Kolibri localhost:8085/kolibri + # 5 Matomo localhost:8085/matomo + # 6 Code on the Go localhost:8085/code ++# 7 Forgejo localhost:8085/forgejo + ############################## + + ############################## +@@ -81,6 +82,12 @@ code_install: False + code_enabled: False + ############################## + ++############################## ++# Forgejo (community fork of Gitea) ++forgejo_install: False ++forgejo_enabled: False ++############################## ++ + ############################## + # Disable sshd as it is not directly used on proot-distro + sshd_install: False +diff --git a/vars/local_vars_android_small.yml b/vars/local_vars_android_small.yml +index 38c0dafa82..f215ab300c 100644 +--- a/vars/local_vars_android_small.yml ++++ b/vars/local_vars_android_small.yml +@@ -14,6 +14,7 @@ is_proot: True + # 4 Kolibri localhost:8085/kolibri + # 5 Matomo localhost:8085/matomo + # 6 Code on the Go localhost:8085/code ++# 7 Forgejo localhost:8085/forgejo + ############################## + + ############################## +@@ -81,6 +82,12 @@ code_install: False + code_enabled: False + ############################## + ++############################## ++# Forgejo (community fork of Gitea) ++forgejo_install: False ++forgejo_enabled: False ++############################## ++ + ############################## + # Disable sshd as it is not directly used on proot-distro + sshd_install: False +diff --git a/vars/local_vars_large.yml b/vars/local_vars_large.yml +index 8a5a5ed95c..e2d5c87971 100644 +--- a/vars/local_vars_large.yml ++++ b/vars/local_vars_large.yml +@@ -232,6 +232,10 @@ nodocs: False + gitea_install: True + gitea_enabled: True + ++# Forgejo (community fork of Gitea) ++forgejo_install: False ++forgejo_enabled: False ++ + # JupyterHub programming environment with student Notebooks + jupyterhub_install: True + jupyterhub_enabled: True From 5247c5f241c57e3a20aa3937939d540eb3d6ba6f Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Wed, 23 Sep 2026 19:30:08 -0600 Subject: [PATCH 2/9] K2GO-212 feat(bake): enable and seed Forgejo per tier Enable the forgejo role for Standard and Full (not Basic) in local_vars, and add static/forgejo/orchestration to seed the AppDevForAll repo mirrors via shallow-adopt when ENABLE_FORGEJO_SEED is set. Standard seeds KnowledgeToGo; Full also seeds CodeOnTheGo and plugin-examples. --- iiab-android | 38 +++++++++ static/forgejo/orchestration | 159 +++++++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 static/forgejo/orchestration diff --git a/iiab-android b/iiab-android index 7869362bc..bb8fee365 100644 --- a/iiab-android +++ b/iiab-android @@ -51,6 +51,7 @@ INSTALL_FALLBACK="https://raw.githubusercontent.com/iiab/iiab-factory/refs/heads ENABLE_INDEX_PAGE="${ENABLE_INDEX_PAGE:-1}" ENABLE_DEMO_CONTENTS="${ENABLE_DEMO_CONTENTS:-0}" +ENABLE_FORGEJO_SEED="${ENABLE_FORGEJO_SEED:-0}" # K2GO-212: seed Forgejo repos (bake only; needs internet) DEMO_REPO_URL="https://iiab.switnet.org/android/static_demo/" PG_CATALOG_URL="https://iiab.switnet.org/android/pg/catalog.db.gz" @@ -630,6 +631,19 @@ else sed -i -E '/^[[:space:]]*code_(install|enabled)[[:space:]]*:/d' "$LOCAL_VARS_DEST" echo "code_install: True" >> "$LOCAL_VARS_DEST" echo "code_enabled: True" >> "$LOCAL_VARS_DEST" + + # K2GO-212: the forgejo role ships install/enabled False in every vars template (carried via + # tools/upstream-patches until iiab#4505 merges), so enable it for Standard/Full here (not Basic). + case "$LOCAL_VARS_URL" in + *_small*) + log "Basic tier: Forgejo left off." + ;; + *) + sed -i -E '/^[[:space:]]*forgejo_(install|enabled)[[:space:]]*:/d' "$LOCAL_VARS_DEST" + echo "forgejo_install: True" >> "$LOCAL_VARS_DEST" + echo "forgejo_enabled: True" >> "$LOCAL_VARS_DEST" + ;; + esac fi #----------------------------- @@ -674,6 +688,30 @@ else log "Skipping index site installation (ENABLE_INDEX_PAGE=0)." fi +#----------------------------- +# K2GO-212: seed Forgejo repos (bake only, needs internet -> gated on ENABLE_FORGEJO_SEED). Repo set +# is tier-scoped (Standard: KnowledgeToGo; Full: + CodeOnTheGo@stage + plugin-examples); Basic has +# forgejo off so is_role_enabled skips it. See static/forgejo/orchestration. +#----------------------------- +if [ "${ENABLE_FORGEJO_SEED:-0}" -eq 1 ] && is_role_enabled forgejo; then + if [ -f "${K2GO_OPT_DIR}/static/forgejo/orchestration" ]; then + . "${K2GO_OPT_DIR}/static/forgejo/orchestration" + # Pick the tier's repo list by name; the URLs live only in the orchestration. + case "$LOCAL_VARS_URL" in + *_large*) FORGEJO_REPOS="$FORGEJO_REPOS_FULL" ;; + *) FORGEJO_REPOS="$FORGEJO_REPOS_STANDARD" ;; + esac + export FORGEJO_REPOS + log "Seeding Forgejo repos (tier vars: ${LOCAL_VARS_URL##*/})..." + # Best-effort: a seed failure must not abort the whole bake (mirrors install_welcome_zim). + seed_forgejo || warn "Forgejo seed failed (non-fatal)." + else + warn "static/forgejo/orchestration not found; skipping Forgejo seed." + fi +else + log "Skipping Forgejo seed (ENABLE_FORGEJO_SEED=${ENABLE_FORGEJO_SEED:-0})." +fi + #----------------------------- # K2Go docs (tier-3) static module. # Install the nginx conf, then deploy the committed manual (static/k2go-docs/content) into the diff --git a/static/forgejo/orchestration b/static/forgejo/orchestration new file mode 100644 index 000000000..e04d1b9ba --- /dev/null +++ b/static/forgejo/orchestration @@ -0,0 +1,159 @@ +# ============================================================================ +# Name : static/forgejo/orchestration +# Author : AppDevForAll +# Copyright : Copyright (c) 2026 AppDevForAll +# Description : K2GO-212. Forgejo repo seeding for the K2Go bake. Sourced by the +# installer ONLY when ENABLE_FORGEJO_SEED=1. Holds the config +# (env-overridable) and the orchestration function seed_forgejo. +# Guard-based and idempotent: Forgejo is the single source of truth +# (it already knows which repos exist and whether they are empty), +# so re-running needs no local state file. +# +# The seeding method is "shallow": a shallow single-branch bare clone adopted +# into Forgejo. The migrate API does a FULL clone and cannot go shallow, so the +# bake clones the shallow dir and adopts it. This carries the full current tree +# (compiles to a working APK) and drops the dead git history: CoGo 1.2 GB -> ~150 MB. +# Study: Confluence "Code on the Go (CoGo) - repo size, subset strategy & +# offline-compile validation". +# ============================================================================ + +# ===== CONFIG (env overrides win) ========================================== +FORGEJO_BIN="${FORGEJO_BIN:-/library/forgejo/forgejo}" # absolute: 'forgejo' is NOT on the forgejo user's PATH under su +FORGEJO_CONFIG="${FORGEJO_CONFIG:-/etc/forgejo/app.ini}" +FORGEJO_RUN_USER="${FORGEJO_RUN_USER:-forgejo}" +FORGEJO_REPO_ROOT="${FORGEJO_REPO_ROOT:-/library/forgejo/data/repositories}" + +# Talk to forgejo DIRECTLY (bypass nginx). A synchronous migrate through nginx +# can hit proxy_read_timeout and make forgejo ROLL BACK the repo. forgejo serves +# its ROOT at :3300, so the API is :3300/api/v1 (nginx strips the /forgejo prefix). +FORGEJO_API="${FORGEJO_API:-http://127.0.0.1:3300/api/v1}" +# Health gate goes THROUGH nginx on purpose: a healthy K2Go needs nginx up too, +# not just forgejo. :8085 is the box nginx; /forgejo/ proxies to :3300. +FORGEJO_HEALTH="${FORGEJO_HEALTH:-http://127.0.0.1:8085/forgejo/}" + +FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-k2goadmin}" # 'admin' is RESERVED by forgejo +FORGEJO_ADMIN_PASS="${FORGEJO_ADMIN_PASS:-changeme}" # override in the env for real deployments +FORGEJO_ADMIN_EMAIL="${FORGEJO_ADMIN_EMAIL:-k2go@box.lan}" + +# Repos live under an org (full-name mirrors of the originals); contributors fork. Admin owns the org. +FORGEJO_ORG="${FORGEJO_ORG:-AppDevForAll}" + +FORGEJO_PROOT="${FORGEJO_PROOT:-1}" # 1 on K2Go (proot): enables the l2s double-rm +FORGEJO_WAIT_TRIES="${FORGEJO_WAIT_TRIES:-60}" # 502 window tolerance (x 5s ~= 5 min) +# "shallow" (git clone --bare --depth 1 + adopt; recommended) or "migrate" +# (forgejo migrate API, full mirror). Shallow keeps the full current tree and +# drops history; the migrate API cannot go shallow. +FORGEJO_SEED_METHOD="${FORGEJO_SEED_METHOD:-shallow}" + +# Canonical repo lists (owner|name|clone_addr|branch; branch optional -> default). Single source +# for the repo URLs: the installer gate selects one by tier, it does not restate the URLs. +FORGEJO_REPOS_FULL="${FORGEJO_ORG}|CodeOnTheGo|https://github.com/appdevforall/CodeOnTheGo|stage +${FORGEJO_ORG}|KnowledgeToGo|https://github.com/appdevforall/KnowledgeToGo| +${FORGEJO_ORG}|plugin-examples|https://github.com/appdevforall/plugin-examples|" +FORGEJO_REPOS_STANDARD="${FORGEJO_ORG}|KnowledgeToGo|https://github.com/appdevforall/KnowledgeToGo|" +FORGEJO_REPOS="${FORGEJO_REPOS:-$FORGEJO_REPOS_FULL}" + +# ===== small helpers (reuse installer's log/ok/warn if present) ============ +command -v log >/dev/null 2>&1 || log(){ printf ' %s\n' "$*"; } +command -v ok >/dev/null 2>&1 || ok(){ printf ' OK: %s\n' "$*"; } +command -v warn >/dev/null 2>&1 || warn(){ printf ' WARN: %s\n' "$*" >&2; } + +_fj_su() { su -s /bin/sh "$FORGEJO_RUN_USER" -c "$1"; } # run as the forgejo user +_fj_api() { curl -fsS -u "$FORGEJO_ADMIN_USER:$FORGEJO_ADMIN_PASS" "$@"; } + +# Wait for forgejo to answer 200 THROUGH nginx (tolerate the post-restart 502 window). +_fj_wait_200() { + local i=0 + while [ "$i" -lt "$FORGEJO_WAIT_TRIES" ]; do + [ "$(curl -s -o /dev/null -w '%{http_code}' "$FORGEJO_HEALTH")" = "200" ] && { ok "forgejo is serving 200"; return 0; } + i=$((i+1)); sleep 5 + done + warn "forgejo did not reach 200 after $((FORGEJO_WAIT_TRIES*5))s"; return 1 +} + +# Admin (guard: create only if missing; run as forgejo user, absolute binary path). +_fj_ensure_admin() { + if _fj_su "$FORGEJO_BIN admin user list --config $FORGEJO_CONFIG" 2>/dev/null | awk '{print $2}' | grep -qx "$FORGEJO_ADMIN_USER"; then + log "admin '$FORGEJO_ADMIN_USER' already exists"; return 0 + fi + _fj_su "$FORGEJO_BIN admin user create --admin --username '$FORGEJO_ADMIN_USER' \ + --password '$FORGEJO_ADMIN_PASS' --email '$FORGEJO_ADMIN_EMAIL' \ + --must-change-password=false --config $FORGEJO_CONFIG" \ + && ok "admin '$FORGEJO_ADMIN_USER' created" || { warn "admin create failed"; return 1; } +} + +# Org (guard: create only if missing). Owned by the admin user; repos live under it. +_fj_ensure_org() { + if _fj_api "$FORGEJO_API/orgs/$FORGEJO_ORG" >/dev/null 2>&1; then + log "org '$FORGEJO_ORG' already exists"; return 0 + fi + if _fj_api -X POST "$FORGEJO_API/orgs" -H 'Content-Type: application/json' \ + -d "{\"username\":\"$FORGEJO_ORG\",\"visibility\":\"public\"}" >/dev/null 2>&1; then + ok "org '$FORGEJO_ORG' created" + else + warn "org '$FORGEJO_ORG' create failed"; return 1 + fi +} + +# Guard: repo exists AND is non-empty (a dangling/empty repo dir still 409s on re-seed). +_fj_repo_ready() { + _fj_api "$FORGEJO_API/repos/$1/$2" 2>/dev/null | grep -q '"empty":false' +} + +# Purge a broken/empty repo before retry. On proot, a single rm leaves dangling +# link2symlink (l2s) entries -> the dir survives -> next seed 409s. So rm TWICE (+ once more). +_fj_purge_repo() { + local owner="$1" name="$2" dir="$FORGEJO_REPO_ROOT/$1/$2.git" + _fj_api -X DELETE "$FORGEJO_API/repos/$owner/$name" >/dev/null 2>&1 || true + if [ "$FORGEJO_PROOT" = "1" ]; then + rm -rf "$dir" 2>/dev/null; rm -rf "$dir" 2>/dev/null; rm -rf "$dir" 2>/dev/null + else + rm -rf "$dir" 2>/dev/null + fi +} + +# Method A: full mirror via the migrate API (direct :3300, bypassing nginx). +# K2GO-212: keep the failure output for debugging instead of discarding it. +_fj_migrate() { + local owner="$1" name="$2" addr="$3" out + out="$(_fj_api -X POST "$FORGEJO_API/repos/migrate" -H 'Content-Type: application/json' \ + -d "{\"clone_addr\":\"$addr\",\"repo_owner\":\"$owner\",\"repo_name\":\"$name\",\"service\":\"git\",\"mirror\":false}" 2>&1)" \ + || { warn "migrate $owner/$name failed: $out"; return 1; } +} + +# Method B: shallow snapshot -> adopt (recommended; full current tree, compilable, no history). +# K2GO-212: keep the failure output for debugging. +_fj_shallow_adopt() { + local owner="$1" name="$2" addr="$3" branch="$4" dir="$FORGEJO_REPO_ROOT/$1/$2.git" out + local br=""; [ -n "$branch" ] && br="--branch $branch" + mkdir -p "$FORGEJO_REPO_ROOT/$owner" + # shellcheck disable=SC2086 + out="$(git clone --bare --depth 1 --single-branch $br "$addr" "$dir" 2>&1)" \ + || { warn "clone $owner/$name failed: $out"; return 1; } + chown -R "$FORGEJO_RUN_USER:$FORGEJO_RUN_USER" "$FORGEJO_REPO_ROOT/$owner" + out="$(_fj_api -X POST "$FORGEJO_API/admin/unadopted/$owner/$name" 2>&1)" \ + || { warn "adopt $owner/$name failed: $out"; return 1; } # HTTP 204 on success +} + +# Seed one repo (guard -> purge-if-broken -> seed -> verify). +_fj_seed_one() { + local owner="$1" name="$2" addr="$3" branch="$4" + if _fj_repo_ready "$owner" "$name"; then log "skip $owner/$name (already seeded)"; return 0; fi + log "seeding $owner/$name [$FORGEJO_SEED_METHOD]" + _fj_purge_repo "$owner" "$name" + if [ "$FORGEJO_SEED_METHOD" = "migrate" ]; then _fj_migrate "$owner" "$name" "$addr" + else _fj_shallow_adopt "$owner" "$name" "$addr" "$branch"; fi + if _fj_repo_ready "$owner" "$name"; then ok "seeded $owner/$name"; else warn "FAILED $owner/$name"; return 1; fi +} + +# ===== entry point (what the installer gate calls) ========================= +seed_forgejo() { + _fj_wait_200 || return 1 + _fj_ensure_admin || return 1 + _fj_ensure_org || return 1 + printf '%s\n' "$FORGEJO_REPOS" | while IFS='|' read -r owner name addr branch; do + [ -n "$owner" ] && [ -n "$addr" ] || continue + _fj_seed_one "$owner" "$name" "$addr" "$branch" || true # one repo must not abort the rest + done + ok "forgejo seeding done" +} From dba7f9f7f280745e9c51790f0af11bb8b0f6d018 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Wed, 23 Sep 2026 19:30:42 -0600 Subject: [PATCH 3/9] K2GO-212 feat(app): add Forgejo module card and strings Add forgejo to the module roster, the Module management hub, and the Library home, with an icon and localized strings across 33 locales (machine-translated, marked for review). --- .../src/main/java/org/appdevforall/k2go/ModuleRegistry.java | 3 ++- .../org/appdevforall/k2go/redesign/LibraryHomeFragment.java | 1 + .../java/org/appdevforall/k2go/redesign/ModuleCards.java | 5 +++++ controller/app/src/main/res/drawable/ic_card_forgejo.xml | 3 +++ controller/app/src/main/res/values-ar/strings.xml | 4 ++++ controller/app/src/main/res/values-az/strings.xml | 4 ++++ controller/app/src/main/res/values-bg/strings.xml | 4 ++++ controller/app/src/main/res/values-bn/strings.xml | 4 ++++ controller/app/src/main/res/values-cs/strings.xml | 4 ++++ controller/app/src/main/res/values-de/strings.xml | 4 ++++ controller/app/src/main/res/values-el/strings.xml | 4 ++++ controller/app/src/main/res/values-es/strings.xml | 4 ++++ controller/app/src/main/res/values-fa/strings.xml | 4 ++++ controller/app/src/main/res/values-fr/strings.xml | 4 ++++ controller/app/src/main/res/values-gu/strings.xml | 4 ++++ controller/app/src/main/res/values-hi/strings.xml | 4 ++++ controller/app/src/main/res/values-hu/strings.xml | 4 ++++ controller/app/src/main/res/values-in/strings.xml | 4 ++++ controller/app/src/main/res/values-it/strings.xml | 4 ++++ controller/app/src/main/res/values-ja/strings.xml | 4 ++++ controller/app/src/main/res/values-ko/strings.xml | 4 ++++ controller/app/src/main/res/values-lt/strings.xml | 4 ++++ controller/app/src/main/res/values-nl/strings.xml | 4 ++++ controller/app/src/main/res/values-no/strings.xml | 4 ++++ controller/app/src/main/res/values-pl/strings.xml | 4 ++++ controller/app/src/main/res/values-pt/strings.xml | 4 ++++ controller/app/src/main/res/values-ro/strings.xml | 4 ++++ controller/app/src/main/res/values-ru-rRU/strings.xml | 4 ++++ controller/app/src/main/res/values-sk/strings.xml | 4 ++++ controller/app/src/main/res/values-sr/strings.xml | 4 ++++ controller/app/src/main/res/values-sw/strings.xml | 4 ++++ controller/app/src/main/res/values-ta/strings.xml | 4 ++++ controller/app/src/main/res/values-tr/strings.xml | 4 ++++ controller/app/src/main/res/values-uk/strings.xml | 4 ++++ controller/app/src/main/res/values-vi/strings.xml | 4 ++++ controller/app/src/main/res/values-yo/strings.xml | 4 ++++ controller/app/src/main/res/values-zh-rCN/strings.xml | 4 ++++ controller/app/src/main/res/values/strings.xml | 6 ++++++ 38 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 controller/app/src/main/res/drawable/ic_card_forgejo.xml diff --git a/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java b/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java index 8577d8754..84606c55f 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/ModuleRegistry.java @@ -37,7 +37,8 @@ public IiabModule(String endpoint, int nameResId, boolean requires64Bit, String new IiabModule("kiwix", R.string.dash_kiwix, true, "kiwix"), // YAML uses kiwix_install. TRUE = Hidden on 32-bit! new IiabModule("kolibri", R.string.dash_kolibri, false, "kolibri"), // YAML uses kolibri_install new IiabModule("maps", R.string.dash_maps, false, "maps"), // YAML uses maps_install - new IiabModule("matomo", R.string.dash_matomo, false, "matomo") // YAML uses matomo_install + new IiabModule("matomo", R.string.dash_matomo, false, "matomo"), // YAML uses matomo_install + new IiabModule("forgejo", R.string.dash_forgejo, false, "forgejo") // YAML uses forgejo_install // ADFA-4842: "dashboard" is intentionally NOT in the roster. It is no longer an // application/module — it became the REST API core of K2Go; without it maps FQR and all // REST content downloads fail. It is part of the core system, not a user-installable module. diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java index e0bfaaa3e..75ec8d24d 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/LibraryHomeFragment.java @@ -181,6 +181,7 @@ private void populateCards() { cards.clear(); cards.add(new Card("books", getString(R.string.k2go_card_books), false, R.drawable.ic_card_book)); cards.add(new Card("code", getString(R.string.k2go_card_code), false, R.drawable.ic_card_code)); + cards.add(new Card("forgejo", getString(R.string.k2go_card_forgejo), false, R.drawable.ic_card_forgejo)); cards.add(new Card("kiwix", getString(R.string.k2go_card_wikipedia), true, R.drawable.ic_card_wikipedia)); cards.add(new Card("kolibri", getString(R.string.k2go_card_courses), false, R.drawable.ic_card_courses)); cards.add(new Card("maps", getString(R.string.k2go_card_maps), false, R.drawable.ic_card_maps)); diff --git a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java index 0e5f46fb6..757d7030b 100644 --- a/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java +++ b/controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleCards.java @@ -63,6 +63,9 @@ public static final class Card { new Card("code", R.string.k2go_mod_code_title, R.string.k2go_mod_code_title, R.string.k2go_mod_code_sub, R.string.k2go_mod_code_desc, R.drawable.k2go_module_placeholder, false), + new Card("forgejo", R.string.k2go_mod_forgejo_title, R.string.k2go_mod_forgejo_title, + R.string.k2go_mod_forgejo_sub, R.string.k2go_mod_forgejo_desc, + R.drawable.k2go_module_placeholder, false), new Card("matomo", R.string.k2go_mod_matomo_title, R.string.k2go_mod_matomo_title, R.string.k2go_mod_matomo_sub, R.string.k2go_mod_matomo_desc, R.drawable.k2go_module_placeholder, false), @@ -108,6 +111,7 @@ public static String license(String key) { case "calibreweb": return "GPL-3.0"; case "kiwix": return "GPL-3.0"; case "code": return "MIT"; + case "forgejo": return "GPL-3.0-or-later"; case "matomo": return "GPL-3.0"; case "maps": return "Mixed data licenses"; // ADFA-4958: OSM ODbL + Natural Earth (PD) + satellite CC BY-NC-SA 4.0 + GeoNames CC BY 4.0 default: return null; @@ -137,6 +141,7 @@ public static int includesRes(String key) { case "calibreweb": return R.string.k2go_mod_calibreweb_includes; case "kiwix": return R.string.k2go_mod_kiwix_includes; case "code": return R.string.k2go_mod_code_includes; + case "forgejo": return R.string.k2go_mod_forgejo_includes; case "matomo": return R.string.k2go_mod_matomo_includes; case "maps": return R.string.k2go_mod_maps_includes; default: return 0; diff --git a/controller/app/src/main/res/drawable/ic_card_forgejo.xml b/controller/app/src/main/res/drawable/ic_card_forgejo.xml new file mode 100644 index 000000000..9cc3bed45 --- /dev/null +++ b/controller/app/src/main/res/drawable/ic_card_forgejo.xml @@ -0,0 +1,3 @@ + + + diff --git a/controller/app/src/main/res/values-ar/strings.xml b/controller/app/src/main/res/values-ar/strings.xml index ab7ce7210..0e2c10266 100644 --- a/controller/app/src/main/res/values-ar/strings.xml +++ b/controller/app/src/main/res/values-ar/strings.xml @@ -1341,4 +1341,8 @@ تستخدم التنزيلات الجديدة الآن بيانات الجوال. قد يترتب على ذلك تكلفة أو يستنفد باقة بياناتك. تنبيهات تكلفة البيانات الدليل + استضف الشيفرة + استضافة الشيفرة + خادم Git خاص على جهازك. خزّن مستودعات الشيفرة وتتبّع المشكلات وراجع التغييرات دون اتصال. لا حاجة إلى الإنترنت. + خادم Git فقط. تنشئ مستودعاتك الخاصة بعد التثبيت. diff --git a/controller/app/src/main/res/values-az/strings.xml b/controller/app/src/main/res/values-az/strings.xml index b9db13fd9..f951140f7 100644 --- a/controller/app/src/main/res/values-az/strings.xml +++ b/controller/app/src/main/res/values-az/strings.xml @@ -1361,4 +1361,8 @@ Yeni yükləmələr indi mobil internetdən istifadə edir. Bu, xərc yarada və ya data paketinizi bitirə bilər. Data xərci bildirişləri Təlimat + Kodu saxla + Kod hostinqi + Cihazınızda özəl Git serveri. Kod anbarlarını saxlayın, problemləri izləyin və dəyişiklikləri oflayn nəzərdən keçirin. İnternet lazım deyil. + Yalnız Git serveri. Quraşdırmadan sonra öz anbarlarınızı yaradırsınız. diff --git a/controller/app/src/main/res/values-bg/strings.xml b/controller/app/src/main/res/values-bg/strings.xml index e8823d50c..bd7ec496c 100644 --- a/controller/app/src/main/res/values-bg/strings.xml +++ b/controller/app/src/main/res/values-bg/strings.xml @@ -1348,4 +1348,8 @@ Новите изтегляния вече използват мобилни данни. Това може да има такса или да изчерпи плана ви за данни. Известия за разход на данни Ръководство + Хостинг на код + Хостинг на код + Частен Git сървър на вашето устройство. Съхранявайте хранилища с код, следете проблеми и преглеждайте промени офлайн. Не е нужен интернет. + Само Git сървърът. Създавате собствените си хранилища след инсталиране. diff --git a/controller/app/src/main/res/values-bn/strings.xml b/controller/app/src/main/res/values-bn/strings.xml index cf0913f04..175dca7eb 100644 --- a/controller/app/src/main/res/values-bn/strings.xml +++ b/controller/app/src/main/res/values-bn/strings.xml @@ -1354,4 +1354,8 @@ নতুন ডাউনলোডগুলি এখন মোবাইল ডেটা ব্যবহার করে। এতে খরচ হতে পারে বা আপনার ডেটা প্ল্যান শেষ হতে পারে। ডেটা খরচের সতর্কতা ম্যানুয়াল + কোড হোস্ট করুন + কোড হোস্টিং + আপনার ডিভাইসে একটি ব্যক্তিগত Git সার্ভার। কোড রিপোজিটরি সংরক্ষণ করুন, সমস্যা ট্র্যাক করুন এবং অফলাইনে পরিবর্তন পর্যালোচনা করুন। ইন্টারনেট লাগবে না। + শুধু Git সার্ভার। ইনস্টল করার পরে আপনি নিজের রিপোজিটরি তৈরি করেন। diff --git a/controller/app/src/main/res/values-cs/strings.xml b/controller/app/src/main/res/values-cs/strings.xml index f80d636bc..48650d56b 100644 --- a/controller/app/src/main/res/values-cs/strings.xml +++ b/controller/app/src/main/res/values-cs/strings.xml @@ -1348,4 +1348,8 @@ Nová stahování nyní používají mobilní data. Může být zpoplatněno nebo vyčerpat váš datový limit. Upozornění na náklady za data Příručka + Hostovat kód + Hostování kódu + Soukromý Git server na vašem zařízení. Ukládejte úložiště kódu, sledujte problémy a kontrolujte změny offline. Internet není potřeba. + Pouze Git server. Vlastní úložiště si vytvoříte po instalaci. diff --git a/controller/app/src/main/res/values-de/strings.xml b/controller/app/src/main/res/values-de/strings.xml index 513fb23d6..cdf7f9b25 100644 --- a/controller/app/src/main/res/values-de/strings.xml +++ b/controller/app/src/main/res/values-de/strings.xml @@ -1341,4 +1341,8 @@ Neue Downloads nutzen jetzt mobile Daten. Das kann Kosten verursachen oder Ihr Datenvolumen aufbrauchen. Warnungen zu Datenkosten Handbuch + Code hosten + Code-Hosting + Ein privater Git-Server auf Ihrer Box. Speichern Sie Code-Repositorys, verfolgen Sie Probleme und prüfen Sie Änderungen offline. Kein Internet nötig. + Nur der Git-Server. Eigene Repositorys erstellen Sie nach der Installation. diff --git a/controller/app/src/main/res/values-el/strings.xml b/controller/app/src/main/res/values-el/strings.xml index fd3f2f5ba..746024427 100644 --- a/controller/app/src/main/res/values-el/strings.xml +++ b/controller/app/src/main/res/values-el/strings.xml @@ -1348,4 +1348,8 @@ Οι νέες λήψεις χρησιμοποιούν τώρα δεδομένα κινητής. Αυτό μπορεί να έχει κόστος ή να εξαντλήσει το πρόγραμμα δεδομένων σας. Ειδοποιήσεις κόστους δεδομένων Εγχειρίδιο + Φιλοξενία κώδικα + Φιλοξενία κώδικα + Ιδιωτικός διακομιστής Git στη συσκευή σας. Αποθηκεύστε αποθετήρια κώδικα, παρακολουθήστε ζητήματα και ελέγξτε αλλαγές εκτός σύνδεσης. Δεν χρειάζεται σύνδεση στο διαδίκτυο. + Μόνο ο διακομιστής Git. Δημιουργείτε τα δικά σας αποθετήρια μετά την εγκατάσταση. diff --git a/controller/app/src/main/res/values-es/strings.xml b/controller/app/src/main/res/values-es/strings.xml index 45f37e316..34ce1c03a 100644 --- a/controller/app/src/main/res/values-es/strings.xml +++ b/controller/app/src/main/res/values-es/strings.xml @@ -1417,4 +1417,8 @@ Las nuevas descargas ahora usan datos móviles. Esto puede tener costo o agotar tu plan de datos. Alertas de costo de datos Manual + Aloja tu código + Alojamiento de código + Un servidor Git privado en tu caja. Guarda repositorios de código, sigue incidencias y revisa cambios sin conexión. No se necesita internet. + Solo el servidor Git. Tú creas tus propios repositorios después de instalarlo. diff --git a/controller/app/src/main/res/values-fa/strings.xml b/controller/app/src/main/res/values-fa/strings.xml index 572c531d8..3d5487602 100644 --- a/controller/app/src/main/res/values-fa/strings.xml +++ b/controller/app/src/main/res/values-fa/strings.xml @@ -1341,4 +1341,8 @@ دانلودهای جدید اکنون از داده تلفن همراه استفاده می‌کنند. این ممکن است هزینه داشته باشد یا بسته داده شما را تمام کند. هشدارهای هزینه داده راهنما + میزبانی کد + میزبانی کد + یک سرور Git خصوصی روی دستگاه شما. مخازن کد را ذخیره کنید، مسائل را دنبال کنید و تغییرات را آفلاین بررسی کنید. به اینترنت نیازی نیست. + فقط سرور Git. پس از نصب، مخازن خود را می‌سازید. diff --git a/controller/app/src/main/res/values-fr/strings.xml b/controller/app/src/main/res/values-fr/strings.xml index faf4293e9..24a4612ea 100644 --- a/controller/app/src/main/res/values-fr/strings.xml +++ b/controller/app/src/main/res/values-fr/strings.xml @@ -1428,4 +1428,8 @@ Les nouveaux téléchargements utilisent désormais les données mobiles. Cela peut coûter cher ou épuiser votre forfait de données. Alertes de coût des données Manuel + Héberger le code + Hébergement de code + Un serveur Git privé sur votre boîtier. Stockez des dépôts de code, suivez les problèmes et examinez les changements hors ligne. Aucune connexion requise. + Le serveur Git seul. Vous créez vos propres dépôts après l\'installation. diff --git a/controller/app/src/main/res/values-gu/strings.xml b/controller/app/src/main/res/values-gu/strings.xml index 86fc46eaa..1f91400fa 100644 --- a/controller/app/src/main/res/values-gu/strings.xml +++ b/controller/app/src/main/res/values-gu/strings.xml @@ -1354,4 +1354,8 @@ નવા ડાઉનલોડ હવે મોબાઇલ ડેટા વાપરે છે. તેનાથી ખર્ચ થઈ શકે અથવા તમારો ડેટા પ્લાન ખતમ થઈ શકે. ડેટા ખર્ચ ચેતવણીઓ માર્ગદર્શિકા + કોડ હોસ્ટ કરો + કોડ હોસ્ટિંગ + તમારા ઉપકરણ પર ખાનગી Git સર્વર. કોડ રિપોઝિટરી સાચવો, સમસ્યાઓ ટ્રૅક કરો અને ફેરફારો ઑફલાઇન સમીક્ષા કરો. ઇન્ટરનેટની જરૂર નથી. + ફક્ત Git સર્વર. ઇન્સ્ટૉલ કર્યા પછી તમે તમારી પોતાની રિપોઝિટરી બનાવો છો. diff --git a/controller/app/src/main/res/values-hi/strings.xml b/controller/app/src/main/res/values-hi/strings.xml index 0082f8868..c09a487d9 100644 --- a/controller/app/src/main/res/values-hi/strings.xml +++ b/controller/app/src/main/res/values-hi/strings.xml @@ -1418,4 +1418,8 @@ नए डाउनलोड अब मोबाइल डेटा का उपयोग करते हैं. इससे शुल्क लग सकता है या आपका डेटा प्लान खत्म हो सकता है. डेटा लागत अलर्ट मैनुअल + कोड होस्ट करें + कोड होस्टिंग + आपके डिवाइस पर एक निजी Git सर्वर। कोड रिपॉज़िटरी सहेजें, समस्याएँ ट्रैक करें और बदलावों की समीक्षा ऑफ़लाइन करें। इंटरनेट की ज़रूरत नहीं। + केवल Git सर्वर। इंस्टॉल करने के बाद आप अपनी रिपॉज़िटरी बनाते हैं। diff --git a/controller/app/src/main/res/values-hu/strings.xml b/controller/app/src/main/res/values-hu/strings.xml index 280654714..9b97f0272 100644 --- a/controller/app/src/main/res/values-hu/strings.xml +++ b/controller/app/src/main/res/values-hu/strings.xml @@ -1341,4 +1341,8 @@ Az új letöltések most mobiladatot használnak. Ez költséggel járhat, vagy elfogyaszthatja az adatkeretét. Adatköltség-figyelmeztetések Kézikönyv + Kód tárolása + Kódtárhely + Privát Git-kiszolgáló a készülékén. Tároljon kódtárakat, kövesse a hibákat, és nézze át a módosításokat offline. Nincs szükség internetre. + Csak a Git-kiszolgáló. A saját tárait a telepítés után hozza létre. diff --git a/controller/app/src/main/res/values-in/strings.xml b/controller/app/src/main/res/values-in/strings.xml index d75a1f87c..1459c1768 100644 --- a/controller/app/src/main/res/values-in/strings.xml +++ b/controller/app/src/main/res/values-in/strings.xml @@ -1348,4 +1348,8 @@ Unduhan baru kini menggunakan data seluler. Ini dapat menimbulkan biaya atau menghabiskan paket data Anda. Peringatan biaya data Manual + Hosting kode + Hosting kode + Server Git pribadi di perangkat Anda. Simpan repositori kode, lacak masalah, dan tinjau perubahan secara offline. Tidak perlu internet. + Hanya server Git. Anda membuat repositori sendiri setelah memasangnya. diff --git a/controller/app/src/main/res/values-it/strings.xml b/controller/app/src/main/res/values-it/strings.xml index 2d7d26a2e..cf782d256 100644 --- a/controller/app/src/main/res/values-it/strings.xml +++ b/controller/app/src/main/res/values-it/strings.xml @@ -1341,4 +1341,8 @@ I nuovi download ora usano i dati mobili. Questo può comportare costi o esaurire il tuo piano dati. Avvisi sui costi dei dati Manuale + Ospita il codice + Hosting del codice + Un server Git privato sul tuo dispositivo. Archivia repository di codice, tieni traccia dei problemi e rivedi le modifiche offline. Nessuna connessione necessaria. + Solo il server Git. Crei i tuoi repository dopo l\'installazione. diff --git a/controller/app/src/main/res/values-ja/strings.xml b/controller/app/src/main/res/values-ja/strings.xml index ff3326073..0f9af9aa6 100644 --- a/controller/app/src/main/res/values-ja/strings.xml +++ b/controller/app/src/main/res/values-ja/strings.xml @@ -1342,4 +1342,8 @@ 新しいダウンロードはモバイルデータを使用します。料金が発生したり、データプランを使い切ったりする可能性があります。 データ料金の通知 マニュアル + コードをホスト + コードのホスティング + デバイス上のプライベート Git サーバー。コードリポジトリを保存し、課題を追跡し、変更をオフラインで確認できます。インターネットは不要です。 + Git サーバーのみ。インストール後にご自身のリポジトリを作成します。 diff --git a/controller/app/src/main/res/values-ko/strings.xml b/controller/app/src/main/res/values-ko/strings.xml index ac536dcf3..2c2a085e0 100644 --- a/controller/app/src/main/res/values-ko/strings.xml +++ b/controller/app/src/main/res/values-ko/strings.xml @@ -1342,4 +1342,8 @@ 새 다운로드가 이제 모바일 데이터를 사용합니다. 요금이 발생하거나 데이터 요금제를 모두 사용할 수 있습니다. 데이터 요금 알림 설명서 + 코드 호스팅 + 코드 호스팅 + 기기의 개인 Git 서버. 코드 저장소를 저장하고 이슈를 추적하며 변경 사항을 오프라인으로 검토하세요. 인터넷이 필요 없습니다. + Git 서버만 제공됩니다. 설치 후 직접 저장소를 만듭니다. diff --git a/controller/app/src/main/res/values-lt/strings.xml b/controller/app/src/main/res/values-lt/strings.xml index cdb475abd..c2f18f247 100644 --- a/controller/app/src/main/res/values-lt/strings.xml +++ b/controller/app/src/main/res/values-lt/strings.xml @@ -1358,4 +1358,8 @@ Nauji atsisiuntimai dabar naudoja mobiliuosius duomenis. Tai gali kainuoti arba išnaudoti duomenų planą. Duomenų sąnaudų įspėjimai Vadovas + Talpinti kodą + Kodo talpinimas + Privatus Git serveris jūsų įrenginyje. Saugokite kodo saugyklas, sekite problemas ir peržiūrėkite pakeitimus neprisijungę. Interneto nereikia. + Tik Git serveris. Savo saugyklas sukuriate įdiegę. diff --git a/controller/app/src/main/res/values-nl/strings.xml b/controller/app/src/main/res/values-nl/strings.xml index 9d42a8c8d..177e9940a 100644 --- a/controller/app/src/main/res/values-nl/strings.xml +++ b/controller/app/src/main/res/values-nl/strings.xml @@ -1341,4 +1341,8 @@ Nieuwe downloads gebruiken nu mobiele data. Dit kan kosten opleveren of je databundel opmaken. Waarschuwingen datakosten Handleiding + Code hosten + Codehosting + Een privé-Git-server op je box. Sla codeopslagplaatsen op, volg problemen en bekijk wijzigingen offline. Geen internet nodig. + Alleen de Git-server. Je maakt je eigen opslagplaatsen na de installatie. diff --git a/controller/app/src/main/res/values-no/strings.xml b/controller/app/src/main/res/values-no/strings.xml index d8b395f80..fb113d53a 100644 --- a/controller/app/src/main/res/values-no/strings.xml +++ b/controller/app/src/main/res/values-no/strings.xml @@ -1351,4 +1351,8 @@ Nye nedlastinger bruker nå mobildata. Det kan koste penger eller bruke opp datapakken din. Varsler om datakostnad Håndbok + Vert for kode + Kodehosting + En privat Git-server på boksen din. Lagre kodelagre, følg saker og se gjennom endringer offline. Ingen internett nødvendig. + Bare Git-serveren. Du oppretter dine egne lagre etter installasjonen. diff --git a/controller/app/src/main/res/values-pl/strings.xml b/controller/app/src/main/res/values-pl/strings.xml index 36ef3b2e6..7d2d8de3a 100644 --- a/controller/app/src/main/res/values-pl/strings.xml +++ b/controller/app/src/main/res/values-pl/strings.xml @@ -1351,4 +1351,8 @@ Nowe pobierania używają teraz danych komórkowych. Może to wiązać się z kosztami lub wyczerpać pakiet danych. Alerty o kosztach danych Podręcznik + Hostuj kod + Hosting kodu + Prywatny serwer Git na Twoim urządzeniu. Przechowuj repozytoria kodu, śledź zgłoszenia i przeglądaj zmiany offline. Internet nie jest potrzebny. + Tylko serwer Git. Własne repozytoria tworzysz po instalacji. diff --git a/controller/app/src/main/res/values-pt/strings.xml b/controller/app/src/main/res/values-pt/strings.xml index 5660d4baa..503b523dc 100644 --- a/controller/app/src/main/res/values-pt/strings.xml +++ b/controller/app/src/main/res/values-pt/strings.xml @@ -1420,4 +1420,8 @@ Os novos downloads agora usam dados móveis. Isso pode gerar custos ou esgotar seu plano de dados. Alertas de custo de dados Manual + Hospedar código + Hospedagem de código + Um servidor Git privado na sua caixa. Guarde repositórios de código, acompanhe problemas e reveja alterações offline. Não é preciso internet. + Apenas o servidor Git. Você cria os seus próprios repositórios após instalar. diff --git a/controller/app/src/main/res/values-ro/strings.xml b/controller/app/src/main/res/values-ro/strings.xml index 2b773b137..8fe326901 100644 --- a/controller/app/src/main/res/values-ro/strings.xml +++ b/controller/app/src/main/res/values-ro/strings.xml @@ -1341,4 +1341,8 @@ Descărcările noi folosesc acum date mobile. Acest lucru poate genera costuri sau îți poate epuiza planul de date. Alerte privind costul datelor Manual + Găzduiește cod + Găzduire de cod + Un server Git privat pe dispozitivul tău. Stochează depozite de cod, urmărește problemele și revizuiește modificările offline. Nu este nevoie de internet. + Doar serverul Git. Îți creezi propriile depozite după instalare. 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 822eba8d9..d764d15a0 100644 --- a/controller/app/src/main/res/values-ru-rRU/strings.xml +++ b/controller/app/src/main/res/values-ru-rRU/strings.xml @@ -1417,4 +1417,8 @@ Новые загрузки теперь используют мобильные данные. Это может повлечь расходы или исчерпать пакет данных. Оповещения о расходе данных Руководство + Хостинг кода + Хостинг кода + Частный сервер Git на вашем устройстве. Храните репозитории кода, отслеживайте задачи и просматривайте изменения офлайн. Интернет не нужен. + Только сервер Git. Свои репозитории вы создаёте после установки. diff --git a/controller/app/src/main/res/values-sk/strings.xml b/controller/app/src/main/res/values-sk/strings.xml index e31a2df08..7c9a37ea2 100644 --- a/controller/app/src/main/res/values-sk/strings.xml +++ b/controller/app/src/main/res/values-sk/strings.xml @@ -1348,4 +1348,8 @@ Nové sťahovania teraz používajú mobilné dáta. Môže byť spoplatnené alebo vyčerpať váš dátový limit. Upozornenia na náklady za dáta Príručka + Hostovať kód + Hosting kódu + Súkromný server Git na vašom zariadení. Ukladajte úložiská kódu, sledujte problémy a kontrolujte zmeny offline. Internet nie je potrebný. + Len server Git. Vlastné úložiská si vytvoríte po inštalácii. diff --git a/controller/app/src/main/res/values-sr/strings.xml b/controller/app/src/main/res/values-sr/strings.xml index 92b668fbd..fa0f84427 100644 --- a/controller/app/src/main/res/values-sr/strings.xml +++ b/controller/app/src/main/res/values-sr/strings.xml @@ -1354,4 +1354,8 @@ Нова преузимања сада користе мобилне податке. Ово може да има трошак или да потроши ваш план за податке. Обавештења о трошку података Приручник + Хостуј код + Хостинг кода + Приватни Git сервер на вашем уређају. Чувајте спремишта кода, пратите проблеме и прегледајте измене офлајн. Интернет није потребан. + Само Git сервер. Сопствена спремишта правите након инсталације. diff --git a/controller/app/src/main/res/values-sw/strings.xml b/controller/app/src/main/res/values-sw/strings.xml index 0d17cf640..17a5b559c 100644 --- a/controller/app/src/main/res/values-sw/strings.xml +++ b/controller/app/src/main/res/values-sw/strings.xml @@ -1361,4 +1361,8 @@ Vipakuliwa vipya sasa vinatumia data ya simu. Hii inaweza kugharimu au kumaliza kifurushi chako cha data. Arifa za gharama ya data Mwongozo + Pangisha msimbo + Upangishaji wa msimbo + Seva ya Git ya faragha kwenye kifaa chako. Hifadhi hazina za msimbo, fuatilia masuala, na kagua mabadiliko nje ya mtandao. Hakuna intaneti inayohitajika. + Seva ya Git pekee. Unaunda hazina zako mwenyewe baada ya kuisakinisha. diff --git a/controller/app/src/main/res/values-ta/strings.xml b/controller/app/src/main/res/values-ta/strings.xml index 359b9c662..ed35ef885 100644 --- a/controller/app/src/main/res/values-ta/strings.xml +++ b/controller/app/src/main/res/values-ta/strings.xml @@ -1361,4 +1361,8 @@ புதிய பதிவிறக்கங்கள் இப்போது மொபைல் டேட்டாவைப் பயன்படுத்துகின்றன. இதனால் கட்டணம் ஏற்படலாம் அல்லது உங்கள் தரவுத் திட்டம் தீர்ந்துபோகலாம். டேட்டா செலவு எச்சரிக்கைகள் கையேடு + குறியீட்டை ஹோஸ்ட் செய் + குறியீடு ஹோஸ்டிங் + உங்கள் சாதனத்தில் தனிப்பட்ட Git சேவையகம். குறியீட்டுக் களஞ்சியங்களைச் சேமியுங்கள், சிக்கல்களைக் கண்காணியுங்கள், மாற்றங்களை ஆஃப்லைனில் மதிப்பாய்வு செய்யுங்கள். இணையம் தேவையில்லை. + Git சேவையகம் மட்டுமே. நிறுவிய பிறகு உங்கள் சொந்தக் களஞ்சியங்களை உருவாக்குகிறீர்கள். diff --git a/controller/app/src/main/res/values-tr/strings.xml b/controller/app/src/main/res/values-tr/strings.xml index 7930756e2..a4d761ab6 100644 --- a/controller/app/src/main/res/values-tr/strings.xml +++ b/controller/app/src/main/res/values-tr/strings.xml @@ -1341,4 +1341,8 @@ Yeni indirmeler artık mobil veri kullanıyor. Bu, ücretlendirilebilir veya veri paketinizi tüketebilir. Veri maliyeti uyarıları Kılavuz + Kodu barındır + Kod barındırma + Cihazınızda özel bir Git sunucusu. Kod depolarını saklayın, sorunları izleyin ve değişiklikleri çevrimdışı inceleyin. İnternet gerekmez. + Yalnızca Git sunucusu. Kendi depolarınızı kurduktan sonra oluşturursunuz. diff --git a/controller/app/src/main/res/values-uk/strings.xml b/controller/app/src/main/res/values-uk/strings.xml index 24a25c299..20d7d1987 100644 --- a/controller/app/src/main/res/values-uk/strings.xml +++ b/controller/app/src/main/res/values-uk/strings.xml @@ -1341,4 +1341,8 @@ Нові завантаження тепер використовують мобільні дані. Це може призвести до витрат або вичерпати пакет даних. Сповіщення про витрати даних Посібник + Хостинг коду + Хостинг коду + Приватний сервер Git на вашому пристрої. Зберігайте репозиторії коду, відстежуйте задачі та переглядайте зміни офлайн. Інтернет не потрібен. + Лише сервер Git. Власні репозиторії ви створюєте після встановлення. diff --git a/controller/app/src/main/res/values-vi/strings.xml b/controller/app/src/main/res/values-vi/strings.xml index ad3286b29..b29a8cb19 100644 --- a/controller/app/src/main/res/values-vi/strings.xml +++ b/controller/app/src/main/res/values-vi/strings.xml @@ -1341,4 +1341,8 @@ Các bản tải xuống mới hiện dùng dữ liệu di động. Điều này có thể phát sinh chi phí hoặc dùng hết gói dữ liệu của bạn. Cảnh báo chi phí dữ liệu Hướng dẫn + Lưu trữ mã + Lưu trữ mã + Máy chủ Git riêng trên thiết bị của bạn. Lưu kho mã, theo dõi vấn đề và xem lại thay đổi ngoại tuyến. Không cần internet. + Chỉ máy chủ Git. Bạn tạo kho của riêng mình sau khi cài đặt. diff --git a/controller/app/src/main/res/values-yo/strings.xml b/controller/app/src/main/res/values-yo/strings.xml index 0c61dfe11..22e541132 100644 --- a/controller/app/src/main/res/values-yo/strings.xml +++ b/controller/app/src/main/res/values-yo/strings.xml @@ -1361,4 +1361,8 @@ Awọn igbasilẹ tuntun n lo data alagbeka bayii. Eyi le san owo tabi lo package data rẹ tan. Awọn ikilọ iye owo data Ìwé Ìtọ́sọ́nà + Gbàlejò kóòdù + Ìgbàlejò kóòdù + Olùpín Git aládàáni lórí ẹ̀rọ rẹ. Fi àwọn ibi ìpamọ́ kóòdù pamọ́, tọpinpin àwọn ìṣòro, kí o sì ṣàyẹ̀wò àwọn ìyípadà láìsí Íńtánẹ́ẹ̀tì. A kò nílò Íńtánẹ́ẹ̀tì. + Olùpín Git nìkan. O máa ń ṣẹ̀dá àwọn ibi ìpamọ́ tìrẹ lẹ́yìn ìfìsórí-ẹ̀rọ. 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 a7595b7ea..a17cd20bf 100644 --- a/controller/app/src/main/res/values-zh-rCN/strings.xml +++ b/controller/app/src/main/res/values-zh-rCN/strings.xml @@ -1342,4 +1342,8 @@ 新的下载现在会使用移动数据。这可能产生费用或用尽您的套餐流量。 数据费用提醒 手册 + 托管代码 + 代码托管 + 设备上的私有 Git 服务器。离线存储代码仓库、跟踪问题并审查更改。无需联网。 + 仅提供 Git 服务器。安装后由你创建自己的仓库。 diff --git a/controller/app/src/main/res/values/strings.xml b/controller/app/src/main/res/values/strings.xml index f5383255b..7a7c2f551 100644 --- a/controller/app/src/main/res/values/strings.xml +++ b/controller/app/src/main/res/values/strings.xml @@ -58,6 +58,7 @@ Kolibri IIAB Maps Matomo + Forgejo System Code @@ -754,6 +755,9 @@ Analytics Matomo Private, self-hosted usage analytics for your library that helps you understand how content is being used. + Code hosting + Forgejo + A private Git server on your box. Store code repositories, track issues, and review changes offline. No internet is needed. Backups & recovery You can save your system to a file, restore your system from a backup file, or reinstall K2Go from scratch @@ -944,6 +948,7 @@ Explore Wikipedia Take courses Navigate maps + Host code Ready to explore Ready Connecting @@ -1345,6 +1350,7 @@ Reader only. Add books in Get more. Reader only. Add Wikipedia and ZIM content in Get more. This downloads the Code on the Go (CoGo) installer to your phone. CoGo runs outside K2Go. In your phone\'s Downloads folder, tap the file to install CoGo. + The Git server only. You create your own repositories after you install it. Analytics for this server — nothing to add. Base map — pick the layers and region when you install. From 27be874b44c38d0ad47b634e328b322ddbeea881 Mon Sep 17 00:00:00 2001 From: "Luis Guzman (AppDevForAll)" Date: Wed, 23 Sep 2026 19:30:42 -0600 Subject: [PATCH 4/9] K2GO-212 feat(portal): add Forgejo card and discovery Add the Forgejo card to the box portal home and register /forgejo/ in the discovery probe, with the card label localized across the portal languages. --- static/site/index.html | 6 ++++++ static/site/js/app.js | 1 + static/site/lang/ar.js | 1 + static/site/lang/az.js | 1 + static/site/lang/bg.js | 1 + static/site/lang/bn.js | 1 + static/site/lang/cs.js | 1 + static/site/lang/de.js | 1 + static/site/lang/el.js | 1 + static/site/lang/en.js | 1 + static/site/lang/es.js | 1 + static/site/lang/fa.js | 1 + static/site/lang/fr.js | 1 + static/site/lang/gu.js | 1 + static/site/lang/hi.js | 1 + static/site/lang/hu.js | 1 + static/site/lang/id.js | 1 + static/site/lang/it.js | 1 + static/site/lang/ja.js | 1 + static/site/lang/ko.js | 1 + static/site/lang/lt.js | 1 + static/site/lang/nl.js | 1 + static/site/lang/no.js | 1 + static/site/lang/pl.js | 1 + static/site/lang/ro.js | 1 + static/site/lang/ru.js | 1 + static/site/lang/sk.js | 1 + static/site/lang/sr.js | 1 + static/site/lang/sw.js | 1 + static/site/lang/ta.js | 1 + static/site/lang/tr.js | 1 + static/site/lang/uk.js | 1 + static/site/lang/vi.js | 1 + static/site/lang/yo.js | 1 + static/site/lang/zh.js | 1 + 35 files changed, 40 insertions(+) diff --git a/static/site/index.html b/static/site/index.html index eb9821511..8ffb03d5d 100644 --- a/static/site/index.html +++ b/static/site/index.html @@ -45,6 +45,12 @@

Knowledge to Go

+ +