# 部署(生产) Web 由 **nginx + php-fpm**(或同等 PHP-FPM)提供;**不要**把 HTTP 交给 PM2。PM2 只跑队列、调度、Workerman。 本地安装见 [install.md](./install.md),迁旧站见 [import.md](./import.md)。 ## 架构 ```text 浏览器 → nginx → php-fpm → Laravel (public/index.php) ↘ /attachments-local 仅开发;生产走 S3/R2 302 PM2 larablog-queue queue:work 只消费 default larablog-schedule schedule:work larablog-ai-workerman workerman:ai 消费 ai-content / ai-moderation ``` `queue:ai` 是开发用的短生命周期 `queue:work` 包装,**不会**起 Workerman。生产用 `workerman:ai` **或** 单独的 `queue:work --queue=ai-content,ai-moderation`,不要和 Workerman **同时**抢同一批 AI Job。 ## 服务器 | 项 | 建议 | |---|---| | PHP | 8.2 / 8.3 FPM,扩展同安装文档,外加 `redis` | | Workerman | CLI PHP 需 `pcntl`、`posix`(与 FPM 不是同一 php.ini 时要两边都查) | | 数据库 | MySQL 8 / MariaDB 10.6+(不要用 SQLite 当生产) | | Redis | 队列 + 缓存;多站点共用时设前缀 | | 附件 | S3 兼容(Cloudflare R2 / 腾讯 COS / 阿里 OSS / MinIO) | | 进程 | Node 的 PM2,或 systemd;配置见仓库根目录 `ecosystem.config.cjs` | ```bash php -m | grep -E 'pcntl|posix|redis|gd' php-fpm -m | grep -E 'redis|gd' ``` ## `.env`(生产要点) ```env APP_ENV=production APP_DEBUG=false APP_URL=https://www.example.com APP_LOCALE=zh_CN DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_DATABASE=larablog DB_USERNAME=larablog DB_PASSWORD= QUEUE_CONNECTION=redis CACHE_STORE=redis SESSION_DRIVER=database REDIS_CLIENT=phpredis REDIS_HOST=127.0.0.1 REDIS_PREFIX=larablog_ CACHE_PREFIX=larablog_cache_ ATTACHMENTS_DRIVER=s3 ATTACHMENTS_DISK=attachments AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=auto AWS_BUCKET= AWS_URL=https://cdn.example.com AWS_ENDPOINT=https://xxx.r2.cloudflarestorage.com AWS_USE_PATH_STYLE_ENDPOINT=true AI_PROVIDER=openai_compatible AI_API_BASE_URL= AI_API_KEY= AI_MODEL= ``` `APP_URL` 必须是对外 origin(含 `https`),否则附件 URL、OG、RSS 会错。 `REDIS_PREFIX` / `CACHE_PREFIX` 避免和同机其他 Laravel 抢键。 ## 首次发布 代码放到目标目录后(示例 `/var/www/larablog`): ```bash cd /var/www/larablog composer install --no-dev --optimize-autoloader cp .env.example .env # 编辑 .env:生产库、Redis、S3、APP_URL、AI_* php artisan key:generate --force php artisan migrate --force php artisan plugins:sync php artisan migrate --force php artisan themes:publish # 新站演示数据(迁 sablog 则跳过,改走 import.md) # php artisan db:seed --force php artisan db:seed --class=Database\\Seeders\\AiSettingsSeeder --force php artisan config:cache php artisan route:cache php artisan view:cache chmod -R ug+rwx storage bootstrap/cache chown -R www-data:www-data storage bootstrap/cache ``` FPM 用户以发行版为准(`www-data` / `nginx` / `php-fpm`)。 然后配 nginx、起 PM2。 ## nginx 示例:`deploy/nginx.conf`。生产至少保证: - `root` 指向 **`.../public`**,不是仓库根 - `try_files` 回 `index.php` - PHP 走 php-fpm socket(版本与套接字路径改成你机器上的) - 限制隐藏文件;按需 `client_max_body_size`(后台传附件) HTTPS 用发行版 certbot / 已有证书终止 TLS。HTTP 仅作跳转。 生产 `ATTACHMENTS_DRIVER=s3` 时,**不必**再配 `location /attachments-local/`。正文与 `/attachment.php?id=` 会 302 到对象存储。 ## 附件 | `ATTACHMENTS_DRIVER` | 行为 | |---|---| | `local` | 文件在 `storage/app/attachments`,URL `/attachments-local/...`(开发) | | `s3` | Flysystem S3;`AWS_*` 指向兼容端点 | 不要把附件长期放在 `public/` 或应用盘当生产方案。导入旧附件见 [import.md](./import.md)。 封面模板字需要中文时,设 `LARABLOG_COVER_FONT` 为服务器上的 TTF/OTF 绝对路径。 ## PM2 `ecosystem.config.cjs` 的 `cwd` 是仓库根。若部署路径不是开发机路径,把该文件里的约定理解成「在项目根执行 `pm2 start ecosystem.config.cjs`」。 ```bash cd /var/www/larablog # 如 PHP 不在 PATH:PHP_BINARY=/usr/bin/php pm2 start ecosystem.config.cjs pm2 start ecosystem.config.cjs pm2 save pm2 startup pm2 status ``` | name | 命令 | 日志 | |---|---|---| | `larablog-queue` | `queue:work redis --queue=default ...` | `storage/logs/pm2-queue.*.log` | | `larablog-schedule` | `schedule:work` | `storage/logs/pm2-schedule.*.log` | | `larablog-ai-workerman` | `workerman:ai start` | `storage/logs/pm2-ai.*.log` | Workerman 自己的 pid / 日志 / 状态文件在 **`storage/logs/workerman-ai.*`**,不要写到仓库根。若根目录已有 `workerman.log` 或 `workerman.artisan.status*`:先停进程再删。 没有 `pcntl`/`posix` 时不要起 `larablog-ai-workerman`,可从 ecosystem 里去掉该 app,改用: ```bash php artisan queue:work redis --queue=ai-content,ai-moderation --sleep=1 --tries=3 ``` 调度任务(`routes/console.php`):Redis 时每天 `cache:prune-stale-tags`;每周清理 7 天前的失败队列。 ## 插件 ```bash php artisan plugins:sync # 后台启用,或: php artisan plugins:sync --enable=larablog/payment,larablog/membership php artisan migrate --force php artisan config:cache ``` 付费内容依赖支付插件。Stub 支付仅打通下单,不是微信/支付宝。 ## 日常更新 ```bash cd /var/www/larablog git pull composer install --no-dev --optimize-autoloader php artisan migrate --force php artisan plugins:sync php artisan themes:publish php artisan config:cache php artisan route:cache php artisan view:cache php artisan filament:upgrade pm2 reload ecosystem.config.cjs ``` 维护窗口: ```bash php artisan down # …发布… php artisan up ``` 改 `.env` 后必须再 `config:cache`。 ## 日志与排障 | 文件 | 来源 | |---|---| | `storage/logs/laravel.log` | 应用 | | `storage/logs/pm2-*.log` | PM2 stdout/err | | `storage/logs/workerman-ai.log` | Workerman | | `storage/logs/workerman-ai.status` | Workerman 状态(`workerman:ai status`) | ```bash php artisan workerman:ai status pm2 logs larablog-ai-workerman --lines 100 ``` 队列堆积:Redis 里看 `REDIS_PREFIX` 下的队列键;确认只开了一种 AI 消费者。 ## 发布后自检 - `https://站点/`、一篇 `/show-{id}.shtml`、`/rss.xml`、`/sitemap.xml` - `/admin` 可登录 - 上传一张附件,前台 `/attachment.php?id=` 能跳到对象存储 - 后台触发一次 AI 润色,对应队列被消费(stub 或真实 API) - `APP_DEBUG=false`,`.env` 不能从 Web 读到 ## CI `.github/workflows/ci.yml`:PHP 8.2/8.3 PHPUnit(sqlite memory)。`deploy` job 只是占位,需绑定 GitHub `production` environment 并改成你的 rsync/ssh + 上文更新步骤。