From 098f0ded66b5f614ec99f0d3efb89b3a5b1bc0f6 Mon Sep 17 00:00:00 2001 From: Cytrogen Date: Thu, 12 Feb 2026 12:53:52 -0500 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4=EF=BC=9Aso?= =?UTF-8?q?urcehut=20Docker=20Compose=20=E8=87=AA=E6=89=98=E7=AE=A1?= =?UTF-8?q?=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 基于 k8ieone 镜像的定制部署,包含: - meta/git/todo/hub 四个服务的 Dockerfile 及补丁 - Jinja2 do 扩展修复、OAuth API 兼容层 - meta 模板汉化 --- .env.example | 2 + .gitignore | 11 + README.md | 141 +++++ compose.yaml | 119 ++++ config/config.ini.example | 220 ++++++++ git-custom/Dockerfile | 33 ++ git-custom/fix_jinja2_do.py | 33 ++ git-custom/sshd_config_patch | 4 + hub-custom/Dockerfile | 9 + hub-custom/fix_jinja2_do.py | 33 ++ init-databases.sh | 20 + meta-custom/Dockerfile | 10 + meta-custom/base-templates/graphql.html | 135 +++++ .../base-templates/internal_error.html | 7 + meta-custom/base-templates/layout-full.html | 6 + meta-custom/base-templates/layout.html | 55 ++ meta-custom/base-templates/nav.html | 60 ++ meta-custom/base-templates/not_found.html | 10 + meta-custom/base-templates/oauth-error.html | 7 + meta-custom/base-templates/pagination.html | 29 + meta-custom/base-templates/read_only.html | 28 + meta-custom/base-templates/suspended.html | 9 + meta-custom/base-templates/unauthorized.html | 10 + meta-custom/fix_jinja2_do.py | 33 ++ meta-custom/templates/already-confirmed.html | 19 + meta-custom/templates/are-you-sure.html | 19 + meta-custom/templates/audit-log.html | 31 + .../templates/billing-change-period.html | 62 ++ meta-custom/templates/billing-complete.html | 14 + meta-custom/templates/billing-initial.html | 167 ++++++ meta-custom/templates/billing-invoice.html | 45 ++ meta-custom/templates/billing.html | 250 +++++++++ meta-custom/templates/client-admin.html | 10 + meta-custom/templates/client-delete.html | 15 + meta-custom/templates/client-security.html | 17 + meta-custom/templates/client-settings.html | 33 ++ meta-custom/templates/client-tabs.html | 20 + meta-custom/templates/forgot.html | 39 ++ meta-custom/templates/index.html | 27 + meta-custom/templates/keys.html | 144 +++++ meta-custom/templates/login.html | 61 ++ meta-custom/templates/meta.html | 34 ++ meta-custom/templates/new-payment.html | 129 +++++ meta-custom/templates/oauth-authorize.html | 69 +++ meta-custom/templates/oauth-error.html | 17 + meta-custom/templates/oauth-oob.html | 15 + .../templates/oauth-personal-token.html | 53 ++ meta-custom/templates/oauth-register.html | 43 ++ meta-custom/templates/oauth-registered.html | 28 + meta-custom/templates/oauth.html | 130 +++++ .../templates/oauth2-authorization.html | 90 +++ .../templates/oauth2-client-registered.html | 33 ++ meta-custom/templates/oauth2-dashboard.html | 156 +++++ meta-custom/templates/oauth2-error.html | 23 + .../templates/oauth2-manage-client.html | 70 +++ .../oauth2-personal-token-issued.html | 22 + .../oauth2-personal-token-registration.html | 94 ++++ .../templates/oauth2-register-client.html | 83 +++ meta-custom/templates/privacy.html | 61 ++ meta-custom/templates/profile-delete.html | 50 ++ meta-custom/templates/profile-deleted.html | 13 + meta-custom/templates/profile.html | 90 +++ meta-custom/templates/register-step2.html | 151 +++++ meta-custom/templates/register.html | 86 +++ meta-custom/templates/registered.html | 19 + meta-custom/templates/reset.html | 35 ++ meta-custom/templates/security.html | 80 +++ meta-custom/templates/tabs.html | 35 ++ meta-custom/templates/totp-challenge.html | 48 ++ meta-custom/templates/totp-enable.html | 41 ++ meta-custom/templates/totp-enabled.html | 25 + meta-custom/templates/totp-recovery.html | 46 ++ meta-custom/templates/user.html | 531 ++++++++++++++++++ meta-custom/templates/users.html | 54 ++ todo-custom/Dockerfile | 84 +++ todo-custom/compat_oauth.py | 61 ++ todo-custom/fix_jinja2_do.py | 33 ++ todo-custom/nginx.conf | 16 + todo-custom/start.sh | 31 + 79 files changed, 4676 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 README.md create mode 100644 compose.yaml create mode 100644 config/config.ini.example create mode 100644 git-custom/Dockerfile create mode 100644 git-custom/fix_jinja2_do.py create mode 100644 git-custom/sshd_config_patch create mode 100644 hub-custom/Dockerfile create mode 100644 hub-custom/fix_jinja2_do.py create mode 100755 init-databases.sh create mode 100644 meta-custom/Dockerfile create mode 100644 meta-custom/base-templates/graphql.html create mode 100644 meta-custom/base-templates/internal_error.html create mode 100644 meta-custom/base-templates/layout-full.html create mode 100644 meta-custom/base-templates/layout.html create mode 100644 meta-custom/base-templates/nav.html create mode 100644 meta-custom/base-templates/not_found.html create mode 100644 meta-custom/base-templates/oauth-error.html create mode 100644 meta-custom/base-templates/pagination.html create mode 100644 meta-custom/base-templates/read_only.html create mode 100644 meta-custom/base-templates/suspended.html create mode 100644 meta-custom/base-templates/unauthorized.html create mode 100644 meta-custom/fix_jinja2_do.py create mode 100644 meta-custom/templates/already-confirmed.html create mode 100644 meta-custom/templates/are-you-sure.html create mode 100644 meta-custom/templates/audit-log.html create mode 100644 meta-custom/templates/billing-change-period.html create mode 100644 meta-custom/templates/billing-complete.html create mode 100644 meta-custom/templates/billing-initial.html create mode 100644 meta-custom/templates/billing-invoice.html create mode 100644 meta-custom/templates/billing.html create mode 100644 meta-custom/templates/client-admin.html create mode 100644 meta-custom/templates/client-delete.html create mode 100644 meta-custom/templates/client-security.html create mode 100644 meta-custom/templates/client-settings.html create mode 100644 meta-custom/templates/client-tabs.html create mode 100644 meta-custom/templates/forgot.html create mode 100644 meta-custom/templates/index.html create mode 100644 meta-custom/templates/keys.html create mode 100644 meta-custom/templates/login.html create mode 100644 meta-custom/templates/meta.html create mode 100644 meta-custom/templates/new-payment.html create mode 100644 meta-custom/templates/oauth-authorize.html create mode 100644 meta-custom/templates/oauth-error.html create mode 100644 meta-custom/templates/oauth-oob.html create mode 100644 meta-custom/templates/oauth-personal-token.html create mode 100644 meta-custom/templates/oauth-register.html create mode 100644 meta-custom/templates/oauth-registered.html create mode 100644 meta-custom/templates/oauth.html create mode 100644 meta-custom/templates/oauth2-authorization.html create mode 100644 meta-custom/templates/oauth2-client-registered.html create mode 100644 meta-custom/templates/oauth2-dashboard.html create mode 100644 meta-custom/templates/oauth2-error.html create mode 100644 meta-custom/templates/oauth2-manage-client.html create mode 100644 meta-custom/templates/oauth2-personal-token-issued.html create mode 100644 meta-custom/templates/oauth2-personal-token-registration.html create mode 100644 meta-custom/templates/oauth2-register-client.html create mode 100644 meta-custom/templates/privacy.html create mode 100644 meta-custom/templates/profile-delete.html create mode 100644 meta-custom/templates/profile-deleted.html create mode 100644 meta-custom/templates/profile.html create mode 100644 meta-custom/templates/register-step2.html create mode 100644 meta-custom/templates/register.html create mode 100644 meta-custom/templates/registered.html create mode 100644 meta-custom/templates/reset.html create mode 100644 meta-custom/templates/security.html create mode 100644 meta-custom/templates/tabs.html create mode 100644 meta-custom/templates/totp-challenge.html create mode 100644 meta-custom/templates/totp-enable.html create mode 100644 meta-custom/templates/totp-enabled.html create mode 100644 meta-custom/templates/totp-recovery.html create mode 100644 meta-custom/templates/user.html create mode 100644 meta-custom/templates/users.html create mode 100644 todo-custom/Dockerfile create mode 100644 todo-custom/compat_oauth.py create mode 100644 todo-custom/fix_jinja2_do.py create mode 100644 todo-custom/nginx.conf create mode 100644 todo-custom/start.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8f223bc --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# PostgreSQL root password +POSTGRES_PASSWORD=changeme_to_a_strong_password diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4733cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# 实际配置文件(含密钥/密码) +config/config.ini +.env + +# PGP 密钥 +config/*.priv +config/*.pub + +# Docker 数据 / 运行时 +postgres_data/ +postgres_sh/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..00a1528 --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# sourcehut 自托管部署 + +基于 Docker Compose 的 [sourcehut](https://sr.ht) 自托管部署,运行于 `*.cytrogen.icu`。 + +## 架构 + +``` + ┌─────────────────────────────────────────┐ + │ Docker Compose 网络 │ + │ │ + :22222 ──────────►│ ┌─────┐ ┌──────┐ ┌──────┐ │ + (SSH git push) │ │ git │──►│ │ │ │ │ + │ └─────┘ │ │ │ │ │ + :5001 ───────────►│ ┌──────┐ │ post │ │ │ │ + (meta web) │ │ meta │──►│ gres │ │redis │ │ + │ └──────┘ │ │ │ │ │ + :5002 ───────────►│ ┌─────┐ │ │ │ │ │ + (hub web) │ │ hub │──►│ │ │ │ │ + │ └─────┘ │ │ │ │ │ + :5003 ───────────►│ ┌──────┐ │ │ │ │ │ + (git web) │ │ │──►│ │──►│ │ │ + │ └──────┘ └──────┘ └──────┘ │ + :5004 ───────────►│ ┌──────┐ │ + (todo web) │ │ todo │──►postgres + redis │ + │ └──────┘ │ + └─────────────────────────────────────────┘ + +6 个服务: + postgres — PostgreSQL 16 (Alpine),4 个数据库(meta/git/todo/hub) + redis — Redis 7 (Alpine),48MB 内存限制 + meta — 用户认证与账户管理 (meta.cytrogen.icu) + git — Git 仓库托管 + SSH (git.cytrogen.icu) + todo — 工单/Issue 跟踪 (todo.cytrogen.icu) + hub — 项目聚合门户 (hub.cytrogen.icu) +``` + +所有 Web 服务绑定 `127.0.0.1`,通过前端反向代理(Caddy/nginx)对外提供 HTTPS。仅 git SSH 端口 (`22222`) 对外开放。 + +## 定制修改及原因 + +本部署基于 [k8ieone 的 sourcehut Docker 镜像](https://github.com/k8ieone/sourcehut-docker),但这些镜像存在若干问题需要修复。以下是每个定制目录的修改说明。 + +### `meta-custom/` + +| 修改 | 原因 | +|------|------| +| Jinja2 `{% do %}` 扩展修复 | 上游 core.sr.ht 已合并修复([patch 39036](https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036))但 k8ieone 镜像未包含 | +| `templates/` — 模板汉化 | 将 meta.sr.ht 全部用户界面翻译为中文 | +| `base-templates/` — 基础模板汉化 | 汉化导航栏、错误页面等公共模板 | + +### `git-custom/` + +| 修改 | 原因 | +|------|------| +| 安装 openssh-server | 镜像不含 SSH 服务器,无法 `git push` | +| SSH key dispatch 配置 | 配置 `sshd` 使用 `gitsrht-dispatch` 进行公钥认证 | +| 安装 pgpy | git.sr.ht 运行时依赖,镜像缺失 | +| libgit2 属主修复 | gunicorn 以 root 运行但 SSH 推送创建的仓库属于 git 用户,libgit2 (pygit2) 拒绝 root 读取非 root 仓库。启动时对仓库顶层目录 `chown root:git` 解决 | +| Jinja2 `{% do %}` 修复 | 同 meta-custom | + +### `hub-custom/` + +| 修改 | 原因 | +|------|------| +| 安装 pgpy | hub.sr.ht 运行时依赖,镜像缺失 | +| Jinja2 `{% do %}` 修复 | 同 meta-custom | + +### `todo-custom/` + +todo.sr.ht 是最复杂的定制——k8ieone **不提供预构建的 todo 镜像**,需要从源码完整构建。 + +| 修改 | 原因 | +|------|------| +| 多阶段 Dockerfile(从源码构建) | k8ieone 无 todo.sr.ht 预构建镜像,使用 [sr.ht-apkbuilds](https://git.sr.ht/~sircmpwn/sr.ht-apkbuilds) 从源码编译 | +| `compat_oauth.py` — OAuth API 兼容层 | core.sr.ht 0.78.6 重命名了 OAuth API(`AbstractOAuthService` → `OAuthService`、`DelegatedScope` → `OAuthScope`、`SrhtFlask` 不再接受 `oauth_service=`),而 todo.sr.ht 0.77.5 仍使用旧 API | +| kombu 版本升级 | Celery 5.5.3 要求 `kombu>=5.4`,但 Alpine 3.20 仅提供 5.3.7 | +| Jinja2 `{% do %}` 修复 | 同 meta-custom | +| `nginx.conf` + `start.sh` | 容器入口:启动 gunicorn (web)、GraphQL API、Celery worker、nginx 反代 | + +### 公共补丁脚本 + +- **`fix_jinja2_do.py`**(4 份副本,各服务目录各一份):运行时修补 `srht/flask.py`,在 `ChoiceLoader` 之后添加 `jinja2.ext.do` 扩展。上游已修复但 k8ieone 镜像未包含此修复。 +- **`compat_oauth.py`**(仅 `todo-custom/`):为 core.sr.ht 0.78.6 + todo.sr.ht 0.77.5 版本差异添加兼容层。 + +## 快速开始 + +```bash +# 1. 克隆仓库 +git clone ssh://git@git.cytrogen.icu:22222/~cytrogen/srht-deploy +cd srht-deploy + +# 2. 创建配置文件 +cp .env.example .env +cp config/config.ini.example config/config.ini + +# 3. 编辑配置 +# - .env: 设置 PostgreSQL root 密码 +# - config/config.ini: 修改下方「配置说明」中列出的字段 +vi .env +vi config/config.ini + +# 4. 生成密钥(需要在 meta 容器内执行) +# 先启动 meta 服务以获取 srht-keygen 工具 +docker compose up -d meta +docker compose exec meta srht-keygen service # → service-key +docker compose exec meta srht-keygen network # → network-key +docker compose exec meta srht-keygen webhook # → webhooks private-key +# 将生成的密钥填入 config/config.ini + +# 5. 将 init-databases.sh 放入 postgres 初始化目录 +mkdir -p postgres_sh +cp init-databases.sh postgres_sh/ + +# 6. 启动所有服务 +docker compose up -d + +# 7. 创建管理员账户 +docker compose exec meta metasrht-manageuser -t admin -e YOUR_EMAIL USERNAME +``` + +## 配置说明 + +`config/config.ini.example` 中需要修改的关键字段: + +| 字段 | 说明 | +|------|------| +| `[sr.ht] service-key` | 会话加密密钥,用 `srht-keygen service` 生成 | +| `[sr.ht] network-key` | 内部通信密钥,用 `srht-keygen network` 生成 | +| `[mail] smtp-password` | SMTP 发信密码 | +| `[webhooks] private-key` | Webhook 签名密钥,用 `srht-keygen webhook` 生成 | +| `[*.sr.ht] oauth-client-id/secret` | 各服务的 OAuth 凭据,在 meta 注册后获取 | +| `[mail] pgp-key-id` | PGP 签名邮件的密钥 ID(可选) | + +数据库连接字符串和 Redis 地址使用 Docker 服务名(`postgres`、`redis`),无需修改。 + +## 上游参考 + +- [k8ieone/sourcehut-docker](https://github.com/k8ieone/sourcehut-docker) — 本部署使用的 Docker 基础镜像 +- [sr.ht-apkbuilds](https://git.sr.ht/~sircmpwn/sr.ht-apkbuilds) — Alpine 打包脚本(todo.sr.ht 从此构建) +- [Jinja2 do 扩展修复](https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036) — 上游补丁 +- [sourcehut 官方文档](https://man.sr.ht/installation.md) — 安装参考 diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..a047ba0 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,119 @@ +services: + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} + volumes: + - postgres_data:/var/lib/postgresql/data + - ./postgres_sh:/docker-entrypoint-initdb.d + deploy: + resources: + limits: + memory: 256M + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --maxmemory 48mb --maxmemory-policy allkeys-lru + deploy: + resources: + limits: + memory: 64M + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + meta: + build: ./meta-custom + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + ports: + - "127.0.0.1:5001:8080" + volumes: + - ./config:/etc/sr.ht + - meta_data:/var/lib/meta.sr.ht + deploy: + resources: + limits: + memory: 256M + + git: + build: ./git-custom + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + meta: + condition: service_started + ports: + - "127.0.0.1:5003:8080" + - "0.0.0.0:22222:22" + volumes: + - ./config:/etc/sr.ht + - git_data:/var/lib/git.sr.ht + - git_repos:/var/lib/git + deploy: + resources: + limits: + memory: 256M + + todo: + build: ./todo-custom + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + meta: + condition: service_started + ports: + - "127.0.0.1:5004:8080" + volumes: + - ./config:/etc/sr.ht + - todo_data:/var/lib/todo.sr.ht + deploy: + resources: + limits: + memory: 256M + + hub: + build: ./hub-custom + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + meta: + condition: service_started + ports: + - "127.0.0.1:5002:8080" + volumes: + - ./config:/etc/sr.ht + deploy: + resources: + limits: + memory: 256M + +volumes: + postgres_data: + meta_data: + git_data: + git_repos: + todo_data: diff --git a/config/config.ini.example b/config/config.ini.example new file mode 100644 index 0000000..94d289c --- /dev/null +++ b/config/config.ini.example @@ -0,0 +1,220 @@ +[sr.ht] +# +# The name of your network of sr.ht-based sites +site-name=Cytrogen +# +# The top-level info page for your site +site-info=https://cytrogen.icu +# +# {{ site-name }}, {{ site-blurb }} +site-blurb=Cytrogen's code forge +# +# If this != production, we add a banner to each page +environment=production +# +# Contact information for the site owners +owner-name=Cytrogen +owner-email=boo@cytrogen.icu +# +# The source code for your fork of sr.ht +source-url=https://git.sr.ht/~sircmpwn/srht +# +# Link to your instance's privacy policy. +privacy-policy=https://man.sr.ht/privacy.md +# +# A key used for encrypting session cookies. +# Generate with: srht-keygen service +service-key=CHANGEME +# +# A secret key to encrypt internal messages with. +# Generate with: srht-keygen network +network-key=CHANGEME +# +# The redis host URL. +redis-host=redis://redis:6379/1 +# +# Optional email address for reporting security-related issues. +security-address=boo@cytrogen.icu +# +# The global domain of the site. +global-domain=cytrogen.icu + +[objects] +# +# Configure S3-compatible object storage for services. Optional. +s3-upstream= +s3-access-key= +s3-secret-key= + +[mail] +# +# Outgoing SMTP settings +smtp-host=mail.cytrogen.icu +smtp-port=587 +smtp-from=srht@cytrogen.icu +# +# Options: starttls, tls, insecure +smtp-encryption=starttls +# +# Options: plain, none +smtp-auth=plain +smtp-user=sourcehut +smtp-password=CHANGEME +# +# Application exceptions are emailed to this address +error-to=boo@cytrogen.icu +error-from=srht@cytrogen.icu +# +# PGP key for signing outgoing emails +pgp-privkey=/etc/sr.ht/email.priv +pgp-pubkey=/etc/sr.ht/email.pub +pgp-key-id= + +[webhooks] +# +# base64-encoded Ed25519 key for signing webhook payloads. +# Generate with: srht-keygen webhook +private-key=CHANGEME + +[meta.sr.ht] +# +# URL meta.sr.ht is being served at (protocol://domain) +origin=https://meta.cytrogen.icu +# +# Address and port to bind the debug server to +debug-host=0.0.0.0 +debug-port=5000 +# +# Database connection string +connection-string=postgresql://metasrht:metasrht@postgres/metasrht?sslmode=disable +# +# Set to "yes" to automatically run migrations on package upgrade. +migrate-on-upgrade=yes +# +# The redis connection used for the webhooks worker +webhooks=redis://redis:6379/1 +# +# Send welcome emails after signup (requires cron) +welcome-emails=no +# +# Origin URL for the API +# 使用 Docker 服务名,供跨容器调用 +api-origin=http://meta:5100 + +[meta.sr.ht::api] +max-complexity=200 +max-duration=3s +# 需包含 Docker 内网,供其他容器调用 API +internal-ipnet=127.0.0.0/8,::1/128,192.168.0.0/16,10.0.0.0/8,172.0.0.0/8 + +[meta.sr.ht::settings] +# +# If "no", public registration will not be permitted. +registration=no +# +# Where to redirect new users upon registration +onboarding-redirect=https://meta.cytrogen.icu + +[meta.sr.ht::aliases] + +[meta.sr.ht::billing] +enabled=no +stripe-public-key= +stripe-secret-key= + +[meta.sr.ht::auth] +# Options: builtin, unix-pam +#auth-method=builtin + +[meta.sr.ht::auth::unix-pam] +email-default-domain= +#service=sshd +create-users=yes +user-group= +admin-group=wheel + +[hub.sr.ht] +origin=https://hub.cytrogen.icu +debug-host=0.0.0.0 +debug-port=5014 +connection-string=postgresql://hubsrht:hubsrht@postgres/hubsrht?sslmode=disable +migrate-on-upgrade=yes +oauth-client-id= +oauth-client-secret= +api-origin=http://hub:5114 + +[todo.sr.ht] +# +# URL todo.sr.ht is being served at (protocol://domain) +origin=https://todo.cytrogen.icu +# +# Address and port to bind the debug server to +debug-host=0.0.0.0 +debug-port=5003 +# +# Database connection string +connection-string=postgresql://todosrht:todosrht@postgres/todosrht?sslmode=disable +# +# Set to "yes" to automatically run migrations on package upgrade. +migrate-on-upgrade=yes +# +# The redis connection used for the webhooks worker +webhooks=redis://redis:6379/1 +# +# todo.sr.ht's OAuth client ID and secret for meta.sr.ht +oauth-client-id= +oauth-client-secret= +# +# Origin URL for the API +api-origin=http://127.0.0.1:5103 + +[todo.sr.ht::mail] +posting-domain=todo.cytrogen.icu + +[todo.sr.ht::api] +max-complexity=200 +max-duration=3s +internal-ipnet=127.0.0.0/8,::1/128,192.168.0.0/16,10.0.0.0/8,172.0.0.0/8 + +[git.sr.ht] +# +# URL git.sr.ht is being served at (protocol://domain) +origin=https://git.cytrogen.icu +# +# Address and port to bind the debug server to +debug-host=0.0.0.0 +debug-port=5001 +# +# Database connection string +connection-string=postgresql://gitsrht:gitsrht@postgres/gitsrht?sslmode=disable +# +# Set to "yes" to automatically run migrations on package upgrade. +migrate-on-upgrade=yes +# +# The redis connection used for the webhooks worker +webhooks=redis://redis:6379/1 +# +# A post-update script which is installed in every git repo. +post-update-script=/usr/bin/gitsrht-update-hook +# +# git.sr.ht's OAuth client ID and secret for meta.sr.ht +oauth-client-id= +oauth-client-secret= +# +# Path to git repositories on disk +repos=/var/lib/git/ +# +# S3 object storage (optional) +s3-bucket= +s3-prefix= +# +# Required for preparing and sending patchsets from git.sr.ht +outgoing-domain= + +[git.sr.ht::api] +max-complexity=200 +max-duration=3s +internal-ipnet=127.0.0.0/8,::1/128,192.168.0.0/16,10.0.0.0/8,172.0.0.0/8 + +[git.sr.ht::dispatch] +/usr/bin/gitsrht-keys=git:git diff --git a/git-custom/Dockerfile b/git-custom/Dockerfile new file mode 100644 index 0000000..6523cd5 --- /dev/null +++ b/git-custom/Dockerfile @@ -0,0 +1,33 @@ +FROM ghcr.io/k8ieone/srht-git:latest + +# --- 依赖修复 --- +# pgpy: git.sr.ht 需要但镜像缺失 +# openssh-server: SSH push 支持 +RUN apk add --no-cache py3-pip openssh-server \ + && pip3 install pgpy + +# --- SSH 配置 --- +# 生成主机密钥 +RUN ssh-keygen -A + +# 解锁 git 用户(SSH 登录需要) +RUN echo 'git:x' | chpasswd + +# 配置 sshd 使用 sourcehut 的 key dispatch +COPY sshd_config_patch /tmp/sshd_config_patch +RUN cat /tmp/sshd_config_patch >> /etc/ssh/sshd_config \ + && rm /tmp/sshd_config_patch + +# 修复 start.sh 中 sshd 启动路径(Alpine 需要绝对路径) +RUN sed -i 's|sshd &|/usr/sbin/sshd \&|' /start.sh + +# --- libgit2 属主检查修复 --- +# gunicorn 以 root 运行,但 SSH 推送创建的仓库属于 git 用户 +# libgit2 (pygit2) 会拒绝 root 访问非 root 的仓库 +# 在启动时将仓库顶层目录 chown 为 root(内部文件保持 git 用户,不影响 SSH 推送) +RUN sed -i '1a find /var/lib/git -mindepth 2 -maxdepth 2 -type d -exec chown root:git {} \\; -exec chmod g+ws {} \\; 2>/dev/null || true' /start.sh + +# --- Jinja2 修复 --- +# 上游 core.sr.ht 已修复但此镜像未包含:启用 {% do %} 模板标签 +COPY fix_jinja2_do.py /tmp/fix_jinja2_do.py +RUN python3 /tmp/fix_jinja2_do.py && rm /tmp/fix_jinja2_do.py diff --git a/git-custom/fix_jinja2_do.py b/git-custom/fix_jinja2_do.py new file mode 100644 index 0000000..613df19 --- /dev/null +++ b/git-custom/fix_jinja2_do.py @@ -0,0 +1,33 @@ +"""Enable Jinja2 'do' extension in sourcehut Flask app. + +Upstream fix: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036 +""" +import glob + +candidates = glob.glob("/usr/lib/python3.*/site-packages/srht/flask.py") +if not candidates: + raise FileNotFoundError("Cannot find srht/flask.py") + +source_file = candidates[0] + +with open(source_file) as f: + content = f.read() + +target = "self.jinja_loader = ChoiceLoader" +patch = ' self.jinja_env.add_extension("jinja2.ext.do")' + +if "jinja2.ext.do" not in content: + lines = content.split("\n") + patched = False + for i, line in enumerate(lines): + if target in line: + lines.insert(i + 1, patch) + patched = True + break + if not patched: + raise RuntimeError(f"Could not find '{target}' in {source_file}") + with open(source_file, "w") as f: + f.write("\n".join(lines)) + print(f"Patched {source_file}: enabled jinja2.ext.do") +else: + print(f"Already patched: {source_file}") diff --git a/git-custom/sshd_config_patch b/git-custom/sshd_config_patch new file mode 100644 index 0000000..751663b --- /dev/null +++ b/git-custom/sshd_config_patch @@ -0,0 +1,4 @@ + +# sourcehut git SSH key dispatch +AuthorizedKeysCommand /usr/bin/gitsrht-dispatch "%u" "%h" "%t" "%k" +AuthorizedKeysCommandUser root diff --git a/hub-custom/Dockerfile b/hub-custom/Dockerfile new file mode 100644 index 0000000..27ccf7e --- /dev/null +++ b/hub-custom/Dockerfile @@ -0,0 +1,9 @@ +FROM ghcr.io/k8ieone/srht-hub:latest + +# pgpy: hub.sr.ht 需要但镜像缺失 +RUN apk add --no-cache py3-pip \ + && pip3 install pgpy + +# --- Jinja2 修复 --- +COPY fix_jinja2_do.py /tmp/fix_jinja2_do.py +RUN python3 /tmp/fix_jinja2_do.py && rm /tmp/fix_jinja2_do.py diff --git a/hub-custom/fix_jinja2_do.py b/hub-custom/fix_jinja2_do.py new file mode 100644 index 0000000..613df19 --- /dev/null +++ b/hub-custom/fix_jinja2_do.py @@ -0,0 +1,33 @@ +"""Enable Jinja2 'do' extension in sourcehut Flask app. + +Upstream fix: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036 +""" +import glob + +candidates = glob.glob("/usr/lib/python3.*/site-packages/srht/flask.py") +if not candidates: + raise FileNotFoundError("Cannot find srht/flask.py") + +source_file = candidates[0] + +with open(source_file) as f: + content = f.read() + +target = "self.jinja_loader = ChoiceLoader" +patch = ' self.jinja_env.add_extension("jinja2.ext.do")' + +if "jinja2.ext.do" not in content: + lines = content.split("\n") + patched = False + for i, line in enumerate(lines): + if target in line: + lines.insert(i + 1, patch) + patched = True + break + if not patched: + raise RuntimeError(f"Could not find '{target}' in {source_file}") + with open(source_file, "w") as f: + f.write("\n".join(lines)) + print(f"Patched {source_file}: enabled jinja2.ext.do") +else: + print(f"Already patched: {source_file}") diff --git a/init-databases.sh b/init-databases.sh new file mode 100755 index 0000000..e0f199e --- /dev/null +++ b/init-databases.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# PostgreSQL init script for sourcehut databases +# This runs automatically on first postgres container start +set -e + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + CREATE USER metasrht WITH PASSWORD 'metasrht'; + CREATE DATABASE metasrht OWNER metasrht; + + CREATE USER gitsrht WITH PASSWORD 'gitsrht'; + CREATE DATABASE gitsrht OWNER gitsrht; + + CREATE USER todosrht WITH PASSWORD 'todosrht'; + CREATE DATABASE todosrht OWNER todosrht; + + CREATE USER hubsrht WITH PASSWORD 'hubsrht'; + CREATE DATABASE hubsrht OWNER hubsrht; +EOSQL + +echo "Databases metasrht, gitsrht, todosrht and hubsrht created." diff --git a/meta-custom/Dockerfile b/meta-custom/Dockerfile new file mode 100644 index 0000000..07bdb16 --- /dev/null +++ b/meta-custom/Dockerfile @@ -0,0 +1,10 @@ +FROM ghcr.io/k8ieone/srht-meta:latest + +# --- Jinja2 修复 --- +# 上游 core.sr.ht 已修复但此镜像未包含:启用 {% do %} 模板标签 +COPY fix_jinja2_do.py /tmp/fix_jinja2_do.py +RUN python3 /tmp/fix_jinja2_do.py && rm /tmp/fix_jinja2_do.py + +# --- 模板汉化 --- +COPY templates/ /usr/lib/python3.10/site-packages/metasrht/templates/ +COPY base-templates/ /usr/lib/python3.10/site-packages/srht/templates/ diff --git a/meta-custom/base-templates/graphql.html b/meta-custom/base-templates/graphql.html new file mode 100644 index 0000000..187a99d --- /dev/null +++ b/meta-custom/base-templates/graphql.html @@ -0,0 +1,135 @@ +{% extends "layout-full.html" %} +{% block head %} + + +{% endblock %} +{% block body %} +
+ {{csrf_token()}} + +
+
+ + +
+
+ {{results}} + +
+
+
+
+
+ View GraphQL schema + {{schema}} +
+
+
+
+{% endblock %} +{% block scripts %} + + + + +{% endblock %} diff --git a/meta-custom/base-templates/internal_error.html b/meta-custom/base-templates/internal_error.html new file mode 100644 index 0000000..02329b0 --- /dev/null +++ b/meta-custom/base-templates/internal_error.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} +{% block body %} +
+

500 服务器内部错误

+

出了点问题,请稍后再试。

+
+{% endblock %} diff --git a/meta-custom/base-templates/layout-full.html b/meta-custom/base-templates/layout-full.html new file mode 100644 index 0000000..42ae45e --- /dev/null +++ b/meta-custom/base-templates/layout-full.html @@ -0,0 +1,6 @@ +{% extends "layout.html" %} +{% block nav %} + +{% endblock %} diff --git a/meta-custom/base-templates/layout.html b/meta-custom/base-templates/layout.html new file mode 100644 index 0000000..ae44f1f --- /dev/null +++ b/meta-custom/base-templates/layout.html @@ -0,0 +1,55 @@ + + + + + + {% block title %} + {{domain}} + {% endblock %} + {% block favicon %} + + + {% endblock favicon %} + {% if app.debug %} + + {% else %} + + {% endif %} + {% if page and page != 1 %} + + {% endif %} + {% if page and page != total_pages %} + + {% endif %} + {% block head %} + {% endblock %} + + + {% block environment %} + {% if environment != "production" or + (current_user and current_user.user_type.value == 'admin' )%} +
+ {{environment.upper()}} ENVIRONMENT +
+ {% endif %} + {% endblock %} + {% block nav %} + + {% endblock %} + {% block body %} +
+ {% block content %}{% endblock %} +
+ {% endblock %} + {% block modal %}{% endblock %} + {% block scripts %}{% endblock %} + + diff --git a/meta-custom/base-templates/nav.html b/meta-custom/base-templates/nav.html new file mode 100644 index 0000000..78770b9 --- /dev/null +++ b/meta-custom/base-templates/nav.html @@ -0,0 +1,60 @@ +{% if "hub.sr.ht" in network %} + + {{icon('circle')}} + + {{site_name}} + + +{% else %} + + {{icon('circle')}} + + {{site_name}} + {{site.split(".")[0]}} + + +{% endif %} + +
+ {% if current_user %} + + 已登录: + {% set hubsrht = get_origin("hub.sr.ht", external=True, default=None) %} + {% if hubsrht %} + + {% else %} + + {% endif %} + {{current_user.username}} + — + 登出 + + {% else %} + + {% if site == 'meta.sr.ht' %} + 登录 + — + 注册 + {% else %} + 登录 + — + 注册 + {% endif %} + + {% endif %} +
diff --git a/meta-custom/base-templates/not_found.html b/meta-custom/base-templates/not_found.html new file mode 100644 index 0000000..5e44968 --- /dev/null +++ b/meta-custom/base-templates/not_found.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} +{% block body %} +
+

404 页面未找到

+

+ 你要找的页面不存在。 + 返回首页 +

+
+{% endblock %} diff --git a/meta-custom/base-templates/oauth-error.html b/meta-custom/base-templates/oauth-error.html new file mode 100644 index 0000000..b687b75 --- /dev/null +++ b/meta-custom/base-templates/oauth-error.html @@ -0,0 +1,7 @@ +{% extends "layout.html" %} +{% block content %} +
+

Error logging in

+

{{ details }}

+
+{% endblock %} diff --git a/meta-custom/base-templates/pagination.html b/meta-custom/base-templates/pagination.html new file mode 100644 index 0000000..fa234ba --- /dev/null +++ b/meta-custom/base-templates/pagination.html @@ -0,0 +1,29 @@ +{% if total_pages > 1 %} +
+
+ {% if page != 1 %} + + {{icon('caret-left')}} + prev + + {% endif %} +
+
+ {{ page }} / {{ total_pages }} +
+
+ {% if page != total_pages %} + + next + {{icon('caret-right')}} + + {% endif %} +
+
+{% endif %} diff --git a/meta-custom/base-templates/read_only.html b/meta-custom/base-templates/read_only.html new file mode 100644 index 0000000..73ed9b5 --- /dev/null +++ b/meta-custom/base-templates/read_only.html @@ -0,0 +1,28 @@ +{% extends "layout.html" %} +{% block nav %}{% endblock %} +{% block environment %}{% endblock %} +{% block body %} + +
+
+

{{cfg('sr.ht', 'site-name')}} 当前处于只读模式

+

+ 系统正在进行维护,暂时无法处理你的请求。这通常发生在计划维护期间或意外故障时。 + 请刷新页面重试,或点击浏览器的返回按钮回到上一页。 +

+
+{% endblock %} diff --git a/meta-custom/base-templates/suspended.html b/meta-custom/base-templates/suspended.html new file mode 100644 index 0000000..219fec0 --- /dev/null +++ b/meta-custom/base-templates/suspended.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} +{% block content %} +

你的账户已被停用

+

原因如下:

+
{{ notice }}
+

+ 请联系管理员。 +

+{% endblock %} diff --git a/meta-custom/base-templates/unauthorized.html b/meta-custom/base-templates/unauthorized.html new file mode 100644 index 0000000..fa95e46 --- /dev/null +++ b/meta-custom/base-templates/unauthorized.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} +{% block body %} +
+

401 未授权

+

+ 你没有访问此页面的权限。 + 返回首页 +

+
+{% endblock %} diff --git a/meta-custom/fix_jinja2_do.py b/meta-custom/fix_jinja2_do.py new file mode 100644 index 0000000..613df19 --- /dev/null +++ b/meta-custom/fix_jinja2_do.py @@ -0,0 +1,33 @@ +"""Enable Jinja2 'do' extension in sourcehut Flask app. + +Upstream fix: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036 +""" +import glob + +candidates = glob.glob("/usr/lib/python3.*/site-packages/srht/flask.py") +if not candidates: + raise FileNotFoundError("Cannot find srht/flask.py") + +source_file = candidates[0] + +with open(source_file) as f: + content = f.read() + +target = "self.jinja_loader = ChoiceLoader" +patch = ' self.jinja_env.add_extension("jinja2.ext.do")' + +if "jinja2.ext.do" not in content: + lines = content.split("\n") + patched = False + for i, line in enumerate(lines): + if target in line: + lines.insert(i + 1, patch) + patched = True + break + if not patched: + raise RuntimeError(f"Could not find '{target}' in {source_file}") + with open(source_file, "w") as f: + f.write("\n".join(lines)) + print(f"Patched {source_file}: enabled jinja2.ext.do") +else: + print(f"Already patched: {source_file}") diff --git a/meta-custom/templates/already-confirmed.html b/meta-custom/templates/already-confirmed.html new file mode 100644 index 0000000..6ad1e28 --- /dev/null +++ b/meta-custom/templates/already-confirmed.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} +{% block title %} +确认账户 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 账户已确认 +

+

+ 你的 {{cfg("sr.ht", "site-name")}} 账户已经确认过了。 +

+ + 继续 {{icon('caret-right')}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/are-you-sure.html b/meta-custom/templates/are-you-sure.html new file mode 100644 index 0000000..05e3705 --- /dev/null +++ b/meta-custom/templates/are-you-sure.html @@ -0,0 +1,19 @@ +{% extends "meta.html" %} +{% block content %} +
+
+

确认操作

+

你确定要 {{ blurb | safe }} 吗?此操作无法撤销。

+
+ {{csrf_token()}} + + 取消 {{icon('caret-right')}} +
+
+
+{% endblock %} diff --git a/meta-custom/templates/audit-log.html b/meta-custom/templates/audit-log.html new file mode 100644 index 0000000..a570712 --- /dev/null +++ b/meta-custom/templates/audit-log.html @@ -0,0 +1,31 @@ +{% extends "meta.html" %} +{% block title %} +审计日志 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

审计日志

+ + + + + + + + + + + {% for log in audit_log %} + + + + + + + {% endfor %} + +
IP 地址操作详情时间
{{log.ip_address}}{{log.event_type}}{{log.details or ""}}{{log.created | date }}
+
+
+{% endblock %} diff --git a/meta-custom/templates/billing-change-period.html b/meta-custom/templates/billing-change-period.html new file mode 100644 index 0000000..a7d0e32 --- /dev/null +++ b/meta-custom/templates/billing-change-period.html @@ -0,0 +1,62 @@ +{% extends "layout.html" %} +{% block title %} +Update billing period - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ Thank you for supporting {{cfg("sr.ht", "site-name")}}! + {% if current_user.user_type != UserType.active_paying %} + You will be charged when you click "submit payment", and your plan will + be automatically renewed at the end of the term. + {% endif %} +

+
+
+

Confirm subscription details

+
+ {{csrf_token()}} +
+ Payment term +
+ + +
+
+ + +
+
+
+ +
+ {% if current_user.user_type == UserType.active_paying %} +
+ Your account is paid for and up-to-date. These changes will take effect + at the start of your next billing period + ({{current_user.payment_due | date}}). +
+ {% endif %} +
+
+
+{% endblock %} diff --git a/meta-custom/templates/billing-complete.html b/meta-custom/templates/billing-complete.html new file mode 100644 index 0000000..a9a96b5 --- /dev/null +++ b/meta-custom/templates/billing-complete.html @@ -0,0 +1,14 @@ +{% extends "layout.html" %} +{% block title %} +Billing complete - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Your payment has been processed successfully. Thank you!

+ + Continue to docs & tutorials {{icon('caret-right')}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/billing-initial.html b/meta-custom/templates/billing-initial.html new file mode 100644 index 0000000..ad850ed --- /dev/null +++ b/meta-custom/templates/billing-initial.html @@ -0,0 +1,167 @@ +{% extends "layout.html" %} +{% block title %} +Choose a plan - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ Choose a plan +

+
+
+
+
+

+ On {{cfg("sr.ht", "site-name")}}, all plans have access to the same + features and in the same quantity. You should pick the plan which best + matches your financial needs and best represents the level of investment + you have in {{cfg("sr.ht", "site-name")}}. If you require financial aid + to use {{cfg("sr.ht", "site-name")}}, please + send us an email + explaining your circumstances and we'll do our best to accommodate your + needs. +

+
+ Notice: {{cfg("sr.ht", "site-name")}} is currently + considered at an alpha stage of development, and the quality of the + service may reflect that. However, the service is reliable, stable, + secure, and mostly complete at this stage of development. To learn + exactly what the alpha entails, + consult this document. + During the alpha, payment is encouraged, but optional, for most features. + Continue without payment {{icon('caret-right')}}. +
+
+
+
+
+
+ {{csrf_token()}} +

Amateur Hackers

+

Includes access to all features.

+
+ $2/month or $20/year +
+ + + +
+
+
+
+ {{csrf_token()}} +

Typical Hackers

+

Includes access to all features.

+
+ $5/month or $50/year +
+ + + +
+
+
+
+ {{csrf_token()}} +

Professional Hackers

+

Includes access to all features.

+
+ $10/month or $100/year +
+ + + +
+
+
+
+
+

+ All prices are shown in US dollars. +

+
+ Notice: Continuing to the next page will execute + non-free JavaScript from our payment processor, + Stripe. If you are uncomfortable with this, please + reach out to us to + discuss other options. For more information, consult our + privacy policy. +
+

+ You can cancel or change your plan at any time. Any questions? Review our + billing & payments FAQ. +

+
+
+
+
+

Other ways to use {{cfg("sr.ht", "site-name")}}

+
+
+
+
+
+

Contributors

+

+ Just here to contribute bug reports, patches, and so on, to existing + projects? You don't need to have a paid account for that. +

+ Continue {{icon("caret-right")}} +
+
+
+
+

Earn free service

+

+ {{cfg("sr.ht", "site-name")}} is itself an + open-source project. Users who contribute patches to the upstream + project are eligible for free service. +

+ Learn more {{icon("caret-right")}} +
+
+
+
+

Install it yourself

+

+ {{cfg("sr.ht", "site-name")}} is 100% free and open source software, and + you can run it on your own servers free of charge if you prefer to host + it yourself. +

+ + Read the docs {{icon("caret-right")}} + +
+
+
+{% endblock %} diff --git a/meta-custom/templates/billing-invoice.html b/meta-custom/templates/billing-invoice.html new file mode 100644 index 0000000..710d001 --- /dev/null +++ b/meta-custom/templates/billing-invoice.html @@ -0,0 +1,45 @@ +{% extends "meta.html" %} +{% block title %} +Download invoice - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Download Invoice

+
+
+

+ + {{icon('check')}} + + ${{"{:.2f}".format(invoice.cents/100)}} + + with {{invoice.source}} + +

+

+ Paid {{invoice.created | date}}
+ Valid for service until {{invoice.valid_thru | date}} +

+
+
+
+ {{csrf_token()}} +
+ + +
+ +
+
+
+{% endblock %} diff --git a/meta-custom/templates/billing.html b/meta-custom/templates/billing.html new file mode 100644 index 0000000..60f05bd --- /dev/null +++ b/meta-custom/templates/billing.html @@ -0,0 +1,250 @@ +{% extends "meta.html" %} +{% block title %} +Billing - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Billing Information

+ {% if message %} +
{{message}}
+ {% endif %} + + {% if current_user.user_type == UserType.active_non_paying %} + +
+ You are currently using an unpaid + {{cfg("sr.ht", "site-name")}} account. Some site features may be + unavailable to your account. +
+ + {% elif current_user.user_type == UserType.active_free %} + +
+ Your account is exempt from billing. All features are + available to you free of charge. You may choose to set up billing + if you wish to support the site. +
+ + {% elif current_user.user_type == UserType.active_paying %} + + {% if current_user.payment_cents == 0 %} + +
+ Your paid service has been cancelled. At the end of your current term, + {{current_user.payment_due | date}}, your account will be downgraded to a + non-paying account. +
+ + {% else %} + +
+ {% if invoices %} + Your account is paid and up-to-date, and your last + payment of {{"${:.2f}".format(invoices[0].cents/100)}} + was made {{invoices[0].created | date}}. Your current + {{current_user.payment_interval.value}} payment is + {{"${:.2f}".format(current_user.payment_cents/100 + if current_user.payment_interval.value == "monthly" + else current_user.payment_cents*10/100)}} and will be billed + {{invoices[0].valid_thru | date}}. + {% else %} + Your account is paid and up-to-date. Your + current {{current_user.payment_interval.value}} payment is + {{"${:.2f}".format(current_user.payment_cents/100 + if current_user.payment_interval.value == "monthly" + else current_user.payment_cents*10/100)}}. + {% endif %} +
+ + {% endif %} + + {% elif current_user.user_type == UserType.active_delinquent %} + +
+ Notice: Your payment is past due. Please check that your + payment information is correct or your service may be impacted. +
+ + {% elif current_user.user_type == UserType.admin %} + +
+ Admins are exempt from billing. +
+ + {% endif %} + +
+
+
{{paid_pct}}% paid
+
13.37%
+
of {{total_users}} registered users
+
+ + Current number of paid accounts on {{cfg("sr.ht", "site-name")}} + +
+
+
+{% if current_user.user_type == UserType.active_paying + and current_user.payment_cents != 0 %} +
+ +
+
+ {{csrf_token()}} + +
+
+
+{% elif current_user.user_type == UserType.active_delinquent %} +
+ +
+
+ {{csrf_token()}} + +
+
+ +
+{% elif current_user.user_type == UserType.active_paying + and current_user.payment_cents == 0 %} +
+ +
+{% elif current_user.user_type in [ + UserType.active_non_paying, + UserType.active_free +] %} +
+ +
+{% endif %} +
+
+ {% if current_user.user_type in [ + UserType.active_paying, + UserType.active_delinquent + ] %} +

Payment methods

+
+ {% for source in customer.sources %} +
+
+ {{source.brand}} ending in {{source.last4}} +
+ + {% if source.address_zip %} + Post code {{source.address_zip}}. + {% endif %} + Expires {{source.exp_month}}/{{source.exp_year}}. + +
+
+
+ {% if source.stripe_id == customer.default_source %} + {{icon('check', cls="text-success")}} Default + {% else %} +
+ {{csrf_token()}} + +
+
+ {{csrf_token()}} + +
+ {% endif %} +
+
+
+ {% endfor %} +
+ New payment method {{icon('caret-right')}} + {% endif %} +
+ {% if len(invoices) != 0 %} +
+

Invoices

+
+ {% for invoice in invoices %} +
+

+ + {{icon('check')}} + + ${{"{:.2f}".format(invoice.cents/100)}} + + with {{invoice.source}} + + + Export as PDF + +

+

+ Paid {{invoice.created | date}}
+ Valid for service until {{invoice.valid_thru | date}} + {% if invoice.comment %} +
+ {{invoice.comment}} + {% endif %} +

+
+ {% endfor %} +
+
+ {% endif %} +
+{% endblock %} diff --git a/meta-custom/templates/client-admin.html b/meta-custom/templates/client-admin.html new file mode 100644 index 0000000..1d5018c --- /dev/null +++ b/meta-custom/templates/client-admin.html @@ -0,0 +1,10 @@ +{% extends "meta.html" %} +{% block blurb %} +

OAuth client settings

+
+

You can manage settings for {{ client.client_name }} here.

+
+{% endblock %} +{% block tabs %} +{% include "client-tabs.html" %} +{% endblock %} diff --git a/meta-custom/templates/client-delete.html b/meta-custom/templates/client-delete.html new file mode 100644 index 0000000..4f1d901 --- /dev/null +++ b/meta-custom/templates/client-delete.html @@ -0,0 +1,15 @@ +{% extends "client-admin.html" %} +{% block content %} +

+ This will permenantely delete your OAuth client, + {{ client.client_name }}, and revoke all OAuth tokens you + have been issued. +

+
+ {{csrf_token()}} + + Nevermind +
+{% endblock %} diff --git a/meta-custom/templates/client-security.html b/meta-custom/templates/client-security.html new file mode 100644 index 0000000..95b9442 --- /dev/null +++ b/meta-custom/templates/client-security.html @@ -0,0 +1,17 @@ +{% extends "client-admin.html" %} +{% block content %} +

Reset client secret

+

If your client secret is compromised, regenerate it here.

+
+ {{csrf_token()}} + +
+

Revoke all tokens

+

You can revoke all issued OAuth tokens at once here.

+ + Revoke {{ len(client.tokens) }} token{% if len(client.tokens) > 1 %}s{% endif %} + {{icon("caret-right")}} + +{% endblock %} diff --git a/meta-custom/templates/client-settings.html b/meta-custom/templates/client-settings.html new file mode 100644 index 0000000..ee832bb --- /dev/null +++ b/meta-custom/templates/client-settings.html @@ -0,0 +1,33 @@ +{% extends "client-admin.html" %} +{% block content %} +
+
+
+ {{csrf_token()}} +
+ + + {{valid.summary("client-name")}} +
+
+ + + {{valid.summary("redirect-uri")}} +
+ +
+
+
+{% endblock %} diff --git a/meta-custom/templates/client-tabs.html b/meta-custom/templates/client-tabs.html new file mode 100644 index 0000000..e526fbc --- /dev/null +++ b/meta-custom/templates/client-tabs.html @@ -0,0 +1,20 @@ +{% macro link(path, title) %} +{{ title }} +{% endmacro %} + + + + + diff --git a/meta-custom/templates/forgot.html b/meta-custom/templates/forgot.html new file mode 100644 index 0000000..4192182 --- /dev/null +++ b/meta-custom/templates/forgot.html @@ -0,0 +1,39 @@ +{% extends "layout.html" %} +{% block title %} +重置密码 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

+ 重置密码 +

+ {% if done %} +

+ 密码重置链接已发送到你的邮箱。 +

+ {% elif allow_password_reset() %} +
+ {{csrf_token()}} +
+ + + {{valid.summary("email")}} +
+ {{valid.summary()}} + +
+ {% else %} +

无法重置密码,因为认证由其他服务管理。 + 请联系系统管理员了解如何重置密码。

+ {% endif %} +
+
+{% endblock %} diff --git a/meta-custom/templates/index.html b/meta-custom/templates/index.html new file mode 100644 index 0000000..5808930 --- /dev/null +++ b/meta-custom/templates/index.html @@ -0,0 +1,27 @@ +{% extends "layout.html" %} +{% block body %} +
+
+
+

+ 欢迎来到 {{domain}}!这是 + + {{cfg("sr.ht", "site-name")}} + 的账户与安全管理中心。功能包括: +

+
    +
  • PGP 加密和签名的服务通知邮件
  • +
  • 基于 TOTP 的两步验证
  • +
  • 详细的账户活动审计日志
  • +
  • 细粒度的第三方 OAuth 访问控制
  • +
+ + 注册 {{icon('caret-right')}} + + 或 + + 登录 {{icon('caret-right')}} +
+
+
+{% endblock %} diff --git a/meta-custom/templates/keys.html b/meta-custom/templates/keys.html new file mode 100644 index 0000000..eabfa1a --- /dev/null +++ b/meta-custom/templates/keys.html @@ -0,0 +1,144 @@ +{% extends "meta.html" %} +{% block title %} +密钥管理 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+
+

SSH 密钥

+ {% if any(current_user.ssh_keys) %} +

以下 SSH 密钥已关联到你的账户:

+ + + + + + + + + + + + {% for key in current_user.ssh_keys %} + + + + + + + + {% endfor %} + +
名称指纹添加时间最后使用
{{key.comment}}{{key.fingerprint}}{{key.created|date}}{{key.last_used|date}} +
+ {{csrf_token()}} + +
+
+ {% endif %} +
+ {{csrf_token()}} +
+ + + + 你的 SSH 公钥列表可通过 + {{current_user.canonical_name}}.keys 公开访问 + + {{valid.summary("ssh-key")}} +
+ {{valid.summary()}} + +
+
+
+

PGP 密钥

+ {% if any(current_user.pgp_keys) %} +

以下 PGP 密钥已关联到你的账户:

+ + + + + + + + + + + {% for key in current_user.pgp_keys %} + + + + {% if not key.expiration %} + + {% elif key.expiration > now %} + + {% else %} + + {% endif %} + + + {% endfor %} + +
指纹添加时间过期时间
{{key.fingerprint_hex}}{{key.created|date}}永不过期{{key.expiration|date}}已过期 {{key.expiration|date}} +
+ {{csrf_token()}} + +
+
+ {% endif %} + {% if tried_to_delete_key_in_use %} +
此密钥当前用于邮件加密。请先在 + 隐私设置中选择其他密钥或禁用邮件加密, + 然后再删除此密钥。
+ {% endif %} +
+ {{csrf_token()}} +
+ + +
+ 你可以使用以下命令导出 PGP 密钥:
+ gpg --armor --export-options export-minimal --export {{email or current_user.email}} +
+ + 你的 PGP 公钥列表可通过 + {{current_user.canonical_name}}.pgp 公开访问 + + {{valid.summary("pgp-key")}} +
+ +
+
+
+
+{% endblock %} diff --git a/meta-custom/templates/login.html b/meta-custom/templates/login.html new file mode 100644 index 0000000..d0a70bc --- /dev/null +++ b/meta-custom/templates/login.html @@ -0,0 +1,61 @@ +{% extends "layout.html" %} +{% block title %} +登录 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 登录 {{cfg("sr.ht", "site-name")}} + + 或 注册 + +

+
+
+
+
+
+ {{csrf_token()}} + {% if login_context %} +
+ {{login_context}} +
+ {% endif %} +
+ + + {{valid.summary("username")}} +
+
+ + + {{valid.summary("password")}} +
+ + {{valid.summary()}} + +

+ 忘记密码? +

+
+
+
+{% endblock %} diff --git a/meta-custom/templates/meta.html b/meta-custom/templates/meta.html new file mode 100644 index 0000000..0519fae --- /dev/null +++ b/meta-custom/templates/meta.html @@ -0,0 +1,34 @@ +{% extends "layout.html" %} +{% block head %} + +{% endblock %} +{% block body %} +
+
+
+ {% block blurb %} +
+

在此管理你的 {{cfg("sr.ht", "site-name")}} 账户。

+
+ {% endblock %} +
+
+
+
+
+ +
+
+
+ {% if notice %} +
+ {{notice}} +
+ {% endif %} + {% block content %}{% endblock %} +
+{% endblock %} diff --git a/meta-custom/templates/new-payment.html b/meta-custom/templates/new-payment.html new file mode 100644 index 0000000..bb05363 --- /dev/null +++ b/meta-custom/templates/new-payment.html @@ -0,0 +1,129 @@ +{% extends "layout.html" %} +{% block title %} +Add payment method - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ Thank you for supporting {{cfg("sr.ht", "site-name")}}! + {% if current_user.user_type != UserType.active_paying %} + You will be charged when you click "submit payment", and your plan will + be automatically renewed at the end of the term. + {% endif %} + Your payment information is securely processed by + Stripe. +

+
+
+

Payment information

+ + +
+
+ + +{% endblock %} diff --git a/meta-custom/templates/oauth-authorize.html b/meta-custom/templates/oauth-authorize.html new file mode 100644 index 0000000..fc57790 --- /dev/null +++ b/meta-custom/templates/oauth-authorize.html @@ -0,0 +1,69 @@ +{% extends "layout.html" %} +{% block title %} +Authorize account access - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+ {{csrf_token()}} +
+

Authorize account access

+

+ {{client.client_name}} would like access to your + {{cfg("sr.ht", "site-name")}} account. + {{client.client_name}} is a third-party application + operated by {{client.user.username}}. You may revoke + this access at any time. They would like permission to access + the following resources on your account: +

+ {% macro render_access(scope) %} + {% if scope.access == 'read' %} + {% if str(scope) == 'profile:read' %} + + {% else %} + + {% endif %} + read + {% elif scope.access == 'write' %} + + read and write + {% endif %} + {% endmacro %} +
    + {% for scope in scopes %} +
  • + {% if not scope.client_id %} + {{render_access(scope)}} access to your + {{scope.friendly()}} + {% else %} + {{render_access(scope)}} access to your + {{scope.friendly()}} on your + {{scope.client.client_name}} account + {% endif %} +
  • + {% endfor %} +
+

+ By unchecking the relevant permissions, you may change how much access + {{client.client_name}} will have. However, note that + this may cause undesirable behavior in the third-party application. +

+ + {% if redirect_uri %} + + {% endif %} + {% if state %} + + {% endif %} + + +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth-error.html b/meta-custom/templates/oauth-error.html new file mode 100644 index 0000000..f25062c --- /dev/null +++ b/meta-custom/templates/oauth-error.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} +{% block title %} +Authorization error - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

An error occured

+

+ An error occurred processing a request to authorize third party access to + your account. This is generally not a problem with + {{cfg("sr.ht", "site-name")}}, but with the application. +

+

{{ details }}

+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth-oob.html b/meta-custom/templates/oauth-oob.html new file mode 100644 index 0000000..8ad3736 --- /dev/null +++ b/meta-custom/templates/oauth-oob.html @@ -0,0 +1,15 @@ +{% extends "layout.html" %} +{% block title %} +OAuth Callback - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Sign In

+

+ Please copy and paste the following into the application. +

+
{{ exchange_token }}
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth-personal-token.html b/meta-custom/templates/oauth-personal-token.html new file mode 100644 index 0000000..a2e9f6a --- /dev/null +++ b/meta-custom/templates/oauth-personal-token.html @@ -0,0 +1,53 @@ +{% extends "meta.html" %} +{% block title %} +Personal access token - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+ {{csrf_token()}} +

Personal Access Token

+ {% if token %} +
+
Personal Access Token
+
{{token}} +
+

+ Your access token will never be shown to you again. Keep + this secret. +

+ Continue {{icon('caret-right')}} + {% else %} +

+ This will generate a valid OAuth token with complete access to your + {{cfg("sr.ht", "site-name")}} account, all {{cfg("sr.ht", "site-name")}} + services, and all third party accounts that use + {{cfg("sr.ht", "site-name")}} for authentication. + It will expire in one year, or when you manually revoke it. +

+
+ + + + Arbitrary comment for personal reference only + + {{valid.summary("comment")}} +
+ + Nevermind {{icon('caret-right')}} + {% endif %} +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth-register.html b/meta-custom/templates/oauth-register.html new file mode 100644 index 0000000..eed1976 --- /dev/null +++ b/meta-custom/templates/oauth-register.html @@ -0,0 +1,43 @@ +{% extends "meta.html" %} +{% block title %} +Register OAuth client - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Register OAuth client

+

+ Be sure to review the + API docs + to understand how this works. +

+
+ {{csrf_token()}} +
+ + + {{valid.summary("client-name")}} +
+
+ + + {{valid.summary("redirect-uri")}} +
+ +
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth-registered.html b/meta-custom/templates/oauth-registered.html new file mode 100644 index 0000000..f484ab4 --- /dev/null +++ b/meta-custom/templates/oauth-registered.html @@ -0,0 +1,28 @@ +{% extends "meta.html" %} +{% block title %} +OAuth client registered - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+ {% if client_event == "registered" %} +

OAuth client registered

+

Your OAuth client has been successfully registered. Write down this information:

+ {% elif client_event == "reset-secret" %} +

OAuth secret reset

+

Your OAuth client secret been successfully reset. Write down the new one:

+ {% endif %} +
+
Client ID
+
{{client_id}}
+
Client Secret
+
{{client_secret}} +
+

+ Your client secret will never be shown to you again, though you can + reset it later if necessary. As the name implies, you should keep it secret. +

+ Continue +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth.html b/meta-custom/templates/oauth.html new file mode 100644 index 0000000..8369692 --- /dev/null +++ b/meta-custom/templates/oauth.html @@ -0,0 +1,130 @@ +{% extends "meta.html" %} +{% block title %} +Authorized applications - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+ Heads up! This is the legacy OAuth dashboard. Credentials + issued here are incompatible with OAuth 2.0 and the GraphQL APIs. + + Return to OAuth 2.0 Dashboard {{icon('caret-right')}} + +
+
+
+
+

Personal Access Tokens

+ {% if any(personal_tokens) %} +

You have obtained the following personal access tokens:

+ + + + + + + + + + + + + {% for token in personal_tokens %} + + + + + + + + + {% endfor %} + +
Access tokenCommentDate issuedLast UsedExpires
{{ token.token_partial }}…{{ token.comment }}{{ token.created | date }}{{ token.updated | date }}{{ token.expires | date }} + Revoke +
+ {% else %} +

You have not created any personal access tokens.

+ {% endif %} + + Generate new token {{icon("caret-right")}} + +
+
+

Authorized Clients

+ {% if any(client_authorizations) %} +

The following third party clients have access to your account:

+ + + + + + + + + + + + + {% for token in client_authorizations %} + + + + + + + + + {% endfor %} + +
NameOwnerFirst AuthorizedLast UsedExpires
{{ token.client.client_name }}{{ token.client.user.username }}{{ token.created | date }}{{ token.updated | date }}{{ token.expires | date }} + Revoke +
+ {% else %} +

You have not granted any third party clients access to your account.

+ {% endif %} +
+
+

Registered Clients

+ {% if any(current_user.oauth_clients) %} +

You have registered the following OAuth clients:

+ + + + + + + + + + + {% for client in current_user.oauth_clients %} + + + + + + + {% endfor %} + +
NameClient IDActive users
{{ client.client_name }}{{ client.client_id }}{{ client_tokens(client) }} + Manage +
+ {% else %} +

You have not registered any OAuth clients yet.

+ {% endif %} + + Register new client {{icon("caret-right")}} + +
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-authorization.html b/meta-custom/templates/oauth2-authorization.html new file mode 100644 index 0000000..1fda110 --- /dev/null +++ b/meta-custom/templates/oauth2-authorization.html @@ -0,0 +1,90 @@ +{% extends "layout.html" %} +{% block title %} +Authorize account access - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Authorize account access

+
+
+
+ {{csrf_token()}} +
+

+ {{client.name}} {{icon('external-link-alt')}} + would like to access to your {{cfg("sr.ht", "site-name")}} account. + {{client.name}} is a third-party application + operated by {{client.owner.canonicalName}}. + You may revoke this access at any time on the OAuth tab of your + meta.sr.ht profile. +

+

Review access request

+

{{client.name}} is requesting the following permissions:

+ {% macro render_access(grant) %} + {% if grant[2] == 'RO' %} + + + {% elif grant[2] == 'RW' %} + + + and + + + {% endif %} + {% endmacro %} +
+
    + {% for grant in grants %} +
  • + {{render_access(grant)}} access to your + {{grant[0]}} {{grant[1]}} + {{valid.summary(grant[0] + "/" + grant[1] + ":RO")}} +
  • + {% endfor %} +
+
+
+ You may uncheck any permission to deny access, but doing so may prevent + this third-party application from working correctly. +
+ + + {% if state %} + + {% endif %} + {{valid.summary()}} + + +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-client-registered.html b/meta-custom/templates/oauth2-client-registered.html new file mode 100644 index 0000000..bb18a0a --- /dev/null +++ b/meta-custom/templates/oauth2-client-registered.html @@ -0,0 +1,33 @@ +{% extends "meta.html" %} +{% block title %} +OAuth 2.0 client registered - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+ {% if not client_reissued %} +

OAuth 2.0 client registered

+ {% else %} +

OAuth 2.0 client reissued

+

+ Your OAuth client has been issued new credentials. All previously issued + bearer tokens have been revoked. You must send users back through the + authorization process to restore access. +

+ {% endif %} +
+
Client ID
+
{{client_uuid}} +
Client secret
+
{{client_secret}} +
+
+ Your client secret will never be shown to you again. +
+ Continue {{icon('caret-right')}} +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-dashboard.html b/meta-custom/templates/oauth2-dashboard.html new file mode 100644 index 0000000..2053d3e --- /dev/null +++ b/meta-custom/templates/oauth2-dashboard.html @@ -0,0 +1,156 @@ +{% extends "meta.html" %} +{% block title %} +OAuth 2.0 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+ Notice! This is the OAuth 2.0 dashboard. Credentials issued + here are incompatible with the legacy API! + + Proceed to legacy OAuth Dashboard {{icon('caret-right')}} + +
+
+
+
+

Personal Access Tokens

+ {% if any(personal_tokens) %} +

You have issued the following personal access tokens:

+ + + + + + + + + + + {% for token in personal_tokens %} + + + + + + + {% endfor %} + +
CommentIssuedExpires
{{ token.comment }}{{ token.issued | date }}{{ token.expires | date }} + Revoke +
+ {% else %} +

You have not created any personal access tokens.

+ {% endif %} + + Generate new token {{icon("caret-right")}} + +
+
+

Authorized Clients

+ {% if any(oauth_grants) %} +

You have granted the following third parties access to your account:

+ + + + + + + + + + + + {% for grant in oauth_grants %} + + + + + + + + {% endfor %} + +
ServiceOperatorIssuedExpires
+ {# lol this hack is awful #} + {{ grant.client.name }} + {{icon('external-link-alt')}} + {{grant.client.owner.canonicalName}}{{grant.issued | date}}{{grant.expires | date}} + Revoke +
+ {% else %} +

You have not granted any third party clients access to your account.

+ {% endif %} +
+
+

Registered Clients

+ {% if client_revoked %} +
+ Your OAuth client has been unregistered. All bearer tokens issued to + your client have been revoked. +
+ {% endif %} +

+ Please consult our OAuth 2.0 documentation for information about OAuth clients. +

+ {% if any(oauth_clients) %} +

You have registered the following OAuth clients:

+ + + + + + + + + + {% for client in oauth_clients %} + + + + + + {% endfor %} + +
NameClient ID
+ {# lol this hack is awful #} + {{ client.name }} + {{icon('external-link-alt')}} + {{ client.uuid }} + Manage {{icon('caret-right')}} +
+ {% else %} +

You have not registered any OAuth clients yet.

+ {% endif %} + + Register new client {{icon("caret-right")}} + +
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-error.html b/meta-custom/templates/oauth2-error.html new file mode 100644 index 0000000..f9a2719 --- /dev/null +++ b/meta-custom/templates/oauth2-error.html @@ -0,0 +1,23 @@ +{% extends "meta.html" %} +{% block title %} +OAuth 2.0 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

OAuth 2.0 error occured

+

+ You're seeing this page because you were directed to a malformed OAuth + 2.0 authorization URL by a third-party client. If you are not the + client administrator, click here to proceed. If you are + the client administrator, the details of this error are: +

+
+
Error code
+
{{code}}
+
Error description
+
{{description}}
+
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-manage-client.html b/meta-custom/templates/oauth2-manage-client.html new file mode 100644 index 0000000..bf6b37a --- /dev/null +++ b/meta-custom/templates/oauth2-manage-client.html @@ -0,0 +1,70 @@ +{% extends "meta.html" %} +{% block title %} +"{{client.name}}" - OAuth 2.0 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +

OAuth 2.0 client management

+
+
+
+ {{csrf_token()}} +

Revoke tokens & client secret

+

+ If OAuth 2.0 bearer tokens issued for your OAuth client, or your client + secret, have been disclosed to a third-party, you must revoke all + tokens and have replacements issued. +

+
+
+ +
+
+
+ +
+ {{csrf_token()}} +

Unregister this OAuth client

+

+ This will permanently unregister your OAuth 2.0 client, + "{{client.name}}", revoke all tokens issued to it, and prohibit the + issuance of new tokens. +

+
+
+ +
+
+
+
+
+
+
Client ID
+
{{client.uuid}} +
Name
+
{{client.name}} +
Description
+
{{client.description}} +
Informative URL
+
+ {{client.url}} + +
Redirect URL
+
{{client.redirectUrl}} +
+
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-personal-token-issued.html b/meta-custom/templates/oauth2-personal-token-issued.html new file mode 100644 index 0000000..74fc991 --- /dev/null +++ b/meta-custom/templates/oauth2-personal-token-issued.html @@ -0,0 +1,22 @@ +{% extends "meta.html" %} +{% block title %} +Personal access token registered - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+
+
Personal Access Token
+
{{secret}} +
+

+ Your access token will never be shown to you again. Keep + this secret. It will expire {{expiry | date}}. +

+ Continue {{icon('caret-right')}} +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-personal-token-registration.html b/meta-custom/templates/oauth2-personal-token-registration.html new file mode 100644 index 0000000..1b041e7 --- /dev/null +++ b/meta-custom/templates/oauth2-personal-token-registration.html @@ -0,0 +1,94 @@ +{% extends "meta.html" %} +{% block title %} +Register personal access token - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Personal Access Token

+
+
+
+
+ {{csrf_token()}} +

+ Personal access tokens are used by third-party applications and scripts + to access to your {{cfg('sr.ht', 'site-name')}} account. +

+ {% if fixed_literal_grants %} +
+ The permissions for this access token have been pre-set to + {{fixed_literal_grants}}. +
+ + {% else %} +
+ Limit scope of access grant +
+ + +
+
+ +
+
+ + + {{valid.summary("literal_grants")}} +
+
+ {% endif %} +
+ + + + Arbitrary comment, for personal reference only + + {{valid.summary("comment")}} +
+
+ Notice: Sharing a personal access token is similar to + sharing your account password. Proceed with caution. +
+ + + Cancel {{icon('caret-right')}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/oauth2-register-client.html b/meta-custom/templates/oauth2-register-client.html new file mode 100644 index 0000000..15c3e7b --- /dev/null +++ b/meta-custom/templates/oauth2-register-client.html @@ -0,0 +1,83 @@ +{% extends "meta.html" %} +{% block title %} +Register OAuth 2.0 client - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

Register OAuth 2.0 client

+
+
+
+
+ {{csrf_token()}} +

+ {{cfg('sr.ht', 'site-name')}} provides API access via + RFC 6749-compatible OAuth 2.0 confidential clients. +

+
+ + + {{valid.summary("client_name")}} +
+
+ + + {{valid.summary("client_description")}} +
+
+ + + + Where can the user go to learn more about your client? + + {{valid.summary("client_url")}} +
+
+ + + + Where should we send the user after they consent to give you API access? + See RFC 6749 section 3.1.2. + + {{valid.summary("redirect_uri")}} +
+ + + Cancel {{icon('caret-right')}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/privacy.html b/meta-custom/templates/privacy.html new file mode 100644 index 0000000..4b25fc7 --- /dev/null +++ b/meta-custom/templates/privacy.html @@ -0,0 +1,61 @@ +{% extends "meta.html" %} +{% block title %} +隐私设置 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

邮件加密

+

+ {% if pgp_key_id %} + 所有来自 {{cfg("sr.ht", "site-name")}} 的邮件均使用以下密钥签名:
+ {{pgp_key_id}} + {% else %} + 来自 {{cfg("sr.ht", "site-name")}} 的邮件未启用加密。请联系 + {{owner.name}} <{{owner.email}}> + 以请求启用 PGP 邮件签名。 + {% endif %} +

+ {% if any(current_user.pgp_keys) %} +
+ {{csrf_token()}} +
+ +
+ {% for key in current_user.pgp_keys %} +
+ +
+ {% endfor %} + +
+
+ {{csrf_token()}} + +
+ {% elif pgp_key_id %} +

如果你添加 PGP 密钥到你的账户,我们可以对发给你的邮件进行加密。

+ {% endif %} +
+
+{% endblock %} diff --git a/meta-custom/templates/profile-delete.html b/meta-custom/templates/profile-delete.html new file mode 100644 index 0000000..9559dca --- /dev/null +++ b/meta-custom/templates/profile-delete.html @@ -0,0 +1,50 @@ +{% extends "meta.html" %} +{% block title %} +删除账户 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

删除你的 {{cfg("sr.ht", "site-name")}} 账户

+
+ {{csrf_token()}} +

+ 删除前,你可能需要先 + 导出账户数据。 + 如果你选择保留用户名,将来任何人(包括你自己)都无法使用该用户名注册。 +

+
+ + + {{valid.summary("confirm")}} +
+
+ + +
+ {{valid.summary()}} +
+ 警告:点击"确认删除"将永久删除你的账户、项目和所有个人数据。此操作无法撤销。 +
+ + + 取消 {{icon("caret-right")}} + +
+
+
+{% endblock %} diff --git a/meta-custom/templates/profile-deleted.html b/meta-custom/templates/profile-deleted.html new file mode 100644 index 0000000..38a6394 --- /dev/null +++ b/meta-custom/templates/profile-deleted.html @@ -0,0 +1,13 @@ +{% extends "layout.html" %} +{% block title %} +账户已删除 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+
+ 你的 {{cfg("sr.ht", "site-name")}} 账户正在删除中,无需进一步操作。 +
+
+
+{% endblock %} diff --git a/meta-custom/templates/profile.html b/meta-custom/templates/profile.html new file mode 100644 index 0000000..c0f0814 --- /dev/null +++ b/meta-custom/templates/profile.html @@ -0,0 +1,90 @@ +{% extends "meta.html" %} +{% block title %} +个人资料 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

编辑个人资料

+
+ {{csrf_token()}} +
+ + +
+
+ + + {% if new_email %} +
+ 已向 {{current_user.new_email}} 发送确认邮件。 +
+ {% endif %} + {{valid.summary("email")}} +
+
+ + + {{valid.summary("url")}} +
+
+ + + {{valid.summary("location")}} +
+
+ + + {{valid.summary("bio")}} +
+ +
+
+
+

导出数据

+

+ 你可以使用 + hut 工具以标准格式导出账户数据。 + 导出的数据可以导入到其他 SourceHut 实例,或用于任何兼容的软件(如 git、GNU Mailman 等)。 +

+ +

注销账户

+

+ 注销账户将永久删除你的项目和所有个人数据。 + 点击下方按钮进入确认页面。 +

+ + 删除我的账户 {{icon('caret-right')}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/register-step2.html b/meta-custom/templates/register-step2.html new file mode 100644 index 0000000..9b7fb3a --- /dev/null +++ b/meta-custom/templates/register-step2.html @@ -0,0 +1,151 @@ +{% extends "layout.html" %} +{% block title %} +注册 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 注册 {{cfg("sr.ht", "site-name")}} + + 或 登录 + +

+
+
+{% if is_external_auth() %} +

注册已禁用,{{cfg("sr.ht", "site-name")}} 的认证由其他服务管理。 + 请联系系统管理员了解详情。

+{% elif allow_registration() %} +{% if cfg("meta.sr.ht::billing", "enabled") == "yes" %} +
+
+

+ {% if payment %} + 你正在注册为维护者。完成注册后,你将前往账单页面了解付款方式和减免政策。 + 改为注册贡献者 {{icon('caret-right')}} + {% else %} + 你正在注册为贡献者,免费但部分功能受限。 + 完成注册后,你可以随时在个人设置中转换为维护者账户。 + 改为注册维护者 {{icon('caret-right')}} + {% endif %} +

+
+
+{% endif %} +
+
+
+ {{csrf_token()}} +
+ + + {{valid.summary("username")}} +
+
+ + + {{valid.summary("email")}} + {% if email and "+" in email %} + +
+ 警告:要正常使用 {{cfg("sr.ht", + "site-name")}},你必须能够使用此邮箱地址收发邮件。 + 如确认无误,请再次提交表单。 +
+ {% endif %} +
+
+ + + {{valid.summary("password")}} +
+ {% if site_key %} +
+
+ PGP 公钥(可选) + + + {{cfg("sr.ht", "site-name")}} 发出的邮件使用以下 PGP 密钥签名:
+ {{site_key}} +

+ 如果你在此添加 PGP 公钥,我们还会对发给你的邮件进行加密。 + 你可以稍后在设置中修改。 +

+

+ 重要! + 如果你现在提供了 PGP 公钥,你必须能够解密确认邮件才能完成注册。 +

+
+ {{valid.summary("pgpKey")}} +
+
+ {% endif %} + +

+
+
+
+ +
+
+ {{valid.summary()}} +
+
+ +
+
+
+ 隐私声明: + {{cfg("sr.ht", "site-name")}} 仅收集提供服务所必需的最少个人信息。 + 我们不会为营销或数据分析目的收集或处理你的个人信息,也不会发送营销邮件。 + 你的信息仅在为提供服务所必需时才会与第三方共享, + 且在此之前会通知你并给予你阻止信息传输的机会。 + 隐私政策 {{icon('external-link-alt')}} +
+
+
+{% else %} +

注册当前已关闭。

+{% endif %} +{% endblock %} diff --git a/meta-custom/templates/register.html b/meta-custom/templates/register.html new file mode 100644 index 0000000..f8186e5 --- /dev/null +++ b/meta-custom/templates/register.html @@ -0,0 +1,86 @@ +{% extends "layout.html" %} +{% block title %} +注册 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 注册 {{cfg("sr.ht", "site-name")}} + + 或 登录 + +

+
+
+{% if is_external_auth() %} +

注册已禁用,{{cfg("sr.ht", "site-name")}} 的认证由其他服务管理。 + 请联系系统管理员了解详情。

+{% elif allow_registration() %} +
+ {{csrf_token()}} +
+
+

注册为贡献者

+

+ 想要参与这里托管的项目? +
+ 你可以免费注册以参与 {{cfg("sr.ht", "site-name")}} 上托管的项目。 + 如果将来你想在这里托管自己的项目,可以随时转换为付费账户。 +

+ +
+
+
+
+

注册为维护者

+

+ 想要在这里托管自己的项目? +
+ 在 {{cfg("sr.ht", "site-name")}} 上托管项目需要付费。 + 有经济困难的用户可以申请减免。你可以随时取消而不会失去数据访问权限。 + + 价格详情 {{icon('external-link-alt')}} + +

+ +
+
+
+ +
+
+
+ 贡献者也可以不注册直接参与。你可以通过邮件提交或评论工单、参与讨论、 + 向 {{cfg("sr.ht", "site-name")}} 上的项目发送补丁,无需注册账户。 + 在未登录状态下,许多服务页面上都能找到通过邮件参与的链接。 +
+
+
+{% else %} +
+
+

注册当前已关闭。

+
+
+{% endif %} +{% endblock %} diff --git a/meta-custom/templates/registered.html b/meta-custom/templates/registered.html new file mode 100644 index 0000000..3cc67f6 --- /dev/null +++ b/meta-custom/templates/registered.html @@ -0,0 +1,19 @@ +{% extends "layout.html" %} +{% block title %} +确认账户 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 注册成功 +

+

+ 你将很快收到一封包含确认链接的邮件,请点击链接完成注册。 + 如需帮助,请联系 + + {{ "{} <{}>".format(cfg("sr.ht", "owner-name"), cfg("sr.ht", "owner-email")) }}。 +

+
+
+{% endblock %} diff --git a/meta-custom/templates/reset.html b/meta-custom/templates/reset.html new file mode 100644 index 0000000..4eb7103 --- /dev/null +++ b/meta-custom/templates/reset.html @@ -0,0 +1,35 @@ +{% extends "layout.html" %} +{% block title %} +重置密码 - {{cfg("sr.ht", "site-name")}} +{% endblock %} +{% block content %} +
+
+

+ 重置密码 +

+
+
+
+
+
+ {{csrf_token()}} +
+ + + {{valid.summary("password")}} +
+ {{valid.summary()}} + +
+
+
+{% endblock %} diff --git a/meta-custom/templates/security.html b/meta-custom/templates/security.html new file mode 100644 index 0000000..4cbde39 --- /dev/null +++ b/meta-custom/templates/security.html @@ -0,0 +1,80 @@ +{% extends "meta.html" %} +{% block title %} +安全设置 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+
+

两步验证

+

+ 两步验证通过在登录时要求额外的验证步骤来增强账户安全性。 + 强烈建议启用此功能。 +

+

TOTP

+ {% if totp %} +
+ 已启用,启用时间:{{totp.created | date}}。 +
+ {{csrf_token()}} + +
+
+ {% else %} +

+ 未启用。启用后,每次登录时需要输入 TOTP 验证码。 +

+

+ + + +

+ {% endif %} +
+
+

修改密码

+

重置链接将发送到你的账户邮箱({{current_user.email}})。

+ {% if allow_password_reset() %} +
+ {{csrf_token()}} + + +
+ {% else %} + 无法重置密码,因为 {{cfg("sr.ht", "site-name")}} 的认证由其他服务管理。 + {% endif %} +
+
+
+

账户活动日志

+ + + + + + + + + + {% for log in audit_log %} + + + + + + {% endfor %} + +
IP 地址详情时间
{{log.ip_address}}{{log.details or log.event_type}}{{log.created|date}}
+ + 查看完整日志 {{icon("caret-right")}} + +
+
+{% endblock %} diff --git a/meta-custom/templates/tabs.html b/meta-custom/templates/tabs.html new file mode 100644 index 0000000..f765ef7 --- /dev/null +++ b/meta-custom/templates/tabs.html @@ -0,0 +1,35 @@ +{% macro link(path, title, cls="") %} +{{ title }} +{% endmacro %} + + + + + + +{% if cfg("meta.sr.ht::billing", "enabled") == "yes" %} + +{% endif %} +{% if current_user.user_type == UserType.admin %} + +{% endif %} diff --git a/meta-custom/templates/totp-challenge.html b/meta-custom/templates/totp-challenge.html new file mode 100644 index 0000000..a56fe61 --- /dev/null +++ b/meta-custom/templates/totp-challenge.html @@ -0,0 +1,48 @@ +{% extends "layout.html" %} +{% block title %} +TOTP 验证 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

+ TOTP 验证 +

+
+
+
+
+

+ {% if challenge_type == "reset" %} + 此账户已启用两步验证,你需要完成验证才能重置密码。 + {% elif challenge_type == "disable_totp" %} + 要禁用两步验证,你需要先完成验证。 + {% endif %} + 请输入 TOTP 验证码继续: +

+
+ {{csrf_token()}} +
+ + + {{valid.summary("code")}} +
+
+ 如果你无法访问 2FA 设备,可以 + 使用恢复码。 + 如仍有问题,请联系管理员。 +
+ +
+
+
+{% endblock %} diff --git a/meta-custom/templates/totp-enable.html b/meta-custom/templates/totp-enable.html new file mode 100644 index 0000000..c172b0f --- /dev/null +++ b/meta-custom/templates/totp-enable.html @@ -0,0 +1,41 @@ +{% extends "meta.html" %} +{% block title %} +配置 TOTP 验证 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

启用 TOTP 验证

+

+ 使用 + Aegis 等应用扫描下方二维码,然后输入生成的验证码以启用 TOTP。 +

+ +
+ {{csrf_token()}} + +
+ + {{valid.summary("code")}} +
+ +
+
+
+{% endblock %} diff --git a/meta-custom/templates/totp-enabled.html b/meta-custom/templates/totp-enabled.html new file mode 100644 index 0000000..1017188 --- /dev/null +++ b/meta-custom/templates/totp-enabled.html @@ -0,0 +1,25 @@ +{% extends "meta.html" %} +{% block title %} +TOTP 设置完成 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

TOTP 已启用

+

+ 请保存以下恢复码,以防丢失 TOTP 设备时使用。 +

+
{% for code in codes -%}
+{{code}}
+{% endfor %}
+

+ 建议你同时保持账户中的 SSH 和 PGP 公钥为最新状态, + 以便在需要账户恢复时作为辅助验证方式。 +

+ 继续 {{icon('caret-right')}} +
+
+{% endblock %} diff --git a/meta-custom/templates/totp-recovery.html b/meta-custom/templates/totp-recovery.html new file mode 100644 index 0000000..2cad3aa --- /dev/null +++ b/meta-custom/templates/totp-recovery.html @@ -0,0 +1,46 @@ +{% extends "layout.html" %} +{% block title %} +TOTP 恢复 - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

+ TOTP 恢复 +

+
+
+
+
+ {% if supported %} +

+ 请输入一个 TOTP 恢复码继续: +

+
+ {{csrf_token()}} +
+ + + {{valid.summary("recovery-code")}} +
+

提交后将禁用你账户上的 TOTP。

+ +
+ {% else %} +
+ 你的 TOTP 在恢复码功能推出前配置,无法使用恢复码。 + 请联系管理员处理。 +
+ {% endif %} +
+
+{% endblock %} diff --git a/meta-custom/templates/user.html b/meta-custom/templates/user.html new file mode 100644 index 0000000..a41bbaa --- /dev/null +++ b/meta-custom/templates/user.html @@ -0,0 +1,531 @@ +{% extends "meta.html" %} +{% block title %} +~{{user.username}} - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+ Notice: This page contains private user information. + Remember your committment to protecting the privacy of the person listed + here and do not share any of this information with unauthorized + individuals. Don't fall for spear phishing — double check that you've + received an authentic support request before doing anything on the user's + behalf! +
+

~{{user.username}}

+
+
+
+
User ID
+
{{user.id}}
+
Email
+
+ {{user.email}} +
+
Registered
+
{{user.created | date}}
+
User type
+
{{user.user_type.value}}
+ {% if user.user_type.value == "suspended" %} +
Suspension Notice
+
{{user.suspension_notice}}
+ {% endif %} + + {% if user.location %} +
Location
+
{{user.location}}
+ {% endif %} + {% if user.url %} +
URL
+
+ {{user.url}} +
+ {% endif %} + + {% if cfg("meta.sr.ht::billing", "enabled") == "yes" + and user.stripe_customer %} +
Stripe customer
+
+ + {{user.stripe_customer}} + +
+
Payment amount
+
+ ${{"{:.2f}".format(user.payment_cents / 100)}} +
+
Payment interval
+
+ {{user.payment_interval.value}} +
+
Payment due
+
{{user.payment_due | date}}
+ {% endif %} +
+ {% if user.bio %} +
+ {{user.bio | md}} +
+ {% endif %} + {% if reset_pending %} +
+ reset link + Password reset pending. +
+ {% endif %} +
+ {{csrf_token()}} + + +
+ {% if totp %} +
+ {{csrf_token()}} + + This account has TOTP enabled. +
+ {% endif %} + +
+ {{csrf_token()}} + Check both boxes to delete this account: + + + + + +
+
+
+
+
+

User notes

+
+ {% for note in user.notes %} +
+ {{note.note}} + {{note.created | date}} +
+ {% endfor %} +
+
+ + {{valid.summary('notes')}} +
+ {{csrf_token()}} + +
+
+
+
+

Suspend account

+ {{csrf_token()}} +
+ +
+ +
+
+
+
+ {% if any(user.ssh_keys) %} +

SSH keys

+ + + + + + + + + + + {% for key in user.ssh_keys %} + + + + + + + {% endfor %} + +
NameFingerprintAuthorizedLast Used
{{key.comment}} +
+ {{key.fingerprint}} +
{{key.key}}
+
+
{{key.created|date}}{{key.last_used|date}}
+ {% endif %} + {% if any(user.pgp_keys) %} +

PGP keys

+ + + + + + + + + + {% for key in user.pgp_keys %} + + + + + + {% endfor %} + +
IDFingerprintAuthorized
{{key.id}} +
+ {{key.fingerprint_hex}} +
{{key.key}}
+
+
{{key.created|date}}
+ {% endif %} + {% if user.confirmation_hash %} +
+ confirmation link + This account is pending confirmation. +
+ {% endif %} +
+
+
+
+

Issue account credit

+ {{csrf_token()}} +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+{% if user.user_type.value == "active_paying" %} +
+
+

Transfer billing information

+
+ + + {{valid.summary('target')}} +
+ {{csrf_token()}} + +
+
+{% endif %} +
+
+

Audit Log

+ + + + + + + + + + + {% for log in audit_log %} + + + + + + + {% endfor %} + +
IP AddressHostDetailsDate
{{log.ip_address}}{{rdns.get(log.ip_address.exploded, "unknown")}}{{log.details or log.event_type}}{{log.created | date }}
+
+
+
+
+
+

Personal Access Tokens

+ + + + + + + + + + + {% for token in personal_tokens %} + + + + + + + {% endfor %} + +
CommentIssuedExpires
{{ token.comment }}{{ token.issued | date }}{{ token.expires | date }} + Revoke +
+
+
+

Authorized Clients

+ + + + + + + + + + + + {% for grant in oauth_grants %} + + + + + + + + {% endfor %} + +
ServiceOperatorIssuedExpires
+ {# lol this hack is awful #} + {{ grant.client.name }} + {{icon('external-link-alt')}} + {{grant.client.owner.canonicalName}}{{grant.issued | date}}{{grant.expires | date}} + Revoke +
+
+
+

Registered Clients

+ + + + + + + + + + {% for client in oauth_clients %} + + + + + + {% endfor %} + +
NameClient ID
+ {# lol this hack is awful #} + {{ client.name }} + {{icon('external-link-alt')}} + {{ client.uuid }} + Manage {{icon('caret-right')}} +
+
+
+

Invoices

+
+ {% for invoice in invoices %} +
+

+ + {{icon('check')}} + + ${{"{:.2f}".format(invoice.cents/100)}} + + with {{invoice.source}} + + + Export as PDF + +

+

+ Paid {{invoice.created | date}}
+ Valid for service until {{invoice.valid_thru | date}} + {% if invoice.comment %} +
+ {{invoice.comment}} + {% endif %} +

+
+ {% endfor %} +
+
+
+
+ {{csrf_token()}} +

Impersonate user

+
+ + + {{valid.summary('reason')}} +
+
+ This will send a security notification to the user and the admin security + mailing list. You must have the user's permission to use this feature. +
+ +
+
+{% endblock %} diff --git a/meta-custom/templates/users.html b/meta-custom/templates/users.html new file mode 100644 index 0000000..196380b --- /dev/null +++ b/meta-custom/templates/users.html @@ -0,0 +1,54 @@ +{% extends "meta.html" %} +{% block title %} +User admin - {{cfg("sr.ht", "site-name")}} meta +{% endblock %} +{% block content %} +
+
+

User administration

+
+ + {% if search_error %} +
{{ search_error }}
+ {% endif %} +
+
+ Notice: This page contains private user information. + Remember your committment to protecting the privacy of the people listed + here and do not share any of this information with unauthorized + individuals. +
+
+ {% for user in users %} +
+

+ + ~{{user.username}} + +

+
+
Email
+
+ {{user.email}} +
+
Registered
+
{{user.created | date}}
+
User type
+
{{user.user_type.value}}
+ {% if user.bio %} +
Bio
+
{{user.bio | md}}
+ {% endif %} +
+
+ {% endfor %} +
+ {{pagination()}} +
+
+{% endblock %} diff --git a/todo-custom/Dockerfile b/todo-custom/Dockerfile new file mode 100644 index 0000000..f3257e9 --- /dev/null +++ b/todo-custom/Dockerfile @@ -0,0 +1,84 @@ +# === Stage 1: Build core.sr.ht + todo.sr.ht from source === +# k8ieone 不提供 todo.sr.ht 镜像,需从源码构建 +FROM docker.io/library/alpine:3.20 AS builder + +RUN adduser -D builder +# 启用 community 仓库(py3-graphql-core 等依赖需要) +RUN echo "https://dl-cdn.alpinelinux.org/alpine/v3.20/community" >> /etc/apk/repositories +RUN apk add git alpine-sdk sudo nodejs npm go sassc minify +RUN echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder \ + && chmod 0440 /etc/sudoers.d/builder +RUN sudo -u builder abuild-keygen -a -i -n +RUN addgroup builder abuild + +RUN git clone https://git.sr.ht/~sircmpwn/sr.ht-apkbuilds +RUN chown -R builder:builder /sr.ht-apkbuilds + +# --- core.sr.ht 依赖 --- +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-mistletoe +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-celery +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-infinity +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-intervals +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-orderedmultidict +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-furl +RUN sudo -u builder abuild -r -K + +# --- core.sr.ht(同时产出 core.sr.ht-dev、py3-srht 子包)--- +WORKDIR /sr.ht-apkbuilds/sr.ht/core.sr.ht +RUN sudo -u builder abuild checksum +RUN sudo -u builder abuild -r -K + +# --- todo.sr.ht 额外构建依赖 --- +# py3-graphql-core 在 Alpine 3.20 不存在,从 edge 安装 +RUN apk add --repository https://dl-cdn.alpinelinux.org/alpine/edge/community py3-graphql-core +WORKDIR /sr.ht-apkbuilds/sr.ht/py3-autoflake +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/ariadne-codegen +# Alpine 3.20 的 hatchling 太旧,不支持新版 license-files 格式 +RUN apk add py3-pip && pip3 install --break-system-packages hatchling --upgrade +RUN sudo -u builder abuild -r -K +WORKDIR /sr.ht-apkbuilds/sr.ht/sourcehut-migrate +RUN sudo -u builder abuild -r -K + +# --- todo.sr.ht --- +WORKDIR /sr.ht-apkbuilds/sr.ht/todo.sr.ht +RUN sudo -u builder abuild checksum +RUN sudo -u builder abuild -r -K + + +# === Stage 2: Runner === +FROM docker.io/library/alpine:3.20 + +RUN echo "https://dl-cdn.alpinelinux.org/alpine/v3.20/community" >> /etc/apk/repositories + +COPY --from=builder /home/builder/packages /home/builder/packages +COPY --from=builder /etc/apk/keys/builder* /etc/apk/keys/ + +RUN apk add --repository /home/builder/packages/sr.ht todo.sr.ht +RUN apk add nginx sudo py3-alembic py3-pip +# celery 5.5.3 需要 kombu >= 5.4,Alpine 3.20 只有 5.3.7 +RUN pip3 install --break-system-packages 'kombu>=5.4' + +# core.sr.ht 0.78.6 重命名了部分 API,todo.sr.ht 0.77.5 仍使用旧名 +COPY compat_oauth.py /tmp/compat_oauth.py +RUN python3 /tmp/compat_oauth.py && rm /tmp/compat_oauth.py + +RUN adduser -D srht + +# --- Jinja2 修复 --- +COPY fix_jinja2_do.py /tmp/fix_jinja2_do.py +RUN python3 /tmp/fix_jinja2_do.py && rm /tmp/fix_jinja2_do.py + +# 创建 nginx 静态文件符号链接(适配 Python 版本) +RUN PYVER=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') \ + && ln -sf /usr/lib/python${PYVER}/site-packages/todosrht /srv/todosrht + +COPY start.sh /start.sh +COPY nginx.conf /etc/nginx/http.d/default.conf + +CMD ["/bin/sh", "/start.sh"] diff --git a/todo-custom/compat_oauth.py b/todo-custom/compat_oauth.py new file mode 100644 index 0000000..24cda98 --- /dev/null +++ b/todo-custom/compat_oauth.py @@ -0,0 +1,61 @@ +"""Compatibility patches for core.sr.ht 0.78.6 + todo.sr.ht 0.77.5. + +core.sr.ht 0.78.6 refactored the OAuth API: +- AbstractOAuthService removed, replaced by OAuthService (different signature) +- DelegatedScope removed, replaced by OAuthScope (different signature) +- SrhtFlask no longer accepts oauth_service=, uses user_class= instead +""" +import glob + +# 1. Patch srht/oauth/__init__.py: add compat aliases +OAUTH_PATCH = ''' + +# --- compat shim for todo.sr.ht 0.77.5 --- +class AbstractOAuthService(OAuthService): + """Compat wrapper: old API accepted (client_id, client_secret, delegated_scopes=, token_class=, user_class=)""" + def __init__(self, client_id=None, client_secret=None, + delegated_scopes=None, token_class=None, user_class=None, **kwargs): + super().__init__("todo.sr.ht", + user_class=user_class, + oauthtoken_class=token_class) + self.delegated_scopes = delegated_scopes or [] + +class DelegatedScope: + def __init__(self, name, description=None, write=False): + self.name = name + self.description = description + self.write = write +# --- end compat shim --- +''' + +for f in glob.glob("/usr/lib/python3.*/site-packages/srht/oauth/__init__.py"): + with open(f) as fh: + content = fh.read() + if "DelegatedScope" not in content: + with open(f, "a") as fh: + fh.write(OAUTH_PATCH) + print(f"Patched {f}: added AbstractOAuthService + DelegatedScope compat") + else: + print(f"Already patched: {f}") + +# 2. Patch todosrht/flask.py: use new SrhtFlask API +# Old: super().__init__("todo.sr.ht", __name__, oauth_service=TodoOAuthService()) +# New: super().__init__("todo.sr.ht", __name__, user_class=User, legacy_oauthtoken_class=OAuthToken) +for f in glob.glob("/usr/lib/python3.*/site-packages/todosrht/flask.py"): + with open(f) as fh: + content = fh.read() + if "oauth_service=TodoOAuthService()" in content: + content = content.replace( + "oauth_service=TodoOAuthService())", + "user_class=User, legacy_oauthtoken_class=OAuthToken)" + ) + # Add OAuthToken import + content = content.replace( + "from todosrht.types import TicketAccess, TicketStatus, TicketResolution, User", + "from todosrht.types import TicketAccess, TicketStatus, TicketResolution, User, OAuthToken" + ) + with open(f, "w") as fh: + fh.write(content) + print(f"Patched {f}: switched to new SrhtFlask API") + else: + print(f"Already patched: {f}") diff --git a/todo-custom/fix_jinja2_do.py b/todo-custom/fix_jinja2_do.py new file mode 100644 index 0000000..613df19 --- /dev/null +++ b/todo-custom/fix_jinja2_do.py @@ -0,0 +1,33 @@ +"""Enable Jinja2 'do' extension in sourcehut Flask app. + +Upstream fix: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39036 +""" +import glob + +candidates = glob.glob("/usr/lib/python3.*/site-packages/srht/flask.py") +if not candidates: + raise FileNotFoundError("Cannot find srht/flask.py") + +source_file = candidates[0] + +with open(source_file) as f: + content = f.read() + +target = "self.jinja_loader = ChoiceLoader" +patch = ' self.jinja_env.add_extension("jinja2.ext.do")' + +if "jinja2.ext.do" not in content: + lines = content.split("\n") + patched = False + for i, line in enumerate(lines): + if target in line: + lines.insert(i + 1, patch) + patched = True + break + if not patched: + raise RuntimeError(f"Could not find '{target}' in {source_file}") + with open(source_file, "w") as f: + f.write("\n".join(lines)) + print(f"Patched {source_file}: enabled jinja2.ext.do") +else: + print(f"Already patched: {source_file}") diff --git a/todo-custom/nginx.conf b/todo-custom/nginx.conf new file mode 100644 index 0000000..2e54ab9 --- /dev/null +++ b/todo-custom/nginx.conf @@ -0,0 +1,16 @@ +server { + listen 8080 default_server; + listen [::]:8080 default_server; + + location / { + proxy_pass http://127.0.0.1:5003; + } + + location /query { + proxy_pass http://127.0.0.1:5103; + } + + location /static { + root /usr/share/sourcehut/; + } +} diff --git a/todo-custom/start.sh b/todo-custom/start.sh new file mode 100644 index 0000000..0730c99 --- /dev/null +++ b/todo-custom/start.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -e + +# 初始化数据库表结构 + 迁移 +todo.sr.ht-initdb +sr.ht-migrate todo.sr.ht upgrade head +todo.sr.ht-migrate upgrade head + +# todo.sr.ht web +mkdir -p /run/todo.sr.ht +chown -R srht:srht /run/todo.sr.ht +chmod 775 /run/todo.sr.ht +sudo -u srht prometheus_multiproc_dir=/run/todo.sr.ht \ + /usr/bin/gunicorn todosrht.app:app \ + -b 0.0.0.0:5003 & + +# todo.sr.ht GraphQL API +sudo -u srht /usr/bin/todo.sr.ht-api \ + -b 0.0.0.0:5103 & + +# todo.sr.ht webhooks worker +sudo -u srht /usr/bin/celery \ + -A todosrht.webhooks worker \ + --loglevel=info & + +# todo.sr.ht LMTP(邮件创建工单,需额外配置 MX 记录) +# sudo -u srht /usr/bin/todo.sr.ht-lmtp & + +nginx & + +tail -f /dev/null -- 2.38.5