feat(ai,remote,media): P0-P2 features - AI assistant, Tailscale remote access, agent/media templates - #20
Merged
Merged
Conversation
…P0-3) - AiAssistantService + /api/ai/chat: OpenAI-compatible chat proxy (default local Ollama, configurable endpoint/model/key), SSE streaming + non-streaming - RemoteAccessService + /api/remote: Tailscale-based zero-config remote access (status/enable/disable), no public IP needed - Agent Catalog: opencode/hermes 24h AI host templates (P0-2), jellyfin media center with /dev/dri GPU passthrough (P1-5), kodi vertical template (P2-7) - ComposeGenerator: devices whitelist restricted to /dev/dri - ConfigMetaRegistry: ai/remote/docker categories with whitelisted entries - AlertEngine.Dispose: tolerate repeated CTS disposal (crash fix) - eng/install: one-click Debian/Ubuntu install + deb packaging (P1-4) - NAbilityConstants: AiChat, RemoteAccess capabilities
…talog - AiAssistantServiceTests: streaming/non-streaming/error/config contract - RemoteAccessServiceTests: status parsing, enable/disable command construction - AgentCatalogTests: opencode/hermes/jellyfin/kodi templates + devices whitelist - ComposeGeneratorTests: /dev/dri allowed, non-dri rejected - ConfigApiTests/ConfigMetaRegistryTests: new categories and sensitive-key rules
There was a problem hiding this comment.
Pull request overview
This PR implements the P0–P2 feature set around AI assistant chat, Tailscale-based remote access, and expanded agent/media templates, along with new configuration categories/keys and an observability crash fix. It also adds Debian/Ubuntu installation packaging/scripts and extends the agent management surface (compose viewing).
Changes:
- Add AI assistant service +
/api/ai/chatendpoint (including streaming SSE support) and related config metadata. - Add Tailscale remote access service +
/api/remoteendpoints and related config metadata. - Expand agent catalog/templates (opencode/hermes/jellyfin/kodi) and tighten compose safety rules (device whitelist), plus add Debian install tooling and fix
AlertEngine.Disposecrash.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/FortOS.Tests.Integration/ConfigMetaRegistryTests.cs | Adds assertions for newly introduced config keys and sensitivity expectations. |
| tests/FortOS.Tests.Integration/Api/RemoteAccessServiceTests.cs | Adds coverage for RemoteAccessService status parsing and enable/disable command behavior. |
| tests/FortOS.Tests.Integration/Api/ConfigApiTests.cs | Updates expected config categories list to include ai/remote/docker. |
| tests/FortOS.Tests.Integration/Api/AiAssistantServiceTests.cs | Adds coverage for AI assistant endpoint/model selection and streaming/non-streaming parsing. |
| tests/FortOS.Tests.Integration/Agent/ComposeGeneratorTests.cs | Adds tests for /dev/dri device allowlist behavior. |
| tests/FortOS.Tests.Integration/Agent/AgentCatalogTests.cs | Extends catalog expectations for new templates and parameter/access note checks. |
| src/FortOS.Security/Models/NAbilityConstants.cs | Adds capability constants for AI chat and remote access management. |
| src/FortOS.Observability/Alerts/AlertEngine.cs | Makes Dispose tolerant of repeated calls to avoid ObjectDisposedException crash. |
| src/FortOS.Modules.Agent/AgentModule.cs | Adds GetComposeAsync to read deployed agent compose files. |
| src/FortOS.Api/Services/RemoteAccessService.cs | Introduces Tailscale-based remote access service (status/enable/disable). |
| src/FortOS.Api/Services/AiAssistantService.cs | Introduces OpenAI-compatible chat relay service with SSE streaming aggregation. |
| src/FortOS.Api/Program.cs | Registers new AI/Remote services in DI. |
| src/FortOS.Api/Controllers/RemoteController.cs | Adds remote access controller endpoints with capability checks. |
| src/FortOS.Api/Controllers/AiController.cs | Adds AI chat controller with JSON + SSE streaming modes. |
| src/FortOS.Api/Controllers/AgentsController.cs | Adds agent compose retrieval endpoint. |
| src/FortOS.Api/Configuration/ConfigMetaRegistry.cs | Adds ai/remote/docker categories and whitelisted config entries. |
| src/FortOS.Agent/Compose/ComposeGenerator.cs | Replaces blanket devices rejection with /dev/dri allowlist validation. |
| src/FortOS.Agent/Catalog/AgentCatalog.cs | Adds built-in templates for opencode/hermes/jellyfin/kodi. |
| eng/install/install.sh | Adds Debian/Ubuntu one-click installer script. |
| eng/install/build-deb.sh | Adds .deb packaging script (systemd unit/env template included). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+143
to
+146
| /// <summary>读取已部署 agent 的 Compose 配置(P1-6 Docker 管理:可视化查看)。</summary> | ||
| [HttpGet("{id}/compose")] | ||
| public async Task<object> Compose(string id, [FromServices] AgentModule agents, CancellationToken ct) | ||
| { |
Comment on lines
+48
to
+61
| // SSE 流式:每段 delta 以 data: 帧推送,结束时发送 [DONE]。 | ||
| HttpContext.Response.Headers.ContentType = "text/event-stream"; | ||
| await HttpContext.Response.WriteAsync("data: {\"start\":true}\n\n", ct).ConfigureAwait(false); | ||
| var streamed = await _ai.ChatAsync( | ||
| aiRequest, | ||
| // 复用请求取消令牌:客户端断开时停止推送,避免向已断开连接继续写。 | ||
| delta => _ = HttpContext.Response.WriteAsync($"data: {JsonSerializer.Serialize(new { delta })}\n\n", ct), | ||
| ct).ConfigureAwait(false); | ||
| if (!string.IsNullOrEmpty(streamed.Error)) | ||
| { | ||
| await HttpContext.Response.WriteAsync($"data: {JsonSerializer.Serialize(new { error = streamed.Error })}\n\n", ct).ConfigureAwait(false); | ||
| } | ||
|
|
||
| await HttpContext.Response.WriteAsync("data: [DONE]\n\n", ct).ConfigureAwait(false); |
Comment on lines
+47
to
+59
| using var doc = JsonDocument.Parse(status); | ||
| var root = doc.RootElement; | ||
| var loggedIn = root.TryGetProperty("BackendState", out var state) | ||
| && string.Equals(state.GetString(), "Running", StringComparison.OrdinalIgnoreCase); | ||
| var self = root.TryGetProperty("Self", out var selfProp) ? selfProp : default; | ||
| var hostName = self.ValueKind == JsonValueKind.Object && self.TryGetProperty("HostName", out var hn) | ||
| ? hn.GetString() | ||
| : configuration[HostNameKey]; | ||
| var ip = self.ValueKind == JsonValueKind.Object && self.TryGetProperty("TailscaleIPs", out var ips) | ||
| && ips.ValueKind == JsonValueKind.Array && ips.GetArrayLength() > 0 | ||
| ? ips[0].GetString() | ||
| : null; | ||
| return new RemoteStatus(true, true, loggedIn, hostName, ip, loggedIn ? "已连接。" : "Tailscale 已安装但未登录。"); |
Comment on lines
+293
to
+313
| if (!service.Children.TryGetValue(new YamlScalarNode("devices"), out var devices) | ||
| || devices is not YamlSequenceNode deviceList) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| foreach (var device in deviceList.Children) | ||
| { | ||
| var value = device is YamlScalarNode scalar ? scalar.Value : null; | ||
| if (string.IsNullOrWhiteSpace(value)) | ||
| { | ||
| throw new InvalidDataException("Agent compose devices entries must be scalar paths."); | ||
| } | ||
|
|
||
| var hostPath = value.Split(':', 2)[0]; | ||
| if (!hostPath.StartsWith("/dev/dri", StringComparison.Ordinal)) | ||
| { | ||
| throw new InvalidDataException($"Agent compose may only mount GPU devices under /dev/dri (got '{hostPath}')."); | ||
| } | ||
| } | ||
| } |
Comment on lines
+22
to
+24
| FORTOS_DEST="${FORTOS_DEST:-/opt/fortos}" | ||
| FORTOS_DATA_ROOT="${FortOS_DATA_ROOT:-/srv/nas}" | ||
| FORTOS_ENV_FILE="${FORTOS_ENV_FILE:-/etc/fortos/fortos.env}" |
Comment on lines
+72
to
+80
| # dotnet runtime: needed to run FortOS.Api. Prefer the runtime from the | ||
| # Microsoft feed; fall back to a distro package if unavailable. | ||
| if ! command -v dotnet >/dev/null 2>&1; then | ||
| log "安装 .NET Runtime…" | ||
| install_pkg dotnet-runtime-8.0 2>/dev/null \ | ||
| || curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0 --runtime aspnetcore --install-dir /usr/share/dotnet \ | ||
| || warn ".NET Runtime 安装失败,请手动安装后重试。" | ||
| export PATH="$PATH:/usr/share/dotnet" | ||
| fi |
Comment on lines
+62
to
+74
| # Control file | ||
| cat > "$STAGE/DEBIAN/control" <<EOF | ||
| Package: fortos | ||
| Version: $VERSION | ||
| Section: admin | ||
| Priority: optional | ||
| Architecture: amd64 | ||
| Depends: dotnet-runtime-8.0 | dotnet-runtime-9.0 | dotnet-runtime-10.0, docker.io | docker-ce, smbclient, nfs-common | ||
| Maintainer: FortOS Team <dev@fortos.example> | ||
| Description: FortOS — security-first Linux NAS management service | ||
| Deploys the FortOS API (REST/gRPC) with container agent orchestration, | ||
| file sharing, backup, AI assistant and Tailscale remote access. | ||
| EOF |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P0-P2 全部需求落地: