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.
Files changed (114) hide show
  1. elenvind-1.0.1/.github/workflows/ci.yml +130 -0
  2. elenvind-1.0.1/.github/workflows/publish.yml +127 -0
  3. elenvind-1.0.1/LICENSE +674 -0
  4. elenvind-1.0.1/MANIFEST.in +32 -0
  5. elenvind-1.0.1/PKG-INFO +246 -0
  6. elenvind-1.0.1/README.md +200 -0
  7. elenvind-1.0.1/elenvind/__init__.py +34 -0
  8. elenvind-1.0.1/elenvind/__main__.py +10 -0
  9. elenvind-1.0.1/elenvind/app.py +188 -0
  10. elenvind-1.0.1/elenvind/cli.py +538 -0
  11. elenvind-1.0.1/elenvind/core/__init__.py +21 -0
  12. elenvind-1.0.1/elenvind/core/app.py +223 -0
  13. elenvind-1.0.1/elenvind/core/auth.py +137 -0
  14. elenvind-1.0.1/elenvind/core/config.py +426 -0
  15. elenvind-1.0.1/elenvind/core/console.py +40 -0
  16. elenvind-1.0.1/elenvind/core/content.py +172 -0
  17. elenvind-1.0.1/elenvind/core/context.py +170 -0
  18. elenvind-1.0.1/elenvind/core/csrf.py +72 -0
  19. elenvind-1.0.1/elenvind/core/http.py +707 -0
  20. elenvind-1.0.1/elenvind/core/i18n.py +90 -0
  21. elenvind-1.0.1/elenvind/core/lifespan.py +114 -0
  22. elenvind-1.0.1/elenvind/core/logging_config.py +121 -0
  23. elenvind-1.0.1/elenvind/core/markdown.py +545 -0
  24. elenvind-1.0.1/elenvind/core/routing.py +345 -0
  25. elenvind-1.0.1/elenvind/core/security.py +554 -0
  26. elenvind-1.0.1/elenvind/core/session.py +129 -0
  27. elenvind-1.0.1/elenvind/core/templating.py +178 -0
  28. elenvind-1.0.1/elenvind/core/utils.py +271 -0
  29. elenvind-1.0.1/elenvind/core/version.py +75 -0
  30. elenvind-1.0.1/elenvind/database/__init__.py +172 -0
  31. elenvind-1.0.1/elenvind/database/auth.py +202 -0
  32. elenvind-1.0.1/elenvind/database/backup.py +108 -0
  33. elenvind-1.0.1/elenvind/database/comment.py +193 -0
  34. elenvind-1.0.1/elenvind/database/comment_rate.py +116 -0
  35. elenvind-1.0.1/elenvind/database/connection.py +180 -0
  36. elenvind-1.0.1/elenvind/database/maintenance.py +101 -0
  37. elenvind-1.0.1/elenvind/database/migration.py +509 -0
  38. elenvind-1.0.1/elenvind/database/session.py +214 -0
  39. elenvind-1.0.1/elenvind/database/transaction.py +111 -0
  40. elenvind-1.0.1/elenvind/database/user.py +177 -0
  41. elenvind-1.0.1/elenvind/i18n/en.toml +150 -0
  42. elenvind-1.0.1/elenvind/i18n/ja.toml +150 -0
  43. elenvind-1.0.1/elenvind/i18n/zh.toml +150 -0
  44. elenvind-1.0.1/elenvind/launcher.py +280 -0
  45. elenvind-1.0.1/elenvind/modules/__init__.py +27 -0
  46. elenvind-1.0.1/elenvind/modules/admin/__init__.py +6 -0
  47. elenvind-1.0.1/elenvind/modules/admin/routes.py +29 -0
  48. elenvind-1.0.1/elenvind/modules/auth/__init__.py +5 -0
  49. elenvind-1.0.1/elenvind/modules/auth/routes.py +345 -0
  50. elenvind-1.0.1/elenvind/modules/blog/__init__.py +9 -0
  51. elenvind-1.0.1/elenvind/modules/blog/logic.py +500 -0
  52. elenvind-1.0.1/elenvind/modules/blog/routes.py +263 -0
  53. elenvind-1.0.1/elenvind/modules/pages/__init__.py +10 -0
  54. elenvind-1.0.1/elenvind/modules/pages/logic.py +139 -0
  55. elenvind-1.0.1/elenvind/modules/pages/routes.py +46 -0
  56. elenvind-1.0.1/elenvind/modules/seo/__init__.py +7 -0
  57. elenvind-1.0.1/elenvind/modules/seo/routes.py +69 -0
  58. elenvind-1.0.1/elenvind/modules/system/__init__.py +10 -0
  59. elenvind-1.0.1/elenvind/modules/system/routes.py +57 -0
  60. elenvind-1.0.1/elenvind/modules/users/__init__.py +5 -0
  61. elenvind-1.0.1/elenvind/modules/users/routes.py +231 -0
  62. elenvind-1.0.1/elenvind/resources/config.example.toml +249 -0
  63. elenvind-1.0.1/elenvind/resources/elenvind.service +79 -0
  64. elenvind-1.0.1/elenvind/reverse_proxy.py +735 -0
  65. elenvind-1.0.1/elenvind/scaffold.py +81 -0
  66. elenvind-1.0.1/elenvind/templates/admin/index.html +29 -0
  67. elenvind-1.0.1/elenvind/templates/article.html +29 -0
  68. elenvind-1.0.1/elenvind/templates/auth/login.html +30 -0
  69. elenvind-1.0.1/elenvind/templates/auth/logout.html +19 -0
  70. elenvind-1.0.1/elenvind/templates/auth/register.html +41 -0
  71. elenvind-1.0.1/elenvind/templates/base.html +26 -0
  72. elenvind-1.0.1/elenvind/templates/errors/400.html +6 -0
  73. elenvind-1.0.1/elenvind/templates/errors/404.html +6 -0
  74. elenvind-1.0.1/elenvind/templates/errors/error.html +6 -0
  75. elenvind-1.0.1/elenvind/templates/home.html +81 -0
  76. elenvind-1.0.1/elenvind/templates/page.html +8 -0
  77. elenvind-1.0.1/elenvind/templates/partials/comments.html +71 -0
  78. elenvind-1.0.1/elenvind/templates/partials/footer.html +15 -0
  79. elenvind-1.0.1/elenvind/templates/partials/form_message.html +10 -0
  80. elenvind-1.0.1/elenvind/templates/partials/navbar.html +38 -0
  81. elenvind-1.0.1/elenvind/templates/users/profile.html +115 -0
  82. elenvind-1.0.1/elenvind/wsgi.py +37 -0
  83. elenvind-1.0.1/elenvind.egg-info/SOURCES.txt +111 -0
  84. elenvind-1.0.1/manual/CLI.md +194 -0
  85. elenvind-1.0.1/manual/CONFIGURATION.md +206 -0
  86. elenvind-1.0.1/manual/HOSTING.md +286 -0
  87. elenvind-1.0.1/manual/OPERATIONS.md +159 -0
  88. elenvind-1.0.1/manual/README.md +43 -0
  89. elenvind-1.0.1/manual/RELEASE.md +142 -0
  90. elenvind-1.0.1/manual/SECURITY.md +176 -0
  91. elenvind-1.0.1/manual/elenvind.service +79 -0
  92. elenvind-1.0.1/manual/logrotate.conf +26 -0
  93. elenvind-1.0.1/manual/nginx.conf.example +94 -0
  94. elenvind-1.0.1/manual/style.example.css +1065 -0
  95. elenvind-1.0.1/pyproject.toml +115 -0
  96. elenvind-1.0.1/requirements.txt +8 -0
  97. elenvind-1.0.1/run.py +80 -0
  98. elenvind-1.0.1/setup.cfg +4 -0
  99. elenvind-1.0.1/tests/__init__.py +10 -0
  100. elenvind-1.0.1/tests/support.py +351 -0
  101. elenvind-1.0.1/tests/test_architecture.py +199 -0
  102. elenvind-1.0.1/tests/test_auth_lockout.py +238 -0
  103. elenvind-1.0.1/tests/test_boundaries.py +157 -0
  104. elenvind-1.0.1/tests/test_ci.py +124 -0
  105. elenvind-1.0.1/tests/test_cli.py +427 -0
  106. elenvind-1.0.1/tests/test_comments.py +151 -0
  107. elenvind-1.0.1/tests/test_concurrency.py +116 -0
  108. elenvind-1.0.1/tests/test_core_contract.py +237 -0
  109. elenvind-1.0.1/tests/test_doc_consistency.py +307 -0
  110. elenvind-1.0.1/tests/test_logging_proxy.py +155 -0
  111. elenvind-1.0.1/tests/test_operations.py +573 -0
  112. elenvind-1.0.1/tests/test_packaging.py +187 -0
  113. elenvind-1.0.1/tests/test_proxy_config.py +329 -0
  114. 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