Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,20 @@ ENABLE_PUBLIC_API=
GH_TOKEN=
# Location of docker client config files
# Default is ~/.docker
DOCKER_CONFIG_PATH=
# https://docs.docker.com/reference/cli/docker/#environment-variables
DOCKER_CONFIG=
#endregion

#region Tugtainer Agent
# Same as for the main image
LOG_LEVEL=
AGENT_SECRET=
AGENT_SIGNATURE_TTL=
DOCKER_CONFIG_PATH=
DOCKER_CONFIG=
# This value is used to run the agent without direct socket mount.
# Address of the socket proxy, e.g. tcp://:socket-proxy:2375
# Default is empty
# https://docs.docker.com/reference/cli/docker/#environment-variables
DOCKER_HOST=
# Docker CLI timeout in seconds for typically fast operations e.g. inspect.
# It does not affect potentially long operations such as container create or image pull.
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ name: Run Tests

on:
workflow_dispatch:
# pull_request:
# branches: [main]
pull_request:

jobs:
frontend-tests:
Expand All @@ -21,7 +20,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22

- name: Install frontend dependencies
run: npm ci
Expand Down
3 changes: 2 additions & 1 deletion agent/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
from .image_api import router as image_router
from .command_api import router as command_router
from .manifest_api import router as manifest_router
from .network_api import router as network_router
from .network_api import router as network_router
from .common_api import router as common_router
21 changes: 21 additions & 0 deletions agent/api/common_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from fastapi import APIRouter, Depends
from agent.auth import verify_signature
from agent.docker_client import DOCKER
from agent.unil.asyncall import asyncall
from shared.schemas.docker_version_scheme import DockerVersionScheme


router = APIRouter(
prefix="/common",
tags=["common"],
dependencies=[Depends(verify_signature)],
)


@router.get(
"/version",
description="Get docker version",
response_model=DockerVersionScheme,
)
async def get_version():
return await asyncall(lambda: DOCKER.version())
7 changes: 4 additions & 3 deletions agent/api/public_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ async def health():
try:
_ = await asyncall(DOCKER.info)
return "OK"
except DockerException as e:
logging.exception(e)
except DockerException:
message = "Failed to get docker cli info"
logging.exception(message)
raise HTTPException(
status.HTTP_424_FAILED_DEPENDENCY,
"Failed to get docker cli info",
message,
)


Expand Down
2 changes: 2 additions & 0 deletions agent/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import FastAPI, HTTPException, Request, status
from python_on_whales import DockerException
from agent.api import (
common_router,
public_router,
container_router,
image_router,
Expand Down Expand Up @@ -30,6 +31,7 @@
app.include_router(command_router)
app.include_router(manifest_router)
app.include_router(network_router)
app.include_router(common_router)


@app.exception_handler(asyncio.TimeoutError)
Expand Down
6 changes: 0 additions & 6 deletions agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ class Config:
AGENT_SECRET: ClassVar[str | None]
AGENT_SIGNATURE_TTL: ClassVar[int]
DOCKER_TIMEOUT: ClassVar[int]
DOCKER_HOST: ClassVar[str | None]
DOCKER_CONFIG_PATH: ClassVar[str]

@classmethod
def load(cls):
Expand All @@ -29,10 +27,6 @@ def load(cls):
cls.DOCKER_TIMEOUT = int(
os.getenv("DOCKER_TIMEOUT") or 15
)
cls.DOCKER_HOST = os.getenv("DOCKER_HOST") or None
cls.DOCKER_CONFIG_PATH = os.getenv(
"DOCKER_CONFIG_PATH", "~/.docker"
)


Config.load()
5 changes: 1 addition & 4 deletions agent/docker_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from python_on_whales import DockerClient
from agent.config import Config

DOCKER = DockerClient(
host=Config.DOCKER_HOST, config=Config.DOCKER_CONFIG_PATH
)
DOCKER = DockerClient()
46 changes: 46 additions & 0 deletions backend/alembic/versions/b9034fe596ee_insecure_registries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""insecure registries

Revision ID: b9034fe596ee
Revises: 463265eb08b8
Create Date: 2026-03-26 23:19:03.376073

"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa

from backend.modules.settings.settings_enum import (
ESettingKey,
ESettingType,
)


# revision identifiers, used by Alembic.
revision: str = "b9034fe596ee"
down_revision: Union[str, Sequence[str], None] = "463265eb08b8"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Upgrade schema."""
op.execute(
sa.text(
"INSERT INTO settings (key, value, value_type) VALUES (:key, :value, :value_type)"
).bindparams(
key=ESettingKey.INSECURE_REGISTRIES.value,
value="",
value_type=ESettingType.STR.value,
)
)


def downgrade() -> None:
"""Downgrade schema."""
op.execute(
sa.text("DELETE FROM settings WHERE key = :key").bindparams(
key=ESettingKey.INSECURE_REGISTRIES.value,
)
)
19 changes: 14 additions & 5 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request, status
from backend.core.cron_manager import schedule_actions_on_init
from backend.core.agent_client import load_agents_on_init
from backend.modules.auth.auth_router import auth_router as auth_router
from backend.core.agent_client import (
AgentClientManager,
load_agents_on_init,
)
from backend.modules.auth.auth_router import (
auth_router as auth_router,
)
from backend.modules.containers.containers_router import (
containers_router as containers_router,
)
from backend.modules.images.images_router import (
images_router as images_router,
)
from backend.modules.hosts.hosts_router import hosts_router as hosts_router
from backend.modules.hosts.hosts_router import (
hosts_router as hosts_router,
)
from backend.modules.public.public_router import (
public_router as public_router,
)
Expand Down Expand Up @@ -49,6 +56,7 @@ async def lifespan(app: FastAPI):
await schedule_actions_on_init()
yield # App
# Code to run on shutdown
await AgentClientManager.remove_all()


app = FastAPI(root_path="/api", lifespan=lifespan)
Expand All @@ -64,10 +72,11 @@ async def lifespan(app: FastAPI):
async def aiohttp_exception_handler(
request: Request, exc: ClientError
):
logging.exception(exc)
message = "Unknown aiohttp error"
logging.exception(message)
raise HTTPException(
status.HTTP_424_FAILED_DEPENDENCY,
f"Unknown aiohttp error\n{str(exc)}",
f"{message}\n{str(exc)}",
)


Expand Down
6 changes: 3 additions & 3 deletions backend/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Config:
DOMAIN: ClassVar[str | None]
ENABLE_PUBLIC_API: ClassVar[bool]
GH_TOKEN: ClassVar[str]
DOCKER_CONFIG_PATH: ClassVar[str]
DOCKER_CONFIG: ClassVar[str]

# OIDC Configuration
OIDC_ENABLED: ClassVar[bool]
Expand Down Expand Up @@ -72,8 +72,8 @@ def load(cls):
== "true"
)
cls.GH_TOKEN = os.getenv("GH_TOKEN", "")
cls.DOCKER_CONFIG_PATH = os.getenv(
"DOCKER_CONFIG_PATH", "~/.docker"
cls.DOCKER_CONFIG = os.getenv(
"DOCKER_CONFIG", "~/.docker"
)

# OIDC Configuration
Expand Down
Loading