Skip to content

Commit b23e513

Browse files
authored
CG-10667: Add local codegen server daemon (#332)
1 parent dfd7511 commit b23e513

File tree

3 files changed

+141
-0
lines changed

3 files changed

+141
-0
lines changed

Dockerfile-runner

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
2+
3+
# Set environment variables to prevent interactive prompts during installation
4+
ENV NVM_DIR=/root/.nvm \
5+
NODE_VERSION=18.17.0 \
6+
DEBIAN_FRONTEND=noninteractive \
7+
NODE_OPTIONS="--max-old-space-size=8192" \
8+
PYTHONUNBUFFERED=1 \
9+
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
10+
PYTHONPATH="/usr/local/lib/python3.13/site-packages" \
11+
IS_SANDBOX=True \
12+
HATCH_BUILD_HOOKS_ENABLE=1
13+
# Update packages lists and install git and curl
14+
RUN apt-get update && apt-get install -y \
15+
git \
16+
curl \
17+
gcc \
18+
build-essential \
19+
python3-dev \
20+
# Cleanup apt cache to reduce image size
21+
&& rm -rf /var/lib/apt/lists/*
22+
23+
# Install nvm and Node.js
24+
SHELL ["/bin/bash", "-c"]
25+
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \
26+
&& source $NVM_DIR/nvm.sh \
27+
&& nvm install $NODE_VERSION \
28+
&& nvm use default \
29+
&& npm install -g yarn pnpm \
30+
&& corepack enable \
31+
&& corepack prepare yarn@stable --activate \
32+
&& corepack prepare pnpm@latest --activate \
33+
&& uv pip install --system uvicorn[standard]
34+
35+
# Add node and npm to PATH
36+
ENV PATH=$NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
37+
RUN node --version \
38+
&& corepack --version \
39+
&& npm --version \
40+
&& yarn --version \
41+
&& pnpm --version \
42+
&& python --version
43+
44+
# Install codegen from source instead of PyPI
45+
WORKDIR /codegen-sdk
46+
COPY . .
47+
48+
# Install dependencies and build codegen with entry points
49+
RUN --mount=type=cache,target=/root/.cache/uv \
50+
uv venv && source .venv/bin/activate \
51+
&& uv sync --frozen --no-dev --all-extras \
52+
&& uv pip install --system -e . --no-deps \
53+
&& uv pip install --system .
54+
55+
RUN codegen --version
56+
57+
# Create a non-root user for local development + debugging
58+
RUN useradd -m -s /bin/bash user
59+
USER root
60+
RUN chown -R user:user /home/user
61+
USER user
62+
63+
WORKDIR /app
64+
ENTRYPOINT ["/bin/bash", "-c"]

src/codegen/cli/cli.py

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from codegen.cli.commands.reset.main import reset_command
1717
from codegen.cli.commands.run.main import run_command
1818
from codegen.cli.commands.run_on_pr.main import run_on_pr_command
19+
from codegen.cli.commands.start.main import start_command
1920
from codegen.cli.commands.style_debug.main import style_debug_command
2021
from codegen.cli.commands.update.main import update_command
2122

@@ -47,6 +48,8 @@ def main():
4748
main.add_command(update_command)
4849
main.add_command(config_command)
4950
main.add_command(lsp_command)
51+
main.add_command(start_command)
52+
5053

5154
if __name__ == "__main__":
5255
main()
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import subprocess
2+
from importlib.metadata import version
3+
from pathlib import Path
4+
5+
import click
6+
import rich
7+
from rich.box import ROUNDED
8+
from rich.panel import Panel
9+
10+
from codegen.configs.models.secrets import SecretsConfig
11+
from codegen.git.schemas.repo_config import RepoConfig
12+
13+
14+
@click.command(name="start")
15+
@click.option("--platform", "-t", type=click.Choice(["linux/amd64", "linux/arm64", "linux/amd64,linux/arm64"]), default="linux/amd64,linux/arm64", help="Target platform(s) for the Docker image")
16+
@click.option("--port", "-p", type=int, default=8000)
17+
@click.option("--detached", "-d", is_flag=True, default=False, help="Starts up the server as detached background process")
18+
def start_command(port: int, platform: str, detached: bool):
19+
"""Starts a local codegen server"""
20+
codegen_version = version("codegen")
21+
rich.print(f"[bold green]Codegen version:[/bold green] {codegen_version}")
22+
codegen_root = Path(__file__).parent.parent.parent.parent.parent.parent
23+
24+
try:
25+
rich.print("[bold blue]Building Docker image...[/bold blue]")
26+
_build_docker_image(codegen_root, platform)
27+
rich.print("[bold blue]Starting Docker container...[/bold blue]")
28+
_run_docker_container(port, platform, detached)
29+
rich.print(Panel(f"[green]Server started successfully![/green]\nAccess the server at: [bold]http://0.0.0.0:{port}[/bold]", box=ROUNDED, title="Codegen Server"))
30+
except subprocess.CalledProcessError as e:
31+
rich.print(f"[bold red]Error:[/bold red] Failed to {e.cmd[0]} Docker container")
32+
raise click.Abort()
33+
except Exception as e:
34+
rich.print(f"[bold red]Error:[/bold red] {e!s}")
35+
raise click.Abort()
36+
37+
38+
def _build_docker_image(codegen_root: Path, platform: str):
39+
build_cmd = [
40+
"docker",
41+
"buildx",
42+
"build",
43+
"--platform",
44+
platform,
45+
"-f",
46+
str(codegen_root / "Dockerfile-runner"),
47+
"-t",
48+
"codegen-runner",
49+
"--load",
50+
str(codegen_root),
51+
]
52+
rich.print(f"build_cmd: {str.join(' ', build_cmd)}")
53+
subprocess.run(build_cmd, check=True)
54+
55+
56+
def _run_docker_container(port: int, detached: bool):
57+
repo_path = Path.cwd().resolve()
58+
repo_config = RepoConfig.from_repo_path(repo_path)
59+
container_repo_path = f"/app/git/{repo_config.name}"
60+
envvars = {
61+
"REPOSITORY_LANGUAGE": repo_config.language.value,
62+
"REPOSITORY_OWNER": repo_config.organization_name,
63+
"REPOSITORY_PATH": container_repo_path,
64+
"GITHUB_TOKEN": SecretsConfig().github_token,
65+
}
66+
envvars_args = [arg for k, v in envvars.items() for arg in ("--env", f"{k}={v}")]
67+
68+
mount_args = ["-v", f"{repo_path}:{container_repo_path}"]
69+
run_mode = "-d" if detached else "-it"
70+
entry_point = f"uv run --frozen uvicorn codegen.runner.sandbox.server:app --host 0.0.0.0 --port {port}"
71+
run_cmd = ["docker", "run", run_mode, "-p", f"8000:{port}", *mount_args, *envvars_args, "codegen-runner", entry_point]
72+
73+
rich.print(f"run_cmd: {str.join(' ', run_cmd)}")
74+
subprocess.run(run_cmd, check=True)

0 commit comments

Comments
 (0)