Compare commits

..
2 Commits
Author SHA1 Message Date
Nitload 8c0edfd546 Finalize Charac release cleanup
Verify / verify (ubuntu-latest, linux-bionic-arm64) (push) Canceled after 0s
Verify / verify (ubuntu-latest, linux-x64) (push) Canceled after 0s
Verify / verify (windows-latest, win-x64) (push) Canceled after 0s
Verify / postgres (push) Canceled after 0s
2026-10-06 08:53:37 +08:00
Nitload 9fa9f29259 License Charac under MIT and include package notices 2026-10-06 08:20:07 +08:00
18 changed files with 58 additions and 166 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: dotnet run --project build/Build.csproj --configuration Release --no-restore -- --target Publish --runtime ${{ matrix.runtime }} --locked-restore
- uses: actions/upload-artifact@v4
with:
name: workspace-access-${{ matrix.runtime }}
name: charac-${{ matrix.runtime }}
path: artifacts/publish/${{ matrix.runtime }}/
- uses: actions/upload-artifact@v4
if: always()
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Nitload-NSI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+11 -3
View File
@@ -1,4 +1,4 @@
# Workspace Access
# Charac
面向 Windows 和 Linux 目标的远程 SSH 工作区管理服务,使用 .NET 10 开发;CLI 客户端另支持 Android ARM64 的 Termux 终端发布。服务端与 CLI 客户端分别位于 `src/server/` 和 `src/client/`。
@@ -10,7 +10,7 @@ SSH CA 是后端凭据的可选方案;正式用户登录使用 OIDC。首期
## 当前状态
当前已实现 Server 服务入口、EF Core/PostgreSQL 授权、OIDC 令牌校验、密码 SSH Broker、Session Manager 与 WebSocket;Client 提供 `login` 登录验证及 `connect` 的资源选择、交互创建/重新附着。demo 的真实 authentik PKCE 登录已通过,正式 Client 登录和 Server 验签已实测通过,资源选择 → SSH 整链路仍待验收,托管后端 SSH 凭据、自动重连和画面恢复待完善。`/health/live` 返回 200,`/health/ready` 返回 503,检查真实数据库状态并列出待集成项,数据库配置与迁移见[数据库文档](docs/database.md)。
当前已实现 Server 服务入口、EF Core/PostgreSQL 授权、OIDC 令牌校验、托管私钥及密码 SSH Broker、Session Manager 与 WebSocket;Client 提供 `login` 登录验证及 `connect` 的资源选择、交互创建/重新附着。正式 Client 已在 `.100 → .101` 环境通过 authentik 登录、目标选择和交互式 `ls`;自动重连和画面恢复仍待完善。`/health/live` 返回 200,`/health/ready` 返回 503,检查真实数据库状态并列出待集成项,数据库配置与迁移见[数据库文档](docs/database.md)。
## 构建与运行
@@ -38,4 +38,12 @@ docs/ 设计、状态与验收文档
deploy/ Windows Service 与 systemd 模板
```
阅读[文档库](docs/README.md)、[单域名通信入口](docs/client-server-transport.md)、[架构](docs/architecture.md)、[交付状态](docs/implementation-status.md)和[贡献指南](CONTRIBUTING.md)。
阅读[文档库](docs/README.md)、[单域名通信入口](docs/client-server-transport.md)、[架构](docs/architecture.md)、[交付状态](docs/implementation-status.md)和[贡献指南](CONTRIBUTING.md)。
## 开发方式
本项目采用 vibe coding 方式开发,代码和文档由 AI 辅助编写与迭代。功能范围与尚未完成的验收项以[交付状态](docs/implementation-status.md)为准。
## 许可证
Charac 项目代码采用 [MIT 许可证](LICENSE)。Client 内嵌的 IBM Plex Mono 字体采用 [SIL Open Font License 1.1](src/client/Assets/Fonts/IBM-Plex-LICENSE.txt),不属于项目的 MIT 授权范围。
+1 -1
View File
@@ -24,7 +24,7 @@ internal sealed class Build : NukeBuild
[Parameter("Require dependency versions from packages.lock.json.")]
readonly bool LockedRestore;
AbsolutePath SolutionFile => RootDirectory / "WorkspaceAccess.slnx";
AbsolutePath SolutionFile => RootDirectory / "Charac.slnx";
AbsolutePath ServerProject => RootDirectory / "src/server/WorkspaceAccessServer.csproj";
AbsolutePath ClientProject => RootDirectory / "src/client/WorkspaceAccessClient.csproj";
AbsolutePath TestProject => RootDirectory / "tests/WorkspaceAccessServer.Tests/WorkspaceAccessServer.Tests.csproj";
-17
View File
@@ -1,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>Charac.Client</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<Using Include="Microsoft.Extensions.Configuration" />
<Compile Include="../../src/client/OidcLogin.cs" Link="OidcLogin.cs" />
<Compile Include="../../src/server/Authentication/OidcSettings.cs" Link="OidcSettings.cs" />
<Compile Include="../../src/server/Hosting/WorkspaceAccessConfiguration.cs" Link="WorkspaceAccessConfiguration.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="../../src/client/Assets/Fonts/*.woff2" LogicalName="Charac.Fonts.%(Filename)%(Extension)" />
<None Include="../../src/client/Assets/Fonts/IBM-Plex-LICENSE.txt" Link="Assets/Fonts/IBM-Plex-LICENSE.txt" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
</Project>
-88
View File
@@ -1,88 +0,0 @@
using System.Net.Http.Headers;
using System.Text.Json;
using Charac.Server.Authentication;
using Charac.Server.Hosting;
namespace Charac.Client;
internal static class Program
{
public static async Task<int> Main(string[] args)
{
if (args is ["--help"] or ["-h"])
{
PrintUsage();
return 0;
}
if (!TryReadConfigPath(args, out var configPath))
{
PrintUsage();
return 2;
}
try
{
var configuration = WorkspaceAccessConfiguration.Load(configPath);
var oidc = OidcSettings.FromConfiguration(configuration) ??
throw new InvalidOperationException("Configure [oidc] issuer and client_id before running this demo.");
var server = ReadServerUri(configuration["oidc:server_url"]);
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
var accessToken = await OidcLogin.LoginAsync(http, oidc.Issuer, oidc.ClientId,
oidc.DiscoveryUrl);
Console.WriteLine("authentik/OIDC login succeeded. Access token remains in this process.");
if (server is not null)
{
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(server, "/identity"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
using var identity = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
Console.WriteLine($"Verified issuer: {identity.RootElement.GetProperty("issuer").GetString()}");
Console.WriteLine($"Verified subject: {identity.RootElement.GetProperty("subject").GetString()}");
}
return 0;
}
catch (Exception exception) when (exception is HttpRequestException or TaskCanceledException or
OperationCanceledException or InvalidOperationException or JsonException or IOException or
KeyNotFoundException or ArgumentException)
{
Console.Error.WriteLine($"OIDC demo failed: {exception.Message}");
return 1;
}
}
private static bool TryReadConfigPath(string[] args, out string? configPath)
{
configPath = null;
if (args.Length == 0)
return true;
if (args is ["--config", var path] && !string.IsNullOrWhiteSpace(path) &&
!path.StartsWith("--", StringComparison.Ordinal))
{
configPath = path;
return true;
}
return false;
}
private static Uri? ReadServerUri(string? value)
{
if (string.IsNullOrWhiteSpace(value))
return null;
if (!Uri.TryCreate(value.Trim(), UriKind.Absolute, out var server) ||
(server.Scheme != Uri.UriSchemeHttps && !(server.Scheme == Uri.UriSchemeHttp && server.IsLoopback)) ||
server.UserInfo.Length != 0 || server.AbsolutePath != "/" ||
server.Query.Length != 0 || server.Fragment.Length != 0)
throw new InvalidOperationException("[oidc] server_url must be an HTTPS origin or a loopback HTTP origin.");
return server;
}
private static void PrintUsage()
{
Console.WriteLine("Usage: dotnet run --project demo/oidc-cli -- [--config <workspace-access.config path>]");
}
}
-34
View File
@@ -1,34 +0,0 @@
# CLI 唤起 authentik:OIDC Playground
这个独立小程序演示现有 Client 的登录代码:读取 authentik 的 OIDC discovery,生成 PKCE 与 `state`,监听 `127.0.0.1` 临时端口,通过系统浏览器打开授权页面,接收 `/callback` 授权码,再向 token endpoint 交换 access token。它复用 `src/client/OidcLogin.cs`、Server 的 OIDC 设置校验及 INI 路径解析,不需要额外 NuGet 包,也不建立 SSH 会话。
先在 authentik 创建 OAuth2/OIDC Application 与 **public client**,允许 Authorization Code + PKCE,配置 `openid` scope、非对称签名密钥,以及允许的 loopback Redirect URI。动态端口可使用 Redirect URIs 正则 `^http://127\.0\.0\.1:[0-9]+/callback$`。不要把 Client Secret 放进 CLI。步骤参考 [authentik 官方文档](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/)。
在仓库根目录的 `workspace-access.config` 中配置与 Server 相同的 `[oidc]` 字段:
```ini
[oidc]
issuer=https://auth.example.com/application/o/workspace-access/
client_id=<authentik-public-client-id>
; 只有 discovery 路径不能由 issuer 推导时才填写,例如 authentik 全局 issuer 模式
; discovery_url=https://auth.example.com/application/o/workspace-access/.well-known/openid-configuration
; 可选:调用已启动的 Server /identity,让 Server 验证令牌并显示身份键
server_url=http://127.0.0.1:5080
```
在仓库根目录运行:
```powershell
dotnet run --project demo/oidc-cli
```
需要使用其他文件时,只传路径:`dotnet run --project demo/oidc-cli -- --config .\workspace-access.config`。未指定时,优先查找仓库根目录的 `workspace-access.config`,与 Server 相同。程序会尝试打开浏览器,并同时打印授权 URL。浏览器回到本机后会显示 RAC 的授权回调页;它只表示已收到授权码,CLI 仍需向 authentik 交换令牌。只有终端显示登录成功才算完成。令牌交换失败时会显示 HTTP 状态及 OAuth 错误代码,不输出 access token。配置 `[oidc] server_url` 且 Server 已启用 OIDC 和数据库时,demo 还会请求 `/identity`,显示用于映射的 `(issuer, subject)`;不配置该字段时只验证 authentik 令牌交换。
默认 discovery URL 为 `<issuer 去掉末尾斜杠>/.well-known/openid-configuration`。authentik 使用全局 issuer 模式,或其他 Provider 的 discovery 路径不同的时候,请在 `[oidc]` 设置 `discovery_url`。`issuer` 必须与 discovery 文档中的 `issuer` 完全一致。浏览器回调必须发生在运行 CLI 的同一台设备上;无本机浏览器的设备授权流程不在此演示范围。
Server 返回的 `(issuer, subject)` 是稳定的外部身份键,不是用户资料。Server 本地若需要易读名称,应称为 **user label**:它只用于管理员识别和显示,不能代替身份键,也不参与授权判断。用户的姓名、邮箱等资料由 authentik 或其他身份提供方管理;RAC 不应把自己的 PostgreSQL 身份映射表当作用户资料主库。
回调页的西文使用随程序集嵌入的 IBM Plex Mono Regular/Bold,中文使用系统字体回退。字体以 data URL 随 HTML 返回,无需外部字体请求;CSP 仅为字体允许 `data:` 来源。字体来自 [IBM Plex 官方仓库固定版本](https://github.com/IBM/plex/tree/763c36ef9117782905ae010056dfbe8fd2653a25/packages/plex-mono),按 [SIL Open Font License 1.1](../../src/client/Assets/Fonts/IBM-Plex-LICENSE.txt) 分发,许可证随构建与发布输出保留。
该流程现已接入正式 Client:使用 `login --server <origin>` 验证登录,或 `connect --server <origin> --target <id>` 登录并进入交互工作区。正式 Client 从 Server 取得 OIDC 配置;本 demo 仍直接读 INI,用于独立排查身份提供方。命令及验收边界见[正式 Client 流程](../../docs/client-server-transport.md)。
-6
View File
@@ -1,6 +0,0 @@
{
"version": 2,
"dependencies": {
"net10.0": {}
}
}
-1
View File
@@ -21,7 +21,6 @@
- [开发与构建](development.md)
- [验证与验收](testing.md)
- [CLI 唤起 authentik Playground](../demo/oidc-cli/README.md)
- [Windows 部署模板](../deploy/windows/README.md)
- [Linux 部署模板](../deploy/linux/README.md)
+1 -1
View File
@@ -20,7 +20,7 @@ Server 对每个工作区持有一条后端 SSH 会话。WebSocket 只是客户
解决方案包含 `src/server/WorkspaceAccessServer.csproj`、`src/client/WorkspaceAccessClient.csproj`、`tests/WorkspaceAccessServer.Tests` 和 `build/Build.csproj`。Server 的 `Authentication/` 放 OIDC 配置、身份模型与授权解析,`Connections/` 放工作区控制权,`Hosting/` 放 HTTP/WebSocket 入口。`Ssh/` 已实现密码及托管私钥 SSH Broker、主机公钥校验、Session Manager 和本机诊断命令;SSH CA 与密钥实机验收仍待完成,详见[SSH 会话设计](ssh-session.md)。
CLI 的 `login` 和 `connect` 通过单一域名发现 OIDC 配置,复用 PKCE 浏览器登录与回调页,并让 Server 验证身份;`connect` 从受保护的 `/resources` 取得当前授权目标及会话状态,选择后按目标或会话 ID 的 query 建立正式 WebSocket 交互;也支持直接指定 ID。原有一次性 Linux `ls` 命令继续用于诊断。demo 和正式 Client 的真实 authentik 登录及 Server 身份验证已实测通过,正式整链路的交互验收尚待完成,见[单域名入口](client-server-transport.md)。Caddy 等反向代理可承接公网 TLS 入口;内网 `sshd` 只允许 Server 访问。Windows OpenSSH 已使用 ConPTY,因此 Server 不再自己启动 `pwsh.exe` 或实现平台 PTY。
CLI 的 `login` 和 `connect` 通过单一域名发现 OIDC 配置,复用 PKCE 浏览器登录与回调页,并让 Server 验证身份;`connect` 从受保护的 `/resources` 取得当前授权目标及会话状态,选择后按目标或会话 ID 的 query 建立正式 WebSocket 交互;也支持直接指定 ID。原有一次性 Linux `ls` 命令继续用于诊断。正式 Client 的真实 authentik 登录、Server 身份验证及 `.100 → .101` 交互式 `ls` 已实测,见[单域名入口](client-server-transport.md)。Caddy 等反向代理可承接公网 TLS 入口;内网 `sshd` 只允许 Server 访问。Windows OpenSSH 已使用 ConPTY,因此 Server 不再自己启动 `pwsh.exe` 或实现平台 PTY。
## 信任与生命周期
+2 -2
View File
@@ -1,10 +1,10 @@
# 身份认证与授权
CLI 已实现 authentik OIDC Authorization Code + PKCE 的浏览器登录和本机回调;令牌仅保存在当前 CLI 进程内。Server 使用 JWT Bearer 中间件从 HTTPS OIDC discovery 获取签名密钥,校验签名、issuer、audience 和有效期,再以稳定的 `(iss, sub)` 识别用户。资源列表、创建、附着、结束工作区均重新查询数据库授权。需在 authentik 创建 public client、启用 PKCE、设置允许的 `http://127.0.0.1:<动态端口>/callback` 回调,并为 Provider 配置非对称 JWT 签名密钥。Server 配置 `[oidc] issuer` 和 `client_id`;若采用 authentik 的全局 issuer 模式,还需设置指向应用 slug 下 discovery 文档的 `discovery_url`。2026-10-03 已通过真实 authentik public client 的 demo 浏览器登录与令牌交换;2026-10-04 正式 Client 的 `login` 已通过 Server 令牌校验;2026-10-05 已在 `.100 → .101` 实测目标授权、资源选择和交互式 `ls`;正常退出及重新附着待复测。
CLI 已实现 authentik OIDC Authorization Code + PKCE 的浏览器登录和本机回调;令牌仅保存在当前 CLI 进程内。Server 使用 JWT Bearer 中间件从 HTTPS OIDC discovery 获取签名密钥,校验签名、issuer、audience 和有效期,再以稳定的 `(iss, sub)` 识别用户。资源列表、创建、附着、结束工作区均重新查询数据库授权。需在 authentik 创建 public client、启用 PKCE、设置允许的 `http://127.0.0.1:<动态端口>/callback` 回调,并为 Provider 配置非对称 JWT 签名密钥。Server 配置 `[oidc] issuer` 和 `client_id`;若采用 authentik 的全局 issuer 模式,还需设置指向应用 slug 下 discovery 文档的 `discovery_url`。正式 Client 的 `login` 已通过真实 authentik 浏览器登录及 Server 令牌校验;2026-10-05 已在 `.100 → .101` 实测目标授权、资源选择和交互式 `ls`;正常退出及重新附着待复测。
管理员将外部身份映射到允许访问的目标机器和系统账户。Windows 目标使用账户 SID,Linux 目标使用 UID/GID;实际登录环境及文件权限由目标 sshd 和操作系统建立。不同外部身份即使映射到同一个系统账户,工作区归属仍独立。
Server 当前的 `access.identities` 只保存 `(issuer, subject)` 身份键和启用状态,并不保存用户资料。若未来加入本地易读名称,应命名为 **user label**,仅供管理员识别和显示;标签不能作为认证或授权依据,也不能声称是从身份平台实时同步的资料。姓名、邮箱等真正的用户资料由 authentik 或其他 OIDC Provider 管理;Server 不直接读取 authentik 的 PostgreSQL。正式 Client 的 `login` 和 `connect` 使用相同的浏览器登录与回调页;独立的 [OIDC CLI Playground](../demo/oidc-cli/README.md) 保留为身份提供方调试入口。具体命令见[正式 Client 流程](client-server-transport.md)。
Server 当前的 `access.identities` 只保存 `(issuer, subject)` 身份键和启用状态,并不保存用户资料。若未来加入本地易读名称,应命名为 **user label**,仅供管理员识别和显示;标签不能作为认证或授权依据,也不能声称是从身份平台实时同步的资料。姓名、邮箱等真正的用户资料由 authentik 或其他 OIDC Provider 管理;Server 不直接读取 authentik 的 PostgreSQL。正式 Client 的 `login` 和 `connect` 使用相同的浏览器登录与回调页。具体命令见[正式 Client 流程](client-server-transport.md)。
当前建连支持 Server 托管的 SSH 登录私钥或一次性输入的 SSH 密码,两者均继续验证数据库中登记的目标主机公钥。密钥授权不要求 Client 提供 SSH 密码;密码授权的密码在 HTTPS 请求体中传给 Server,不写入数据库或日志。私钥内容只保存在服务账户可访问的文件中,数据库保存引用。目标 sshd 只在内网对 Server 开放。正式用户登录只使用 OIDC;SSH CA 仍是可选的后端凭据方案,不是用户登录步骤。
+1 -1
View File
@@ -2,7 +2,7 @@
使用 .NET 10 或更高版本 SDK。global.json 设置最低 SDK 并允许更高版本;项目目标框架为 net10.0,入口使用显式 Program.Main。包版本集中在 Directory.Packages.props,锁文件固定依赖解析。
解决方案 WorkspaceAccess.slnx 包含 src/server/WorkspaceAccessServer.csproj、src/client/WorkspaceAccessClient.csproj、测试项目和 NUKE 项目。各项目的构建输出统一位于根目录 `temp/<项目名>/bin/`,中间文件位于 `temp/<项目名>/obj/`;`temp/` 被 Git 忽略。Windows 用 ./build.ps1,Linux 用 bash ./build.sh。
解决方案 Charac.slnx 包含 src/server/WorkspaceAccessServer.csproj、src/client/WorkspaceAccessClient.csproj、测试项目和 NUKE 项目。各项目的构建输出统一位于根目录 `temp/<项目名>/bin/`,中间文件位于 `temp/<项目名>/obj/`;`temp/` 被 Git 忽略。Windows 用 ./build.ps1,Linux 用 bash ./build.sh。
| NUKE 目标 | 行为 |
| --- | --- |
+1 -1
View File
@@ -12,7 +12,7 @@
| SSH Broker 与会话 | SSH.NET 密码及登录密钥建连、授权账户与目标解析、主机公钥固定校验、PTY Shell、独立工作区控制权、有界输出与断线保留;本机 `broker-probe` 可发送 Linux `ls`,见[SSH 会话设计](ssh-session.md) |
| 构建与打包 | Restore、Compile、Test、CheckDocs、Verify、Publish、RunServer、RunClient;Windows x64 Client MSI、Linux x64 Server/Client RPM、Termux aarch64 Client DEB 和 Android 调试 APK 已生成,仍需目标设备安装与运行验收;Client Windows x64 Native AOT 试编译成功,尚未作为默认包 |
旧 Host/Agent、PowerShell 标准流后端及自行管理 PTY 的代码已移除。Server 的受保护调用入口和一次性 CLI 验收路径已接通;回环无 OIDC 的 `probe-session` 已在 `.100 → .101` 实测通过,`shell` 的持续键盘输入和 VT 画面已在 VS Code 终端试用。Windows VT 鼠标转发已接入但尚待复测;2026-10-05 托管 SSH 登录密钥已通过本机 `key-probe` 完成 `.100 → .101` 数据库授权、主机公钥、PTY 和 `ls` 实测;登录密钥登记只保存受保护私钥的文件引用,不要求 `.pub` 副本,目标主机公钥固定校验仍保留;正式 Client 已实测通过 authentik 登录、资源选择及 `.101` 交互式 `ls`;SSH 正常退出时的 WebSocket 关闭修复待复测。`demo/oidc-cli` 已在真实 authentik public client 下完成浏览器 PKCE 登录与令牌交换;2026-10-04 正式 Client 的 `login` 也已实测通过 Server 令牌验证;资源选择到 SSH 交互主链路已在 `.100 → .101` 实测,见[通信入口](client-server-transport.md)。
旧 Host/Agent、PowerShell 标准流后端及自行管理 PTY 的代码已移除。Server 的受保护调用入口和一次性 CLI 验收路径已接通;回环无 OIDC 的 `probe-session` 已在 `.100 → .101` 实测通过,`shell` 的持续键盘输入和 VT 画面已在 VS Code 终端试用。Windows VT 鼠标转发已接入但尚待复测;2026-10-05 托管 SSH 登录密钥已通过本机 `key-probe` 完成 `.100 → .101` 数据库授权、主机公钥、PTY 和 `ls` 实测;登录密钥登记只保存受保护私钥的文件引用,不要求 `.pub` 副本,目标主机公钥固定校验仍保留;正式 Client 已实测通过 authentik 登录、资源选择及 `.101` 交互式 `ls`;SSH 正常退出时的 WebSocket 关闭修复待复测。正式 Client 的 `login` 已实测通过真实 authentik 浏览器 PKCE 登录与 Server 令牌验证;资源选择到 SSH 交互主链路已在 `.100 → .101` 实测,见[通信入口](client-server-transport.md)。
## 待实现
+13 -6
View File
@@ -50,7 +50,7 @@ if ($Target -eq 'ClientMsi') {
if (-not $WixPath) { $WixPath = Join-Path $repository 'temp/tools/wix.exe' }
if (-not (Test-Path -LiteralPath $WixPath -PathType Leaf)) { throw 'WiX CLI 6.0.2 is required; pass -WixPath.' }
$output = Join-Path $packages "charac-client_${version}_win-x64.msi"
& $WixPath build '-arch' 'x64' '-d' "PackageVersion=$version" '-d' "PayloadDir=$payload" '-d' "ConfigTemplatePath=$(Join-Path $repository 'client.config.example')" '-pdbtype' 'none' '-o' $output (Join-Path $PSScriptRoot 'windows/Client.wxs')
& $WixPath build '-arch' 'x64' '-d' "PackageVersion=$version" '-d' "PayloadDir=$payload" '-d' "ConfigTemplatePath=$(Join-Path $repository 'client.config.example')" '-d' "LicensePath=$(Join-Path $repository 'LICENSE')" '-pdbtype' 'none' '-o' $output (Join-Path $PSScriptRoot 'windows/Client.wxs')
if ($LASTEXITCODE -ne 0) { throw "WiX failed with exit code $LASTEXITCODE." }
Write-Output $output
exit 0
@@ -65,12 +65,12 @@ if ($Target -eq 'ClientTermuxDeb') {
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add('name: charac')
$lines.Add("version: $version")
$lines.Add('release: 1')
$lines.Add('release: 2')
$lines.Add('arch: aarch64')
$lines.Add('platform: linux')
$lines.Add('maintainer: nitload')
$lines.Add('vendor: nitload')
$lines.Add('license: Proprietary')
$lines.Add('license: MIT AND OFL-1.1')
$lines.Add('description: Charac CLI for Termux')
$lines.Add('depends:')
$lines.Add(' - dotnet-runtime-10.0')
@@ -82,9 +82,11 @@ if ($Target -eq 'ClientTermuxDeb') {
Add-PackageFile $lines $file.FullName "$prefix/opt/charac/$relative" $mode
}
Add-PackageFile $lines (Join-Path $PSScriptRoot 'termux/charac') "$prefix/bin/charac" '0755'
Add-PackageFile $lines (Join-Path $repository 'LICENSE') "$prefix/share/doc/charac/LICENSE"
Add-PackageFile $lines (Join-Path $repository 'src/client/Assets/Fonts/IBM-Plex-LICENSE.txt') "$prefix/share/doc/charac/IBM-Plex-LICENSE.txt"
$config = Join-Path $scratch 'charac-termux.yaml'
[IO.File]::WriteAllLines($config, [string[]]$lines)
$output = Join-Path $packages "charac_${version}-1_aarch64.deb"
$output = Join-Path $packages "charac_${version}-2_aarch64.deb"
& $NfpmPath package --config $config --packager deb --target $output
if ($LASTEXITCODE -ne 0) { throw "nFPM failed with exit code $LASTEXITCODE." }
Write-Output $output
@@ -99,7 +101,7 @@ if (-not $NfpmPath) { $NfpmPath = Join-Path $repository 'temp/tools/nfpm/nfpm.ex
if (-not (Test-Path -LiteralPath $NfpmPath -PathType Leaf)) { throw 'nFPM 2.47.0 is required; pass -NfpmPath.' }
$name = "charac-$kind"
$release = if ($kind -eq 'server') { '2' } else { '1' }
$release = if ($kind -eq 'server') { '3' } else { '2' }
$lines = [System.Collections.Generic.List[string]]::new()
$lines.Add("name: $name")
$lines.Add("version: $version")
@@ -108,7 +110,7 @@ $lines.Add('arch: amd64')
$lines.Add('platform: linux')
$lines.Add('maintainer: nitload')
$lines.Add('vendor: nitload')
$lines.Add('license: Proprietary')
$lines.Add($(if ($kind -eq 'server') { 'license: MIT' } else { 'license: MIT AND OFL-1.1' }))
$lines.Add("description: Charac $kind")
if ($kind -eq 'server') {
$lines.Add('depends:')
@@ -118,6 +120,11 @@ if ($kind -eq 'server') {
$lines.Add(' postremove: ' + (ConvertTo-Json -InputObject (Join-Path $PSScriptRoot 'linux/server-postremove.sh') -Compress))
}
$lines.Add('contents:')
$licenseDirectory = "/usr/share/licenses/$name"
Add-PackageFile $lines (Join-Path $repository 'LICENSE') "$licenseDirectory/LICENSE"
if ($kind -eq 'client') {
Add-PackageFile $lines (Join-Path $repository 'src/client/Assets/Fonts/IBM-Plex-LICENSE.txt') "$licenseDirectory/IBM-Plex-LICENSE.txt"
}
$files = Get-ChildItem -LiteralPath $payload -Recurse -File | Sort-Object FullName
foreach ($file in $files) {
$relative = [IO.Path]::GetRelativePath($payload, $file.FullName).Replace('\', '/')
+5 -1
View File
@@ -17,12 +17,16 @@
<Component Id="ClientConfigTemplate" Directory="INSTALLFOLDER" Guid="*">
<File Id="ClientConfigTemplateFile" Source="$(var.ConfigTemplatePath)" KeyPath="yes" />
</Component>
<Component Id="ClientLicense" Directory="INSTALLFOLDER" Guid="*">
<File Id="ClientLicenseFile" Source="$(var.LicensePath)" KeyPath="yes" />
</Component>
<Feature Id="Complete" Title="Charac Client" Level="1">
<Files Directory="INSTALLFOLDER" Include="$(var.PayloadDir)\**">
<Exclude Files="$(var.PayloadDir)\**.pdb" />
</Files>
<ComponentRef Id="ClientPath" />
<ComponentRef Id="ClientConfigTemplate" />
<ComponentRef Id="ClientLicense" />
</Feature>
</Package>
</Wix>
</Wix>
@@ -50,7 +50,7 @@ internal static class WorkspaceAccessConfiguration
{
for (var directory = new DirectoryInfo(start); directory is not null; directory = directory.Parent)
{
if (File.Exists(Path.Combine(directory.FullName, "WorkspaceAccess.slnx")))
if (File.Exists(Path.Combine(directory.FullName, "Charac.slnx")))
return directory.FullName;
}
}
-2
View File
@@ -11,8 +11,6 @@ listen=http://127.0.0.1:5080
; issuer=https://auth.example.com/application/o/workspace-access/
; client_id=<authentik-client-id>
; discovery_url=https://auth.example.com/application/o/workspace-access/.well-known/openid-configuration
; Optional: the OIDC CLI playground uses this URL to call Server /identity.
; server_url=http://127.0.0.1:5080
; Local same-service-account control channel. Enable user disconnection only for an isolated service account.
; [management]