GRM-111: feat: unified --become-password-file, --verbose, --no-status, labels fix
Post-merge / detect-type (push) Successful in 51s
Post-merge / release (push) Successful in 1m14s
Post-merge / validate-commit-msg (push) Successful in 1m14s
Post-merge / vikunja (push) Successful in 1m14s
Post-merge / badges (push) Successful in 1m25s
Post-merge / sync-wiki (push) Successful in 1m53s
Post-merge / configure-repo (push) Successful in 1m20s
Post-merge / publish (push) Successful in 1m21s

This commit was merged in pull request #178.
This commit is contained in:
2026-06-28 11:21:11 +00:00
parent 763f7640af
commit f67dff8458
16 changed files with 865 additions and 73 deletions
+108 -29
View File
@@ -6,6 +6,7 @@ import functools
import os
import sys
from collections.abc import Callable
from pathlib import Path
from typing import Any
import click
@@ -19,6 +20,34 @@ from .runner_manager import RunnerManager
load_dotenv(override=True)
def _default_user() -> str:
"""Default SSH user from env or current OS user."""
if user := os.getenv("GITEA_RUNNER_USER"):
return user
try:
return os.getlogin()
except OSError:
return os.getenv("USER", "root")
def _get_become_password_file() -> str | None:
"""Read become-password-file from Click context or env vars."""
ctx = click.get_current_context(silent=True)
if ctx and ctx.obj:
path = ctx.obj.get("become_password_file")
if path:
return path
return os.getenv("GRM_BECOME_PASSWORD_FILE") or os.getenv("ANSIBLE_BECOME_PASSWORD_FILE")
def _get_verbose() -> bool:
"""Read verbose flag from Click context."""
ctx = click.get_current_context(silent=True)
if ctx and ctx.obj:
return ctx.obj.get("verbose", False)
return False
def _runner_options(func: Callable[..., Any]) -> Callable[..., Any]:
"""Apply common override options for registry-based lifecycle commands."""
func = click.option(
@@ -50,8 +79,25 @@ def _handle_errors(msg_key: str) -> Callable[[Callable[..., Any]], Callable[...,
@click.group(help=_("Gitea Runner Manager — manage Gitea Actions runners."))
@click.version_option(version=__version__)
def cli() -> None:
pass
@click.option(
"--become-password-file",
envvar="GRM_BECOME_PASSWORD_FILE",
type=click.Path(exists=True, dir_okay=False, readable=True),
default=None,
help=_("Read sudo password from a file instead of prompting (env: GRM_BECOME_PASSWORD_FILE)"),
)
@click.option(
"--verbose",
"-v",
is_flag=True,
default=False,
help=_("Enable verbose Ansible output (-v flag passed to ansible)"),
)
@click.pass_context
def cli(ctx: click.Context, become_password_file: str | None, verbose: bool) -> None:
ctx.ensure_object(dict)
ctx.obj["become_password_file"] = become_password_file
ctx.obj["verbose"] = verbose
@cli.command(help=_("Install and configure a Gitea Runner on a remote host."))
@@ -59,8 +105,8 @@ def cli() -> None:
@click.option(
"--user",
"-u",
default=lambda: os.getenv("GITEA_RUNNER_USER", os.getlogin()),
help=_("SSH user"),
default=_default_user,
help=_("SSH user (env: GITEA_RUNNER_USER)"),
)
@click.option("--key", "-k", default=lambda: os.getenv("GITEA_RUNNER_KEY"), help=_("Path to SSH private key"))
@click.option("--name", "-n", help=_("Gitea Runner name (default: host)"))
@@ -91,14 +137,19 @@ def cli() -> None:
@click.option(
"--labels",
"-l",
default=lambda: os.getenv("GITEA_RUNNER_LABELS", ""),
help=_("Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest"),
default=None,
help=_(
"Runner labels (env: GITEA_RUNNER_LABELS). "
"Pass an empty string for no labels. "
"Example: docker:docker://alpine:latest"
),
)
@click.option(
"--ask-become-pass/--no-ask-become-pass",
default=True,
help=_("Prompt for sudo password (default)"),
)
@_handle_errors("Installation failed: {error}")
def install(
host: str,
user: str,
@@ -108,25 +159,26 @@ def install(
url: str,
admin_token: str | None,
integration_retries: int,
labels: str,
labels: str | None,
ask_become_pass: bool,
) -> None:
if labels is None:
labels = os.getenv("GITEA_RUNNER_LABELS")
manager = RunnerManager()
try:
manager.install(
host=host,
user=user,
key=key,
name=name,
token=token,
gitea_url=url,
admin_token=admin_token,
integration_retries=integration_retries,
labels=labels or None,
ask_become_pass=ask_become_pass,
)
except GRMError as e:
raise click.ClickException(_("Installation failed: {error}", error=e)) from e
manager.install(
host=host,
user=user,
key=key,
name=name,
token=token,
gitea_url=url,
admin_token=admin_token,
integration_retries=integration_retries,
labels=labels,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@cli.command(help=_("Update the Gitea Runner binary on a remote host."))
@@ -134,8 +186,8 @@ def install(
@click.option(
"--user",
"-u",
default=lambda: os.getenv("GITEA_RUNNER_USER", os.getlogin()),
help=_("SSH user"),
default=_default_user,
help=_("SSH user (env: GITEA_RUNNER_USER)"),
)
@click.option("--key", "-k", default=lambda: os.getenv("GITEA_RUNNER_KEY"), help=_("Path to SSH private key"))
@click.option("--version", "-v", help=_("Specific Gitea Runner version"))
@@ -159,6 +211,8 @@ def update(
key=key,
version=version,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -180,6 +234,8 @@ def start(
user=user,
key=key,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -201,6 +257,8 @@ def stop(
user=user,
key=key,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -222,6 +280,8 @@ def restart(
user=user,
key=key,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -243,6 +303,8 @@ def enable(
user=user,
key=key,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -279,6 +341,8 @@ def disable(
token=token,
gitea_url=url,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -300,6 +364,8 @@ def status(
user=user,
key=key,
ask_become_pass=ask_become_pass,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
@@ -344,15 +410,22 @@ def remove(
gitea_url=url,
ask_become_pass=ask_become_pass,
force=force,
become_password_file=_get_become_password_file(),
verbose=_get_verbose(),
)
def _collect_become_pass(ask_become_pass: bool) -> str | None:
"""Collect sudo password for ad-hoc status checks.
When stdin is a TTY, prompts interactively with hidden input.
When stdin is piped, reads the first line (e.g. ``echo 'pass' | grm list``).
Priority:
1. ``--become-password-file`` / ``GRM_BECOME_PASSWORD_FILE`` env var
2. ``ANSIBLE_BECOME_PASSWORD_FILE`` env var
3. Interactive prompt (TTY) or piped stdin (first line)
"""
password_file = _get_become_password_file()
if password_file:
return Path(password_file).read_text(encoding="utf-8").strip() or None
if not ask_become_pass:
return None
if sys.stdin.isatty():
@@ -374,12 +447,18 @@ def _collect_become_pass(ask_become_pass: bool) -> str | None:
default=True,
help=_("Prompt for sudo password once for all status checks (default)."),
)
@click.option(
"--no-status",
is_flag=True,
default=False,
help=_("Skip live SSH status checks and show registry entries only"),
)
@_handle_errors("List failed: {error}")
def list_runners(ask_become_pass: bool) -> None:
become_pass = _collect_become_pass(ask_become_pass)
def list_runners(ask_become_pass: bool, no_status: bool) -> None:
become_pass = None if no_status else _collect_become_pass(ask_become_pass)
manager = RunnerManager()
runners = manager.list_runners(become_pass=become_pass)
runners = manager.list_runners(become_pass=become_pass, no_status=no_status)
if not runners:
click.echo(_("No runners registered. Use 'grm install' to add one."))
+6 -2
View File
@@ -116,12 +116,16 @@ class AnsibleExecutor:
if become and ask_become_pass:
if become_pass:
fd, password_file = tempfile.mkstemp(suffix=".txt", prefix="grm-become-")
os.fchmod(fd, 0o600)
with os.fdopen(fd, "w") as f:
f.write(become_pass)
os.chmod(password_file, 0o600)
cmd.extend(["--become-password-file", password_file])
else:
cmd.append("--ask-become-pass")
env_password_file = os.getenv("ANSIBLE_BECOME_PASSWORD_FILE")
if env_password_file:
cmd.extend(["--become-password-file", env_password_file])
else:
cmd.append("--ask-become-pass")
env = os.environ.copy()
try:
+13 -3
View File
@@ -7,12 +7,22 @@ Supported: en, bg, de, ru, zh, pl.
from __future__ import annotations
import json
import logging
import os
from pathlib import Path
TRANSLATIONS: dict[str, dict[str, str]] = json.loads(
(Path(__file__).parent / "translations.json").read_text(encoding="utf-8")
)
logger = logging.getLogger("grm")
def _load_translations() -> dict[str, dict[str, str]]:
try:
return json.loads((Path(__file__).parent / "translations.json").read_text(encoding="utf-8"))
except (json.JSONDecodeError, OSError) as e:
logger.warning("Failed to load translations.json: %s — falling back to English", e)
return {}
TRANSLATIONS: dict[str, dict[str, str]] = _load_translations()
def _(key: str, **kwargs: object) -> str:
+16 -6
View File
@@ -6,6 +6,7 @@ so that subsequent lifecycle commands only need the runner name.
from __future__ import annotations
import fcntl
import json
from datetime import UTC, datetime
from pathlib import Path
@@ -23,20 +24,29 @@ class RunnerRegistry:
self._data: dict[str, dict[str, Any]] = self._load()
def _load(self) -> dict[str, dict[str, Any]]:
if self._path.exists():
try:
with open(self._path) as f:
if not self._path.exists():
return {}
try:
with open(self._path) as f:
fcntl.flock(f.fileno(), fcntl.LOCK_SH)
try:
data: Any = json.load(f)
if isinstance(data, dict):
return cast(dict[str, dict[str, Any]], data)
except (json.JSONDecodeError, OSError):
pass
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except (json.JSONDecodeError, OSError):
pass
return {}
def _save(self) -> None:
self._path.parent.mkdir(parents=True, exist_ok=True)
with open(self._path, "w") as f:
json.dump(self._data, f, indent=2)
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
json.dump(self._data, f, indent=2)
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
def add(
self,
-1
View File
@@ -49,7 +49,6 @@ def track_steps() -> Generator[StepTracker, None, None]:
for step in reversed(tracker.steps):
if step.status == "in_progress":
step.status = "failed"
break
raise
finally:
_print_report(tracker.steps)
+77 -6
View File
@@ -42,9 +42,9 @@ class RunnerManager:
return
fd, path = tempfile.mkstemp(suffix=".json", prefix="grm-vars-")
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "w") as f:
json.dump(extra_vars, f)
os.chmod(path, 0o600)
yield path
finally:
with contextlib.suppress(FileNotFoundError):
@@ -59,10 +59,21 @@ class RunnerManager:
key: str | None = None,
ask_become_pass: bool = False,
description: str = "",
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Build command with temp-file extra-vars and execute via executor."""
with self._extra_vars_file(extra_vars) as vars_file:
cmd = self._build_cmd(playbook_name, host, user, vars_file, key, ask_become_pass)
cmd = self._build_cmd(
playbook_name,
host,
user,
vars_file,
key,
ask_become_pass,
become_password_file,
verbose,
)
self._executor.run(cmd, description=description)
def install(
@@ -77,6 +88,8 @@ class RunnerManager:
integration_retries: int = 3,
ask_become_pass: bool = False,
labels: str | None = None,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Install a runner on a remote host using Ansible."""
if not name:
@@ -94,7 +107,7 @@ class RunnerManager:
}
if admin_token:
extra_vars["gitea_admin_token"] = admin_token
if labels:
if labels is not None:
extra_vars["runner_labels"] = labels
with track_steps() as tracker:
@@ -107,6 +120,8 @@ class RunnerManager:
key,
ask_become_pass,
description=_("Installing Gitea Runner on {host}", host=host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -128,6 +143,8 @@ class RunnerManager:
key: str | None = None,
version: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Update the gitea_runner binary on a remote host."""
extra_vars: dict[str, str | int] | None = None
@@ -144,6 +161,8 @@ class RunnerManager:
key,
ask_become_pass,
description=_("Updating Gitea Runner on {host}", host=host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -187,6 +206,8 @@ class RunnerManager:
user: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Start a runner instance on a remote host."""
actual_host, actual_user, actual_key, _gitea_url = self._resolve_runner(name, host, user, key)
@@ -200,6 +221,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Starting Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -210,6 +233,8 @@ class RunnerManager:
user: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Stop a runner instance on a remote host."""
actual_host, actual_user, actual_key, _gitea_url = self._resolve_runner(name, host, user, key)
@@ -223,6 +248,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Stopping Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -233,6 +260,8 @@ class RunnerManager:
user: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Restart a runner instance on a remote host (stop, prune images, start)."""
actual_host, actual_user, actual_key, _gitea_url = self._resolve_runner(name, host, user, key)
@@ -246,6 +275,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Restarting Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -256,6 +287,8 @@ class RunnerManager:
user: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Enable a runner instance to start on boot."""
actual_host, actual_user, actual_key, _gitea_url = self._resolve_runner(name, host, user, key)
@@ -269,6 +302,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Enabling Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -281,6 +316,8 @@ class RunnerManager:
token: str | None = None,
gitea_url: str = "",
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Disable and deregister a runner instance."""
actual_host, actual_user, actual_key, registry_gitea_url = self._resolve_runner(name, host, user, key)
@@ -299,6 +336,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Disabling Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -309,6 +348,8 @@ class RunnerManager:
user: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Check the status of a runner instance."""
actual_host, actual_user, actual_key, _gitea_url = self._resolve_runner(name, host, user, key)
@@ -322,6 +363,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Checking status of Gitea Runner {name} on {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -335,6 +378,8 @@ class RunnerManager:
gitea_url: str = "",
ask_become_pass: bool = False,
force: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> None:
"""Remove a runner instance completely.
@@ -361,6 +406,8 @@ class RunnerManager:
actual_key,
ask_become_pass,
description=_("Removing Gitea Runner {name} from {host}", name=name, host=actual_host),
become_password_file=become_password_file,
verbose=verbose,
)
tracker.done()
@@ -368,13 +415,18 @@ class RunnerManager:
self._registry.remove(name)
tracker.done()
def list_runners(self, become_pass: str | None = None) -> list[dict[str, str]]:
def list_runners(
self,
become_pass: str | None = None,
no_status: bool = False,
) -> list[dict[str, str]]:
"""Return a list of registered runners with live service status.
Args:
become_pass: Sudo password for ad-hoc status checks. When provided,
it is passed via ``--become-password-file`` to avoid stdin
consumption issues when checking multiple runners in sequence.
no_status: Skip live SSH status checks and return ``"n/a"`` for status.
"""
runners = self._registry.list()
result: list[dict[str, str]] = []
@@ -382,6 +434,19 @@ class RunnerManager:
host = info["host"]
user = info["user"]
key = info.get("key")
if no_status:
result.append(
{
"name": name,
"host": host,
"user": user,
"labels": info.get("labels", ""),
"status": _("n/a"),
}
)
continue
say(
_(
"Checking status of Gitea Runner {name} on {host} as {user} (sudo required)",
@@ -402,7 +467,7 @@ class RunnerManager:
f"XDG_RUNTIME_DIR=/run/user/$(id -u grm-{name}) "
f"systemctl --user is-active gitea-runner 2>/dev/null",
become=True,
ask_become_pass=True,
ask_become_pass=become_pass is not None,
check=False,
become_pass=become_pass,
)
@@ -437,6 +502,8 @@ class RunnerManager:
extra_vars_file: str | None = None,
key: str | None = None,
ask_become_pass: bool = False,
become_password_file: str | None = None,
verbose: bool = False,
) -> list[str]:
"""Build the ansible-playbook command.
@@ -459,6 +526,10 @@ class RunnerManager:
cmd.extend(["--extra-vars", f"@{extra_vars_file}"])
if key:
cmd.extend(["--private-key", key])
if ask_become_pass:
if become_password_file:
cmd.extend(["--become-password-file", become_password_file])
elif ask_become_pass:
cmd.append("--ask-become-pass")
if verbose:
cmd.append("-v")
return cmd
+46 -14
View File
@@ -103,6 +103,14 @@
"ru": "Включение не удалось: {error}",
"zh": "启用失败: {error}"
},
"Enable verbose Ansible output (-v flag passed to ansible)": {
"bg": "Подробен изход от Ansible (-v флаг към ansible)",
"de": "Ausführliche Ansible-Ausgabe (-v Flag an ansible)",
"en": "Enable verbose Ansible output (-v flag passed to ansible)",
"pl": "Szczegółowe wyjście Ansible (flaga -v przekazana do ansible)",
"ru": "Подробный вывод Ansible (флаг -v передаётся в ansible)",
"zh": "启用 Ansible 详细输出(-v 标志传递给 ansible"
},
"Enabling Gitea Runner {name} on {host}": {
"bg": "Активиране на Gitea Runner {name} на {host}",
"de": "Aktiviere Gitea Runner {name} auf {host}",
@@ -239,6 +247,14 @@
"ru": "ИМЯ",
"zh": "名称"
},
"n/a": {
"bg": "н/д",
"de": "n. v.",
"en": "n/a",
"pl": "n/d",
"ru": "н/д",
"zh": "不适用"
},
"No runners registered. Use 'grm install' to add one.": {
"bg": "Няма регистрирани runners. Използвайте 'grm install', за да добавите.",
"de": "Keine Runner registriert. Verwenden Sie 'grm install', um einen hinzuzufügen.",
@@ -343,6 +359,14 @@
"ru": "Удаление Gitea Runner {name} с {host}",
"zh": "正在从 {host} 移除 Gitea Runner {name}"
},
"Read sudo password from a file instead of prompting (env: GRM_BECOME_PASSWORD_FILE)": {
"bg": "Четене на sudo парола от файл вместо интерактивно (env: GRM_BECOME_PASSWORD_FILE)",
"de": "Sudo-Passwort aus Datei lesen statt abfragen (env: GRM_BECOME_PASSWORD_FILE)",
"en": "Read sudo password from a file instead of prompting (env: GRM_BECOME_PASSWORD_FILE)",
"pl": "Odczytaj hasło sudo z pliku zamiast pytać (env: GRM_BECOME_PASSWORD_FILE)",
"ru": "Читать sudo-пароль из файла вместо ввода (env: GRM_BECOME_PASSWORD_FILE)",
"zh": "从文件读取 sudo 密码而非提示输入(环境变量: GRM_BECOME_PASSWORD_FILE"
},
"Restart a registered Gitea Runner (stop, prune images, start).": {
"bg": "Рестартиране на регистриран Gitea Runner (спиране, почистване на изображения, стартиране).",
"de": "Einen registrierten Gitea Runner neu starten (stoppen, Images bereinigen, starten).",
@@ -383,13 +407,13 @@
"ru": "Runner '{name}' не найден в реестре. Сначала используйте 'grm install' или укажите --host и --user.",
"zh": "注册表中未找到 Runner '{name}'。请先使用 'grm install' 或提供 --host 和 --user。"
},
"Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest": {
"bg": "Етикети на runner за Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"de": "Runner-Labels für Gitea Actions (env: GITEA_RUNNER_LABELS). Beispiel: docker:docker://alpine:latest",
"en": "Runner labels for Gitea Actions (env: GITEA_RUNNER_LABELS). Example: docker:docker://alpine:latest",
"pl": "Etykiety runnera dla Gitea Actions (env: GITEA_RUNNER_LABELS). Przykład: docker:docker://alpine:latest",
"ru": "Метки runner для Gitea Actions (env: GITEA_RUNNER_LABELS). Пример: docker:docker://alpine:latest",
"zh": "Gitea Actions 的 runner 标签(环境变量: GITEA_RUNNER_LABELS)。示例: docker:docker://alpine:latest"
"Runner labels (env: GITEA_RUNNER_LABELS). Pass an empty string for no labels. Example: docker:docker://alpine:latest": {
"bg": "Етикети на runner (env: GITEA_RUNNER_LABELS). Подайте празен низ за без етикети. Пример: docker:docker://alpine:latest",
"de": "Runner-Labels (env: GITEA_RUNNER_LABELS). Leerstring für keine Labels. Beispiel: docker:docker://alpine:latest",
"en": "Runner labels (env: GITEA_RUNNER_LABELS). Pass an empty string for no labels. Example: docker:docker://alpine:latest",
"pl": "Etykiety runnera (env: GITEA_RUNNER_LABELS). Pusty ciąg = brak etykiet. Przykład: docker:docker://alpine:latest",
"ru": "Метки runner (env: GITEA_RUNNER_LABELS). Пустая строка = без меток. Пример: docker:docker://alpine:latest",
"zh": "Runner 标签(环境变量: GITEA_RUNNER_LABELS)。空字符串表示无标签。示例: docker:docker://alpine:latest"
},
"Running Ansible playbook": {
"bg": "Изпълнение на Ansible playbook",
@@ -399,13 +423,13 @@
"ru": "Выполнение Ansible playbook",
"zh": "正在运行 Ansible playbook"
},
"SSH user": {
"bg": "SSH потребител",
"de": "SSH-Benutzer",
"en": "SSH user",
"pl": "Użytkownik SSH",
"ru": "SSH пользователь",
"zh": "SSH 用户"
"SSH user (env: GITEA_RUNNER_USER)": {
"bg": "SSH потребител (env: GITEA_RUNNER_USER)",
"de": "SSH-Benutzer (env: GITEA_RUNNER_USER)",
"en": "SSH user (env: GITEA_RUNNER_USER)",
"pl": "Użytkownik SSH (env: GITEA_RUNNER_USER)",
"ru": "SSH пользователь (env: GITEA_RUNNER_USER)",
"zh": "SSH 用户(环境变量: GITEA_RUNNER_USER"
},
"STATUS": {
"bg": "СТАТУС",
@@ -431,6 +455,14 @@
"ru": "Пропустить удаленную очистку и удалить только локальную запись реестра",
"zh": "跳过远程清理,仅删除本地注册表条目"
},
"Skip live SSH status checks and show registry entries only": {
"bg": "Пропуснете проверките на SSH състоянието и покажете само записите от регистъра",
"de": "Live-SSH-Statusprüfungen überspringen und nur Registrierungseinträge anzeigen",
"en": "Skip live SSH status checks and show registry entries only",
"pl": "Pomiń sprawdzanie statusu SSH i pokaż tylko wpisy z rejestru",
"ru": "Пропустить проверки статуса SSH и показать только записи реестра",
"zh": "跳过 SSH 状态检查,仅显示注册表条目"
},
"Specific Gitea Runner version": {
"bg": "Конкретна версия на Gitea Runner",
"de": "Spezifische Gitea Runner-Version",