elenvind 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- elenvind-1.0.1/.github/workflows/ci.yml +130 -0
- elenvind-1.0.1/.github/workflows/publish.yml +127 -0
- elenvind-1.0.1/LICENSE +674 -0
- elenvind-1.0.1/MANIFEST.in +32 -0
- elenvind-1.0.1/PKG-INFO +246 -0
- elenvind-1.0.1/README.md +200 -0
- elenvind-1.0.1/elenvind/__init__.py +34 -0
- elenvind-1.0.1/elenvind/__main__.py +10 -0
- elenvind-1.0.1/elenvind/app.py +188 -0
- elenvind-1.0.1/elenvind/cli.py +538 -0
- elenvind-1.0.1/elenvind/core/__init__.py +21 -0
- elenvind-1.0.1/elenvind/core/app.py +223 -0
- elenvind-1.0.1/elenvind/core/auth.py +137 -0
- elenvind-1.0.1/elenvind/core/config.py +426 -0
- elenvind-1.0.1/elenvind/core/console.py +40 -0
- elenvind-1.0.1/elenvind/core/content.py +172 -0
- elenvind-1.0.1/elenvind/core/context.py +170 -0
- elenvind-1.0.1/elenvind/core/csrf.py +72 -0
- elenvind-1.0.1/elenvind/core/http.py +707 -0
- elenvind-1.0.1/elenvind/core/i18n.py +90 -0
- elenvind-1.0.1/elenvind/core/lifespan.py +114 -0
- elenvind-1.0.1/elenvind/core/logging_config.py +121 -0
- elenvind-1.0.1/elenvind/core/markdown.py +545 -0
- elenvind-1.0.1/elenvind/core/routing.py +345 -0
- elenvind-1.0.1/elenvind/core/security.py +554 -0
- elenvind-1.0.1/elenvind/core/session.py +129 -0
- elenvind-1.0.1/elenvind/core/templating.py +178 -0
- elenvind-1.0.1/elenvind/core/utils.py +271 -0
- elenvind-1.0.1/elenvind/core/version.py +75 -0
- elenvind-1.0.1/elenvind/database/__init__.py +172 -0
- elenvind-1.0.1/elenvind/database/auth.py +202 -0
- elenvind-1.0.1/elenvind/database/backup.py +108 -0
- elenvind-1.0.1/elenvind/database/comment.py +193 -0
- elenvind-1.0.1/elenvind/database/comment_rate.py +116 -0
- elenvind-1.0.1/elenvind/database/connection.py +180 -0
- elenvind-1.0.1/elenvind/database/maintenance.py +101 -0
- elenvind-1.0.1/elenvind/database/migration.py +509 -0
- elenvind-1.0.1/elenvind/database/session.py +214 -0
- elenvind-1.0.1/elenvind/database/transaction.py +111 -0
- elenvind-1.0.1/elenvind/database/user.py +177 -0
- elenvind-1.0.1/elenvind/i18n/en.toml +150 -0
- elenvind-1.0.1/elenvind/i18n/ja.toml +150 -0
- elenvind-1.0.1/elenvind/i18n/zh.toml +150 -0
- elenvind-1.0.1/elenvind/launcher.py +280 -0
- elenvind-1.0.1/elenvind/modules/__init__.py +27 -0
- elenvind-1.0.1/elenvind/modules/admin/__init__.py +6 -0
- elenvind-1.0.1/elenvind/modules/admin/routes.py +29 -0
- elenvind-1.0.1/elenvind/modules/auth/__init__.py +5 -0
- elenvind-1.0.1/elenvind/modules/auth/routes.py +345 -0
- elenvind-1.0.1/elenvind/modules/blog/__init__.py +9 -0
- elenvind-1.0.1/elenvind/modules/blog/logic.py +500 -0
- elenvind-1.0.1/elenvind/modules/blog/routes.py +263 -0
- elenvind-1.0.1/elenvind/modules/pages/__init__.py +10 -0
- elenvind-1.0.1/elenvind/modules/pages/logic.py +139 -0
- elenvind-1.0.1/elenvind/modules/pages/routes.py +46 -0
- elenvind-1.0.1/elenvind/modules/seo/__init__.py +7 -0
- elenvind-1.0.1/elenvind/modules/seo/routes.py +69 -0
- elenvind-1.0.1/elenvind/modules/system/__init__.py +10 -0
- elenvind-1.0.1/elenvind/modules/system/routes.py +57 -0
- elenvind-1.0.1/elenvind/modules/users/__init__.py +5 -0
- elenvind-1.0.1/elenvind/modules/users/routes.py +231 -0
- elenvind-1.0.1/elenvind/resources/config.example.toml +249 -0
- elenvind-1.0.1/elenvind/resources/elenvind.service +79 -0
- elenvind-1.0.1/elenvind/reverse_proxy.py +735 -0
- elenvind-1.0.1/elenvind/scaffold.py +81 -0
- elenvind-1.0.1/elenvind/templates/admin/index.html +29 -0
- elenvind-1.0.1/elenvind/templates/article.html +29 -0
- elenvind-1.0.1/elenvind/templates/auth/login.html +30 -0
- elenvind-1.0.1/elenvind/templates/auth/logout.html +19 -0
- elenvind-1.0.1/elenvind/templates/auth/register.html +41 -0
- elenvind-1.0.1/elenvind/templates/base.html +26 -0
- elenvind-1.0.1/elenvind/templates/errors/400.html +6 -0
- elenvind-1.0.1/elenvind/templates/errors/404.html +6 -0
- elenvind-1.0.1/elenvind/templates/errors/error.html +6 -0
- elenvind-1.0.1/elenvind/templates/home.html +81 -0
- elenvind-1.0.1/elenvind/templates/page.html +8 -0
- elenvind-1.0.1/elenvind/templates/partials/comments.html +71 -0
- elenvind-1.0.1/elenvind/templates/partials/footer.html +15 -0
- elenvind-1.0.1/elenvind/templates/partials/form_message.html +10 -0
- elenvind-1.0.1/elenvind/templates/partials/navbar.html +38 -0
- elenvind-1.0.1/elenvind/templates/users/profile.html +115 -0
- elenvind-1.0.1/elenvind/wsgi.py +37 -0
- elenvind-1.0.1/elenvind.egg-info/SOURCES.txt +111 -0
- elenvind-1.0.1/manual/CLI.md +194 -0
- elenvind-1.0.1/manual/CONFIGURATION.md +206 -0
- elenvind-1.0.1/manual/HOSTING.md +286 -0
- elenvind-1.0.1/manual/OPERATIONS.md +159 -0
- elenvind-1.0.1/manual/README.md +43 -0
- elenvind-1.0.1/manual/RELEASE.md +142 -0
- elenvind-1.0.1/manual/SECURITY.md +176 -0
- elenvind-1.0.1/manual/elenvind.service +79 -0
- elenvind-1.0.1/manual/logrotate.conf +26 -0
- elenvind-1.0.1/manual/nginx.conf.example +94 -0
- elenvind-1.0.1/manual/style.example.css +1065 -0
- elenvind-1.0.1/pyproject.toml +115 -0
- elenvind-1.0.1/requirements.txt +8 -0
- elenvind-1.0.1/run.py +80 -0
- elenvind-1.0.1/setup.cfg +4 -0
- elenvind-1.0.1/tests/__init__.py +10 -0
- elenvind-1.0.1/tests/support.py +351 -0
- elenvind-1.0.1/tests/test_architecture.py +199 -0
- elenvind-1.0.1/tests/test_auth_lockout.py +238 -0
- elenvind-1.0.1/tests/test_boundaries.py +157 -0
- elenvind-1.0.1/tests/test_ci.py +124 -0
- elenvind-1.0.1/tests/test_cli.py +427 -0
- elenvind-1.0.1/tests/test_comments.py +151 -0
- elenvind-1.0.1/tests/test_concurrency.py +116 -0
- elenvind-1.0.1/tests/test_core_contract.py +237 -0
- elenvind-1.0.1/tests/test_doc_consistency.py +307 -0
- elenvind-1.0.1/tests/test_logging_proxy.py +155 -0
- elenvind-1.0.1/tests/test_operations.py +573 -0
- elenvind-1.0.1/tests/test_packaging.py +187 -0
- elenvind-1.0.1/tests/test_proxy_config.py +329 -0
- elenvind-1.0.1/tests/test_styles.py +81 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# CI:静态检查 + 全量测试 + 启动冒烟。
|
|
2
|
+
#
|
|
3
|
+
# 只跑 Linux:应用只支持提供 `fcntl.flock` 的 POSIX 系统(跨进程写互斥靠文件锁),
|
|
4
|
+
# 因此在 Windows / macOS runner 上跑测试只会得到"平台不支持"的噪音。
|
|
5
|
+
#
|
|
6
|
+
# 主仓库在 Codeberg;这里同时服务于镜像仓库与发布流水线(见 manual/RELEASE.md)。
|
|
7
|
+
|
|
8
|
+
name: ci
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [main, master, trunk]
|
|
13
|
+
pull_request:
|
|
14
|
+
workflow_dispatch:
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ci-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
test:
|
|
25
|
+
name: test (Python ${{ matrix.python }})
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
strategy:
|
|
28
|
+
fail-fast: false
|
|
29
|
+
matrix:
|
|
30
|
+
python: ["3.11", "3.12", "3.13", "3.14"]
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python }}
|
|
37
|
+
|
|
38
|
+
- name: 安装依赖(含开发工具)
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install --upgrade pip
|
|
41
|
+
pip install -r requirements.txt
|
|
42
|
+
pip install ruff trove-classifiers
|
|
43
|
+
|
|
44
|
+
- name: 安装 nginx(官方源,验证目标环境的 HTTP/2 写法)
|
|
45
|
+
run: |
|
|
46
|
+
set -euo pipefail
|
|
47
|
+
# runner 自带的是发行版 nginx(Ubuntu 24.04 = 1.24,不认识 `http2 on;`),
|
|
48
|
+
# 先移除再从官方 mainline 源装,让 CI 与真实生产环境一致。
|
|
49
|
+
sudo apt-get remove -y --purge nginx nginx-common nginx-core || true
|
|
50
|
+
sudo systemctl mask nginx 2>/dev/null || true
|
|
51
|
+
curl -fsSL https://nginx.org/keys/nginx_signing.key \
|
|
52
|
+
| sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
|
|
53
|
+
. /etc/os-release
|
|
54
|
+
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu ${UBUNTU_CODENAME:-$VERSION_CODENAME} nginx" \
|
|
55
|
+
| sudo tee /etc/apt/sources.list.d/nginx.list >/dev/null
|
|
56
|
+
sudo apt-get update -qq
|
|
57
|
+
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq nginx
|
|
58
|
+
|
|
59
|
+
- name: 确认 nginx 版本(必须支持 `http2 on;`)
|
|
60
|
+
run: |
|
|
61
|
+
python3 - <<'PY'
|
|
62
|
+
import subprocess
|
|
63
|
+
|
|
64
|
+
result = subprocess.run(["nginx", "-v"], capture_output=True, text=True)
|
|
65
|
+
version = (result.stderr + result.stdout).split("nginx/")[1].split()[0]
|
|
66
|
+
parts = tuple(int(part) for part in version.split("."))
|
|
67
|
+
print("nginx", version)
|
|
68
|
+
assert parts >= (1, 25, 1), (
|
|
69
|
+
f"CI 的 nginx 太旧:{version};本项目的生成配置面向支持 `http2 on;` 的环境,"
|
|
70
|
+
"请确认上面那一步从 nginx.org 安装成功")
|
|
71
|
+
PY
|
|
72
|
+
|
|
73
|
+
- name: 静态检查
|
|
74
|
+
run: ruff check .
|
|
75
|
+
|
|
76
|
+
- name: 全量测试
|
|
77
|
+
run: python -m unittest discover -s tests -t . -v
|
|
78
|
+
|
|
79
|
+
- name: 打包元数据可构建
|
|
80
|
+
run: |
|
|
81
|
+
pip install build
|
|
82
|
+
python -m build
|
|
83
|
+
python - <<'PY'
|
|
84
|
+
import glob, tarfile, zipfile
|
|
85
|
+
|
|
86
|
+
wheel = glob.glob("dist/*.whl")[0]
|
|
87
|
+
sdist = glob.glob("dist/*.tar.gz")[0]
|
|
88
|
+
wheel_names = zipfile.ZipFile(wheel).namelist()
|
|
89
|
+
with tarfile.open(sdist) as archive:
|
|
90
|
+
sdist_names = archive.getnames()
|
|
91
|
+
|
|
92
|
+
leaked = [n for n in wheel_names
|
|
93
|
+
if n.endswith("config.toml") or "/articles/" in n
|
|
94
|
+
or "/custom/" in n or "/data/" in n]
|
|
95
|
+
assert not leaked, f"wheel 里混进了站点实例数据:{leaked}"
|
|
96
|
+
|
|
97
|
+
leaked = [n for n in sdist_names
|
|
98
|
+
if n.endswith("/config.toml") or "/articles/" in n
|
|
99
|
+
or "/custom/" in n or "/data/" in n or "/logs/" in n
|
|
100
|
+
or "/backups/" in n]
|
|
101
|
+
assert not leaked, f"sdist 里混进了站点实例数据:{leaked}"
|
|
102
|
+
|
|
103
|
+
for need in ("elenvind/templates/base.html", "elenvind/i18n/en.toml",
|
|
104
|
+
"elenvind/resources/config.example.toml"):
|
|
105
|
+
assert any(n.endswith(need) for n in wheel_names), f"wheel 缺少 {need}"
|
|
106
|
+
print("产物检查通过:无站点数据,包数据齐全")
|
|
107
|
+
PY
|
|
108
|
+
|
|
109
|
+
- name: 启动冒烟(console script + 只监听回环)
|
|
110
|
+
run: |
|
|
111
|
+
set -e
|
|
112
|
+
pip install -e .
|
|
113
|
+
WORK="$RUNNER_TEMP/instance"
|
|
114
|
+
mkdir -p "$WORK"/{data,logs,backups,articles,custom}
|
|
115
|
+
cp elenvind/resources/config.example.toml "$WORK/config.toml"
|
|
116
|
+
cd "$WORK"
|
|
117
|
+
nohup elenvind serve --bind 127.0.0.1:6789 --yes > serve.log 2>&1 &
|
|
118
|
+
for _ in $(seq 1 40); do
|
|
119
|
+
curl -sf -o /dev/null http://127.0.0.1:6789/ && break
|
|
120
|
+
sleep 0.5
|
|
121
|
+
done
|
|
122
|
+
for path in / /login /robots.txt /sitemap.xml; do
|
|
123
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:6789$path")"
|
|
124
|
+
echo "$path -> $code"
|
|
125
|
+
[ "$code" = "200" ]
|
|
126
|
+
done
|
|
127
|
+
# 静态路径必须 404:本服务不发出任何静态文件
|
|
128
|
+
[ "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:6789/css/style.css)" = "404" ]
|
|
129
|
+
grep -q "TLS is terminated by the reverse proxy" serve.log
|
|
130
|
+
echo "冒烟通过"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# 发布:打 tag 后自动构建、校验、发布到 PyPI,并在 GitHub 建 Release。
|
|
2
|
+
#
|
|
3
|
+
# 认证用 PyPI 的 **Trusted Publishing**(OIDC):不需要在仓库里存任何 token。
|
|
4
|
+
# 首次发布前必须在 PyPI 上登记 pending trusted publisher(见 manual/RELEASE.md):
|
|
5
|
+
# Owner: ortzikantu Repository: elenvind
|
|
6
|
+
# Workflow: publish.yml Environment: pypi
|
|
7
|
+
#
|
|
8
|
+
# 触发方式是**只认 tag**(如 `v1.0.0`):tag 名去掉前缀必须与 pyproject.toml 的
|
|
9
|
+
# version 完全一致,否则第一步就失败 —— 避免"发出去的包和 tag 对不上"。
|
|
10
|
+
|
|
11
|
+
name: publish
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
tags:
|
|
16
|
+
- "v[0-9]+.[0-9]+.[0-9]+"
|
|
17
|
+
- "v[0-9]+.[0-9]+.[0-9]+*" # 预发布:v1.0.1rc1 / v1.1.0.dev1 …
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
verify:
|
|
24
|
+
name: 校验版本与产物
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- uses: actions/setup-python@v5
|
|
30
|
+
with:
|
|
31
|
+
python-version: "3.12"
|
|
32
|
+
|
|
33
|
+
- name: 安装依赖
|
|
34
|
+
run: |
|
|
35
|
+
python -m pip install --upgrade pip
|
|
36
|
+
pip install -r requirements.txt
|
|
37
|
+
pip install ruff build trove-classifiers
|
|
38
|
+
|
|
39
|
+
- name: tag 必须与 pyproject 的版本一致
|
|
40
|
+
run: |
|
|
41
|
+
python - <<'PY'
|
|
42
|
+
import os, pathlib, tomllib
|
|
43
|
+
|
|
44
|
+
version = tomllib.loads(
|
|
45
|
+
pathlib.Path("pyproject.toml").read_text(encoding="utf-8")
|
|
46
|
+
)["project"]["version"]
|
|
47
|
+
tag = os.environ["GITHUB_REF_NAME"].lstrip("v")
|
|
48
|
+
assert tag == version, f"tag {tag!r} 与 pyproject 的 version {version!r} 不一致"
|
|
49
|
+
print(f"版本一致:{version}")
|
|
50
|
+
PY
|
|
51
|
+
|
|
52
|
+
- name: 静态检查与全量测试
|
|
53
|
+
run: |
|
|
54
|
+
ruff check .
|
|
55
|
+
python -m unittest discover -s tests -t . -v
|
|
56
|
+
|
|
57
|
+
- name: 构建
|
|
58
|
+
run: |
|
|
59
|
+
rm -rf dist build ./*.egg-info
|
|
60
|
+
python -m build
|
|
61
|
+
ls -l dist/
|
|
62
|
+
|
|
63
|
+
- name: 产物自检(元数据 + 不含站点实例数据)
|
|
64
|
+
run: |
|
|
65
|
+
set -e
|
|
66
|
+
pip install twine
|
|
67
|
+
twine check dist/*
|
|
68
|
+
python - <<'PY'
|
|
69
|
+
import glob, tarfile, zipfile
|
|
70
|
+
|
|
71
|
+
wheel = glob.glob("dist/*.whl")[0]
|
|
72
|
+
sdist = glob.glob("dist/*.tar.gz")[0]
|
|
73
|
+
wheel_names = zipfile.ZipFile(wheel).namelist()
|
|
74
|
+
with tarfile.open(sdist) as archive:
|
|
75
|
+
sdist_names = archive.getnames()
|
|
76
|
+
|
|
77
|
+
for names, label in ((wheel_names, "wheel"), (sdist_names, "sdist")):
|
|
78
|
+
leaked = [n for n in names
|
|
79
|
+
if n.endswith("config.toml") or "/articles/" in n
|
|
80
|
+
or "/custom/" in n or "/data/" in n]
|
|
81
|
+
assert not leaked, f"{label} 混进了站点实例数据:{leaked}"
|
|
82
|
+
print("产物检查通过")
|
|
83
|
+
PY
|
|
84
|
+
|
|
85
|
+
- uses: actions/upload-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: dist
|
|
88
|
+
path: dist/
|
|
89
|
+
if-no-files-found: error
|
|
90
|
+
|
|
91
|
+
publish:
|
|
92
|
+
name: 发布到 PyPI
|
|
93
|
+
needs: verify
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
environment: pypi # 与 PyPI 上登记的 environment 一致
|
|
96
|
+
permissions:
|
|
97
|
+
id-token: write # Trusted Publishing 必需;不需要任何 secret
|
|
98
|
+
steps:
|
|
99
|
+
- uses: actions/download-artifact@v4
|
|
100
|
+
with:
|
|
101
|
+
name: dist
|
|
102
|
+
path: dist/
|
|
103
|
+
|
|
104
|
+
- name: 发布
|
|
105
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
106
|
+
# 默认目标 https://upload.pypi.org/legacy/,凭据由 OIDC 换取。
|
|
107
|
+
|
|
108
|
+
release:
|
|
109
|
+
name: 建 GitHub Release
|
|
110
|
+
needs: publish
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
permissions:
|
|
113
|
+
contents: write # 建 Release 需要
|
|
114
|
+
steps:
|
|
115
|
+
- uses: actions/download-artifact@v4
|
|
116
|
+
with:
|
|
117
|
+
name: dist
|
|
118
|
+
path: dist/
|
|
119
|
+
|
|
120
|
+
- name: 创建 Release 并附上产物
|
|
121
|
+
env:
|
|
122
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
123
|
+
run: |
|
|
124
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
125
|
+
--repo "$GITHUB_REPOSITORY" \
|
|
126
|
+
--title "$GITHUB_REF_NAME" \
|
|
127
|
+
--generate-notes
|