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
44 changes: 44 additions & 0 deletions .github/workflows/native-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,47 @@ jobs:
name: macos-native-service-logs
path: ${{ runner.temp }}/powercontext-native-tests/**/logs/*
if-no-files-found: ignore

windows-task-scheduler:
runs-on: windows-latest
env:
POWERCONTEXT_RUN_NATIVE_SERVICE_TESTS: "1"
POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER: PowerContext-Native-${{ github.run_id }}-${{ github.run_attempt }}
steps:
- name: Check out
uses: actions/checkout@v7

- name: Set up the environment
uses: ./.github/actions/setup-python-env

- name: Install the built distribution in a fresh virtual environment
shell: pwsh
run: |
uv build --wheel
$wheel = (Get-ChildItem dist\*.whl | Select-Object -First 1).FullName
$wheelUri = "file:///" + $wheel.Replace('\', '/')
uv venv .native-venv
uv pip install --python .native-venv\Scripts\python.exe "powercontext[cli,server] @ $wheelUri" pytest

- name: Exercise the real Task Scheduler lifecycle
shell: pwsh
run: >-
.native-venv\Scripts\python.exe -m pytest -q
--basetemp "$env:RUNNER_TEMP\powercontext-native-tests"
tests/native/test_personal_service_lifecycle.py

- name: Capture Task Scheduler diagnostics after failure
if: failure()
shell: pwsh
run: |
schtasks.exe /Query /TN "$env:POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER" /XML /HRESULT
if ($LASTEXITCODE -ne 0) { exit 0 }
schtasks.exe /Query /TN "$env:POWERCONTEXT_NATIVE_SERVICE_IDENTIFIER" /FO LIST /V /HRESULT

- name: Upload Windows native-service logs after failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: windows-native-service-logs
path: ${{ runner.temp }}/powercontext-native-tests/**/logs/*
if-no-files-found: ignore
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ uv tool install "powercontext[cli,server]==0.1.0"
# uv tool install "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@master"
```

Start a local Server in its own terminal:
For a persistent personal Server that survives terminal closure and can start again after login, install the native current-user service:

```bash
powercontext service install # Uninstall with `powercontext service uninstall`
powercontext service status
```

On Windows, the installer asks whether to enable startup at the next login when no login option is supplied; pressing
Enter keeps it disabled. Use `--start-on-login` or `--no-start-on-login` to choose explicitly.

If you want to use it foreground in a terminal, you can run:

```bash
powercontext server run
Expand Down
12 changes: 11 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,17 @@ uv tool install "powercontext[cli,server]==0.1.0"
# uv tool install "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@master"
```

在单独的终端中启动本地 Server:
如需让个人 Server 在终端关闭后继续运行,并可以在下次登录后再次启动,请安装原生当前用户服务:

```bash
powercontext service install # 卸载请运行 `powercontext service uninstall`
powercontext service status
```

在 Windows 上,未提供登录启动选项时,安装器会询问是否在下次登录时自动启动;直接按 Enter 的默认选择是不启用。
需要显式选择时,请使用 `--start-on-login` 或 `--no-start-on-login`。

如果想在终端以前台方式使用,可以运行:

```bash
powercontext server run
Expand Down
13 changes: 12 additions & 1 deletion README_JP.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ uv tool install "powercontext[cli,server]==0.1.0"
# uv tool install "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@master"
```

別のターミナルでローカル Server を起動します:
ターミナルを閉じても動作を続け、次回ログイン時に再び起動できる個人用 Server を実行するには、現在のユーザー用
ネイティブサービスをインストールします:

```bash
powercontext service install # Uninstall with `powercontext service uninstall`
powercontext service status
```

Windows では、ログイン起動のオプションを指定しない場合、次回ログイン時の自動起動を有効にするか確認します。
Enter を押すと既定では無効のままです。明示的に選ぶ場合は `--start-on-login` または `--no-start-on-login` を指定します。

ターミナルでフォアグラウンド実行する場合は、次のコマンドを使用できます:

```bash
powercontext server run
Expand Down
19 changes: 14 additions & 5 deletions docs/en/docs/how-to/deploy-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Run PowerContext with persistent data, health checks, authenticatio

# Deploy the Server

`powercontext server run` is a foreground process. On a personal macOS or Linux workstation, PowerContext can register
`powercontext server run` is a foreground process. On a personal macOS, Linux, or Windows workstation, PowerContext can register
that same Server runner with the native current-user service manager. Managed deployments should continue to use a
container platform or an administrator-owned service manager.

Expand All @@ -18,9 +18,11 @@ powercontext service install
powercontext service status
```

Linux uses `systemd --user` and writes logs to the user journal. macOS uses a per-user LaunchAgent and writes stdout
and stderr below the PowerContext user data directory. `service status` reports the exact log selector or path. The
installer never requests administrator privileges and accepts only a loopback Server bind.
On Windows, the command asks whether to enable startup at the current user's next login when neither
`--start-on-login` nor `--no-start-on-login` is supplied; pressing Enter keeps login auto-start disabled. Use either
option for a non-interactive choice.

Linux uses `systemd --user` and writes logs to the user journal. macOS uses a per-user LaunchAgent, and Windows uses a current-user Task Scheduler task; both write stdout and stderr below the PowerContext user data directory. `service status` reports the exact log selector or path.

For an explicit Server configuration, protect the environment file before installing:

Expand All @@ -30,7 +32,14 @@ powercontext config validate --env-file /path/to/powercontext.env
powercontext service install --env-file /path/to/powercontext.env
```

The native definition stores only the absolute file path and non-content file identity metadata; it does not copy
On Windows, remove inherited access and grant the file only to the current user, `SYSTEM`, and local `Administrators` before validation, for example:

```powershell
icacls $env:USERPROFILE\powercontext.env /inheritance:r /grant:r "$env:USERNAME:(F)" "SYSTEM:(F)" "Administrators:(F)"
```

The native definition stores only the absolute file path and non-content file identity metadata. On Windows this
includes the current user's owner SID, which is revalidated whenever the launcher starts. It does not copy
credentials or the caller's shell environment. Re-run `service install` after upgrading PowerContext or changing the
environment file. Remove the registration without deleting Server data or logs with:

Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/how-to/install-and-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Server startup, seekDB, diagnostics, and updates for readers who already know wh

## Install the application

You need Python 3.11 or newer, Git, and [`uv`](https://docs.astral.sh/uv/) on macOS or Linux. Then install
You need Python 3.11 or newer, Git, and [`uv`](https://docs.astral.sh/uv/) on macOS, Linux, or Windows. Then install
PowerContext directly from a Git ref:

```bash
Expand Down
3 changes: 2 additions & 1 deletion docs/en/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export POWERCONTEXT_HOME=/srv/powercontext
Without an override, the default is:

- Linux: `$XDG_DATA_HOME/powercontext`, or `~/.local/share/powercontext`;
- macOS: `~/Library/Application Support/powercontext`.
- macOS: `~/Library/Application Support/powercontext`;
- Windows: `%LOCALAPPDATA%\\powercontext`.

The default SQLite database is `powercontext.db` in this directory. Scheduled processing uses `scheduler.db` in the
same directory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ lifecycle commands.
The initial distribution contract is:

```text
powercontext service install
powercontext service install [--start-on-login | --no-start-on-login]
powercontext service uninstall
powercontext service status
```
Expand All @@ -193,7 +193,9 @@ Install performs these steps:
endpoint with an invalid response is a conflict and fails before native state changes.
5. Render and validate an artifact containing the fixed ownership marker, package version, definition version, intended
endpoint, and launcher command.
6. Create or update only PowerContext's personal Server registration and enable it for future user logins.
6. Create or update only PowerContext's personal Server registration. On Windows, when neither login option is
supplied, ask whether to add the current-user login trigger; the prompt defaults to no. The explicit
`--start-on-login` and `--no-start-on-login` options select the behavior without prompting.
7. Start it immediately by default unless step 4 found an already-live PowerContext Server.
8. Report registration, definition, native manager, liveness, and log-location facts after the operation.

Expand Down Expand Up @@ -275,8 +277,9 @@ current-user domain, configures explicit PowerContext-owned per-user stdout and

### Windows

The Windows adapter is a `Task Scheduler` task triggered when the current user logs on. It runs as that user and never
as `SYSTEM`. A hidden process window is acceptable. The launcher redirects Server output to explicit
The Windows adapter is a `Task Scheduler` task triggered when login auto-start is selected. It runs as that user and
never as `SYSTEM`; when login auto-start is disabled, it has no login trigger. A hidden process window is acceptable.
The launcher redirects Server output to explicit
PowerContext-owned per-user log files because Task Scheduler history is not Server stdout or stderr. The adapter does
not install a Windows Service.

Expand All @@ -287,7 +290,9 @@ rendering and ownership tests.

## Configuration and credentials

The service installer records the executable, required arguments, and non-secret service metadata. It does not copy
The service installer records the executable, required arguments, and non-secret service metadata. For a Windows
environment file, that metadata includes the current user's owner SID and the launcher revalidates it on every start.
It does not copy
the caller's complete environment, shell profile, API keys, bearer tokens, or provider credentials into a native
registration artifact.

Expand Down
21 changes: 15 additions & 6 deletions docs/zh/docs/how-to/deploy-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ description: 使用持久化数据、健康检查、鉴权和安全网络边界

# 部署 Server

`powercontext server run` 是前台进程。在个人 macOS 或 Linux 工作站上,PowerContext 可以把同一个 Server runner 注册到
原生当前用户服务管理器。托管部署仍应使用容器平台或管理员拥有的服务管理器。
`powercontext server run` 是前台进程。在个人 macOS、Linux 或 Windows 工作站上,PowerContext 可以把同一个 Server runner 注册到原生当前用户服务管理器。托管部署仍应使用容器平台或管理员拥有的服务管理器。

## 运行持久个人 Server

Expand All @@ -17,9 +16,12 @@ powercontext service install
powercontext service status
```

Linux 使用 `systemd --user`,日志进入 user journal;macOS 使用当前用户 LaunchAgent,stdout 和 stderr 写入
PowerContext 用户数据目录。`service status` 会返回精确的日志 selector 或路径。安装器不请求管理员权限,并且只接受
loopback Server bind。
Linux 使用 `systemd --user`,日志进入 user journal;macOS 使用当前用户 LaunchAgent;Windows 使用当前用户的 Task Scheduler task。macOS 和 Windows 的 stdout、stderr 写入 PowerContext 用户数据目录。

`service status` 会返回精确的日志 selector 或路径。

在 Windows 上,如果没有提供 `--start-on-login` 或 `--no-start-on-login`,命令会询问是否在当前用户下次登录时
自动启动;直接按 Enter 的默认选择是不启用。需要非交互选择时,请提供其中一个选项。

使用显式 Server 配置时,先保护并验证环境文件:

Expand All @@ -29,7 +31,14 @@ powercontext config validate --env-file /path/to/powercontext.env
powercontext service install --env-file /path/to/powercontext.env
```

原生定义只记录环境文件的绝对路径和不含内容的文件 identity metadata,不复制 credential 或调用者的 shell environment。
在 Windows 上,校验前需要移除继承权限,只授予当前用户、`SYSTEM` 和本机 `Administrators` 访问权限,例如:

```powershell
icacls $env:USERPROFILE\powercontext.env /inheritance:r /grant:r "$env:USERNAME:(F)" "SYSTEM:(F)" "Administrators:(F)"
```

原生定义只记录环境文件的绝对路径和不含内容的文件 identity metadata;在 Windows 上还记录当前用户的 owner SID,
launcher 每次启动都会重新校验它。不复制 credential 或调用者的 shell environment。
升级 PowerContext 或修改环境文件后应重新执行 `service install`。以下命令会删除注册,但保留 Server 数据和日志:

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/zh/docs/how-to/install-and-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: 从 Git 安装 PowerContext,并运行本地 Server。

## 安装应用

需要在 macOS 或 Linux 上准备 Python 3.11 或更新版本、Git 和
需要在 macOS、LinuxWindows 上准备 Python 3.11 或更新版本、Git 和
[`uv`](https://docs.astral.sh/uv/),然后从指定 Git ref 直接安装 PowerContext:

```bash
Expand Down
3 changes: 2 additions & 1 deletion docs/zh/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export POWERCONTEXT_HOME=/srv/powercontext
未覆盖时,默认目录为:

- Linux:`$XDG_DATA_HOME/powercontext`,未设置时为 `~/.local/share/powercontext`;
- macOS:`~/Library/Application Support/powercontext`。
- macOS:`~/Library/Application Support/powercontext`;
- Windows:`%LOCALAPPDATA%\\powercontext`。

默认 SQLite 数据库是该目录下的 `powercontext.db`。启用定时处理时,调度状态保存在同一目录的
`scheduler.db`。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Server role。
初始 distribution 契约为:

```text
powercontext service install
powercontext service install [--start-on-login | --no-start-on-login]
powercontext service uninstall
powercontext service status
```
Expand All @@ -167,6 +167,10 @@ powercontext service status
管理员安装模式。它们从本地 `ServerSettings` 推导 endpoint;根命令的 Client `--server-url` 选项和
`ClientSettings.server_url` 都不会选择 service target。

在 Windows 上,如果没有显式提供 `--start-on-login` 或 `--no-start-on-login`,Install 会询问是否启用当前用户
登录触发器,直接按 Enter 的默认选择是不启用。提供任一显式选项后不会再次询问;Linux 和 macOS 不提供关闭
其原生用户服务正常启动行为的选项。

### Install

Install 执行以下步骤:
Expand All @@ -176,10 +180,10 @@ Install 执行以下步骤:
3. 解析 distribution 所有的内部 launcher 对应的绝对、非 shell 命令。
4. 探测目标 endpoint:有效的 PowerContext liveness 响应会跳过立即启动;端口被占用但响应无效时,作为冲突在改变
原生状态前失败。
5. 渲染并验证包含固定 ownership marker、package version、definition version、目标 endpointlauncher command
的注册产物
6. 只创建或更新 PowerContext 的个人 Server 注册,并为后续用户登录启用
7. 除非第 4 步发现 PowerContext Server 已 live,否则默认立即启动
5. 渲染并验证包含固定 ownership marker、package version、definition version、目标 endpointlauncher command
和登录启动选择的注册产物
6. 只创建或更新 PowerContext 的个人 Server 注册;Windows 根据交互式回答或显式选项决定是否写入当前用户登录触发器
7. 除非第 4 步发现 PowerContext Server 已 live,否则立即启动本次安装的服务;登录启动选择只影响后续用户登录
8. 操作完成后报告 registration、definition、原生 manager、liveness 和 log location 事实。

使用相同目标定义重复安装应成功,且不产生语义变化。如果 PowerContext 拥有的定义已过期,则在原生 manager 支持时
Expand Down Expand Up @@ -252,17 +256,19 @@ privileged helper。

### Windows

Windows adapter 使用在当前用户登录时触发的 `Task Scheduler` task。它以该用户身份运行,绝不使用 `SYSTEM`。
允许隐藏 process window。由于 Task Scheduler history 不是 Server stdout 或 stderr,launcher 会把 Server 输出重定向
到明确的 PowerContext-owned 当前用户日志文件。该 adapter 不安装 Windows Service。
Windows adapter 使用当前用户的 `Task Scheduler` task;选择登录自启时,在当前用户登录时触发,选择不自启时不写入登录
触发器。它以该用户身份运行,绝不使用 `SYSTEM`。允许隐藏 process window。由于 Task Scheduler history 不是 Server
stdout 或 stderr,launcher 会把 Server 输出重定向到明确的 PowerContext-owned 当前用户日志文件。该 adapter 不安装
Windows Service。

原生 identifier 和 path 是一个服务对应一组固定的项目常量。每个产物都包含稳定的 ownership marker 和 definition
version,使 status 和 uninstall 在兼容的 package rename 后仍能区分 PowerContext-owned definition 与外部资源。
具体平台字符串属于 adapter 常量,并由 rendering 和 ownership test 覆盖。

## Configuration and credentials

服务安装器记录 executable、必需参数和不敏感的 service metadata。它不会把调用者的完整 environment、shell profile、
服务安装器记录 executable、必需参数和不敏感的 service metadata。Windows 环境文件的 metadata 还包含当前用户的
owner SID,launcher 每次启动都会重新校验该 SID。它不会把调用者的完整 environment、shell profile、
API key、bearer token 或 provider credential 复制进原生注册产物。

因此,初始个人服务模式依赖原生当前用户服务环境中可获得的配置。`powercontext service status` 和
Expand Down
4 changes: 2 additions & 2 deletions src/powercontext/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import shutil
import sys
import tempfile
from collections.abc import Iterator, Mapping, Sequence
from collections.abc import Generator, Mapping, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import UTC, datetime
Expand Down Expand Up @@ -914,7 +914,7 @@ def _validate_server_settings(values: Mapping[str, str]) -> None:


@contextmanager
def _temporary_environment(values: Mapping[str, str], *, clear: set[str]) -> Iterator[None]:
def _temporary_environment(values: Mapping[str, str], *, clear: set[str]) -> Generator[None, None, None]:
original = {name: os.environ.get(name) for name in clear | set(values)}
try:
for name in clear:
Expand Down
6 changes: 3 additions & 3 deletions src/powercontext/cli/env_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import os
import re
from collections.abc import Collection, Iterator, Mapping, MutableMapping
from collections.abc import Collection, Generator, Mapping, MutableMapping
from contextlib import contextmanager
from pathlib import Path

Expand Down Expand Up @@ -159,7 +159,7 @@ def apply_environment_file(


@contextmanager
def environment_file_context(path: Path, *, override: bool = False) -> Iterator[Mapping[str, str]]:
def environment_file_context(path: Path, *, override: bool = False) -> Generator[Mapping[str, str], None, None]:
"""Apply a file for one process scope, then restore every affected value."""

loaded = read_environment_file(path)
Expand All @@ -173,7 +173,7 @@ def environment_context(
*,
override: bool = False,
clear: Collection[str] = (),
) -> Iterator[None]:
) -> Generator[None, None, None]:
"""Apply parsed values for one process scope, then restore every affected value."""

loaded = dict(values)
Expand Down
Loading
Loading