refactor: remove authentication for internal deployment
Verify and deploy production / verify-and-deploy (push) Successful in 2m33s

This commit is contained in:
2026-10-05 10:37:12 +08:00
parent 81d73340d7
commit fbca08c8e0
5 changed files with 6 additions and 53 deletions
+2 -13
View File
@@ -39,17 +39,10 @@ jobs:
- name: Deploy persistent production service
if: gitea.event_name == 'workflow_dispatch' || (gitea.event_name == 'push' && gitea.ref == 'refs/heads/main')
env:
STUDIO_USERNAME: studio
STUDIO_PASSWORD: ${{ secrets.STUDIO_PASSWORD }}
DEPLOY_PORT: ${{ vars.DEPLOY_PORT }}
RELEASE_SHA: ${{ gitea.sha }}
run: |
set -eu
if [ -z "$STUDIO_PASSWORD" ]; then
echo "STUDIO_PASSWORD repository secret is required for production deployment"
exit 1
fi
if [ "$(id -u)" -eq 0 ]; then
SUDO=""
elif sudo -n true 2>/dev/null; then
@@ -79,10 +72,7 @@ jobs:
$SUDO chown -R root:root "$APP_ROOT"
$SUDO chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_ROOT"
$SUDO ln -sfn "$RELEASE_DIR" "$APP_ROOT/current"
printf '%s' "$STUDIO_PASSWORD" | $SUDO tee /etc/rednote-tool-studio.password >/dev/null
$SUDO chown root:"$SERVICE_USER" /etc/rednote-tool-studio.password
$SUDO chmod 640 /etc/rednote-tool-studio.password
$SUDO rm -f /etc/rednote-tool-studio.password
cat > rednote-tool-studio.service <<EOF
[Unit]
@@ -98,8 +88,7 @@ jobs:
Environment=NODE_ENV=production
Environment=HOSTNAME=0.0.0.0
Environment=PORT=$PORT
Environment=STUDIO_USERNAME=$STUDIO_USERNAME
ExecStart=/bin/sh -c 'export STUDIO_PASSWORD="\$(cat /etc/rednote-tool-studio.password)"; exec $APP_ROOT/bin/node server.js'
ExecStart=$APP_ROOT/bin/node server.js
Restart=always
RestartSec=5
+1 -1
View File
@@ -26,4 +26,4 @@ Rednote Content Studio 正在改造成小红书内容生成工作台。最高层
内容草稿和实际生成接口尚未实现。不要把旧版小工具的 build、validate、package 接口当作新的内容生成能力。
生产环境使用 HTTP Basic Auth。用户名默认为 `studio`,密码由 `STUDIO_PASSWORD` 提供;`GET /api/health` 无需认证。
应用面向受控内网运行,不提供登录鉴权。生产环境应通过内网和安全组限制访问范围。
+2 -2
View File
@@ -13,7 +13,7 @@
- 可复用的内容生成方法配置
- revision 乐观并发控制
- 下一阶段内容生成流程的界面占位
- 生产环境 Basic Auth、Docker 镜像与持久化目录
- 面向内网运行的生产部署、Docker 镜像与持久化目录
实际的选题简报、文案生成、草稿管理、AI 模型接入和导出尚未实现。
@@ -35,7 +35,7 @@ npm install
npm run dev
```
访问 `http://localhost:3000`。开发环境未配置 `STUDIO_PASSWORD` 时允许本地访问;生产环境必须提供密码。
访问 `http://localhost:3000`。应用本身不设置登录密码,生产环境应通过内网和安全组限制访问范围。
## 检查
-33
View File
@@ -1,33 +0,0 @@
import { timingSafeEqual } from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
function sameValue(left: string, right: string) {
const leftBuffer = Buffer.from(left);
const rightBuffer = Buffer.from(right);
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
}
export function proxy(request: NextRequest) {
if (request.nextUrl.pathname === "/api/health") return NextResponse.next();
const password = process.env.STUDIO_PASSWORD;
const username = process.env.STUDIO_USERNAME || "studio";
if (!password) {
if (process.env.NODE_ENV !== "production") return NextResponse.next();
return NextResponse.json({ error: "STUDIO_PASSWORD is not configured" }, { status: 503 });
}
const authorization = request.headers.get("authorization") || "";
const expected = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
if (!sameValue(authorization, expected)) {
return new NextResponse("Authentication required", {
status: 401,
headers: { "WWW-Authenticate": 'Basic realm="Rednote Tool Studio", charset="UTF-8"' },
});
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
+1 -4
View File
@@ -10,12 +10,9 @@ const volume = `${service}-data`;
const sha = (process.env.GITHUB_SHA || process.env.CI_COMMIT_SHA || "dev").slice(0, 12);
const image = `${service}:${sha}`;
const port = process.env.DEPLOY_PORT || "3000";
const password = process.env.STUDIO_PASSWORD;
const username = process.env.STUDIO_USERNAME || "studio";
const dockerfile = process.env.DEPLOY_DOCKERFILE || "Dockerfile";
const usePrebuiltOutput = dockerfile === "Dockerfile.deploy";
if (!password) throw new Error("STUDIO_PASSWORD secret is required");
if (!/^\d{2,5}$/.test(port) || Number(port) > 65535) throw new Error("DEPLOY_PORT must be a valid TCP port");
async function removeContainer(name) {
@@ -98,7 +95,7 @@ try {
next = await docker.createContainer({
name: candidate,
Image: image,
Env: [`STUDIO_USERNAME=${username}`, `STUDIO_PASSWORD=${password}`, "NODE_ENV=production"],
Env: ["NODE_ENV=production"],
ExposedPorts: { "3000/tcp": {} },
HostConfig: {
RestartPolicy: { Name: "unless-stopped" },