feat!: full-access Gitea MCP server (1.0.0): full API coverage, stdio + streamable HTTP, admin and per-user OAuth #71
No files matched your search
@@ -38,6 +38,7 @@ NAME_PREFIX = "aegis-it-"
|
||||
GITEA_PORT = 3000
|
||||
ADMIN_LOGIN = "aegis-admin"
|
||||
ADMIN_EMAIL = "aegis-admin@example.test"
|
||||
ADMIN_PASSWORD_ENV = "GITEA_ADMIN_PASSWORD"
|
||||
READY_TIMEOUT_SECONDS = 90.0
|
||||
NETWORK_MODE_ENV = "AEGIS_IT_NETWORK_MODE"
|
||||
HTTP_TIMEOUT_SECONDS = 30.0
|
||||
@@ -53,12 +54,16 @@ class HarnessError(RuntimeError):
|
||||
|
||||
|
||||
def run_docker(
|
||||
*args: str, check: bool = True, timeout: float = 120.0
|
||||
*args: str,
|
||||
check: bool = True,
|
||||
timeout: float = 120.0,
|
||||
env: Mapping[str, str] | None = None,
|
||||
) -> subprocess.CompletedProcess[str]:
|
||||
"""Run ``docker <args>`` and return the completed process.
|
||||
|
||||
On failure the error names only the first two arguments (never the full
|
||||
argv, which may carry a password) plus the command's stderr.
|
||||
Secrets must not go into ``args`` (argv is visible to every local user): pass them
|
||||
in ``env`` and forward them with ``docker exec -e NAME``. On failure the error names
|
||||
only the first two arguments plus the command's stderr.
|
||||
"""
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
@@ -67,6 +72,7 @@ def run_docker(
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
env={**os.environ, **env} if env else None,
|
||||
)
|
||||
except (FileNotFoundError, PermissionError) as exc:
|
||||
raise HarnessError("the 'docker' CLI was not found or is not executable") from exc
|
||||
@@ -480,23 +486,24 @@ def start_gitea() -> GiteaInstance:
|
||||
res.attached_container = job_container
|
||||
_wait_ready(base_url, res.container)
|
||||
admin_password = secrets.token_urlsafe(24)
|
||||
# The password travels in the environment of the docker CLI process and is forwarded
|
||||
# with ``-e NAME`` (no value), so it never appears in any argv on the host. Inside
|
||||
# the container it is read from the shell environment, not from the command line.
|
||||
run_docker(
|
||||
"exec",
|
||||
"-e",
|
||||
ADMIN_PASSWORD_ENV,
|
||||
"-u",
|
||||
"git",
|
||||
res.container,
|
||||
"gitea",
|
||||
"admin",
|
||||
"user",
|
||||
"create",
|
||||
"--admin",
|
||||
"--username",
|
||||
"sh",
|
||||
"-c",
|
||||
'exec gitea admin user create --admin --username "$1" --email "$2" '
|
||||
f'--password "${ADMIN_PASSWORD_ENV}" --must-change-password=false',
|
||||
"sh",
|
||||
ADMIN_LOGIN,
|
||||
"--password",
|
||||
admin_password,
|
||||
"--email",
|
||||
ADMIN_EMAIL,
|
||||
"--must-change-password=false",
|
||||
env={ADMIN_PASSWORD_ENV: admin_password},
|
||||
)
|
||||
instance = GiteaInstance(
|
||||
base_url=base_url,
|
||||
|
||||
@@ -157,3 +157,27 @@ def test_container_id_candidates_fall_back_to_proc(tmp_path: Path) -> None:
|
||||
|
||||
def test_container_id_candidates_tolerate_missing_files(tmp_path: Path) -> None:
|
||||
assert harness.container_id_candidates({}, proc_root=tmp_path) == []
|
||||
|
||||
|
||||
def test_run_docker_passes_secrets_through_the_environment_not_argv(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
seen: dict[str, object] = {}
|
||||
|
||||
def fake_run(argv: list[str], **kwargs: object) -> object:
|
||||
seen["argv"] = argv
|
||||
seen["env"] = kwargs.get("env")
|
||||
|
||||
class Done:
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
|
||||
return Done()
|
||||
|
||||
monkeypatch.setattr(harness.subprocess, "run", fake_run)
|
||||
harness.run_docker("exec", "-e", "SECRET_VAR", "box", env={"SECRET_VAR": "s3cret-value"})
|
||||
|
||||
assert "s3cret-value" not in " ".join(seen["argv"]) # type: ignore[call-overload]
|
||||
assert seen["env"]["SECRET_VAR"] == "s3cret-value" # type: ignore[index]
|
||||
assert "PATH" in seen["env"] # type: ignore[operator]
|
||||
Reference in new issue
Block a user