fix: harden news analysis and deploy verified image tags
Development CI / check-and-build (push) Successful in 2m4s
GitHub Git mirror / mirror (push) Successful in 6s
Development CI / deploy-development (push) Successful in 2m30s

This commit is contained in:
eezd committed 2026-11-02 15:37:42 +08:00
1 parent 3b7aec0024
commit adb73f956b
27 files changed
+840 -147

No files matched your search

+4 -2
View File
@@ -11,11 +11,13 @@ Only a `dev` push publishes the accepted images:
- `git.134333.xyz/eezd/dev/backend/alphapulse:YYYYMMDD-xxxxxxxx`
- `git.134333.xyz/eezd/dev/web/alphapulse:YYYYMMDD-xxxxxxxx`
Deployment, migration commands, Compose interpolation and CI outputs use these version tags. Accepted digests remain internal receipt metadata: pulls and startup verify the tag's content, full commit and version before use; an overwritten tag fails validation. SearXNG uses `docker.io/searxng/searxng:2026.10.30-ffa88e3ba`, with its accepted content checked internally as well. Historical digest-only application receipts select the corresponding version tag without discarding their identity checks. Recovery converts the known historical SearXNG digest to its verified tag and rejects unknown digest references.
The date comes from the commit's committer timestamp in UTC, and the suffix is the first eight characters of its full SHA. Existing versions are immutable. Images are public on Gitea only; private configuration and data are excluded. `/api/health` and `/version.json` identify the deployed API and web revision. `build.json` is the CI receipt, not a tracked source file.
## The single data-bearing instance
`/docker/gitea/dev/AlphaPulse` contains the runtime Compose configuration, `.env`, digest selection, deployment receipts and `.deploy.lock`. The repository-root `.env` points to that sole private configuration. The Compose project is `alphapulse-dev`; `AlphaPulse-dev` is the web container, and the other application containers append their service name (`-api`, `-crawler`, `-market`, `-calendar`, `-trader`, or the new `-news-risk`).
`/docker/gitea/dev/AlphaPulse` contains the runtime Compose configuration, `.env`, version-tag selection, deployment receipts (which retain accepted digests for identity checks) and `.deploy.lock`. The repository-root `.env` points to that sole private configuration. The Compose project is `alphapulse-dev`; `AlphaPulse-dev` is the web container, and the other application containers append their service name (`-api`, `-crawler`, `-market`, `-calendar`, `-trader`, or the new `-news-risk`).
This project's DEV intentionally uses the existing database, Redis DB 14 (`ap:*` only), model credentials and Demo trading configuration. The user authorized restoring all six services. It is the only instance, served at `https://alpha.134333.xyz` through `127.0.0.1:21005`. CI never uses these data for testing. Do not start a second Compose project against this configuration.
@@ -105,7 +107,7 @@ The subsequent `d4e6f8012345` migration adds events, membership and cross-event
### Search and subscriptions
Compose includes a digest-pinned SearXNG service on the existing internal Docker network, with no host port. Deployment creates its instance-local settings from `deploy/searxng.yml`, generates its server secret once, and checks `/healthz` through the API container. Pulls, service changes and recovery continue through `scripts/deploy.py` and `.deploy.lock`. `SEARXNG_URL` can select a different internal endpoint; its default is `http://searxng:8080`. External search availability depends on upstream engines and is reported as evidence limitations. Web reads reject private/mixed DNS answers, pin the checked address, recheck redirects and bound decoded response sizes. No external research response is inserted into the original news stream.
Compose includes a version-tagged SearXNG service on the existing internal Docker network, with no host port. Deployment creates its instance-local settings from `deploy/searxng.yml`, generates its server secret once, and checks `/healthz` through the API container. Pulls, service changes and recovery continue through `scripts/deploy.py` and `.deploy.lock`. `SEARXNG_URL` can select a different internal endpoint; its default is `http://searxng:8080`. External search availability depends on upstream engines and is reported as evidence limitations. Web reads reject private/mixed DNS answers, pin the checked address, recheck redirects and bound decoded response sizes. No external research response is inserted into the original news stream.
The crawler console accepts administrator-managed public RSS/Atom subscriptions, names, intervals and enable/disable controls. New subscriptions are paused, no default feeds are added, and the manager discovers new subscriptions without a restart. Only feed-provided content is stored; a summary is labeled as a summary. Arbitrary private-network feed URLs are rejected.
+3 -3
View File
@@ -2,7 +2,7 @@
name: alphapulse-dev
x-backend: &backend
image: ${ALPHAPULSE_BACKEND_IMAGE:?Set a verified backend digest}
image: ${ALPHAPULSE_BACKEND_IMAGE:?Set a verified backend version tag}
env_file: .env
restart: unless-stopped
stop_grace_period: 60s
@@ -10,7 +10,7 @@ x-backend: &backend
services:
searxng:
image: docker.io/searxng/searxng@sha256:f4aa660c54d062dedb268be84e821ea24e39f48c7a496af8dd02cc9eedb93847
image: docker.io/searxng/searxng:2026.10.30-ffa88e3ba
restart: unless-stopped
volumes: [./searxng.yml:/etc/searxng/settings.yml:ro]
networks: [dev_default]
@@ -55,7 +55,7 @@ services:
command: [python, -m, alphapulse.calendar.jin10_worker]
web:
image: ${ALPHAPULSE_WEB_IMAGE:?Set a verified web digest}
image: ${ALPHAPULSE_WEB_IMAGE:?Set a verified web version tag}
container_name: AlphaPulse-dev
restart: unless-stopped
depends_on:
+10 -4
View File
@@ -8,7 +8,8 @@ import re
import subprocess
SERVICES = ('api', 'web', 'market', 'crawler', 'calendar', 'trader', 'news-risk', 'jin10-calendar', 'searxng')
SEARCH_IMAGE = 'docker.io/searxng/searxng@sha256:f4aa660c54d062dedb268be84e821ea24e39f48c7a496af8dd02cc9eedb93847'
SEARCH_IMAGE = 'docker.io/searxng/searxng:2026.10.30-ffa88e3ba'
SEARCH_DIGEST = 'sha256:f4aa660c54d062dedb268be84e821ea24e39f48c7a496af8dd02cc9eedb93847'
def require(condition, message):
@@ -28,9 +29,9 @@ def start(directory, command, env):
require(build.get('verified') is True, '部署回执未通过验收')
require(re.fullmatch('[0-9a-f]{40}', build['commit']), '部署回执提交无效')
require(re.fullmatch(r'\d{8}-' + build['commit'][:8], build['version']), '部署回执版本不匹配')
images = {part: build['images'][part]['digest'] for part in ('backend', 'web')}
images = {part: 'git.134333.xyz/eezd/dev/' + part + '/alphapulse:' + build['version'] for part in ('backend', 'web')}
for part, image in images.items():
require(re.fullmatch(r'git\.134333\.xyz/eezd/dev/' + part + r'/alphapulse@sha256:[0-9a-f]{64}', image), '部署镜像摘要无效')
require(re.fullmatch(r'git\.134333\.xyz/eezd/dev/' + part + r'/alphapulse:\d{8}-[0-9a-f]{8}', image), '部署镜像版本标签无效')
# Inspect interpolation without reading service credentials into the controller.
config = json.loads(run([*command, 'config', '--format', 'json', '--no-env-resolution'], env))
require(config['name'] == 'alphapulse-dev' and set(config['services']) == set(SERVICES), 'Compose 实例或服务集合不匹配')
@@ -40,7 +41,12 @@ def start(directory, command, env):
for image in set(expected.values()):
info, = json.loads(run(['docker', 'image', 'inspect', image], env))
require(info['Architecture'] == 'amd64' and info['Os'] == 'linux', '本地镜像平台不匹配')
if image != SEARCH_IMAGE:
if image == SEARCH_IMAGE:
require(any(ref.rsplit('@', 1)[-1] == SEARCH_DIGEST for ref in info.get('RepoDigests', [])),
'本地搜索版本标签指向的镜像与验收摘要不一致')
else:
part = 'web' if image == images['web'] else 'backend'
require(build['images'][part]['digest'] in info.get('RepoDigests', []), '本地版本标签指向的镜像与验收摘要不一致')
labels = info['Config'].get('Labels') or {}
require(labels.get('org.opencontainers.image.revision') == build['commit'] and
labels.get('org.opencontainers.image.version') == build['version'], '本地镜像身份与部署回执不一致')
+3 -3
View File
@@ -10,12 +10,12 @@ if [ -e "$instance/deployment.pending.json" ]; then
exit 1
fi
if [ -z "${ALPHAPULSE_BACKEND_IMAGE:-}" ]; then
echo '请通过 ALPHAPULSE_BACKEND_IMAGE 明确指定已验收的候选镜像摘要;迁移须独立授权' >&2
echo '请通过 ALPHAPULSE_BACKEND_IMAGE 明确指定已验收的候选镜像版本标签;迁移须独立授权' >&2
exit 1
fi
case "$ALPHAPULSE_BACKEND_IMAGE" in
git.134333.xyz/eezd/dev/backend/alphapulse@sha256:*) ;;
*) echo '迁移必须使用开发镜像摘要' >&2; exit 1 ;;
git.134333.xyz/eezd/dev/backend/alphapulse:[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ;;
*) echo '迁移必须使用开发镜像版本标签' >&2; exit 1 ;;
esac
if [ -n "$(docker ps -q --filter label=com.docker.compose.project=alphapulse-dev)" ]; then
echo '迁移前须在维护窗口内停止应用服务;不要并发运行采集或交易' >&2