Align project introduction with bilingual PIGWay READMEs

This commit is contained in:
Pigway
2026-10-11 07:24:32 +08:00
parent a11a88456c
commit f45fce7d69
3 changed files with 250 additions and 320 deletions
+54 -320
View File
@@ -1,359 +1,93 @@
# Pigway Frame Flow # Pigway Frame Flow
Pigway Frame Flow is a lightweight self-hosted platform for programmable smart-frame automation. [简体中文](README.zh-CN.md) | English
独立运行的智能画框服务:中英文 Web UI、深浅色模式、设备驱动抽象、KoKonna 全部已公开接口、图片处理、模板、SQLite 调度与播放列表、临时覆盖恢复和 REST API。 Pigway Frame Flow is a lightweight, self-hosted platform for programmable smart-frame automation. It runs independently on a NAS or server, with its own Web UI, image library, rendering, scheduler and REST API. KoKonna is a supported device integration; external applications do not need its credentials or SDK.
KoKonna is a supported device integration and is not affiliated with this project unless otherwise stated. ### Installation
## Quick Start / 快速开始 Docker Compose on an AMD64 host:
```sh ```bash
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.lock
.venv/bin/python scripts/run_local.py
```
Open http://127.0.0.1:18787. The local demo creates a private `.env` with a random
FRAME_FLOW_TOKEN. Read that token locally in an editor and enter it on the login
screen. Do not paste a KoKonna API Key into Web UI. Demo always forces mock mode
and disables real writes. It leaves data persistent in ./data and ./config.
网页使用账号密码登录。通过 `python scripts/create-account.py --username YOUR_NAME` 创建账号,再启动服务;密码不会写入代码或明文配置。勾选记住登录可保持 30 天,设置页面可修改密码或退出。
## Architecture / 架构
```
providers → templates / renderers → generated image
↓
Web UI / external REST → DisplayService → FrameDeviceDriver → KokonnaDriver
Scheduler / Playlist ─────────────┘
SQLite: operations / history / jobs / overrides / assets
```
Package: pigway_frame_flow. Device composition is in core/registry.py;
vendor-specific code is in drivers/kokonna. One process, one worker, no Redis,
message broker, OpenClaw, Home Assistant or external application dependency.
## Configuration / 配置
Copy `.env.example` to `.env`, set a strong FRAME_FLOW_TOKEN, chmod 600 .env.
- KOKONNA_MOCK=true: independent persistent demo, no cloud calls.
- KOKONNA_WRITE_ENABLED=false: live write protection, mandatory before verification.
- KOKONNA_API_KEY: device key, never stored in DB or returned to Web UI.
- *_FILE: optional Docker Secret equivalent.
- WEB_BIND/WEB_PORT: Compose host bind and port. Internal app binds container network.
- CORS_ALLOWED_ORIGINS: exact comma-separated origins; default empty.
- IMAGE_URL_ALLOWED_HOSTS: exact public HTTPS hosts; default disabled. Redirects denied.
- TZ=Asia/Shanghai; DATA_DIR=/data; CONFIG_DIR=/config.
For multiple devices copy deploy/devices.yml.example into config/devices.yml,
reference a separate environment key per device. Jobs bind a device_id. Layout
currently targets 1200 × 1600; incompatible geometry is refused before writes.
## Docker
```sh
cp .env.example .env cp .env.example .env
# Configure a token; keep API key empty, mock true, writes false. # Set a strong FRAME_FLOW_TOKEN; keep mock=true and writes=false initially.
chmod 600 .env
mkdir -p data/images data/generated data/cache config mkdir -p data/images data/generated data/cache config
sudo chown -R 10333:10333 data config sudo chown -R 10333:10333 data config
./scripts/build-verify.sh ./scripts/build-verify.sh
# Create the browser administrator account; enter its password interactively.
docker compose run --rm --no-deps -e PYTHONPATH=/app \
-v "$PWD/scripts:/bootstrap:ro" --entrypoint python pigway-frame-flow \
/bootstrap/create-account.py --username admin --config-dir /config
docker compose up -d docker compose up -d
python3 scripts/smoke-test.py
``` ```
Image pigway-frame-flow:latest, service/container pigway-frame-flow, AMD64, Open `http://127.0.0.1:18787` and sign in with the account you created. The browser uses an HttpOnly session cookie; it does not require an API token. Remembered sessions last 30 days. Change the password or sign out in Settings. `FRAME_FLOW_TOKEN` authenticates external API clients and is separate from the KoKonna API key.
Python 3.14.5, pinned runtime dependencies, CJK fonts, non-root UID/GID 10333,
read-only root filesystem, writable /data and /config, no capabilities, no host
network, no Docker socket. Restart unless-stopped. JSON-file logs capped 10m × 3.
Healthcheck uses service /health, not cloud availability. No second log database.
## Synology NAS The published interface defaults to loopback. For LAN access, explicitly set `WEB_BIND` to your NAS LAN address and choose an available `WEB_PORT`. See [Synology deployment](docs/NAS.md), [build mirrors and caching](docs/BUILDING.md), and [Gitea deployment/rollback](docs/deployment/GITEA_CD.md). No firewall, router or reverse-proxy settings are changed automatically.
Target `/volume2/docker/pigway-frame-flow/`. See [NAS deployment](docs/NAS.md). For a local mock demo, use Python 3.14:
Set WEB_BIND to the NAS LAN IP to restrict the published interface, or explicitly
0.0.0.0 when desired. Port 18787 must be free. No router/firewall/reverse proxy
rules are modified. Source and persistent volumes stay in this project directory.
No artificial logs directory is required. Compose creates its own bridge network.
## KoKonna Setup / 真实设备 ```bash
python3.14 -m venv .venv
.venv/bin/python -m pip install -r requirements.lock
.venv/bin/python scripts/create-account.py --username admin
.venv/bin/python scripts/run_local.py
```
First deploy Mock and verify persistence. Then set KOKONNA_MOCK=false and fill The demo forces mock mode and disables device writes. Data and configuration remain in `data/` and `config/` across restarts.
KOKONNA_API_KEY through private environment/secret. Keep writes false. /ready checks
configuration/DB/worker/token without a cloud call. Query device and images metadata;
compare model, geometry, timezone, heartbeat, battery, imageId, coverId, sync.
If different, stop and report. **Do not enable writes or perform the single test
until the owner explicitly authorizes it.** Test design: [REAL_DEVICE_TEST.md](docs/REAL_DEVICE_TEST.md).
## Security ### Features and boundaries
Protected /api/v1 capabilities accept an administrator account session, or external - Chinese and English Web UI, light/dark themes, account login, device dashboard and image previews.
Frame Flow Bearer auth with scope checks. - Local staging and KoKonna cloud gallery, uploads, image switching, stars, source/task filters and batch actions.
SQLite stores token hashes. Device keys live only in env/secret and runtime memory. - Aspect-ratio-preserving contain/cover/smart conversion, color/grayscale profiles and orientation selection for photo sources.
Browser login uses SQLite accounts with salted scrypt password hashes and revocable, - Seven templates: Daily Dashboard, Weather, Calendar, Quote, Photo + Text, Daily Poetry and System Alert.
HttpOnly/SameSite=Strict cookies (30 days when remembered, 12 hours otherwise). - Layout Studio for collages/posters with 1–12 images; optional OpenAI-compatible layout planning and source-driven automatic collages.
Mutations require a session CSRF header. Logout and password changes revoke sessions. - Device-bound schedules, ordered/random playlists, temporary priority overrides, restore and persistent operation history.
Passwords and browser sessions are never stored in localStorage. Bootstrap hashes - Read-only NAS folders, Synology Photos person filtering, WebDAV and optional cloud-drive connectors. Each source has its own configuration and authorization.
in private `config/accounts.json` seed new accounts only; restarting does not undo - Optional weather, calendar, monitoring, AI, MQTT and webhook integrations. Provider configuration is required; an unconnected provider is not a working connection.
a password change. Five failed logins per source IP cause a five-minute lockout.
The browser needs no API Token. Bearer authentication remains compatible for
external clients; the KoKonna key is never used for account login.
The LAN deployment uses HTTP; use an explicitly configured HTTPS endpoint if
transport encryption is needed. No reverse proxy or firewall is changed automatically.
## API Templates **render images**, including text-only templates. Choose a template, edit its data, generate a preview, then display it. Preview generation and saving template settings do not update the frame. Photo + Text and Daily Dashboard can include a selected local photo; other templates do not automatically choose one. Scheduled template jobs render afresh on each run. See [photo sources](docs/PHOTO_SOURCES.md), [automatic collages](docs/AUTOMATIC_COLLAGE.md) and [integration setup/limits](docs/EXTENSIONS.md).
OpenAPI 3.x at /openapi.json; Swagger at /docs; stable /api/v1. Display/render/upload Real weather uses the configured provider/location and is refreshed when rendered; it does not update an already displayed image by itself. Official KoKonna widgets are separate from Frame Flow templates and require optional vendor-account authorization. API-key access remains independent.
return 202 + operation_id. Poll /api/v1/operations/{id}; completion requires matching
image ID and cloud synced flag. Idempotency-Key retained seven days. X-Request-ID
traces history. UNKNOWN writes block later mutations; read-only reconciliation
and explicit admin acknowledgement release the block, never replay a write.
The older /api/display route is retained and deprecated (same v1 payload).
See [external integration contract](docs/integrations/TICKTAR.md).
## Scheduler / Playback ### Device integration and API
Cron (5 fields), daily, weekdays, weekends, date and temporary tasks; Shanghai time. ```text
Use source {"type":"restore"} for explicit restore jobs. Unique job/slot and persistent Providers → Templates / Renderers → Generated image
idempotency prevent duplicate dispatch. Overdue tasks >5 minutes are skipped rather Web UI / Scheduler / REST API → DisplayService → FrameDeviceDriver → KoKonna
than unexpectedly overwriting artwork after downtime. State persists across restart. ↓
Overrides use priority then recency; lower priorities wait without uploading. SQLite: jobs, operations, history, overrides, assets
If another app changes the frame, expired overrides do not overwrite its artwork. ```
Frame Flow playlists support ordered/random cloud image IDs and >=5-minute intervals;
only one enabled playlist per device. They pause for overrides/unknown writes.
Native cloud slideshow mode/interval are readable; membership/editing are unavailable
in the official API. Avoid having native and Frame Flow playback compete.
## Templates / 图片 Configure the device key privately through `.env` or a secret file. Start with `KOKONNA_MOCK=true` and `KOKONNA_WRITE_ENABLED=false`; after mock verification, connect read-only before explicitly enabling writes. Multi-device definitions use `config/devices.yml` and device-specific key references. Vendor code stays in `drivers/kokonna`; other applications use the same service API as the Web UI.
Seven JSON layout templates: daily-dashboard, weather, calendar, quote, photo-text, The public API is `/api/v1`, with OpenAPI at `/openapi.json` and Swagger UI at `/docs`. Display/render/upload operations return HTTP 202 and an operation ID; poll `/api/v1/operations/{id}` for the outcome. `Idempotency-Key` prevents duplicate dispatch, and `X-Request-ID` links requests to history. An uncertain write blocks subsequent mutations until read-only reconciliation or explicit administrator acknowledgement; it is never automatically replayed.
daily-poetry and system-alert, with color and grayscale profiles. Every template
renders an image, including templates that contain only text.
使用流程:选择模板 → 编辑数据 → 生成预览 → 显示到画框。生成预览只创建本地图片; Ticktar integration is optional. Frame Flow has no runtime dependency on Ticktar and never accesses its database. See the [external integration contract](docs/integrations/TICKTAR.md).
保存默认数据只保存配置,两者都不会更新设备。自动化模板任务每次运行重新生成图片。
- **照片与文字 / Photo + Text**:标题、中央照片区域、下方正文;与照片数据源任务的底部水印布局不同。 ### Storage and security
- **家庭早报 / Daily Dashboard**:日期、天气、事项、一句话与所选照片区域。
- **天气 / Weather**:当前天气与三天预报;天气 JSON 留空时使用配置的 Provider。
- **日历、每日诗词、一句话、系统告警**:将相应信息排版为图片,不自动选择图库照片。
Photos use a local asset ID (`photo_id`); without a selected photo, a placeholder Docker persists `/data/frameflow.db`, images/generated/cache under `/data`, and private configuration/session files under `/config`. It runs as UID/GID 10333 with a read-only root filesystem, its own bridge network, `unless-stopped` restart policy and bounded stdout/stderr logs. No privileged mode, host network or Docker socket is required.
is rendered. Configure Open-Meteo and location coordinates in **Settings → Weather
source** for real weather. Forecasts are fetched on demand and cached for ten minutes;
unavailable/stale data is labeled explicitly. Weather remains static on the frame
until the next render and display, so use an Automation job for regular updates.
Official KoKonna widgets are a separate integration, not these local templates.
No unreliable lunar date is fabricated.
Contain preserves composition with margins; cover crops centrally; smart is an
edge-saliency heuristic. Always inspect preview; face-aware guarantees are not claimed.
Cloud uploads activate immediately, so gallery-only means **local staging**.
## Development / Testing Passwords are hashed; browser mutations require CSRF protection. API keys and provider credentials stay server-side and must never enter Git or logs. CORS is disabled unless origins are explicitly configured. `/health` checks the service; `/ready` checks local dependencies without making cloud availability a restart trigger. See [public repository privacy](docs/PUBLICATION.md), [LICENSE](LICENSE) and [NOTICE](NOTICE.md).
```sh KoKonna is a supported integration. This project is not affiliated with or endorsed by KoKonna.
### Development and known limitations
```bash
.venv/bin/python -m pip install -e '.[test]' .venv/bin/python -m pip install -e '.[test]'
.venv/bin/python -m pytest -q
.venv/bin/python -m ruff check pigway_frame_flow tests scripts .venv/bin/python -m ruff check pigway_frame_flow tests scripts
.venv/bin/python -m ruff format --check pigway_frame_flow tests scripts .venv/bin/python -m ruff format --check pigway_frame_flow tests scripts
.venv/bin/python -m mypy pigway_frame_flow .venv/bin/python -m mypy pigway_frame_flow
.venv/bin/python -m pytest -q
.venv/bin/python -m pip_audit -r requirements.lock --no-deps --disable-pip .venv/bin/python -m pip_audit -r requirements.lock --no-deps --disable-pip
``` ```
Tests mock every cloud request; no real-device writes. scripts/ui-smoke.cjs runs an Tests mock cloud/device calls. CI also verifies an isolated Docker build, readiness, smoke tests, restart and cleanup. External fork PRs do not execute on the production-capable NAS runner.
isolated headless browser and checks settings.mock before acting. CI builds AMD64,
validates Compose, starts a container, checks health/ready and restarts for persistence.
## Known Limitations / 已知边界 Cloud synchronization does not prove the physical pixels refreshed. KoKonna cloud uploads activate immediately, so upload-only uses local staging. Native slideshow membership editing is unavailable; avoid running native and Frame Flow playback simultaneously. Smart cropping is a saliency heuristic, not guaranteed face detection. The service uses one worker and is not designed for multiple replicas. Browser OAuth, AI daily-art scheduling, additional calendar recurrence features and more device drivers remain roadmap items.
Cloud acknowledgements do not prove physical display pixels. Model fields may be Related projects: [PIGWay Device Agent](https://tea.pigway.com/way/pigway-device-agent) · [PIGWay Device Console](https://tea.pigway.com/way/pigway-device-console) · [PIGWay Yahboom Cooling HAT](https://tea.pigway.com/way/pigway-yahboom-cooling-hat)
omitted and are explicitly marked configured. No native slideshow membership API,
no cloud upload-only API, no automatic retries after uncertain writes. Opt-in local retention protects manual uploads, stars and active references; its quota is a cleanup target, not a hard disk limit. Rendering assets are local, limited
to 25M pixels/20 MiB. This release uses a single worker and is not multi-replica.
Gallery reads have no pagination because upstream documents none. Web UI hides
secrets and lazy-loads thumbnails; generating a first thumbnail fetches one original.
Scoped client provisioning and bounded signed webhook delivery are available in Connections. Docker build/NAS
results should be checked in the Actions run for the relevant commit.
## Roadmap
Browser OAuth login, AI daily-art scheduling, custom ICS timezones/EXRULE and additional hardware-specific adapters remain pending. MQTT, bounded recurrence, token refresh, transition events and single-image AI generation are optional capabilities. See [optional capabilities and exact limits](docs/EXTENSIONS.md).
## Layout Studio and optional integrations
Bilingual Layout Studio imports 1–12 images, edits bounded regions and renders a collage or poster. Optional OpenAI-compatible AI plans receive text and dimensions only. Connections configures provider credentials, calendars/state, retention, client scopes and webhooks. Photo Sources includes NAS, WebDAV, Google Drive, AliPan and Baidu readers. Authorization is separate from implementation; unconnected providers return explicit errors. See [setup and limitations](docs/EXTENSIONS.md).
## Template editing
Templates provide bilingual form fields, event lists, local photo selection, advanced
JSON, color/grayscale previews, persistent defaults and reset to built-in defaults.
Changing content invalidates the previous preview. Weather uses the labeled Mock
provider until a real provider is configured. Live display controls stay disabled
until real writes are separately approved.
Browser smoke tests use a disposable Mock account via FRAME_FLOW_TEST_USERNAME
and FRAME_FLOW_TEST_PASSWORD; never run the mutation smoke suite against live devices.
## Current content and completed page workflows
Dashboard displays the actual current image and recorded content source. Frame Flow
stores immutable template/data/profile snapshots when it renders an asset and maps
successful upload results to the device/image ID. The current-content endpoint
matches the live device image ID; vendor images with no recorded source remain
explicitly unknown. Switching externally never makes the previous template appear
as the current one. Restore also restores the matching content provenance.
Templates can load the current recorded template or continue editing a generated
local image. They support direct photo import and protected image downloads.
Gallery provides search, sorting, dimensions and downloads. Display confirmation
supports duration, priority and restoration. History includes operation details.
Automation offers content pickers, template profiles, previews and manual execution;
playlists offer gallery image checkboxes. Real writes remain blocked by the server
regardless of the UI controls.
Settings persists weather coordinates/provider and image URL host allowlists in
SQLite. Open-Meteo provides current model weather and a three-day forecast, cached
for ten minutes on demand. Network failures show unavailable/stale data and use a
two-minute backoff, without replacing it with fictitious live data. Default remains
Mock until configured in Settings. Open-Meteo attribution is included in the UI and
weather/dashboard renders. Image URL import is HTTPS-only with explicit hostnames,
public-address validation and pinned DNS; redirects and private addresses are blocked.
Additional v1 endpoints: `/devices/{id}/current-content`, `/assets/{id}/source`,
`/providers/weather`, `/settings/weather`, `/settings/image-url`, `/jobs/{id}`,
`/jobs/{id}/preview`, `/jobs/{id}/run` (all under `/api/v1`). Existing contracts remain
compatible; new endpoints are included in generated OpenAPI and contract tests.
## Daily Poetry / 每日诗词
The daily-poetry template includes 20 public-domain classical Chinese poems, selected deterministically by Shanghai date, or manually. Create a daily job from its editor; new poetry jobs are paused by default until you enable them. Custom title, author and verses are editable. This is an independent Frame Flow content provider, not the KoKonna mini-program subscription. Official public API does not expose mini-program subscription/template settings.
每日诗词支持按上海日期选诗、手选 20 首古典诗词、自定义文本以及每日任务。模板显示操作使用统一上传/显示服务;真实写入须开启 KOKONNA_WRITE_ENABLED,结果未知不会自动重试。官方小程序原有每日诗词订阅不能通过当前公开 API 查询或修改。
### 官方账号连接 / Official account connection
在 Templates → 官方小组件中,由管理员输入 KoKonna 官方账号和密码,查询账号下
的设备后手动关联当前 Frame Flow 设备。此流程不绑定或解绑云端设备,也不改变
图片或订阅。现有 API Key 接入继续负责设备、图库和显示能力。
密码不保存;会话与关联信息保存在 `/config/official-{device_id}.secret`,权限 0600。
容器重启可恢复连接。会话过期需要重新登录;断开连接会清除本地会话,不撤销
官方服务器上的其他登录。局域网访问沿用现有部署方式。
Official account connection is admin-only and separate from API Key access.
Login, device discovery, local association and disconnect are available through
`/api/v1/devices/{device_id}/official/connection`. Passwords are never persisted;
server-side sessions are stored as restricted secret files. No official widget
mutation or other extra RPC write is enabled by connecting.
### Photo sources / 照片数据源
Gallery supports read-only NAS folders and HTTPS WebDAV sources, random photo
preview, and device-bound scheduled random photos. Source definitions are persisted
in SQLite. See [setup and limits](docs/PHOTO_SOURCES.md).
NAS 本地目录需要显式只读挂载;网盘先支持通用 WebDAV,也可使用同步到 NAS 的目录。
### Build networking and cache
The Dockerfile retains the pinned Python base, locked Python dependencies,
`fonts-noto-cjk`, Debian repository signature checks, and offline artifact
SHA256 verification. Both production and CI use `scripts/build-verify.sh`,
which enables BuildKit and retains `docker compose build --pull`.
APT mirror hosts are configurable through `DEBIAN_MIRROR` and
`DEBIAN_SECURITY_MIRROR` (hosts only, without scheme or path). Defaults are
`deb.debian.org`; HTTPS is used for both Debian and security repositories.
For mainland-China NAS builds, use the shared profile:
```sh
. ./deploy/build-cn.env
./scripts/build-verify.sh
```
CI loads this same profile, using Tsinghua TUNA for both repositories unless
Gitea repository variables `DEBIAN_MIRROR` / `DEBIAN_SECURITY_MIRROR` override
it. Production may also set the two hosts directly in `.env` when calling the
build script without the profile. An alternate mainland mirror is
`mirrors.ustc.edu.cn`; availability and throughput depend on the Runner network.
No fallback disables signature verification or removes packages.
Layer order stays: pinned base → APT/font dependencies → `requirements.lock`
→ pip dependencies → application/vendor source → build metadata.
A source-only change therefore reuses the APT and pip layers, provided the
same builder, base digest, mirror arguments, and optional offline artifacts
are used. Changing the lockfile rebuilds pip and later layers, not APT.
Changing the mirror or APT instructions rebuilds APT and later layers.
Previously an APT layer miss redownloaded indexes/packages, and pip used
`--no-cache-dir`. Now locked BuildKit mounts retain APT indexes, downloaded
`.deb` files, and pip downloads/wheels outside the final image. Debian's
`docker-clean` hook is disabled so it cannot discard cached packages.
Re-executed APT steps still run `apt-get update` and validate repository
signatures; cached packages and pip artifacts can avoid repeat large downloads.
The initial build after this Dockerfile change will miss dependency layers;
subsequent builds benefit. No measured speedup is claimed before CI completes.
Caches belong to the Docker builder, not the ephemeral CI workspace. Keep the
NAS Runner's builder storage across jobs; removing the builder, pruning caches,
or using a fresh Docker daemon loses the benefit. Cache mounts are disposable:
an empty cache still builds correctly. `--pull` remains enabled; a pinned base
digest check alone does not invalidate dependency layers. To deliberately
refresh installed APT versions, use `docker compose build --pull --no-cache`.
### CI with a host Docker socket
Run `python scripts/ci-compose-verify.py` after loading the build mirror profile.
The script derives a temporary Compose document from production settings, with
an absolute client-side build context, a UUID project name, a unique CI image
tag, and project-scoped `data` / `config` named volumes. It removes production
bind mounts, the fixed container name, host ports, and the production `.env`
reference. A fresh test token, mock driver, and disabled device writes are
explicitly configured. Docker initializes empty named volumes from the image's
owned `/data` and `/config` directories; the service remains UID/GID 10333.
Build contexts are sent by the Docker client, so the job workspace can remain
inside the job container. Runtime storage is managed by the daemon; no matching
NAS host `/workspace` directory is needed. HTTP readiness and the unmodified
nine-endpoint smoke script run inside the service with `compose exec -T`.
Restart is followed by another readiness check and the same smoke test.
A `finally` block removes the test containers, network, volumes, and unique
image tag on success or failure. The workflow records the private temporary
Compose path in `GITHUB_ENV` and repeats cleanup in an `always()` step, covering
normal cancellation/interruption. Failed cleanup retains the manifest so the
fallback can retry. Abrupt host/daemon loss or a forcibly terminated entire job
can prevent both cleanup paths; no CI script can guarantee cleanup in that case.
No global prune or production resource removal is performed. Keep builder
caches: only the run-specific image tag is removed.
### CI Job pip indexes versus Docker build indexes
The Job bootstrap runs `python scripts/ci-install.py` before Ruff/mypy/tests.
It needs only the Python standard library, so it runs before PyYAML or tooling
is installed. `CI_PIP_INDEX_URL` and `CI_PIP_FALLBACK_INDEX_URL` are optional
Gitea repository variables. Defaults are the TUNA HTTPS PyPI simple endpoint
and official `https://pypi.org/simple`. Each is attempted separately; the
fallback repeats the unchanged editable `.[test]` installation, including
PEP 517 build isolation and `setuptools>=75`. No simultaneous extra index is
used, no build dependency is removed, and both failures fail CI.
Logs show inherited index/config/proxy hosts and the selected index host for
each attempt, with URL credentials and query strings removed. Bootstrap
disables inherited pip config files via `PIP_CONFIG_FILE=/dev/null`, explicitly
sets the selected index, and clears no-index/extra-index/find-links, insecure
trusted-host, and no-build-isolation environment settings. Constraints, TLS
certificate environment settings, and proxy settings remain in effect.
These `CI_PIP_*` settings apply only to the Job's pip install. They are not
passed into Docker build arguments or persisted into the production image.
The Dockerfile keeps its own cached/offline dependency installation and logs
the Docker pip index host for online installs. Debian mirror variables affect
APT only and are loaded later in the Docker verification step.
If dependency installation fails, the always() cleanup step still executes.
It skips the Python/PyYAML cleanup script when no CI Compose manifest exists,
as no Docker verification resources have been created at that point.
Production CD: [safe trial, configuration, and rollback](docs/deployment/GITEA_CD.md).
+93
View File
@@ -0,0 +1,93 @@
# Pigway Frame Flow
简体中文 | [English](README.md)
轻量、自托管的智能画框自动化平台,可独立运行在 NAS 或服务器上,提供自己的 Web 界面、图库、图片渲染、调度器和 REST API。KoKonna 是支持的设备集成之一;外部应用无需持有它的凭据或引入其 SDK。
### 安装
在 AMD64 主机上使用 Docker Compose:
```bash
cp .env.example .env
# 设置强 FRAME_FLOW_TOKEN;首次保持 mock=true、writes=false。
chmod 600 .env
mkdir -p data/images data/generated data/cache config
sudo chown -R 10333:10333 data config
./scripts/build-verify.sh
# 创建网页管理员账号,密码通过交互输入。
docker compose run --rm --no-deps -e PYTHONPATH=/app \
-v "$PWD/scripts:/bootstrap:ro" --entrypoint python pigway-frame-flow \
/bootstrap/create-account.py --username admin --config-dir /config
docker compose up -d
```
打开 `http://127.0.0.1:18787`,使用刚创建的账号登录。网页使用 HttpOnly 会话 Cookie,无需输入 API Token;勾选记住登录后会话有效期为 30 天,可在设置中修改密码或退出。`FRAME_FLOW_TOKEN` 用于外部 API 客户端鉴权,与 KoKonna API Key 分开。
默认端口只绑定本机回环地址。需要局域网访问时,显式将 `WEB_BIND` 设置为 NAS 的局域网地址,并通过 `WEB_PORT` 选择空闲端口。参见[群晖部署](docs/NAS.md)、[构建镜像源与缓存](docs/BUILDING.md)和 [Gitea 部署与回滚](docs/deployment/GITEA_CD.md)。不会自动修改防火墙、路由器或反向代理。
使用 Python 3.14 运行本地 Mock 演示:
```bash
python3.14 -m venv .venv
.venv/bin/python -m pip install -r requirements.lock
.venv/bin/python scripts/create-account.py --username admin
.venv/bin/python scripts/run_local.py
```
演示强制使用 Mock,并关闭设备写入。数据与配置保存在 `data/`、`config/`,重启后保留。
### 功能与边界
- 中英文 Web 界面、深浅色主题、账号登录、设备面板与图片预览。
- 本地暂存和 KoKonna 云端图库,上传、切换、星标、来源/任务筛选及批量操作。
- 保持比例的 contain/cover/smart 图片转换,彩色/灰度模式与照片数据源的横竖方向选择。
- 七种模板:家庭早报、天气、日历、一句话、照片与文字、每日诗词、系统告警。
- 拼图工作室支持 1–12 张图片的拼图与海报;可选 OpenAI 兼容接口排版,以及从数据源自动选图拼接。
- 绑定设备的定时任务、顺序/随机播放列表、带优先级的临时覆盖、恢复与持久化操作历史。
- 只读 NAS 文件夹、Synology Photos 人物筛选、WebDAV 和可选网盘连接器;每个数据源独立配置、独立授权。
- 可选天气、日历、监控、AI、MQTT 与 Webhook 集成。需要配置对应 Provider;尚未授权的数据源不会被当作已连接。
模板会**生成图片**,纯文字模板也是如此。使用顺序是:选择模板 → 编辑内容 → 生成预览 → 显示到画框。生成预览和保存模板设置都不会更新画框。“照片与文字”和“家庭早报”可以包含指定的本地照片,其他模板不会自动选图;定时模板任务每次运行会重新渲染。参见[照片数据源](docs/PHOTO_SOURCES.md)、[自动拼图](docs/AUTOMATIC_COLLAGE.md)和[集成配置及限制](docs/EXTENSIONS.md)。
天气使用配置的服务和地点,在渲染时更新,不会自行刷新已显示的图片。KoKonna 官方小组件与 Frame Flow 模板是两项独立能力;使用官方小组件需要额外授权厂商账号,API Key 接入仍可单独使用。
### 设备集成与 API
```text
Provider → 模板 / Renderer → 生成图片
Web UI / Scheduler / REST API → DisplayService → FrameDeviceDriver → KoKonna
↓
SQLite:任务、操作、历史、覆盖状态、图片
```
设备 Key 通过私有 `.env` 或 Secret 文件配置。首次保持 `KOKONNA_MOCK=true`、`KOKONNA_WRITE_ENABLED=false`,Mock 验证后先只读连接,再明确开启写入。多设备使用 `config/devices.yml` 配置,每台设备引用自己的 Key。厂商代码位于 `drivers/kokonna`;Web 界面与外部程序使用相同业务服务。
正式 API 为 `/api/v1`,OpenAPI 位于 `/openapi.json`,Swagger 位于 `/docs`。显示、渲染、上传返回 HTTP 202 和操作 ID,通过 `/api/v1/operations/{id}` 查询结果。`Idempotency-Key` 防止重复派发,`X-Request-ID` 串联请求与历史。写入结果不确定时会阻止后续修改,必须只读核对或由管理员明确确认,不会自动重放写请求。
Ticktar 集成为可选项,Frame Flow 不依赖 Ticktar 启动,也不访问其数据库。参见[外部集成契约](docs/integrations/TICKTAR.md)。
### 存储与安全
Docker 持久化 `/data/frameflow.db`、`/data` 下的图片/生成文件/缓存,以及 `/config` 下的私有配置和会话文件。容器以 UID/GID 10333 运行,根文件系统只读,使用独立桥接网络、`unless-stopped` 重启策略和有大小限制的标准输出日志;不需要 privileged、host network 或 Docker socket。
账号密码保存为哈希,网页修改操作有 CSRF 校验。API Key 和 Provider 凭据仅留在服务端,不得提交 Git 或写入日志。CORS 默认不开启,需明确配置来源。`/health` 检查服务自身,`/ready` 检查本地依赖,不以云端可用性触发重启。参见[公开仓库隐私](docs/PUBLICATION.md)、[LICENSE](LICENSE) 和 [NOTICE](NOTICE.md)。
KoKonna 是支持的设备集成。本项目与 KoKonna 无隶属关系,也不代表其官方认可。
### 开发与已知限制
```bash
.venv/bin/python -m pip install -e '.[test]'
.venv/bin/python -m ruff check pigway_frame_flow tests scripts
.venv/bin/python -m ruff format --check pigway_frame_flow tests scripts
.venv/bin/python -m mypy pigway_frame_flow
.venv/bin/python -m pytest -q
.venv/bin/python -m pip_audit -r requirements.lock --no-deps --disable-pip
```
测试使用 Mock 云端/设备调用;CI 还验证隔离的 Docker 构建、就绪检查、smoke test、重启与清理。外部 Fork PR 不会在可访问生产环境的 NAS Runner 上执行。
云端同步不等于已确认物理屏幕刷新。KoKonna 云端上传会立即切换,因此“仅上传”使用本地暂存。官方接口不支持编辑原生轮播成员,应避免原生轮播与 Frame Flow 播放同时运行。smart 裁切采用显著性启发式,不保证识别人脸。当前服务使用单 Worker,不面向多副本部署。浏览器 OAuth、AI 每日艺术调度、更多日历重复规则及设备驱动仍属于后续计划。
相关项目:[PIGWay Device Agent](https://tea.pigway.com/way/pigway-device-agent) · [PIGWay Device Console](https://tea.pigway.com/way/pigway-device-console) · [PIGWay Yahboom Cooling HAT](https://tea.pigway.com/way/pigway-yahboom-cooling-hat)
+103
View File
@@ -0,0 +1,103 @@
# Build networking, cache and CI
### Build networking and cache
The Dockerfile retains the pinned Python base, locked Python dependencies,
`fonts-noto-cjk`, Debian repository signature checks, and offline artifact
SHA256 verification. Production uses `scripts/build-verify.sh`; CI uses `scripts/ci-compose-verify.py`.
Both build the same Dockerfile with BuildKit and retain `docker compose build --pull`.
APT mirror hosts are configurable through `DEBIAN_MIRROR` and
`DEBIAN_SECURITY_MIRROR` (hosts only, without scheme or path). Defaults are
`deb.debian.org`; HTTPS is used for both Debian and security repositories.
For mainland-China NAS builds, use the shared profile:
```sh
. ./deploy/build-cn.env
./scripts/build-verify.sh
```
CI loads this same profile, using Tsinghua TUNA for both repositories unless
Gitea repository variables `DEBIAN_MIRROR` / `DEBIAN_SECURITY_MIRROR` override
it. Production may also set the two hosts directly in `.env` when calling the
build script without the profile. An alternate mainland mirror is
`mirrors.ustc.edu.cn`; availability and throughput depend on the Runner network.
No fallback disables signature verification or removes packages.
Layer order stays: pinned base → APT/font dependencies → `requirements.lock`
→ pip dependencies → application/vendor source → build metadata.
A source-only change therefore reuses the APT and pip layers, provided the
same builder, base digest, mirror arguments, and optional offline artifacts
are used. Changing the lockfile rebuilds pip and later layers, not APT.
Changing the mirror or APT instructions rebuilds APT and later layers.
Previously an APT layer miss redownloaded indexes/packages, and pip used
`--no-cache-dir`. Now locked BuildKit mounts retain APT indexes, downloaded
`.deb` files, and pip downloads/wheels outside the final image. Debian's
`docker-clean` hook is disabled so it cannot discard cached packages.
Re-executed APT steps still run `apt-get update` and validate repository
signatures; cached packages and pip artifacts can avoid repeat large downloads.
The initial build after this Dockerfile change will miss dependency layers;
subsequent builds benefit. No measured speedup is claimed before CI completes.
Caches belong to the Docker builder, not the ephemeral CI workspace. Keep the
NAS Runner's builder storage across jobs; removing the builder, pruning caches,
or using a fresh Docker daemon loses the benefit. Cache mounts are disposable:
an empty cache still builds correctly. `--pull` remains enabled; a pinned base
digest check alone does not invalidate dependency layers. To deliberately
refresh installed APT versions, use `docker compose build --pull --no-cache`.
### CI with a host Docker socket
Run `python scripts/ci-compose-verify.py` after loading the build mirror profile.
The script derives a temporary Compose document from production settings, with
an absolute client-side build context, a UUID project name, a unique CI image
tag, and project-scoped `data` / `config` named volumes. It removes production
bind mounts, the fixed container name, host ports, and the production `.env`
reference. A fresh test token, mock driver, and disabled device writes are
explicitly configured. Docker initializes empty named volumes from the image's
owned `/data` and `/config` directories; the service remains UID/GID 10333.
Build contexts are sent by the Docker client, so the job workspace can remain
inside the job container. Runtime storage is managed by the daemon; no matching
NAS host `/workspace` directory is needed. HTTP readiness and the unmodified
nine-endpoint smoke script run inside the service with `compose exec -T`.
Restart is followed by another readiness check and the same smoke test.
A `finally` block removes the test containers, network, volumes, and unique
image tag on success or failure. The workflow records the private temporary
Compose path in `GITHUB_ENV` and repeats cleanup in an `always()` step, covering
normal cancellation/interruption. Failed cleanup retains the manifest so the
fallback can retry. Abrupt host/daemon loss or a forcibly terminated entire job
can prevent both cleanup paths; no CI script can guarantee cleanup in that case.
No global prune or production resource removal is performed. Keep builder
caches: only the run-specific image tag is removed.
### CI Job pip indexes versus Docker build indexes
The Job bootstrap runs `python scripts/ci-install.py` before Ruff/mypy/tests.
It needs only the Python standard library, so it runs before PyYAML or tooling
is installed. `CI_PIP_INDEX_URL` and `CI_PIP_FALLBACK_INDEX_URL` are optional
Gitea repository variables. Defaults are the TUNA HTTPS PyPI simple endpoint
and official `https://pypi.org/simple`. Each is attempted separately; the
fallback repeats the unchanged editable `.[test]` installation, including
PEP 517 build isolation and `setuptools>=75`. No simultaneous extra index is
used, no build dependency is removed, and both failures fail CI.
Logs show inherited index/config/proxy hosts and the selected index host for
each attempt, with URL credentials and query strings removed. Bootstrap
disables inherited pip config files via `PIP_CONFIG_FILE=/dev/null`, explicitly
sets the selected index, and clears no-index/extra-index/find-links, insecure
trusted-host, and no-build-isolation environment settings. Constraints, TLS
certificate environment settings, and proxy settings remain in effect.
These `CI_PIP_*` settings apply only to the Job's pip install. They are not
passed into Docker build arguments or persisted into the production image.
The Dockerfile keeps its own cached/offline dependency installation and logs
the Docker pip index host for online installs. Debian mirror variables affect
APT only and are loaded later in the Docker verification step.
If dependency installation fails, the always() cleanup step still executes.
It skips the Python/PyYAML cleanup script when no CI Compose manifest exists,
as no Docker verification resources have been created at that point.