From b53c339bf384a94ec2ca9acf0ec687408a04d5dd Mon Sep 17 00:00:00 2001 From: EugeneTes Date: Wed, 7 Oct 2026 14:08:11 +0000 Subject: [PATCH] chore(docker): aspnet runtime image, port 8080 and A2A settings --- Dockerfile | 8 ++++++-- docker-compose.yml | 4 ++++ hosts/Volcanic.AgenticCore.Telegram/Program.cs | 1 + .../Hosting/A2ACardProvider.cs | 4 +++- tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs | 4 ++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 509dc79..affe689 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN dotnet publish hosts/Volcanic.AgenticCore.Telegram/Volcanic.AgenticCore.Tele && test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.dll \ && test -f /out/Volcanic.AgenticCore.Extensions.ClaudeCode.Bridge.runtimeconfig.json -FROM mcr.microsoft.com/dotnet/runtime:10.0-noble +FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble ARG DEBIAN_FRONTEND=noninteractive ARG NODE_MAJOR=24 ARG CLAUDE_CODE_VERSION=2.1 @@ -81,7 +81,11 @@ ENV Telegram__WorkspaceRoot=/data/workspaces \ DOTNET_CLI_TELEMETRY_OPTOUT=1 \ DOTNET_NOLOGO=1 \ TZ=UTC \ - DOTNET_EnableDiagnostics=0 + DOTNET_EnableDiagnostics=0 \ + ASPNETCORE_URLS=http://+:8080 + +# The A2A server (A2A__Enabled=true); with A2A off the bot opens no port at all. +EXPOSE 8080 # No USER: PID 1 (docker-init with compose's init: true) must stay root, or the agent could read every secret from # /proc/1/environ. setpriv drops to app and execs dotnet, so Environment.ProcessPath is the muxer the bridge needs. diff --git a/docker-compose.yml b/docker-compose.yml index 3101e1f..d5f4583 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: # Chromium crashes with Docker's 64 MB /dev/shm. shm_size: 1gb stop_grace_period: 30s + # The A2A server's port inside the network (Coolify's proxy routes the domain to it); nothing listens with A2A off. + expose: ["8080"] environment: - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} - TELEGRAM_ALLOWED_USER_IDS=${TELEGRAM_ALLOWED_USER_IDS} @@ -28,6 +30,8 @@ services: - ADMIN_USER_ID=${ADMIN_USER_ID:-} # may /impersonate other users (telegram-bot.md) - DEFAULT_TIMEZONE=${DEFAULT_TIMEZONE:-} # chats' default zone (scheduler spec §7); empty → TZ - PROCESS_ENV_ALLOW=${PROCESS_ENV_ALLOW:-} # deny-listed variables the agent may see, e.g. GH_TOKEN + - A2A__Enabled=${A2A_ENABLED:-false} # A2A server (a2a-server spec); needs a domain in Coolify + - A2A__PublicUrl=${A2A_PUBLIC_URL:-} # e.g. https://bot.example.com; required for push webhooks # Playwright MCP preset (MCP spec): the preinstalled binary, no npx download. Set PLAYWRIGHT_ENABLED=false for an # image built with INSTALL_PLAYWRIGHT=false (no nested default: Coolify parses these expressions itself). - PLAYWRIGHT_ENABLED=${PLAYWRIGHT_ENABLED:-true} diff --git a/hosts/Volcanic.AgenticCore.Telegram/Program.cs b/hosts/Volcanic.AgenticCore.Telegram/Program.cs index 1f52628..bcf94e4 100644 --- a/hosts/Volcanic.AgenticCore.Telegram/Program.cs +++ b/hosts/Volcanic.AgenticCore.Telegram/Program.cs @@ -41,6 +41,7 @@ var mcp = builder.Configuration.GetSection(McpOptions.SectionName).Get() ?? new A2AHostOptions(); +if (string.IsNullOrWhiteSpace(a2a.PublicUrl)) a2a.PublicUrl = null; // compose passes an empty A2A__PublicUrl // One agent directory for sessions, chat-settings.json and scheduler/jobs.json. var agentDir = VolcanicPaths.DefaultAgentDir; var scheduling = SchedulerSetup.Options(builder.Configuration.GetSection(SchedulerOptions.SectionName), agentDir); diff --git a/src/Volcanic.AgenticCore.A2A.Server/Hosting/A2ACardProvider.cs b/src/Volcanic.AgenticCore.A2A.Server/Hosting/A2ACardProvider.cs index d96e234..97a02bb 100644 --- a/src/Volcanic.AgenticCore.A2A.Server/Hosting/A2ACardProvider.cs +++ b/src/Volcanic.AgenticCore.A2A.Server/Hosting/A2ACardProvider.cs @@ -50,7 +50,9 @@ internal sealed class A2ACardProvider(IOptions options, ILogger< private AgentCard Build(string name, string description, List skills, string? requestBaseUrl) { - var root = (options.Value.PublicUrl ?? requestBaseUrl ?? "http://localhost").TrimEnd('/'); + // A blank PublicUrl (e.g. compose's A2A__PublicUrl=${A2A_PUBLIC_URL:-}) counts as unset. + var publicUrl = string.IsNullOrWhiteSpace(options.Value.PublicUrl) ? null : options.Value.PublicUrl; + var root = (publicUrl ?? requestBaseUrl ?? "http://localhost").TrimEnd('/'); return new AgentCard { Name = name, diff --git a/tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs b/tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs index 8bcd5bc..cb9747b 100644 --- a/tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs +++ b/tests/Volcanic.AgenticCore.A2A.Server.Tests/CardTests.cs @@ -29,6 +29,10 @@ public sealed class CardTests : IDisposable public void Card_Public_UsesRequestBaseUrlWithoutPublicUrl() => Assert.Equal("http://localhost:5000/a2a", Cards().Public("http://localhost:5000").SupportedInterfaces[0].Url); + [Fact] + public void Card_Public_BlankPublicUrlCountsAsUnset() => + Assert.Equal("http://localhost:5000/a2a", Cards("").Public("http://localhost:5000").SupportedInterfaces[0].Url); + [Fact] public void Card_Extended_FromCardJson() {