657 lines
27 KiB
Python
657 lines
27 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""批量生成论坛演示内容:用户、帖子、评论、点赞、收藏。"""
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
from app import app, db
|
||
from models import User, ForumPost, ForumComment, ForumCategory, ForumPostLike, ForumPostBookmark
|
||
|
||
|
||
DEFAULT_PASSWORD = "Forum123456"
|
||
|
||
USERS = [
|
||
{"username": "ops_alan", "password": DEFAULT_PASSWORD},
|
||
{"username": "dev_yuki", "password": DEFAULT_PASSWORD},
|
||
{"username": "cloud_nana", "password": DEFAULT_PASSWORD},
|
||
{"username": "linux_mason", "password": DEFAULT_PASSWORD},
|
||
{"username": "sec_neo", "password": DEFAULT_PASSWORD},
|
||
{"username": "ai_rookie", "password": DEFAULT_PASSWORD},
|
||
]
|
||
|
||
|
||
POSTS = [
|
||
{
|
||
"title": "Ubuntu 22.04 安装宝塔面板后的 10 项安全加固(实战清单)",
|
||
"author": "ops_alan",
|
||
"category": "运维经验",
|
||
"days_ago": 7,
|
||
"is_featured": True,
|
||
"content": """背景:很多同学装完宝塔就直接上线,结果 2-3 天就被扫端口、爆破后台。
|
||
|
||
我自己在生产环境里执行的加固顺序如下,基本可以在 30 分钟内做完:
|
||
1. 安装后第一时间修改宝塔面板入口和端口,不使用默认路径。
|
||
2. 宝塔后台开启登录限制(失败次数锁定 + 验证码 + 仅允许白名单 IP)。
|
||
3. SSH 禁止 root 密码登录,只保留密钥登录;同时改 SSH 端口并记录在资产台账。
|
||
4. 开启 UFW / firewalld,只放行必须端口(22/80/443),其余全部拒绝。
|
||
5. 云厂商安全组与系统防火墙双层控制,避免“安全组放行过宽”。
|
||
6. 删除不必要的默认站点,避免目录遍历和弱口令后台暴露。
|
||
7. 数据库不要对公网开放 3306,应用与数据库尽量走内网。
|
||
8. 配置定时备份(站点、数据库、配置文件),并异地存储。
|
||
9. 安装 fail2ban,对 SSH 和 Web 登录做自动封禁。
|
||
10. 上线后立即做一次端口扫描与弱口令自检,确认暴露面。
|
||
|
||
建议:宝塔是效率工具,不是安全工具。上线前至少做一次“攻击面最小化”检查。""",
|
||
"comments": [
|
||
("sec_neo", "这个清单很实用,尤其是 SSH 密钥登录 + fail2ban,很多人会忽略。"),
|
||
("cloud_nana", "补充一点:最好把宝塔登录入口放到仅内网访问,再通过跳板机管理。"),
|
||
],
|
||
"likes": ["dev_yuki", "cloud_nana", "linux_mason", "sec_neo"],
|
||
"bookmarks": ["dev_yuki", "ai_rookie", "linux_mason"],
|
||
},
|
||
{
|
||
"title": "服务器开放端口的正确姿势:安全组、防火墙、服务监听三层排查",
|
||
"author": "sec_neo",
|
||
"category": "运维经验",
|
||
"days_ago": 6,
|
||
"content": """很多“端口开了但访问不到”的问题,本质是三层中有一层没打通。
|
||
|
||
排查顺序建议固定为:
|
||
第一层:云安全组
|
||
- 入站规则是否允许目标端口(如 80/443);
|
||
- 来源 CIDR 是否过于严格(如只放了办公网);
|
||
- 是否误把规则加在了错误的网卡或实例组。
|
||
|
||
第二层:系统防火墙
|
||
- `ufw status` / `firewall-cmd --list-all` 检查是否放行;
|
||
- 是否有默认拒绝策略导致端口被拦;
|
||
- 改完记得 reload。
|
||
|
||
第三层:服务监听
|
||
- `ss -lntp | grep 80` 检查进程是否监听;
|
||
- 注意监听地址是 `0.0.0.0` 还是 `127.0.0.1`;
|
||
- 应用容器端口映射是否正确(Docker 常见问题)。
|
||
|
||
最后一步:
|
||
- 本机 `curl 127.0.0.1:端口`;
|
||
- 内网 `curl 内网IP:端口`;
|
||
- 外网 `curl 公网IP:端口`。
|
||
|
||
这样基本 10-15 分钟能定位到具体环节。""",
|
||
"comments": [
|
||
("ops_alan", "三层模型非常清晰,新手排障直接照这个顺序来就行。"),
|
||
("ai_rookie", "我之前就是服务只监听 127.0.0.1,难怪外网怎么都不通。"),
|
||
],
|
||
"likes": ["ops_alan", "dev_yuki", "ai_rookie"],
|
||
"bookmarks": ["ops_alan", "cloud_nana", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "Nginx 绑定域名 + HTTPS 全流程(含 301、HSTS、自动续期)",
|
||
"author": "cloud_nana",
|
||
"category": "运维经验",
|
||
"days_ago": 5,
|
||
"is_featured": True,
|
||
"content": """这篇给一个可直接落地的域名绑定流程,适合新站上线:
|
||
|
||
步骤 1:DNS 解析
|
||
- A 记录指向服务器公网 IP;
|
||
- 等待生效(通常 1-10 分钟,部分服务商更久)。
|
||
|
||
步骤 2:Nginx server block
|
||
- `server_name` 填主域名 + www;
|
||
- 先用 HTTP 跑通站点,不要一开始就上证书。
|
||
|
||
步骤 3:申请证书(Let's Encrypt)
|
||
- 推荐 certbot;
|
||
- 申请成功后检查证书路径和 Nginx 引用是否一致。
|
||
|
||
步骤 4:强制 HTTPS
|
||
- 80 端口统一做 301 跳转到 https;
|
||
- 保留 ACME 验证路径例外(如果你用 webroot 方式)。
|
||
|
||
步骤 5:安全头
|
||
- 开启 HSTS(先短时间,再逐步拉长);
|
||
- 可补充 `X-Content-Type-Options`、`X-Frame-Options`。
|
||
|
||
步骤 6:自动续期
|
||
- `certbot renew --dry-run` 先验证;
|
||
- 用 systemd timer / crontab 定期续期并 reload Nginx。
|
||
|
||
上线后建议在 SSL Labs 跑一遍,确保协议和套件配置达标。""",
|
||
"comments": [
|
||
("linux_mason", "赞同先 HTTP 再 HTTPS,很多人一上来配证书容易定位困难。"),
|
||
("sec_neo", "HSTS 建议分阶段,别一开始就 preload,回滚会很麻烦。"),
|
||
],
|
||
"likes": ["ops_alan", "linux_mason", "sec_neo", "dev_yuki"],
|
||
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "OpenClaw 在 Ubuntu 服务器安装与 systemd 守护(可维护版)",
|
||
"author": "dev_yuki",
|
||
"category": "运维经验",
|
||
"days_ago": 4,
|
||
"content": """分享我在 Ubuntu 22.04 上部署 OpenClaw 的方式,重点是“稳定运行 + 易于维护”。
|
||
|
||
推荐目录结构:
|
||
- `/opt/openclaw`:程序目录
|
||
- `/etc/openclaw/`:配置目录
|
||
- `/var/log/openclaw/`:日志目录
|
||
|
||
部署建议:
|
||
1. 用独立系统用户运行(不要用 root)。
|
||
2. 配置文件与程序文件分离,便于升级回滚。
|
||
3. 写 systemd 服务:
|
||
- `Restart=always`
|
||
- `RestartSec=5`
|
||
- 限制权限(`NoNewPrivileges=true` 等)
|
||
4. 日志统一走 journald 或文件滚动,避免磁盘被打满。
|
||
5. 升级流程采用“解压新版本 -> 切换软链 -> 重启服务”。
|
||
|
||
上线前 checklist:
|
||
- `systemctl status openclaw`
|
||
- `journalctl -u openclaw -n 200`
|
||
- 健康检查接口可用
|
||
- 端口仅对需要的来源开放
|
||
|
||
这样做的好处是:故障定位快、升级可回滚、不会因为单次异常导致服务长期不可用。""",
|
||
"comments": [
|
||
("ops_alan", "目录分层 + 软链切换这个方案很稳,适合长期维护。"),
|
||
("cloud_nana", "建议再加个健康检查脚本,配合告警一起用效果更好。"),
|
||
],
|
||
"likes": ["ops_alan", "cloud_nana", "linux_mason"],
|
||
"bookmarks": ["ops_alan", "sec_neo", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "宝塔部署 Flask:Gunicorn + Nginx + Supervisor 一次跑通",
|
||
"author": "linux_mason",
|
||
"category": "运维经验",
|
||
"days_ago": 3,
|
||
"content": """很多同学卡在“本地能跑,宝塔上线 502”。我这边给一个稳定组合:
|
||
|
||
架构:
|
||
Nginx (80/443) -> Gunicorn (127.0.0.1:8000) -> Flask App
|
||
|
||
关键点:
|
||
1. Flask 不直接对外暴露,Gunicorn 只监听本机回环地址。
|
||
2. Nginx `proxy_pass` 指向 Gunicorn,注意 `proxy_set_header` 要完整。
|
||
3. Gunicorn worker 数量按 CPU 计算,不要盲目拉满。
|
||
4. 用 Supervisor/systemd 托管 Gunicorn,防止进程意外退出。
|
||
5. 目录权限统一,避免静态文件 403。
|
||
|
||
常见坑:
|
||
- 虚拟环境没激活导致依赖缺失;
|
||
- Nginx 与 Gunicorn socket/端口不一致;
|
||
- 项目根目录配置错误导致模块导入失败。
|
||
|
||
建议每次发布后执行:
|
||
- `nginx -t`
|
||
- 访问健康检查 URL
|
||
- 查看 Nginx 和应用日志是否有 5xx。""",
|
||
"comments": [
|
||
("dev_yuki", "Gunicorn 只监听 127.0.0.1 这点很重要,安全收益很高。"),
|
||
("ai_rookie", "终于理解 502 的定位方式了,之前一直只看应用日志。"),
|
||
],
|
||
"likes": ["ops_alan", "dev_yuki", "cloud_nana", "ai_rookie"],
|
||
"bookmarks": ["cloud_nana", "ai_rookie", "sec_neo"],
|
||
},
|
||
{
|
||
"title": "为什么放行了 80/443 还是打不开网站?15 分钟排障流程",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 2,
|
||
"is_pinned": True,
|
||
"content": """我把安全组 80/443 放开了,但浏览器还是超时。请大家帮忙看看排障顺序是否正确:
|
||
|
||
我目前做了:
|
||
1. 云安全组入站已放行 80/443(来源 0.0.0.0/0)。
|
||
2. Ubuntu 上 `ufw allow 80,443/tcp`。
|
||
3. Nginx 已启动,`systemctl status nginx` 显示 active。
|
||
4. 域名 A 记录已指向服务器公网 IP。
|
||
|
||
我准备进一步排查:
|
||
- `ss -lntp | grep :80` 确认监听地址;
|
||
- 本机 curl 127.0.0.1;
|
||
- 外网 curl 公网 IP;
|
||
- 检查 Nginx default server 是否误拦截。
|
||
|
||
如果还有常见遗漏项,请大家补充一下,我整理后回帖反馈结果。""",
|
||
"comments": [
|
||
("sec_neo", "再查一下运营商端口封禁和实例是否有公网带宽,这两个也常见。"),
|
||
("cloud_nana", "域名解析生效可以用 dig/nslookup 验证,避免本地 DNS 缓存干扰。"),
|
||
("ops_alan", "可以先不用域名,直接公网 IP 打通后再回到域名层。"),
|
||
],
|
||
"likes": ["ops_alan", "cloud_nana", "dev_yuki", "linux_mason"],
|
||
"bookmarks": ["ops_alan", "dev_yuki"],
|
||
},
|
||
{
|
||
"title": "新服务器首日初始化 SOP:账号、时间、日志、监控一步到位",
|
||
"author": "ops_alan",
|
||
"category": "运维经验",
|
||
"days_ago": 1,
|
||
"content": """为了避免“上线后再补安全”这种被动局面,我把新机初始化收敛成一个 SOP:
|
||
|
||
基础项:
|
||
- 创建普通运维账号 + sudo;
|
||
- 配置 SSH 密钥登录;
|
||
- 关闭 root 密码登录;
|
||
- 设置时区、NTP 同步。
|
||
|
||
系统项:
|
||
- 安装常用诊断工具(curl, wget, vim, htop, lsof, ss);
|
||
- 配置日志轮转;
|
||
- 开启防火墙并最小放行。
|
||
|
||
可观测项:
|
||
- 主机监控(CPU、内存、磁盘、负载);
|
||
- 进程可用性检查;
|
||
- 磁盘与证书过期告警。
|
||
|
||
交付项:
|
||
- 资产信息登记(IP、用途、负责人、到期时间);
|
||
- 变更记录模板;
|
||
- 回滚方案。
|
||
|
||
这个流程固定下来后,服务器上线质量会稳定很多。""",
|
||
"comments": [
|
||
("linux_mason", "强烈建议把资产台账自动化,不然机器一多很容易混乱。"),
|
||
("sec_neo", "同意,首日就把监控和告警接好,能省很多夜间故障时间。"),
|
||
],
|
||
"likes": ["dev_yuki", "cloud_nana", "linux_mason", "sec_neo", "ai_rookie"],
|
||
"bookmarks": ["dev_yuki", "cloud_nana", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "服务器端口规划建议:Web、数据库、SSH 与内网隔离实践",
|
||
"author": "sec_neo",
|
||
"category": "综合讨论",
|
||
"days_ago": 1,
|
||
"is_featured": True,
|
||
"content": """分享一个适合中小团队的端口规划思路,核心目标是“暴露最小化”:
|
||
|
||
公网可见:
|
||
- 80/443:Web 流量入口(建议统一走 Nginx/网关)
|
||
- 22:仅白名单 IP,最好配合堡垒机
|
||
|
||
仅内网可见:
|
||
- 3306/5432:数据库
|
||
- 6379:缓存
|
||
- 9200:搜索服务(若有)
|
||
|
||
管理面:
|
||
- 面板、监控、日志平台尽量不直接暴露公网;
|
||
- 必要时使用 VPN / 跳板机访问。
|
||
|
||
执行原则:
|
||
1. 先拒绝,后放行(default deny)。
|
||
2. 安全组与主机防火墙同时配置。
|
||
3. 定期做“端口盘点”和“僵尸规则清理”。
|
||
|
||
端口规划不是一次性工作,建议每月复盘一次,防止规则持续膨胀。""",
|
||
"comments": [
|
||
("ops_alan", "我们团队也是按这个思路做,尤其是数据库绝不出公网。"),
|
||
("dev_yuki", "建议再加一条:高危端口变更必须走审批和审计。"),
|
||
],
|
||
"likes": ["ops_alan", "cloud_nana", "dev_yuki", "linux_mason"],
|
||
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie", "cloud_nana"],
|
||
},
|
||
{
|
||
"title": "宝塔里 Nginx 显示运行中,但网站一直 502,应该按什么顺序排查?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"is_pinned": True,
|
||
"content": """我在宝塔上部署了 Flask,Nginx 状态是绿色“运行中”,但是访问域名一直 502。
|
||
|
||
目前我确认过:
|
||
1. 域名解析已经指向服务器 IP;
|
||
2. 80/443 在安全组里放行;
|
||
3. 应用进程偶尔会起来,但不稳定。
|
||
|
||
请问排查顺序是不是这样更高效?
|
||
- 看 Nginx 错误日志(确认是 upstream 超时还是连接拒绝);
|
||
- 用 `ss -lntp` 看 Gunicorn 是否在监听;
|
||
- 本机 curl `127.0.0.1:应用端口`;
|
||
- 检查宝塔反向代理的目标端口是否写错;
|
||
- 查看应用日志是否有导入错误或环境变量缺失。
|
||
|
||
如果还有常见坑,请大家补充一下,我整理成 checklist。""",
|
||
"comments": [
|
||
("linux_mason", "顺序正确。先从 Nginx error.log 判断是 connect refused 还是 timeout,再决定下一步。"),
|
||
("dev_yuki", "补充:检查 Python 虚拟环境路径,宝塔里最容易因为路径错导致 Gunicorn 启动失败。"),
|
||
("ops_alan", "再确认一下 systemd/supervisor 是否开启自动拉起,不然进程崩了就一直 502。"),
|
||
],
|
||
"likes": ["ops_alan", "linux_mason", "dev_yuki", "cloud_nana"],
|
||
"bookmarks": ["ops_alan", "dev_yuki", "sec_neo"],
|
||
},
|
||
{
|
||
"title": "域名解析已经改了,为什么访问还是旧服务器?DNS 缓存怎么判断?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """我把 A 记录改到新 VPS 后,自己电脑访问还是旧站点,但手机 4G 打开有时是新站。
|
||
|
||
我怀疑是 DNS 缓存问题,想确认排查步骤:
|
||
1. `dig 域名 +short` 看当前解析结果;
|
||
2. 对比不同 DNS(8.8.8.8 / 1.1.1.1 / 本地运营商);
|
||
3. 清本地 DNS 缓存 + 浏览器缓存;
|
||
4. 检查是否还有 AAAA 记录指向旧机器;
|
||
5. 观察 TTL 到期时间。
|
||
|
||
有没有更稳妥的切换方案,避免业务迁移时出现“部分用户命中旧站”?""",
|
||
"comments": [
|
||
("cloud_nana", "迁移前建议先把 TTL 从 600 调到 60,等全网生效后再切 IP。"),
|
||
("sec_neo", "你提到 AAAA 很关键,很多人只改 A 记录,IPv6 用户会继续走旧站。"),
|
||
],
|
||
"likes": ["cloud_nana", "sec_neo", "ops_alan"],
|
||
"bookmarks": ["ops_alan", "linux_mason", "dev_yuki"],
|
||
},
|
||
{
|
||
"title": "Ubuntu 放行了 8080 还是连不上,安全组和防火墙到底谁优先?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """我在服务器里执行了 `ufw allow 8080/tcp`,应用也监听 `0.0.0.0:8080`,但外网还是连不上。
|
||
|
||
我想搞清楚优先关系:
|
||
- 云安全组没放行时,系统防火墙放行有没有意义?
|
||
- 安全组放行了但 UFW 拒绝,会是什么表现?
|
||
- 能否用一个最简流程快速判断问题在哪一层?
|
||
|
||
现在新人常被“服务正常、端口不通”卡住,求一个固定排障模板。""",
|
||
"comments": [
|
||
("sec_neo", "先看安全组再看系统防火墙,任意一层拒绝都不通。两层都要放行才行。"),
|
||
("ops_alan", "建议固定三步:安全组 -> 防火墙 -> 服务监听,避免来回猜。"),
|
||
],
|
||
"likes": ["sec_neo", "ops_alan", "dev_yuki"],
|
||
"bookmarks": ["ai_rookie", "cloud_nana", "linux_mason"],
|
||
},
|
||
{
|
||
"title": "绑定域名后总跳到 Nginx Welcome Page,server_name 应该怎么配?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """我已经把域名解析到了服务器,也有自己的站点配置,但访问域名总是落到默认欢迎页。
|
||
|
||
我检查到:
|
||
1. `/etc/nginx/sites-enabled` 里同时存在 default 和我的站点;
|
||
2. 我的 server_name 只写了主域名,没写 www;
|
||
3. 有多个配置都监听 80。
|
||
|
||
请问正确做法是不是:
|
||
- 删除/禁用默认站点;
|
||
- server_name 同时写 `example.com` 和 `www.example.com`;
|
||
- 用 `nginx -t` 检查冲突,再 reload。""",
|
||
"comments": [
|
||
("linux_mason", "是的,默认站点优先命中非常常见。建议明确一个 default_server,其他按域名精确匹配。"),
|
||
("cloud_nana", "别忘了 HTTPS 的 server block 也要同步配置,不然 443 还是会走错。"),
|
||
],
|
||
"likes": ["linux_mason", "cloud_nana", "ops_alan"],
|
||
"bookmarks": ["dev_yuki", "ops_alan", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "OpenClaw 服务启动成功但页面空白,日志里没有明显报错怎么办?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """我按教程装了 OpenClaw,systemd 状态是 active,但前端页面空白或一直 loading。
|
||
|
||
我目前想到的排查:
|
||
1. 检查配置文件里 API 地址是否写错(内网/公网);
|
||
2. 浏览器开发者工具看网络请求是否 4xx/5xx;
|
||
3. 检查反向代理路径和 WebSocket 升级头;
|
||
4. 看 openclaw 日志级别是否太低,临时改为 debug;
|
||
5. 校验数据库连接和初始化状态。
|
||
|
||
有没有人踩过类似坑,最后是哪里的问题?""",
|
||
"comments": [
|
||
("dev_yuki", "优先看浏览器 network 面板,空白页大概率是静态资源 404 或 API 跨域。"),
|
||
("sec_neo", "如果走反代,确认 `Upgrade`/`Connection` 头,WebSocket 缺这个会卡 loading。"),
|
||
],
|
||
"likes": ["dev_yuki", "sec_neo", "cloud_nana"],
|
||
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "申请 Let's Encrypt 一直失败(too many failed authorizations),还能怎么处理?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """我在同一个域名上连续尝试了很多次证书申请,现在提示 `too many failed authorizations`。
|
||
|
||
我理解是被限流了,但不确定下一步:
|
||
1. 是不是要先等一段时间再重试?
|
||
2. 失败期间怎么保证业务可访问(临时 HTTP / 自签证书)?
|
||
3. 下次重试前要先验证哪些条件,避免再次失败?
|
||
|
||
我现在的域名解析已经正常,80 端口也放开了。""",
|
||
"comments": [
|
||
("cloud_nana", "先等限流窗口过去,再用 `--dry-run` 或 staging 环境验证流程,别直接打生产接口。"),
|
||
("ops_alan", "重试前先用 HTTP 明确能访问 `/.well-known/acme-challenge/`,这一步最关键。"),
|
||
],
|
||
"likes": ["cloud_nana", "ops_alan", "linux_mason"],
|
||
"bookmarks": ["dev_yuki", "ops_alan", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "宝塔用几天后磁盘爆满,新手该先清哪些目录?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """2C4G 小机器,刚装宝塔一周磁盘就 90% 了,怕直接删错文件。
|
||
|
||
目前怀疑的占用来源:
|
||
- Nginx / 应用日志;
|
||
- 宝塔自动备份;
|
||
- Docker 镜像与容器日志;
|
||
- 数据库 binlog。
|
||
|
||
有没有一个“安全清理顺序”?
|
||
我希望先释放空间,后面再补定期清理策略。""",
|
||
"comments": [
|
||
("ops_alan", "先 `du -sh /*` 定位大头,再按日志->备份->镜像顺序清理,不要盲删系统目录。"),
|
||
("linux_mason", "Docker 场景补一句:`docker system df` 先看占用,再按策略 prune。"),
|
||
],
|
||
"likes": ["ops_alan", "linux_mason", "sec_neo"],
|
||
"bookmarks": ["cloud_nana", "dev_yuki", "ai_rookie"],
|
||
},
|
||
{
|
||
"title": "新手配置选型:2核4G 能不能同时跑 Flask + MySQL + OpenClaw?",
|
||
"author": "ai_rookie",
|
||
"category": "新手提问",
|
||
"days_ago": 0,
|
||
"content": """预算有限,先买了 2核4G 机器,想跑:
|
||
1. Flask 网站
|
||
2. MySQL
|
||
3. OpenClaw
|
||
|
||
担心点:
|
||
- 高峰时内存不够导致 OOM;
|
||
- MySQL 占用太大拖慢接口;
|
||
- 后续扩容迁移复杂。
|
||
|
||
有没有比较稳妥的起步建议?例如哪些服务先拆、哪些参数先限制?""",
|
||
"comments": [
|
||
("dev_yuki", "2核4G 能跑,但建议先把 MySQL 参数收紧,Gunicorn worker 不要开太多。"),
|
||
("cloud_nana", "如果流量上来,优先把数据库拆到独立实例,应用层水平扩展更简单。"),
|
||
("sec_neo", "别忘了开启 swap 兜底,但它只是缓冲,不能替代真正扩容。"),
|
||
],
|
||
"likes": ["dev_yuki", "cloud_nana", "sec_neo", "ops_alan"],
|
||
"bookmarks": ["ops_alan", "linux_mason", "ai_rookie", "cloud_nana"],
|
||
},
|
||
{
|
||
"title": "OpenClaw 安装实战:从 0 到可用(官方安装页版)",
|
||
"author": "dev_yuki",
|
||
"category": "运维经验",
|
||
"days_ago": 0,
|
||
"is_featured": True,
|
||
"content": """这篇按官方安装页(https://openclaw.im/#install)整理,目标是:新手 10-20 分钟跑通。
|
||
|
||
一、安装方式(推荐一键脚本)
|
||
1. 服务器执行:
|
||
`curl -fsSL https://openclaw.im/install.sh | bash`
|
||
2. 安装后检查:
|
||
`openclaw --version`
|
||
|
||
如果你更习惯包管理器,也可以:
|
||
- npm: `npm install -g openclaw@latest`
|
||
- pnpm: `pnpm add -g openclaw@latest`
|
||
|
||
二、初始化(关键步骤)
|
||
1. 执行引导:
|
||
`openclaw onboard --install-daemon`
|
||
2. 按提示完成 Provider、模型、存储等配置。
|
||
3. 安装完成后,建议先跑健康检查:
|
||
`openclaw gateway status`
|
||
|
||
三、Web 控制台与连接
|
||
1. 默认控制台地址:
|
||
`http://127.0.0.1:18789/`
|
||
2. 如果你是远程服务器,建议用 Nginx 反向代理并开启 HTTPS。
|
||
3. 需要连接频道时,使用:
|
||
`openclaw channels login`
|
||
|
||
四、Docker 部署(可选)
|
||
官方也提供 Docker 工作流,常见顺序:
|
||
1. `./docker-setup.sh`
|
||
2. `docker compose run --rm openclaw-cli onboard`
|
||
3. `docker compose up -d openclaw-gateway`
|
||
|
||
五、排错清单(最常见)
|
||
1. 命令执行失败:先检查 Node 版本(官方建议 Node >= 22)。
|
||
2. 页面打不开:确认 18789 端口监听与防火墙/安全组放行。
|
||
3. 网关状态异常:先看 `openclaw gateway status`,再复查 onboard 配置。
|
||
4. 远程访问不稳定:优先通过反向代理统一入口,不直接暴露高风险端口。
|
||
|
||
六、上线建议
|
||
1. 把配置与日志目录分离,方便升级和回滚。
|
||
2. 使用守护方式运行(onboard 的 daemon 选项),避免进程意外退出。
|
||
3. 做最小暴露:仅开放必要端口,后台入口加访问控制。""",
|
||
"comments": [
|
||
("ops_alan", "这篇很适合新手,先一键安装再做反代是比较稳的路径。"),
|
||
("sec_neo", "建议补一句:公网部署务必加 HTTPS 和访问控制,别裸奔。"),
|
||
("ai_rookie", "我刚按这个流程跑通了,`openclaw gateway status` 这个检查很有用。"),
|
||
],
|
||
"likes": ["ops_alan", "cloud_nana", "linux_mason", "sec_neo", "ai_rookie"],
|
||
"bookmarks": ["ops_alan", "cloud_nana", "ai_rookie"],
|
||
},
|
||
]
|
||
|
||
|
||
def _utcnow_naive():
|
||
"""兼容 Python 3.13:返回无时区 UTC 时间。"""
|
||
return datetime.now(timezone.utc).replace(tzinfo=None)
|
||
|
||
|
||
def _resolve_category(name):
|
||
if not name:
|
||
return "综合讨论"
|
||
row = ForumCategory.query.filter_by(name=name).first()
|
||
if row:
|
||
return row.name
|
||
active = ForumCategory.query.filter_by(is_active=True).order_by(ForumCategory.sort_order.asc()).first()
|
||
if active:
|
||
return active.name
|
||
any_row = ForumCategory.query.order_by(ForumCategory.sort_order.asc()).first()
|
||
if any_row:
|
||
return any_row.name
|
||
return "综合讨论"
|
||
|
||
|
||
def _get_or_create_user(username, password):
|
||
user = User.query.filter_by(username=username).first()
|
||
created = False
|
||
if user is None:
|
||
user = User(username=username)
|
||
user.set_password(password)
|
||
user.last_login_at = _utcnow_naive()
|
||
db.session.add(user)
|
||
db.session.flush()
|
||
created = True
|
||
return user, created
|
||
|
||
|
||
def main():
|
||
created_users = 0
|
||
skipped_users = 0
|
||
created_posts = 0
|
||
skipped_posts = 0
|
||
created_comments = 0
|
||
created_likes = 0
|
||
created_bookmarks = 0
|
||
|
||
with app.app_context():
|
||
user_map = {}
|
||
for u in USERS:
|
||
user, created = _get_or_create_user(u["username"], u["password"])
|
||
user_map[u["username"]] = user
|
||
if created:
|
||
created_users += 1
|
||
else:
|
||
skipped_users += 1
|
||
|
||
db.session.flush()
|
||
|
||
for idx, spec in enumerate(POSTS):
|
||
title = spec["title"].strip()
|
||
author = user_map.get(spec["author"])
|
||
if not author:
|
||
continue
|
||
|
||
exists = ForumPost.query.filter_by(title=title).first()
|
||
if exists:
|
||
skipped_posts += 1
|
||
continue
|
||
|
||
created_at = _utcnow_naive() - timedelta(days=int(spec.get("days_ago", 0)), hours=max(0, idx))
|
||
post = ForumPost(
|
||
user_id=author.id,
|
||
category=_resolve_category(spec.get("category")),
|
||
title=title,
|
||
content=spec["content"].strip(),
|
||
is_pinned=bool(spec.get("is_pinned")),
|
||
is_featured=bool(spec.get("is_featured")),
|
||
is_locked=bool(spec.get("is_locked")),
|
||
view_count=120 + idx * 19,
|
||
created_at=created_at,
|
||
updated_at=created_at,
|
||
)
|
||
db.session.add(post)
|
||
db.session.flush()
|
||
created_posts += 1
|
||
|
||
for c_idx, (comment_user, comment_text) in enumerate(spec.get("comments", [])):
|
||
c_user = user_map.get(comment_user)
|
||
if not c_user:
|
||
continue
|
||
comment_at = created_at + timedelta(hours=2 + c_idx * 3)
|
||
db.session.add(ForumComment(
|
||
post_id=post.id,
|
||
user_id=c_user.id,
|
||
content=comment_text.strip(),
|
||
created_at=comment_at,
|
||
updated_at=comment_at,
|
||
))
|
||
created_comments += 1
|
||
|
||
for like_user in spec.get("likes", []):
|
||
l_user = user_map.get(like_user)
|
||
if not l_user:
|
||
continue
|
||
db.session.add(ForumPostLike(post_id=post.id, user_id=l_user.id))
|
||
created_likes += 1
|
||
|
||
for bookmark_user in spec.get("bookmarks", []):
|
||
b_user = user_map.get(bookmark_user)
|
||
if not b_user:
|
||
continue
|
||
db.session.add(ForumPostBookmark(post_id=post.id, user_id=b_user.id))
|
||
created_bookmarks += 1
|
||
|
||
db.session.commit()
|
||
|
||
print("用户:新增 {},已存在 {}".format(created_users, skipped_users))
|
||
print("帖子:新增 {},已存在 {}".format(created_posts, skipped_posts))
|
||
print("评论:新增 {}".format(created_comments))
|
||
print("点赞:新增 {}".format(created_likes))
|
||
print("收藏:新增 {}".format(created_bookmarks))
|
||
print("默认测试密码(新用户):{}".format(DEFAULT_PASSWORD))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|