{"id":134,"date":"2026-09-21T13:09:34","date_gmt":"2026-09-21T13:09:34","guid":{"rendered":"https:\/\/wp.jyy7998.tk\/?p=134"},"modified":"2026-09-22T11:47:57","modified_gmt":"2026-09-22T03:47:57","slug":"%e4%bb%8e%e9%9b%b6%e9%83%a8%e7%bd%b2-workbuddy2api-%e7%bd%91%e5%85%b3%ef%bc%9adocker-%e7%bc%96%e6%8e%92%e3%80%81npm-%e5%8f%8d%e4%bb%a3%e4%b8%8e-deepseek-harness-%e6%8e%a5%e5%85%a5","status":"publish","type":"post","link":"https:\/\/wp.jyy7998.tk\/?p=134","title":{"rendered":"\u4ece\u96f6\u90e8\u7f72 workbuddy2api \u7f51\u5173\uff1aDocker \u7f16\u6392\u3001NPM \u53cd\u4ee3\u4e0e DeepSeek Harness \u63a5\u5165"},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"zh-CN\"><br \/>\n<head><br \/>\n<meta charset=\"UTF-8\"><br \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><br \/>\n<title>\u4ece\u96f6\u90e8\u7f72 workbuddy2api \u7f51\u5173\uff1aDocker \u7f16\u6392\u3001NPM \u53cd\u4ee3\u4e0e DeepSeek Harness \u63a5\u5165<\/title><\/p>\n<style>\n\/* ===================== \u9884\u89c8\u5916\u58f3 ===================== *\/\n:root { --bs-dark:#212529; --bs-white:#ffffff; }\n* { box-sizing: border-box; }\nhtml { -webkit-text-size-adjust: 100%; }\nbody {\n    margin: 0;\n    background: #ffffff;\n    color: #1f2328;\n    font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\",\n                 \"Hiragino Sans GB\", \"Microsoft YaHei\", \"Helvetica Neue\", Arial, sans-serif;\n}\n.single-content { max-width: 860px; margin: 0 auto; padding: 48px 24px 80px; }\n.preview-title {\n    font-size: 1.9rem; font-weight: 700; line-height: 1.35;\n    color: #111827; margin: 0 0 0.4em; letter-spacing: -0.01em;\n}\n.preview-sub {\n    font-size: 0.92rem; color: #6b7280; margin: 0 0 2.2em;\n    padding-bottom: 1.2em; border-bottom: 1px solid #e5e7eb;\n}\n@media (max-width: 767px) {\n    .single-content { padding: 28px 16px 56px; }\n    .preview-title { font-size: 1.45rem; }\n}<\/p>\n<p>\/* ===== \u4ee5\u4e0b\u4e3a\u535a\u5ba2\u4e3b\u9898\u7684 doc-unified.css\uff08\u539f\u6837\u5f15\u7528\uff09 ===== *\/\n\/* ============================================================================\n * \u6587\u7ae0\u6b63\u6587\u7edf\u4e00\u6392\u7248\uff08doc \u9aa8\u67b6\uff09\n * ----------------------------------------------------------------------------\n * \u9002\u7528\u8303\u56f4\uff1a\u4ec5\u6587\u7ae0\u6b63\u6587\u5bb9\u5668 .single-content \u5185\u7684 .doc\n * \u4e3b\u9898\u8272\uff1a\u6bcf\u7bc7\u6587\u7ae0\u901a\u8fc7\u5bb9\u5668\u5185\u8054\u6837\u5f0f\u4f20\u5165\n *         <\/p>\n<div class=\"doc\" style=\"--doc-accent:#76b900;--doc-accent-soft:#f4f9eb\">\n * \u8bbe\u8ba1\u7ea6\u675f\uff1a\u6240\u6709\u9009\u62e9\u5668\u4e00\u5f8b\u4ee5 .single-content .doc \u5f00\u5934\uff0c\u4e0d\u51fa\u73b0 body \/ * \/ \u88f8\u6807\u7b7e\uff0c\n *           \u56e0\u6b64\u4e0d\u4f1a\u5f71\u54cd\u7ad9\u70b9\u5176\u5b83\u4efb\u4f55\u90e8\u5206\u3002\n * ========================================================================== *\/<\/p>\n<p>.single-content .doc {\n    --doc-accent: #2c3e50;\n    --doc-accent-soft: #eef0f2;\n    --doc-border: #d0d7de;\n    --doc-code-bg: #f6f8fa;\n    --doc-text: #1f2328;\n    --doc-muted: #57606a;\n    font-size: 16px;\n    line-height: 1.78;\n    color: var(--doc-text);\n    overflow-wrap: break-word;\n    word-break: normal;\n}<\/p>\n<p>.single-content .doc > *:first-child { margin-top: 0; }\n.single-content .doc > *:last-child  { margin-bottom: 0; }<\/p>\n<p>\/* \u8986\u76d6\u4e3b\u9898\u5bf9 .single-content \u7684\u5168\u5c40\u5b57\u53f7 \/ \u65ad\u8bcd\u8bbe\u7f6e *\/\n.single-content .doc p,\n.single-content .doc li,\n.single-content .doc td,\n.single-content .doc th {\n    font-size: inherit;\n    line-height: inherit;\n    color: inherit;\n    word-break: normal;\n    overflow-wrap: break-word;\n}<\/p>\n<p>\/* ---------------------------------------------------------------- \u5143\u4fe1\u606f\u884c *\/\n.single-content .doc .doc-meta {\n    font-size: 14px;\n    line-height: 1.75;\n    color: var(--doc-muted);\n    background: var(--doc-accent-soft);\n    border-radius: 8px;\n    padding: 10px 16px;\n    margin: 0 0 1.7em;\n}<\/p>\n<p>\/* ------------------------------------------------------------------ \u5bfc\u8bed *\/\n.single-content .doc .doc-lead {\n    font-size: 1.05em;\n    color: var(--doc-muted);\n    margin: 0 0 1.4em;\n}\n.single-content .doc .doc-center { text-align: center; }<\/p>\n<p>\/* ------------------------------------------------------------------ \u76ee\u5f55 *\/\n.single-content .doc .doc-toc {\n    background: #f6f8fa;\n    border: 1px solid var(--doc-border);\n    border-radius: 10px;\n    padding: 16px 22px;\n    margin: 1.8em 0;\n    font-size: 15px;\n    line-height: 1.6;\n}\n.single-content .doc .doc-toc > strong {\n    display: block;\n    font-size: 15px;\n    font-weight: 600;\n    color: var(--doc-accent);\n    margin-bottom: 0.6em;\n}\n.single-content .doc .doc-toc ul { list-style: none; margin: 0; padding: 0; }\n.single-content .doc .doc-toc li { margin: 0.3em 0; }\n.single-content .doc .doc-toc a {\n    color: var(--doc-text);\n    text-decoration: none;\n    border-bottom: 1px dashed transparent;\n    transition: color 0.15s, border-color 0.15s;\n}\n.single-content .doc .doc-toc a:hover {\n    color: var(--doc-accent);\n    border-bottom-color: var(--doc-accent);\n}<\/p>\n<p>\/* ------------------------------------------------------------------ \u6807\u9898 *\/\n.single-content .doc h2 {\n    font-size: 1.5rem;\n    font-weight: 600;\n    line-height: 1.35;\n    color: var(--doc-text);\n    border-left: 5px solid var(--doc-accent);\n    padding-left: 14px;\n    margin: 2.2em 0 0.9em;\n    scroll-margin-top: 90px;\n}\n.single-content .doc h3 {\n    font-size: 1.22rem;\n    font-weight: 600;\n    line-height: 1.4;\n    color: var(--doc-text);\n    border-bottom: 1px solid var(--doc-border);\n    padding-bottom: 0.3em;\n    margin: 1.9em 0 0.7em;\n    scroll-margin-top: 90px;\n}\n.single-content .doc h4 {\n    font-size: 1.08rem;\n    font-weight: 600;\n    line-height: 1.45;\n    color: #2c3e50;\n    margin: 1.6em 0 0.6em;\n    scroll-margin-top: 90px;\n}\n.single-content .doc h5,\n.single-content .doc h6 {\n    font-size: 1rem;\n    font-weight: 600;\n    margin: 1.4em 0 0.5em;\n    scroll-margin-top: 90px;\n}<\/p>\n<p>\/* ------------------------------------------------------------------ \u6b63\u6587 *\/\n.single-content .doc p { margin: 1em 0; }\n.single-content .doc p.doc-tight { margin: 0.35em 0; }\n.single-content .doc strong { font-weight: 600; color: #111827; }\n.single-content .doc a {\n    color: var(--doc-accent);\n    text-decoration: none;\n    border-bottom: 0;\n}\n.single-content .doc a:hover { text-decoration: underline; }\n.single-content .doc .doc-accent-link { color: var(--doc-accent); text-decoration: underline; }\n.single-content .doc .doc-accent-link--strong { font-weight: 700; }\n.single-content .doc .doc-ok-text { color: #1a7f37; font-weight: 600; }<\/p>\n<p>.single-content .doc ul,\n.single-content .doc ol { margin: 1em 0; padding-left: 1.7em; }\n.single-content .doc li { margin: 0.4em 0; }\n.single-content .doc li > ul,\n.single-content .doc li > ol { margin: 0.35em 0; }<\/p>\n<p>\/* ---------------------------------------------------------------- \u5206\u9694\u7ebf *\/\n.single-content .doc hr {\n    border: 0;\n    border-top: 1px solid var(--doc-border);\n    opacity: 1;\n    margin: 2.2em 0;\n}<\/p>\n<p>\/* -------------------------------------------------------------- \u884c\u5185\u4ee3\u7801 *\/\n.single-content .doc code {\n    font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, monospace;\n    font-size: 0.9em;\n    background: #f0f2f4;\n    color: #b1305c;\n    padding: 2px 6px;\n    border-radius: 4px;\n    word-break: break-word;\n}<\/p>\n<p>\/* -------------------------------------------------------------- \u4ee3\u7801\u5757 *\/\n.single-content .doc pre {\n    position: relative;              \/* \u4f5c\u4e3a\u53f3\u4e0a\u89d2\u300c\u590d\u5236\u4ee3\u7801\u300d\u6309\u94ae\u7684\u5b9a\u4f4d\u53c2\u7167 *\/\n    background: var(--doc-code-bg);\n    border: 1px solid var(--doc-border);\n    border-left: 4px solid var(--doc-accent);\n    border-radius: 8px;\n    \/* \u9876\u90e8\u7559\u51fa 34px \u7ed9\u590d\u5236\u6309\u94ae\uff0c\u907f\u514d\u6309\u94ae\u538b\u4f4f\u9996\u884c\u4ee3\u7801 *\/\n    padding: 34px 18px 14px;\n    margin: 1.2em 0;\n    overflow-x: auto;\n    font-size: 14px;\n    line-height: 1.62;\n    -webkit-overflow-scrolling: touch;\n}\n.single-content .doc pre code {\n    background: none;\n    border: 0;\n    padding: 0;\n    color: var(--doc-text);\n    font-size: inherit;\n    line-height: inherit;\n    white-space: pre;\n    word-break: normal;\n}\n.single-content .doc pre.doc-arch {\n    background: #fafbfc;\n    border-left-color: var(--doc-border);\n    color: var(--doc-muted);\n    font-size: 13px;\n    line-height: 1.55;\n}<\/p>\n<p>\/* ------------------------------------------------- \u4ee3\u7801\u5757\u53f3\u4e0a\u89d2\u300c\u590d\u5236\u4ee3\u7801\u300d\u6309\u94ae\n * initCodeCopy \u4f1a\u628a\u6bcf\u4e2a pre \u653e\u8fdb .code-copy-wrap\uff0c\u518d\u628a\u6309\u94ae\u653e\u5728 pre \u5916\u5c42\u3002\n * \u8fd9\u6837\u6309\u94ae\u4e0d\u4f1a\u88ab pre \u7684 overflow-x:auto \u88c1\u526a\uff0c\u4e5f\u4e0d\u4f1a\u968f\u4ee3\u7801\u6a2a\u5411\u6eda\u52a8\u79fb\u8d70\u3002\n * pre \u9876\u90e8\u4ecd\u7559\u51fa 34px\uff0c\u907f\u514d\u6309\u94ae\u906e\u4f4f\u7b2c\u4e00\u884c\u4ee3\u7801\u3002 *\/\n.single-content .doc .code-copy-wrap {\n    position: relative;\n    margin: 1.2em 0;\n}\n.single-content .doc .code-copy-wrap > pre {\n    margin: 0;\n}\n.single-content .doc .code-copy-wrap > .btn-copy {\n    position: absolute;\n    top: 8px;\n    right: 10px;\n    z-index: 3;\n    cursor: pointer;\n    background: var(--bs-dark);\n    padding: 1px 6px;\n    border-radius: 4px;\n    font-size: 12px;\n    color: var(--bs-white);\n    display: none;\n}\n.single-content .doc .code-copy-wrap .single-copy {\n    cursor: pointer;\n}\n\/* \u4e3b\u9898\u53ea\u5728 `pre.prettyprint:hover` \u65f6\u663e\u793a\uff1b\u8fd9\u91cc\u653e\u5bbd\u5230\u6240\u6709\u4ee3\u7801\u5757\uff0c\n * \u8ba9\u6ca1\u6709 prettyprint \u7c7b\uff08\u4f8b\u5982\u5e26 data-lang\uff09\u7684\u4ee3\u7801\u5757\u4e5f\u80fd\u590d\u5236\u3002 *\/\n.single-content .doc .code-copy-wrap:hover > .btn-copy,\n.single-content .doc .code-copy-wrap:focus-within > .btn-copy {\n    display: block;\n}\n\/* \u89e6\u5c4f\u8bbe\u5907\u6ca1\u6709 hover\uff0c\u76f4\u63a5\u5e38\u663e\uff0c\u5426\u5219\u624b\u673a\u4e0a\u6839\u672c\u6309\u4e0d\u5230 *\/\n@media (hover: none) {\n    .single-content .doc .code-copy-wrap > .btn-copy { display: block; }\n}<\/p>\n<p>\/* -------------------------------------------------------------- \u952e\u76d8\u6309\u952e *\/\n.single-content .doc kbd {\n    display: inline-block;\n    font-family: inherit;\n    font-size: 0.85em;\n    line-height: 1.4;\n    color: var(--doc-text);\n    background: #ffffff;\n    border: 1px solid var(--doc-border);\n    border-bottom-width: 2px;\n    border-radius: 4px;\n    padding: 1px 6px;\n    margin: 0 2px;\n    white-space: nowrap;\n}<\/p>\n<p>\/* ---------------------------------------------------------------- \u63d0\u793a\u6846 *\/\n.single-content .doc .doc-note {\n    border-left: 4px solid var(--doc-accent);\n    background: var(--doc-accent-soft);\n    border-radius: 0 8px 8px 0;\n    padding: 13px 18px;\n    margin: 1.4em 0;\n    font-size: 0.97em;\n    line-height: 1.72;\n}\n.single-content .doc .doc-note--info { border-left-color: var(--doc-accent); background: var(--doc-accent-soft); }\n.single-content .doc .doc-note--tip  { border-left-color: #17a2b8; background: #eef8fa; }\n.single-content .doc .doc-note--ok   { border-left-color: #28a745; background: #eef8f1; }\n.single-content .doc .doc-note--warn { border-left-color: #f0b400; background: #fff8e5; }\n.single-content .doc .doc-note p { margin: 0.5em 0; }\n.single-content .doc .doc-note > *:first-child { margin-top: 0; }\n.single-content .doc .doc-note > *:last-child  { margin-bottom: 0; }<\/p>\n<p>\/* ------------------------------------------------------------------ \u8868\u683c *\/\n.single-content .doc .doc-table-wrap {\n    margin: 1.4em 0;\n    overflow-x: auto;\n    -webkit-overflow-scrolling: touch;\n}\n.single-content .doc .doc-table {\n    width: 100%;\n    border-collapse: collapse;\n    margin: 1.4em 0;\n    font-size: 0.94em;\n    line-height: 1.62;\n}\n.single-content .doc .doc-table-wrap > .doc-table { margin: 0; }\n.single-content .doc .doc-table th,\n.single-content .doc .doc-table td {\n    border: 1px solid var(--doc-border);\n    padding: 9px 13px;\n    text-align: left;\n    vertical-align: top;\n}\n.single-content .doc .doc-table th {\n    background: var(--doc-accent-soft);\n    color: var(--doc-text);\n    font-weight: 600;\n    white-space: nowrap;\n}\n.single-content .doc .doc-table tbody tr:nth-child(even) { background: #fafbfc; }<\/p>\n<p>\/* ---------------------------------------------------------------- \u5c0f\u6807\u7b7e *\/\n.single-content .doc .doc-badge {\n    display: inline-block;\n    font-size: 0.78em;\n    font-weight: 400;\n    line-height: 1.5;\n    color: var(--doc-muted);\n    background: #eef0f2;\n    border-radius: 4px;\n    padding: 1px 7px;\n    margin-left: 6px;\n    vertical-align: middle;\n    white-space: nowrap;\n}\n.single-content .doc .doc-badge--opt { color: var(--doc-accent); background: var(--doc-accent-soft); }<\/p>\n<p>\/* ------------------------------------------------------------ \u5927\u53f7\u5f3a\u8c03\u6846 *\/\n.single-content .doc .doc-callout {\n    font-size: 1.05em;\n    text-align: center;\n    background: var(--doc-accent-soft);\n    border-radius: 10px;\n    padding: 16px 20px;\n    margin: 1.6em 0;\n}<\/p>\n<p>\/* ------------------------------------------------------------------ \u7ed3\u5c3e *\/\n.single-content .doc .doc-foot {\n    font-size: 0.92em;\n    line-height: 1.72;\n    color: var(--doc-muted);\n    margin-top: 2em;\n}<\/p>\n<p>\/* ------------------------------------------------------------------ \u56fe\u7247 *\/\n.single-content .doc img { max-width: 100%; height: auto; border-radius: 8px; }<\/p>\n<p>\/* ------------------------------------------------------------------ \u7a84\u5c4f *\/\n@media (max-width: 767px) {\n    .single-content .doc { font-size: 15.5px; }\n    .single-content .doc h2 { font-size: 1.3rem; }\n    .single-content .doc h3 { font-size: 1.12rem; }\n    .single-content .doc pre { font-size: 13px; padding: 32px 14px 12px; }\n    .single-content .doc .doc-toc { padding: 14px 16px; }\n}\n<\/style>\n<p><\/head><br \/>\n<body><\/p>\n<article class=\"single-content\">\n<h1 class=\"preview-title\">\u4ece\u96f6\u90e8\u7f72 workbuddy2api \u7f51\u5173\uff1aDocker \u7f16\u6392\u3001NPM \u53cd\u4ee3\u4e0e DeepSeek Harness \u63a5\u5165<\/h1>\n<p class=\"preview-sub\">\u72ec\u7acb\u9884\u89c8\u7248 \u00b7 \u6b63\u6587\u5185\u5bb9\u4e0e\u300c\u7c98\u8d34\u7248\u300d\u5b8c\u5168\u4e00\u81f4 \u00b7 \u7c98\u8d34\u5230 WordPress \u65f6\u8bf7\u53ea\u590d\u5236\u4e0b\u65b9 &lt;div class=&#8221;doc&#8221;&gt; \u6574\u5757<\/p>\n<p><!-- ================= \u6587\u7ae0\u6b63\u6587\u5f00\u59cb\uff08\u6b64\u5757\u5373\u53ef\u76f4\u63a5\u7c98\u8d34\u5230 WordPress\uff09 ================= --><\/p>\n<div class=\"doc\" style=\"--doc-accent:#4f46e5;--doc-accent-soft:#f1f0fd\">\n<div class=\"doc-meta\">\n    \ud83d\udcc5 \u6700\u540e\u66f4\u65b0\uff1a2026\u5e749\u670821\u65e5 &nbsp;|&nbsp;<br \/>\n    \ud83c\udff7\ufe0f \u6807\u7b7e\uff1aDocker, Nginx Proxy Manager, Let&#8217;s Encrypt, \u53cd\u5411\u4ee3\u7406, workbuddy2api, DeepSeek Harness, OpenAI \u517c\u5bb9\n<\/div>\n<p>\u672c\u6587\u8bb0\u5f55\u4e00\u5957\u5b8c\u6574\u7684\u81ea\u5efa AI \u7f51\u5173\u90e8\u7f72\u65b9\u6848\uff1a\u7528 Docker \u90e8\u7f72 <strong>workbuddy2api<\/strong>\uff08\u7f51\u5173 + Web \u7ba1\u7406\u53f0\u4e24\u4e2a\u5bb9\u5668\uff09\uff0c\u628a\u5b83\u63a5\u8fdb\u65e2\u6709\u7684 <strong>Nginx Proxy Manager<\/strong> \u53cd\u5411\u4ee3\u7406\uff0c\u7b7e\u53d1 Let&#8217;s Encrypt \u8bc1\u4e66\u5e76\u5f00\u542f HTTPS\uff0c\u6700\u540e\u628a\u7f51\u5173\u63a5\u5165 <strong>DeepSeek Harness\uff08dsh\uff09<\/strong>\u3002<\/p>\n<p>\u53cd\u4ee3\u4e0e\u8bc1\u4e66\u90e8\u5206<strong>\u5168\u90e8\u5728 NPM \u9762\u677f\u91cc\u70b9\u51e0\u4e0b\u5b8c\u6210<\/strong>\uff0c\u4e0d\u9700\u8981\u6572\u547d\u4ee4\u3001\u4e0d\u9700\u8981\u8c03 API\u3002\u5168\u7a0b\u4e0d\u65b0\u8d77\u5bbf\u4e3b\u673a Nginx\u3001\u4e0d\u6539\u52a8\u65e2\u6709\u7ad9\u70b9\uff0c\u65b0\u589e\u670d\u52a1\u53ea\u6302\u5728\u5df2\u6709\u7684 Docker \u7f51\u7edc\u91cc\uff0c\u5bf9\u7ebf\u4e0a\u73af\u5883\u63a5\u8fd1\u96f6\u4fb5\u5165\u3002<\/p>\n<div class=\"doc-note doc-note--info\">\n    <strong>\ud83d\udccc \u9002\u7528\u573a\u666f\uff1a<\/strong><br \/>\n    \u5df2\u6709\u4e00\u53f0\u8dd1\u7740 Nginx Proxy Manager \u7684 VPS\uff0c\u5e0c\u671b\u5728\u540c\u4e00\u4e2a\u53cd\u4ee3\u4e0b\u518d\u6302\u4e00\u4e2a OpenAI \u517c\u5bb9\u7684\u6a21\u578b\u7f51\u5173\uff0c\u5e76\u628a\u7edf\u4e00\u51fa\u53e3\u63d0\u4f9b\u7ed9 dsh\u3001Cursor\u3001Cline\u3001Continue\u3001Codex CLI \u7b49\u5ba2\u6237\u7aef\u3002\n<\/div>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u5b89\u5168\u63d0\u9192\uff1a<\/strong><br \/>\n    \u672c\u6587\u4e0d\u5305\u542b\u4efb\u4f55\u5177\u4f53\u670d\u52a1\u5668\u7684\u516c\u7f51 IP\u3001SSH \u7aef\u53e3\u3001\u771f\u5b9e\u57df\u540d\u3001API Key\u3001\u540e\u53f0\u5730\u5740\u6216\u5176\u4ed6\u53ef\u8bc6\u522b\u670d\u52a1\u5668\u7684\u4fe1\u606f\u3002<br \/>\n    \u6587\u4e2d <code>api.example.com<\/code>\u3001<code>203.0.113.10<\/code> \u5747\u4e3a\u5360\u4f4d\u7b26\uff0c\u53d1\u5e03\u4e0e\u4f7f\u7528\u65f6\u8bf7\u66ff\u6362\u4e3a\u81ea\u5df1\u7684\u5b9e\u9645\u53c2\u6570\u3002\n<\/div>\n<nav class=\"doc-toc\">\n    <strong>\ud83d\udcd6 \u76ee\u5f55<\/strong><\/p>\n<ul>\n<li><a href=\"\/#step0\">\u524d\u7f6e\u6b65\u9aa4\uff1a\u73af\u5883\u4e0e\u89c4\u5212<\/a><\/li>\n<li><a href=\"\/#step1\">\u7b2c 1 \u6b65\uff1a\u90e8\u7f72\u7f51\u5173\u4e0e\u7ba1\u7406\u53f0<\/a><\/li>\n<li><a href=\"\/#step2\">\u7b2c 2 \u6b65\uff1a\u63a5\u5165\u65e2\u6709 Docker \u7f51\u7edc<\/a><\/li>\n<li><a href=\"\/#step3\">\u7b2c 3 \u6b65\uff1a\u5728 NPM \u9762\u677f\u91cc\u914d\u7f6e\u53cd\u4ee3\u4e0e HTTPS<\/a><\/li>\n<li><a href=\"\/#step4\">\u7b2c 4 \u6b65\uff1a\u521d\u59cb\u5316\u7ba1\u7406\u53f0\u5e76\u6388\u6743\u8d26\u53f7<\/a><\/li>\n<li><a href=\"\/#step5\">\u7b2c 5 \u6b65\uff1a\u6574\u4f53\u9a8c\u6536<\/a><\/li>\n<li><a href=\"\/#step6\">\u7b2c 6 \u6b65\uff1a\u63a5\u5165 DeepSeek Harness<\/a><\/li>\n<\/ul>\n<\/nav>\n<hr>\n<h2 id=\"step0\">\u524d\u7f6e\u6b65\u9aa4\uff1a\u73af\u5883\u4e0e\u89c4\u5212<\/h2>\n<p>\u8fd9\u5957\u65b9\u6848\u7684\u601d\u8def\u662f<strong>\u590d\u7528\u65e2\u6709\u53cd\u4ee3<\/strong>\uff0c\u800c\u4e0d\u662f\u53e6\u8d77\u4e00\u5957\u3002\u56e0\u6b64\u524d\u7f6e\u6761\u4ef6\u91cc\u6700\u91cd\u8981\u7684\u4e0d\u662f\u88c5\u8f6f\u4ef6\uff0c\u800c\u662f\u5148\u786e\u8ba4\u300c80 \/ 443 \u5f52\u8c01\u7ba1\u300d\u3002<\/p>\n<h3>0.1 \u73af\u5883\u8981\u6c42<\/h3>\n<table class=\"doc-table\">\n<tr>\n<th>\u9879\u76ee<\/th>\n<th>\u8981\u6c42<\/th>\n<\/tr>\n<tr>\n<td>\u64cd\u4f5c\u7cfb\u7edf<\/td>\n<td>Debian 12 \/ 13 \u6216 Ubuntu 22.04+<\/td>\n<\/tr>\n<tr>\n<td>Docker<\/td>\n<td>24 \u4ee5\u4e0a\uff0c\u542b <code>docker compose<\/code> \u63d2\u4ef6<\/td>\n<\/tr>\n<tr>\n<td>Python 3<\/td>\n<td>\u7528\u4e8e\u751f\u6210\u7f51\u5173\u914d\u7f6e\u6587\u4ef6<\/td>\n<\/tr>\n<tr>\n<td>jq<\/td>\n<td>\u67e5\u770b\u7f51\u5173\u63a5\u53e3\u8fd4\u56de\uff08\u53ef\u9009\uff09<\/td>\n<\/tr>\n<tr>\n<td>Nginx Proxy Manager<\/td>\n<td>\u5df2\u5728 Docker \u4e2d\u8fd0\u884c\uff0c\u4e14\u80fd\u767b\u5f55\u7ba1\u7406\u540e\u53f0<\/td>\n<\/tr>\n<tr>\n<td>DNS<\/td>\n<td>\u4e00\u4e2a\u5df2\u89e3\u6790\u5230\u672c\u673a\u7684\uff08\u5b50\uff09\u57df\u540d<\/td>\n<\/tr>\n<\/table>\n<p>\u5b89\u88c5\u57fa\u7840\u5de5\u5177\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>apt update\napt install -y ca-certificates curl git python3 jq<\/code><\/pre>\n<h3>0.2 \u5730\u5740\u4e0e\u7aef\u53e3\u89c4\u5212<\/h3>\n<p>\u672c\u6587\u7edf\u4e00\u628a\u65b0\u670d\u52a1\u6302\u8fdb\u5df2\u6709\u7684 <code>web<\/code> \u7f51\u7edc\uff08<code>172.20.0.0\/16<\/code>\uff09\uff0c\u5e76\u4f7f\u7528<strong>\u9759\u6001 IP<\/strong>\u3002<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u7ec4\u4ef6<\/th>\n<th>\u5bb9\u5668 IP<\/th>\n<th>\u7aef\u53e3<\/th>\n<th>\u8bf4\u660e<\/th>\n<\/tr>\n<tr>\n<td><code>npm<\/code><\/td>\n<td><code>172.20.0.20<\/code><\/td>\n<td>80 \/ 443 \/ 81<\/td>\n<td>\u65e2\u6709\u53cd\u4ee3<\/td>\n<\/tr>\n<tr>\n<td><code>workbuddy2api<\/code><\/td>\n<td><code>172.20.0.60<\/code><\/td>\n<td>7863<\/td>\n<td>\u7f51\u5173<\/td>\n<\/tr>\n<tr>\n<td><code>wb2api-admin<\/code><\/td>\n<td><code>172.20.0.61<\/code><\/td>\n<td>7864<\/td>\n<td>Web \u7ba1\u7406\u53f0<\/td>\n<\/tr>\n<\/table>\n<p>\u5982\u679c\u8fd8\u6ca1\u6709\u8fd9\u4e2a\u7f51\u7edc\uff0c\u5148\u521b\u5efa\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>docker network create \\\n  --driver bridge \\\n  --subnet 172.20.0.0\/16 \\\n  --gateway 172.20.0.1 \\\n  web<\/code><\/pre>\n<div class=\"doc-note doc-note--tip\">\n    <strong>\ud83d\udccc \u4e3a\u4ec0\u4e48\u8981\u7528\u9759\u6001 IP\uff1a<\/strong><br \/>\n    \u7b2c 3 \u6b65\u8981\u5728 NPM \u9762\u677f\u91cc<strong>\u624b\u5de5\u586b<\/strong>\u8f6c\u53d1\u76ee\u6807 IP\u3002\u82e5\u8ba9 Docker \u52a8\u6001\u5206\u914d\uff0c\u5bb9\u5668\u91cd\u5efa\u540e IP \u53ef\u80fd\u53d8\u5316\uff0c<br \/>\n    \u53cd\u4ee3\u4f1a\u9759\u9ed8\u6307\u5411\u9519\u8bef\u5730\u5740\uff08\u8868\u73b0\u4e3a 502\uff09\u3002\u56fa\u5b9a IP \u540e\uff0c\u5bb9\u5668\u91cd\u5efa\u3001\u955c\u50cf\u5347\u7ea7\u90fd\u4e0d\u9700\u8981\u6539\u53cd\u4ee3\u914d\u7f6e\u3002\n<\/div>\n<h3>0.3 \u5173\u952e\u524d\u63d0\uff1a443 \u5df2\u7ecf\u88ab\u5360\u7528\u4e86\u600e\u4e48\u529e<\/h3>\n<p>\u5f88\u591a VPS \u4e0a 443 \u7aef\u53e3\u5df2\u7ecf\u88ab\u522b\u7684 TLS \u670d\u52a1\u5360\u7528\uff08\u4f8b\u5982 Reality \u5165\u7ad9\uff09\u3002\u8fd9\u79cd\u60c5\u51b5\u4e0b\u6709\u4e24\u6761\u8def\uff1a<\/p>\n<ul>\n<li><strong>443 \u7a7a\u95f2<\/strong>\uff1a\u76f4\u63a5\u628a 443 \u4ea4\u7ed9 NPM\uff0c\u6b63\u5e38\u4f7f\u7528\u5373\u53ef\u3002<\/li>\n<li><strong>443 \u5df2\u88ab\u5360\u7528<\/strong>\uff1aNPM \u53ea\u53d1\u5e03 80 \u7aef\u53e3\uff0c\u7531\u5360\u7528 443 \u7684\u670d\u52a1\u628a\u4e0d\u5339\u914d\u7684\u6d41\u91cf fallback \u5230 NPM \u5bb9\u5668\u7684 443\u3002\u6b64\u65f6 NPM \u5bb9\u5668\u5185\u7684 443 \u4ecd\u7136\u6b63\u5e38\u5de5\u4f5c\uff0c\u53ea\u662f\u5165\u53e3\u4e0d\u5728\u5bbf\u4e3b\u673a\u4e0a\u3002<\/li>\n<\/ul>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u4e0d\u8981\u505a\u7684\u4e8b\uff1a<\/strong><br \/>\n    \u4e0d\u8981\u4e3a\u4e86\u8fd9\u4e2a\u7f51\u5173\u53bb\u5bbf\u4e3b\u673a\u4e0a\u53e6\u8d77\u4e00\u4e2a Nginx \u62a2 80 \/ 443\u3002\u5bbf\u4e3b\u673a\u7aef\u53e3\u51b2\u7a81\u4f1a\u8ba9\u65e2\u6709\u7ad9\u70b9\u76f4\u63a5\u4e0b\u7ebf\uff0c<br \/>\n    \u800c\u4e14\u8bc1\u4e66\u7eed\u671f\u3001\u65e5\u5fd7\u3001\u914d\u7f6e\u5168\u90fd\u53d8\u6210\u4e24\u5957\u3002\u6b63\u786e\u505a\u6cd5\u662f<strong>\u63a5\u8fdb\u65e2\u6709 NPM<\/strong>\u3002\n<\/div>\n<hr>\n<h2 id=\"step1\">\u7b2c 1 \u6b65\uff1a\u90e8\u7f72\u7f51\u5173\u4e0e\u7ba1\u7406\u53f0<\/h2>\n<p>\u9879\u76ee\u76ee\u5f55\u7edf\u4e00\u653e\u5728 <code>\/opt\/workbuddy2api<\/code>\u3002<\/p>\n<h3>1.1 \u62c9\u53d6\u4ed3\u5e93<\/h3>\n<pre class=\"prettyprint linenums\" ><code>git clone --depth 1 https:\/\/github.com\/dddmiku\/workbuddy2api.git \/opt\/workbuddy2api\ncd \/opt\/workbuddy2api<\/code><\/pre>\n<div class=\"doc-note doc-note--info\">\n    <strong>\ud83d\udccc \u8bf4\u660e\uff1a<\/strong><br \/>\n    <code>--depth 1<\/code> \u53ea\u62c9\u6700\u65b0\u4e00\u6b21\u63d0\u4ea4\uff0c\u90e8\u7f72\u7528\u8db3\u591f\u4e86\u3002\u4ed3\u5e93\u57fa\u4e8e MIT \u534f\u8bae\u7684<br \/>\n    <code>Sliverkiss\/workbuddy2api<\/code> \u4e8c\u6b21\u5f00\u53d1\uff0c\u955c\u50cf\u662f<br \/>\n    <code>ghcr.io\/dddmiku\/workbuddy2api:latest<\/code> \u4e0e<br \/>\n    <code>ghcr.io\/dddmiku\/workbuddy2api-panel:latest<\/code>\u3002\n<\/div>\n<h3>1.2 \u751f\u6210 API Key \u5e76\u5199\u51fa config.json<\/h3>\n<p>\u7f51\u5173\u7684\u5bf9\u5916\u5bc6\u94a5\u5728\u8fd9\u91cc\u4e00\u6b21\u6027\u751f\u6210\uff0c\u540e\u9762\u6240\u6709\u5ba2\u6237\u7aef\u90fd\u7528\u5b83\u3002\u4e0d\u8981\u624b\u5199\u4e00\u4e2a\u5f31\u53e3\u4ee4\u3002<\/p>\n<pre class=\"prettyprint linenums\" ><code>API_KEY=\"$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')\"\nexport API_KEY\npython3 - &lt;&lt;'PY'\nimport json, os\ncfg = json.load(open(\"config.example.json\", encoding=\"utf-8\"))\ncfg[\"api_key\"] = os.environ[\"API_KEY\"]\ncfg[\"api_keys_file\"] = \".\/data\/api_keys.json\"\ncfg[\"api_keys_socket\"] = \".\/data\/api_keys.sock\"\ncfg[\"usage_file\"] = \".\/data\/usage.json\"\njson.dump(cfg, open(\"config.json\", \"w\", encoding=\"utf-8\"), indent=2, ensure_ascii=False)\nprint(\"config.json \u5df2\u751f\u6210\")\nPY<\/code><\/pre>\n<p>\u4e09\u4e2a\u8def\u5f84\u5b57\u6bb5\u5206\u522b\u8d1f\u8d23\uff1a\u591a\u5bc6\u94a5\u6587\u4ef6\u3001\u7ba1\u7406\u53f0\u4e0e\u7f51\u5173\u901a\u4fe1\u7684 Unix socket\u3001\u7528\u91cf\u7edf\u8ba1\u843d\u76d8\u3002\u7ba1\u7406\u53f0\u8981\u9760\u5b83\u4eec\u624d\u80fd\u8bfb\u5230\u5b9e\u65f6\u72b6\u6001\u3002<\/p>\n<h3>1.3 \u76ee\u5f55\u4e0e\u5c5e\u4e3b<\/h3>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u6700\u5bb9\u6613\u8e29\u7684\u4e00\u6b65\uff1a<\/strong><br \/>\n    \u7f51\u5173\u5bb9\u5668\u4ee5\u56fa\u5b9a\u975e root \u8eab\u4efd <code>10001:10001<\/code> \u8fd0\u884c\u3002\u5bbf\u4e3b\u673a\u4e0a\u6302\u8fdb\u53bb\u7684\u76ee\u5f55\u5982\u679c\u5c5e\u4e3b\u4e0d\u5bf9\uff0c<br \/>\n    \u5bb9\u5668\u867d\u7136\u80fd\u8d77\u6765\uff0c\u4f46\u6388\u6743\u6587\u4ef6\u5199\u4e0d\u8fdb\u53bb\uff0c\u8868\u73b0\u4e3a\u300c\u767b\u5f55\u6210\u529f\u4e86\u4f46\u8d26\u53f7\u5217\u8868\u4e00\u76f4\u662f\u7a7a\u7684\u300d\u3002\n<\/div>\n<pre class=\"prettyprint linenums\" ><code>install -d -o 10001 -g 10001 -m 700 auths data\nmkdir -p panel-data\nchown 10001:10001 config.json\nchmod 600 config.json\n\nls -ld auths data panel-data<\/code><\/pre>\n<p>\u786e\u8ba4 <code>auths<\/code> \u4e0e <code>data<\/code> \u7684\u5c5e\u4e3b\u662f <code>10001 10001<\/code>\u3002<\/p>\n<h3>1.4 \u7f16\u5199\u7f16\u6392\u6587\u4ef6<\/h3>\n<p>\u521b\u5efa <code>docker-compose.yml<\/code>\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>cd \/opt\/workbuddy2api\nnano docker-compose.yml<\/code><\/pre>\n<pre class=\"prettyprint linenums\" ><code>services:\n  wb2api:\n    image: ghcr.io\/dddmiku\/workbuddy2api:latest\n    container_name: workbuddy2api\n    restart: unless-stopped\n    stop_grace_period: 3m\n    environment:\n      - TZ=Asia\/Shanghai\n    ports:\n      - \"127.0.0.1:7863:7863\"\n    volumes:\n      - .\/auths:\/app\/auths\n      - .\/data:\/app\/data\n      - .\/config.json:\/app\/config.json:ro\n    networks:\n      web:\n        ipv4_address: 172.20.0.60\n\n  wb2api-admin:\n    image: ghcr.io\/dddmiku\/workbuddy2api-panel:latest\n    container_name: wb2api-admin\n    restart: unless-stopped\n    environment:\n      - TZ=Asia\/Shanghai\n      - WB2API_GATEWAY_DIR=\/gateway\n      - WB2API_ADMIN_DIR=\/data\n      - WB2API_ADMIN_HOST=0.0.0.0\n      - WB2API_GATEWAY_URL=http:\/\/wb2api:7863\n      - WB2API_CONTAINER=workbuddy2api\n    ports:\n      - \"127.0.0.1:7864:7864\"\n    volumes:\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock\n      - .\/:\/gateway\n      - .\/panel-data:\/data\n    depends_on:\n      - wb2api\n    networks:\n      web:\n        ipv4_address: 172.20.0.61\n\nnetworks:\n  web:\n    external: true<\/code><\/pre>\n<p>\u4e09\u4e2a\u8bbe\u8ba1\u70b9\u503c\u5f97\u5355\u72ec\u8bf4\u660e\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u914d\u7f6e<\/th>\n<th>\u4f5c\u7528<\/th>\n<\/tr>\n<tr>\n<td><code>stop_grace_period: 3m<\/code><\/td>\n<td>\u5347\u7ea7\u5bb9\u5668\u65f6\u7ed9\u6b63\u5728\u8fdb\u884c\u7684\u6d41\u5f0f\u8bf7\u6c42\u7559\u8db3\u65f6\u95f4\uff0c\u907f\u514d\u957f\u56de\u7b54\u88ab\u786c\u4e2d\u65ad<\/td>\n<\/tr>\n<tr>\n<td><code>127.0.0.1:7863:7863<\/code><\/td>\n<td>\u53ea\u7ed1\u56de\u73af\u5730\u5740\u3002\u5199\u6210 <code>7863:7863<\/code> \u4f1a\u76f4\u63a5\u66b4\u9732\u5230\u516c\u7f51\uff0c\u7ed5\u8fc7\u53cd\u4ee3\u548c HTTPS<\/td>\n<\/tr>\n<tr>\n<td><code>wb2api-admin<\/code> \u6302 <code>docker.sock<\/code><\/td>\n<td>\u7ba1\u7406\u53f0\u9700\u8981\u91cd\u542f\u7f51\u5173\u5bb9\u5668\u6765\u52a0\u8f7d\u65b0\u6388\u6743\uff0c\u8fd9\u662f\u5b83\u552f\u4e00\u7684\u7279\u6743\u6765\u6e90<\/td>\n<\/tr>\n<\/table>\n<div class=\"doc-note doc-note--tip\">\n    <strong>\ud83d\udccc \u4e3a\u4ec0\u4e48\u8981\u6302 <code>.\/:\/gateway<\/code>\uff1a<\/strong><br \/>\n    \u7ba1\u7406\u53f0\u9700\u8981\u76f4\u63a5\u8bfb\u5199\u9879\u76ee\u76ee\u5f55\u4e0b\u7684 <code>auths\/<\/code>\uff0c\u800c\u5b83\u81ea\u5df1\u662f\u4e2a\u72ec\u7acb\u5bb9\u5668\u3002<br \/>\n    \u6302\u8f7d\u6574\u4e2a\u9879\u76ee\u76ee\u5f55\u540e\uff0c\u4e24\u4e2a\u5bb9\u5668\u770b\u5230\u7684\u662f\u540c\u4e00\u4efd\u6388\u6743\u6570\u636e\u3002\n<\/div>\n<h3>1.5 \u542f\u52a8<\/h3>\n<pre class=\"prettyprint linenums\" ><code>docker compose pull\ndocker compose up -d\n\ndocker ps --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'<\/code><\/pre>\n<p>\u786e\u8ba4\u4e24\u4e2a\u5bb9\u5668\u90fd\u662f <code>Up<\/code>\u3002\u7136\u540e\u672c\u673a\u81ea\u6d4b\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>curl -s http:\/\/127.0.0.1:7863\/healthz\necho\ncurl -s -o \/dev\/null -w 'admin login -> %{http_code}\\n' http:\/\/127.0.0.1:7864\/login<\/code><\/pre>\n<div class=\"doc-note doc-note--info\">\n    <strong>\ud83d\udccc <code>healthy:0<\/code> \u662f\u6b63\u5e38\u7684\uff1a<\/strong><br \/>\n    \u6b64\u65f6\u8fd8\u6ca1\u6709\u6388\u6743\u4efb\u4f55\u4e0a\u6e38\u8d26\u53f7\uff0c\u7f51\u5173\u81ea\u62a5 <code>healthy:0<\/code> \u5c5e\u4e8e\u9884\u671f\u72b6\u6001\uff0c\u4e0d\u662f\u90e8\u7f72\u5931\u8d25\u3002<br \/>\n    \u7b49\u7b2c 4 \u6b65\u6388\u6743\u5b8c\u6210\u540e\uff0c\u8fd9\u4e2a\u6570\u5b57\u4f1a\u53d8\u6210\u5b9e\u9645\u53ef\u7528\u8d26\u53f7\u6570\u3002\n<\/div>\n<hr>\n<h2 id=\"step2\">\u7b2c 2 \u6b65\uff1a\u63a5\u5165\u65e2\u6709 Docker \u7f51\u7edc<\/h2>\n<p>\u4e0a\u4e00\u6b65\u7684\u7f16\u6392\u91cc\u5df2\u7ecf\u5199\u6b7b\u4e86 <code>web<\/code> \u7f51\u7edc\u548c\u9759\u6001 IP\u3002\u8fd9\u4e00\u6b65\u53ea\u505a\u786e\u8ba4\u3002<\/p>\n<h3>2.1 \u786e\u8ba4\u7f51\u7edc\u5b58\u5728<\/h3>\n<pre class=\"prettyprint linenums\" ><code>docker network inspect web --format '{{.Name}} {{.IPAM.Config}}'<\/code><\/pre>\n<p>\u5e94\u8f93\u51fa <code>web [{172.20.0.0\/16 172.20.0.1}]<\/code> \u4e4b\u7c7b\u7684\u4fe1\u606f\u3002<\/p>\n<h3>2.2 \u786e\u8ba4\u5bb9\u5668\u771f\u7684\u62ff\u5230\u4e86\u9884\u671f IP<\/h3>\n<pre class=\"prettyprint linenums\" ><code>docker inspect -f '{{.Name}} -> {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \\\n  workbuddy2api wb2api-admin<\/code><\/pre>\n<p>\u671f\u671b\u770b\u5230\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>\/workbuddy2api -> 172.20.0.60\n\/wb2api-admin -> 172.20.0.61<\/code><\/pre>\n<h3>2.3 \u9a8c\u8bc1 NPM \u80fd\u8fde\u901a\u7f51\u5173<\/h3>\n<p>\u4e0d\u8981\u7528\u5bbf\u4e3b\u673a curl\uff0c\u8981\u4ece NPM \u5bb9\u5668\u5185\u90e8\u6d4b\uff0c\u624d\u80fd\u9a8c\u8bc1\u5bb9\u5668\u95f4\u7f51\u7edc\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>docker exec npm sh -c \\\n  'wget -qO- --timeout=10 http:\/\/172.20.0.60:7863\/healthz'<\/code><\/pre>\n<p>\u80fd\u8fd4\u56de JSON \u5c31\u8bf4\u660e\u53cd\u4ee3\u94fe\u8def\u7684\u7b2c\u4e00\u6bb5\u5df2\u7ecf\u901a\u4e86\u3002<\/p>\n<hr>\n<h2 id=\"step3\">\u7b2c 3 \u6b65\uff1a\u5728 NPM \u9762\u677f\u91cc\u914d\u7f6e\u53cd\u4ee3\u4e0e HTTPS<\/h2>\n<p>\u8fd9\u4e00\u6b65\u5168\u7a0b\u5728\u6d4f\u89c8\u5668\u91cc\u70b9\uff0c\u4e0d\u78b0\u547d\u4ee4\u884c\u3002\u8981\u505a\u7684\u5c31\u662f\u4e00\u4ef6\u4e8b\uff1a<strong>\u65b0\u5efa\u4e00\u4e2a Proxy Host\uff0c\u628a\u57df\u540d\u6307\u5411\u7f51\u5173\uff0c\u987a\u624b\u7533\u8bf7\u8bc1\u4e66\u3002<\/strong><\/p>\n<h3>3.1 \u65b0\u5efa Proxy Host<\/h3>\n<p>\u767b\u5f55 NPM \u540e\u53f0\uff0c\u8fdb\u5165 <strong>Hosts \u2192 Proxy Hosts \u2192 Add Proxy Host<\/strong>\uff0c\u5728 <strong>Details<\/strong> \u6807\u7b7e\u9875\u586b\u5199\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u5b57\u6bb5<\/th>\n<th>\u586b\u4ec0\u4e48<\/th>\n<\/tr>\n<tr>\n<td>Domain Names<\/td>\n<td><code>api.example.com<\/code><\/td>\n<\/tr>\n<tr>\n<td>Scheme<\/td>\n<td><code>http<\/code><\/td>\n<\/tr>\n<tr>\n<td>Forward Hostname \/ IP<\/td>\n<td><code>172.20.0.60<\/code><\/td>\n<\/tr>\n<tr>\n<td>Forward Port<\/td>\n<td><code>7863<\/code><\/td>\n<\/tr>\n<tr>\n<td>Cache Assets<\/td>\n<td>\u4e0d\u52fe<\/td>\n<\/tr>\n<tr>\n<td>Block Common Exploits<\/td>\n<td>\u52fe\u4e0a<\/td>\n<\/tr>\n<tr>\n<td>Websockets Support<\/td>\n<td>\u52fe\u4e0a<\/td>\n<\/tr>\n<\/table>\n<div class=\"doc-note doc-note--tip\">\n    <strong>\ud83d\udccc \u4e24\u4e2a\u5bb9\u6613\u586b\u9519\u7684\u5730\u65b9\uff1a<\/strong><br \/>\n    \u2460 <strong>Forward Hostname \/ IP \u586b\u5bb9\u5668\u9759\u6001 IP <code>172.20.0.60<\/code><\/strong>\uff0c\u4e0d\u8981\u586b <code>localhost<\/code> \u6216 <code>127.0.0.1<\/code>\u2014\u2014NPM \u81ea\u5df1\u4e5f\u5728\u5bb9\u5668\u91cc\uff0c\u56de\u73af\u5730\u5740\u6307\u7684\u662f\u5b83\u81ea\u5df1\u3002<br \/>\n    \u2461 \u8fd9\u91cc\u8fde\u7684\u662f\u7f51\u5173\u5bb9\u5668\uff0c<strong>\u4e0d\u662f<\/strong>\u7ba1\u7406\u53f0\u3002\u7ba1\u7406\u53f0\u8d70\u4e0b\u9762 3.2 \u7684\u5355\u72ec\u8def\u5f84\u3002\n<\/div>\n<h3>3.2 \u9ad8\u7ea7\uff1a\u628a \/admin\/ \u5355\u72ec\u8f6c\u7ed9\u7ba1\u7406\u53f0<\/h3>\n<p>\u5207\u5230 <strong>Advanced<\/strong> \u6807\u7b7e\u9875\uff0c\u5728 <strong>Custom Nginx Configuration<\/strong> \u6587\u672c\u6846\u91cc\u7c98\u8d34\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>location \/admin\/ {\n    proxy_pass http:\/\/172.20.0.61:7864\/;\n    proxy_http_version 1.1;\n    proxy_set_header Host $http_host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_read_timeout 600s;\n    proxy_send_timeout 600s;\n}<\/code><\/pre>\n<p>\u8fd9\u6837\u540c\u4e00\u4e2a\u57df\u540d\u4e0b\uff1a<code>\/<\/code> \u8d70\u7f51\u5173 API\uff0c<code>\/admin\/<\/code> \u8d70\u7ba1\u7406\u53f0\uff0c\u4e24\u8005\u4e92\u4e0d\u5e72\u6270\u3002<\/p>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u4e24\u4e2a\u7ec6\u8282\u522b\u6539\uff1a<\/strong><br \/>\n    \u2460 <code>proxy_pass<\/code> \u672b\u5c3e\u7684<strong>\u659c\u6760\u4e0d\u80fd\u7701<\/strong>\u3002\u5c11\u4e86\u5b83\uff0c<code>\/admin\/login<\/code> \u4f1a\u88ab\u539f\u6837\u8f6c\u7ed9\u7ba1\u7406\u53f0\uff0c<br \/>\n    \u800c\u7ba1\u7406\u53f0\u7684\u8def\u5f84\u662f <code>\/login<\/code>\uff0c\u7ed3\u679c\u5c31\u662f 404\u3002<br \/>\n    \u2461 <code>proxy_read_timeout 600s<\/code> \u8981\u4fdd\u7559\u3002\u7ba1\u7406\u53f0\u6388\u6743\u65f6\u8981\u957f\u65f6\u95f4\u8f6e\u8be2\u4e0a\u6e38\u767b\u5f55\u72b6\u6001\uff0c\u9ed8\u8ba4 60 \u79d2\u4f1a\u88ab\u5207\u65ad\u3002\n<\/div>\n<h3>3.3 SSL\uff1a\u7533\u8bf7\u8bc1\u4e66\u5e76\u5f00\u542f HTTPS<\/h3>\n<p>\u5207\u5230 <strong>SSL<\/strong> \u6807\u7b7e\u9875\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u5b57\u6bb5<\/th>\n<th>\u586b\u4ec0\u4e48<\/th>\n<\/tr>\n<tr>\n<td>SSL Certificate<\/td>\n<td>\u4e0b\u62c9\u9009 <strong>Request a new SSL Certificate<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Force SSL<\/td>\n<td>\u52fe\u4e0a<\/td>\n<\/tr>\n<tr>\n<td>HTTP\/2 Support<\/td>\n<td>\u52fe\u4e0a<\/td>\n<\/tr>\n<tr>\n<td>HSTS Enabled<\/td>\n<td>\u4e0d\u52fe<\/td>\n<\/tr>\n<tr>\n<td>HSTS Subdomains<\/td>\n<td>\u4e0d\u52fe<\/td>\n<\/tr>\n<tr>\n<td>Email Address<\/td>\n<td>\u4f60\u7684\u90ae\u7bb1\uff08\u8bc1\u4e66\u5230\u671f\u63d0\u9192\uff09<\/td>\n<\/tr>\n<tr>\n<td>I Agree to the Let&#8217;s Encrypt Terms of Service<\/td>\n<td>\u52fe\u4e0a<\/td>\n<\/tr>\n<\/table>\n<p>\u70b9 <strong>Save<\/strong>\u3002\u5269\u4e0b\u7684 NPM \u81ea\u5df1\u4f1a\u505a\u5b8c\uff1a<\/p>\n<div class=\"doc-note doc-note--tip\">\u4fdd\u5b58<br \/>\n  \u2193<br \/>\n\u627e\u51fa\u6240\u6709\u5360\u7528\u8fd9\u4e2a\u57df\u540d\u7684 host<br \/>\n  \u2193<br \/>\n\u4e34\u65f6\u7981\u7528\u5b83\u4eec\uff08\u817e\u51fa 80 \u7aef\u53e3\uff09<br \/>\n  \u2193<br \/>\n\u5199\u4e00\u4efd\u53ea\u670d\u52a1 ACME \u6821\u9a8c\u7684\u4e34\u65f6\u914d\u7f6e<br \/>\n  \u2193<br \/>\nLet&#8217;s Encrypt \u5b8c\u6210\u9a8c\u8bc1\u3001\u7b7e\u53d1\u8bc1\u4e66<br \/>\n  \u2193<br \/>\n\u5220\u6389\u4e34\u65f6\u914d\u7f6e<br \/>\n  \u2193<br \/>\n\u6062\u590d\u88ab\u7981\u7528\u7684 host\u3001\u6302\u4e0a\u8bc1\u4e66<\/div>\n<div class=\"doc-note doc-note--ok\">\n    \u2705 \u6574\u4e2a\u8fc7\u7a0b\u51e0\u79d2\u5230\u51e0\u5341\u79d2\u3002\u7b7e\u53d1\u6210\u529f\u540e\u56de\u5230 Proxy Hosts \u5217\u8868\uff0c<br \/>\n    \u8be5\u6761\u76ee\u53f3\u4fa7\u4f1a\u663e\u793a\u8bc1\u4e66\u5230\u671f\u65f6\u95f4\uff0c\u8bbf\u95ee <code>https:\/\/<\/code> \u4e5f\u662f\u7eff\u9501\u4e86\u3002\n<\/div>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u5343\u4e07\u4e0d\u8981\u5728 Advanced \u91cc\u624b\u5199 ACME \u6821\u9a8c\u7684 location\u3002<\/strong><br \/>\n    \u7f51\u4e0a\uff08\u5305\u62ec\u672c\u535a\u5ba2\u7684\u65e9\u671f\u7248\u672c\uff09\u6709\u8bf4\u6cd5\u79f0\u300c\u9996\u6b21\u7b7e\u53d1\u9700\u8981\u624b\u52a8\u8865<br \/>\n    <code>location ^~ \/.well-known\/acme-challenge\/<\/code>\u300d\uff0c<strong>\u8fd9\u662f\u8bef\u5224<\/strong>\u3002<br \/>\n    NPM \u81ea\u5e26\u4e0a\u9762\u90a3\u5957\u5f15\u5bfc\u6d41\u7a0b\uff0c\u4f1a\u81ea\u5df1\u89e3\u51b3\u300c\u8fd8\u6ca1\u6709\u8bc1\u4e66\u65f6\u600e\u4e48\u5b8c\u6210\u6821\u9a8c\u300d\u7684\u95ee\u9898\uff0c\u4e0d\u9700\u8981\u4f60\u63d2\u624b\u3002<br \/>\n    \u66f4\u9ebb\u70e6\u7684\u662f\uff1a\u4e00\u65e6\u542f\u7528 SSL\uff0cNPM \u4f1a\u81ea\u52a8\u5f80\u8fd9\u4e2a vhost \u91cc\u6ce8\u5165<br \/>\n    <code>include conf.d\/include\/letsencrypt-acme-challenge.conf<\/code>\uff0c\u800c\u90a3\u4e2a\u6587\u4ef6\u91cc\u5c31\u542b\u540c\u540d location\u3002<br \/>\n    \u4f60\u518d\u624b\u5199\u4e00\u4efd\uff0cnginx \u4f1a\u62a5 <strong>duplicate location<\/strong>\uff0c\u800c NPM \u7684\u5904\u7406\u65b9\u5f0f\u662f<strong>\u9759\u9ed8\u4e22\u5f03<\/strong>\u6574\u4e2a\u751f\u6210\u7684\u914d\u7f6e\u6587\u4ef6\uff1a<br \/>\n    \u6570\u636e\u5e93\u5df2\u66f4\u65b0\u3001\u9762\u677f\u4e0d\u62a5\u9519\u3001\u624b\u5de5\u8dd1 <code>nginx -t<\/code> \u8fd8\u662f\u7eff\u7684\uff08\u56e0\u4e3a\u6587\u4ef6\u6839\u672c\u6ca1\u5199\u8fdb\u53bb\uff09\uff0c<br \/>\n    \u4f46\u7ad9\u70b9\u8bbf\u95ee\u65f6\u62a5 TLS <code>unrecognized name<\/code>\u3002<br \/>\n    \u5224\u65ad\u65b9\u6cd5\u5f88\u7b80\u5355\uff0c\u76f4\u63a5\u770b\u6587\u4ef6\u5728\u4e0d\u5728\uff1a<code>docker exec npm ls \/data\/nginx\/proxy_host\/<\/code>\n<\/div>\n<h3>3.4 \u9a8c\u8bc1<\/h3>\n<p>\u57df\u540d\u89e3\u6790\u751f\u6548\u540e\uff08\u6216\u52a0 <code>--resolve<\/code> \u4ece\u672c\u673a\u76f4\u8fde\uff09\uff0c\u9010\u9879\u786e\u8ba4\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code># \u4e0d\u5e26\u5bc6\u94a5\uff1a\u5e94\u4e3a 401\uff0c\u8bf4\u660e\u9274\u6743\u5728\u5de5\u4f5c\ncurl -sS -o \/dev\/null -w '%{http_code}\\n' \\\n  https:\/\/api.example.com\/v1\/models\n\n# \u8bc1\u4e66\u94fe\u6821\u9a8c\uff1atls=0 \u624d\u7b97\u771f\u901a\u8fc7\ncurl -sS -o \/dev\/null -w '%{http_code} tls=%{ssl_verify_result}\\n' \\\n  https:\/\/api.example.com\/v1\/models\n\n# \u8bc1\u4e66\u4e3b\u4f53\necho | openssl s_client -connect api.example.com:443 -servername api.example.com 2&gt;\/dev\/null \\\n  | openssl x509 -noout -subject -issuer -dates\n\n# \u7ba1\u7406\u53f0\ncurl -sS -o \/dev\/null -w '%{http_code}\\n' \\\n  https:\/\/api.example.com\/admin\/login<\/code><\/pre>\n<p>\u671f\u671b\u7ed3\u679c\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u68c0\u67e5<\/th>\n<th>\u671f\u671b<\/th>\n<\/tr>\n<tr>\n<td>\u65e0\u5bc6\u94a5\u8bbf\u95ee <code>\/v1\/models<\/code><\/td>\n<td><code>401<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u8bc1\u4e66\u94fe\u6821\u9a8c<\/td>\n<td><code>tls=0<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u8bc1\u4e66\u4e3b\u4f53<\/td>\n<td><code>CN=api.example.com<\/code>\uff0c\u7b7e\u53d1\u8005 Let&#8217;s Encrypt<\/td>\n<\/tr>\n<tr>\n<td><code>\/admin\/login<\/code><\/td>\n<td><code>200<\/code><\/td>\n<\/tr>\n<\/table>\n<div class=\"doc-note doc-note--info\">\n    <strong>\ud83d\udccc DNS \u8fd8\u6ca1\u751f\u6548\u600e\u4e48\u529e\uff1a<\/strong><br \/>\n    \u5728 curl \u540e\u9762\u52a0 <code>--resolve api.example.com:443:&lt;\u670d\u52a1\u5668IP&gt;<\/code>\uff0c<br \/>\n    \u5c31\u80fd\u8df3\u8fc7 DNS \u76f4\u63a5\u4ece\u672c\u673a\u9a8c\u8bc1\u53cd\u4ee3\u548c\u8bc1\u4e66\u3002\u6ce8\u610f\u8bc1\u4e66\u6821\u9a8c\u8981<strong>\u4e0d\u5e26 <code>-k<\/code><\/strong>\uff0c<br \/>\n    \u770b\u5230 <code>tls=0<\/code> \u624d\u8bf4\u660e\u8bc1\u4e66\u94fe\u5b8c\u6574\u3001\u57df\u540d\u5339\u914d\u3002\n<\/div>\n<hr>\n<h2 id=\"step4\">\u7b2c 4 \u6b65\uff1a\u521d\u59cb\u5316\u7ba1\u7406\u53f0\u5e76\u6388\u6743\u8d26\u53f7<\/h2>\n<h3>4.1 \u751f\u6210\u7ba1\u7406\u53f0\u521d\u59cb\u5bc6\u7801<\/h3>\n<p>\u7ba1\u7406\u53f0\u9996\u6b21\u542f\u52a8\u4e0d\u4f1a\u81ea\u52a8\u5efa\u8d26\u53f7\uff0c\u9700\u8981\u624b\u52a8\u89e6\u53d1\u4e00\u6b21\u51ed\u636e\u521d\u59cb\u5316\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>cd \/opt\/workbuddy2api\ndocker compose exec wb2api-admin python3 -c \\\n  'import sys; sys.path.insert(0, \"\/app\"); import app; app.load_credentials()'<\/code><\/pre>\n<p>\u968f\u540e\u521d\u59cb\u5bc6\u7801\u4f1a\u5199\u5728 <code>panel-data\/initial-password.txt<\/code>\uff08\u6743\u9650 600\uff09\uff0c\u8d26\u53f7\u540d\u662f <code>wbadmin<\/code>\u3002<\/p>\n<pre class=\"prettyprint linenums\" ><code>cat panel-data\/initial-password.txt\nls -la panel-data\/<\/code><\/pre>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u6ce8\u610f\uff1a<\/strong><br \/>\n    \u62ff\u5230\u5bc6\u7801\u540e<strong>\u7acb\u5373\u4fee\u6539<\/strong>\uff0c\u5e76\u786e\u8ba4\u8be5\u6587\u4ef6\u6743\u9650\u662f <code>600<\/code>\u3002<br \/>\n    \u7ba1\u7406\u53f0\u6302\u7740 <code>docker.sock<\/code>\uff0c\u7b49\u4e8e\u6301\u6709\u5bbf\u4e3b\u673a Docker \u7684\u5b8c\u5168\u63a7\u5236\u6743\uff0c\u4e0d\u80fd\u5f31\u53e3\u4ee4\u3002\n<\/div>\n<h3>4.2 \u6dfb\u52a0\u4e0a\u6e38\u8d26\u53f7<\/h3>\n<p>\u767b\u5f55 <code>https:\/\/api.example.com\/admin\/<\/code>\uff0c\u70b9\u300c\u6dfb\u52a0\u8d26\u53f7\u300d\uff0c\u6d41\u7a0b\u662f\u4e24\u6b65\uff1a<\/p>\n<ol>\n<li>\u9009\u62e9\u533a\u57df\uff1a<strong>\u56fd\u5185\u7248 CN<\/strong> \u6216 <strong>\u56fd\u9645\u7248 Global<\/strong>\uff1b<\/li>\n<li>\u70b9\u300c\u751f\u6210\u6388\u6743\u94fe\u63a5\u300d\uff0c\u9762\u677f\u4f1a\u663e\u793a\u4e8c\u7ef4\u7801\u548c\u4e00\u6bb5\u660e\u6587 URL\u3002<\/li>\n<\/ol>\n<div class=\"doc-note doc-note--tip\">\n    <strong>\ud83d\udccc \u4e8c\u7ef4\u7801\u53ea\u662f URL \u7684\u56fe\u5f62\u5316\uff1a<\/strong><br \/>\n    \u70b9\u5f39\u7a97\u91cc\u7684\u300c\u590d\u5236\u94fe\u63a5\u300d\uff0c\u628a URL \u8d34\u5230<strong>\u7535\u8111\u6d4f\u89c8\u5668<\/strong>\u6253\u5f00\u5373\u53ef\u5b8c\u6210\u6388\u6743\uff0c<br \/>\n    <strong>\u4e0d\u9700\u8981\u5b89\u88c5\u5bf9\u5e94\u533a\u57df\u7684\u624b\u673a App<\/strong>\u3002\u767b\u5f55\u65b9\u5f0f\u5efa\u8bae\u9009\u300c\u624b\u673a\u53f7\u300d\uff0c\u7eaf\u7f51\u9875 + \u77ed\u4fe1\u9a8c\u8bc1\u7801\uff0c\u6700\u7a33\u3002<br \/>\n    \u6388\u6743\u6210\u529f\u540e\u9875\u9762\u4f1a\u63d0\u793a\u300c\u8bf7\u8fd4\u56de CLI \u7ee7\u7eed\u300d\uff0c\u76f4\u63a5\u7b49\u9762\u677f\u8f6e\u8be2\u5373\u53ef\uff0c\u4e0d\u9700\u8981\u5728\u9875\u9762\u4e0a\u989d\u5916\u70b9\u300c\u6388\u6743\u300d\u3002\n<\/div>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f \u6388\u6743\u8fc7\u7a0b\u4e2d\u4e0d\u8981\u91cd\u542f <code>wb2api-admin<\/code>\uff1a<\/strong><br \/>\n    \u767b\u5f55 state \u4fdd\u5b58\u5728\u9762\u677f\u8fdb\u7a0b\u7684\u5185\u5b58\u91cc\u3002\u5bb9\u5668\u4e00\u91cd\u542f\uff0c\u6b63\u5728\u8fdb\u884c\u7684\u6388\u6743\u6d41\u7a0b\u5c31\u4f5c\u5e9f\uff0c\u53ea\u80fd\u91cd\u65b0\u751f\u6210\u94fe\u63a5\u3002\n<\/div>\n<p>\u4e60\u60ef\u547d\u4ee4\u884c\u7684\u4e5f\u53ef\u4ee5\u7528\u5bb9\u5668\u5185\u7684\u767b\u5f55\u811a\u672c\uff0c\u6309\u533a\u57df\u5206\u522b\u6267\u884c\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>docker compose run --rm --entrypoint \/bin\/bash wb2api \\\n  \/app\/login.sh --realm=global\n\n# \u56fd\u5185\u7248\u7528\n# \/app\/login.sh --realm=cn<\/code><\/pre>\n<h3>4.3 \u9a8c\u8bc1\u8d26\u53f7\u5df2\u751f\u6548<\/h3>\n<p>\u9762\u677f\u6bcf\u51e0\u79d2\u81ea\u52a8\u8f6e\u8be2\u4e00\u6b21\uff0c\u6388\u6743\u6210\u529f\u540e\u4f1a\u81ea\u52a8\u5199\u8fdb <code>auths\/<\/code> \u5e76\u91cd\u542f\u7f51\u5173\u3002\u4e4b\u540e\u68c0\u67e5\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>curl -s http:\/\/127.0.0.1:7863\/healthz | jq .\ndocker compose logs --tail=20 wb2api<\/code><\/pre>\n<p>\u671f\u671b <code>healthy<\/code> \u53d8\u6210\u5b9e\u9645\u8d26\u53f7\u6570\uff0c\u5e76\u4e14\u4e24\u4e2a\u533a\u57df\u90fd\u53ef\u670d\u52a1\u3002<\/p>\n<pre class=\"prettyprint linenums\" ><code>curl -s -H \"Authorization: Bearer $API_KEY\" \\\n  http:\/\/127.0.0.1:7863\/v1\/models | jq '.data | length'<\/code><\/pre>\n<p>\u80fd\u5217\u51fa\u6a21\u578b\u6e05\u5355\u5c31\u8bf4\u660e\u7f51\u5173\u771f\u6b63\u53ef\u7528\u4e86\u3002<\/p>\n<hr>\n<h2 id=\"step5\">\u7b2c 5 \u6b65\uff1a\u6574\u4f53\u9a8c\u6536<\/h2>\n<p>\u6309\u4e0b\u9762\u8fd9\u5f20\u8868\u9010\u9879\u8fc7\u4e00\u904d\uff0c\u5168\u7eff\u624d\u7b97\u90e8\u7f72\u5b8c\u6210\u3002<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u9879\u76ee<\/th>\n<th>\u9a8c\u8bc1\u65b9\u5f0f<\/th>\n<th>\u671f\u671b<\/th>\n<\/tr>\n<tr>\n<td>\u7f51\u5173\u5b58\u6d3b<\/td>\n<td><code>curl -s http:\/\/127.0.0.1:7863\/healthz<\/code><\/td>\n<td>JSON\uff0c<code>healthy<\/code> &gt; 0<\/td>\n<\/tr>\n<tr>\n<td>\u9274\u6743\u751f\u6548<\/td>\n<td>\u65e0\u5bc6\u94a5\u8bf7\u6c42 <code>\/v1\/models<\/code><\/td>\n<td><code>401<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u6a21\u578b\u6e05\u5355<\/td>\n<td>\u5e26\u5bc6\u94a5\u8bf7\u6c42 <code>\/v1\/models<\/code><\/td>\n<td><code>200<\/code> + \u6a21\u578b\u6570\u7ec4<\/td>\n<\/tr>\n<tr>\n<td>\u516c\u7f51 HTTPS<\/td>\n<td>\u76f4\u8fde 443 \u4e0d\u5e26 <code>-k<\/code><\/td>\n<td><code>401 tls=0<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u8bc1\u4e66\u4e3b\u4f53<\/td>\n<td><code>openssl s_client<\/code><\/td>\n<td><code>CN=api.example.com<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u7ba1\u7406\u53f0<\/td>\n<td><code>\/admin\/login<\/code><\/td>\n<td><code>200<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u7ba1\u7406\u53f0\u63a5\u53e3\u9694\u79bb<\/td>\n<td>\u672a\u767b\u5f55\u8bbf\u95ee <code>\/admin\/api\/*<\/code><\/td>\n<td><code>401<\/code>\uff08\u8def\u5f84\u5265\u79bb\u6b63\u786e\uff09<\/td>\n<\/tr>\n<tr>\n<td>\u65e2\u6709\u57df\u540d\u56de\u5f52<\/td>\n<td>\u9010\u4e2a curl \u539f\u7ad9\u70b9<\/td>\n<td>\u5168 <code>200<\/code><\/td>\n<\/tr>\n<\/table>\n<p>\u6700\u540e\u4e00\u9879\u5c24\u5176\u91cd\u8981\u2014\u2014\u6211\u4eec\u5728 NPM \u91cc\u65b0\u589e\u4e86\u4e00\u4e2a vhost\uff0c\u5fc5\u987b\u786e\u8ba4\u8001\u7ad9\u70b9\u6ca1\u88ab\u7275\u8fde\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>for d in wp.example.com panel.example.com blog.example.com; do\n  code=$(curl -sS -o \/dev\/null -w '%{http_code}' \\\n    --resolve \"$d:443:127.0.0.1\" \"https:\/\/$d\/\" --max-time 15 2&gt;\/dev\/null)\n  echo \"$d -> $code\"\ndone<\/code><\/pre>\n<div class=\"doc-note doc-note--ok\">\n    \u2705 \u65e2\u6709\u57df\u540d\u5168\u90e8 <code>200<\/code>\uff0c\u8bf4\u660e\u65b0\u589e\u7684 vhost \u6ca1\u6709\u5f71\u54cd\u4efb\u4f55\u539f\u6709\u670d\u52a1\u3002\n<\/div>\n<hr>\n<h2 id=\"step6\">\u7b2c 6 \u6b65\uff1a\u63a5\u5165 DeepSeek Harness<\/h2>\n<p>\u7f51\u5173\u4e0a\u7ebf\u540e\uff0c\u4efb\u4f55 OpenAI \u517c\u5bb9\u5ba2\u6237\u7aef\u90fd\u53ef\u4ee5\u76f4\u63a5\u7528\u5b83\u3002\u8fd9\u91cc\u4ee5 DeepSeek Harness\uff08<code>dsh<\/code>\uff09\u4e3a\u4f8b\u3002<\/p>\n<h3>6.1 \u542f\u52a8 dsh<\/h3>\n<pre class=\"prettyprint linenums\" ><code>cd \/\u4f60\u7684\u9879\u76ee\u7edd\u5bf9\u8def\u5f84\nnpx @deepseek-ai\/dsh web<\/code><\/pre>\n<p>\u9ed8\u8ba4\u6253\u5f00 <code>http:\/\/127.0.0.1:3080<\/code>\uff0c\u7aef\u53e3\u88ab\u5360\u7528\u65f6\u52a0 <code>--port 3081<\/code>\u3002<\/p>\n<div class=\"doc-note doc-note--info\">\n    <strong>\ud83d\udccc \u7528 <code>npx<\/code> \u5c31\u591f\u4e86\uff1a<\/strong><br \/>\n    dsh \u8fd8\u5728 Developer Preview \u9636\u6bb5\uff0c\u7248\u672c\u66f4\u65b0\u5f88\u5feb\uff0c\u4e0d\u5fc5\u5168\u5c40\u5b89\u88c5\u3002<br \/>\n    \u542f\u52a8\u76ee\u5f55\u4f1a\u4f5c\u4e3a\u9ed8\u8ba4\u5de5\u4f5c\u533a\uff0c<strong>\u522b\u5728\u7528\u6237\u4e3b\u76ee\u5f55\u4e0b\u542f\u52a8<\/strong>\u3002\n<\/div>\n<h3>6.2 \u6dfb\u52a0\u81ea\u5b9a\u4e49\u63d0\u4f9b\u65b9<\/h3>\n<p>\u6253\u5f00 <strong>\u8bbe\u7f6e \u2192 \u6a21\u578b \u2192 \u6dfb\u52a0\u81ea\u5b9a\u4e49\u63d0\u4f9b\u65b9<\/strong>\uff0c\u6309\u4e0b\u8868\u586b\u5199\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u5b57\u6bb5<\/th>\n<th>\u586b\u4ec0\u4e48<\/th>\n<\/tr>\n<tr>\n<td>Provider ID<\/td>\n<td><code>wb2api<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u663e\u793a\u540d\u79f0<\/td>\n<td><code>WorkBuddy2API<\/code><\/td>\n<\/tr>\n<tr>\n<td>API \u5730\u5740<\/td>\n<td><code>https:\/\/api.example.com\/v1<\/code><\/td>\n<\/tr>\n<tr>\n<td>API \u534f\u8bae<\/td>\n<td><code>openai-completions<\/code><\/td>\n<\/tr>\n<tr>\n<td>API \u5bc6\u94a5<\/td>\n<td>\u7b2c 1.2 \u6b65\u751f\u6210\u7684 <code>$API_KEY<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u6a21\u578b<\/td>\n<td>\u89c1\u4e0b<\/td>\n<\/tr>\n<\/table>\n<div class=\"doc-note doc-note--warn\">\n    <strong>\u26a0\ufe0f Provider ID \u4e00\u65e6\u4fdd\u5b58\u5c31\u662f\u6c38\u4e45\u7684\uff1a<\/strong><br \/>\n    \u8bf7\u6c42\u3001\u4f1a\u8bdd\u3001\u51ed\u636e\u5f15\u7528\u90fd\u7528\u5b83\uff0c\u60f3\u6539\u540d\u53ea\u80fd\u65b0\u5efa\u4e00\u4e2a\u518d\u5220\u65e7\u7684\u3002<br \/>\n    \u663e\u793a\u540d\u79f0\u3001\u5730\u5740\u3001\u534f\u8bae\u3001\u5bc6\u94a5\u3001\u6a21\u578b\u90fd\u8fd8\u80fd\u6539\u3002\n<\/div>\n<h3>6.3 \u62c9\u53d6\u6a21\u578b\u6e05\u5355<\/h3>\n<p>\u70b9\u300c\u83b7\u53d6\u53ef\u7528\u6a21\u578b\u300d\uff0cdsh \u4f1a\u8bf7\u6c42 <code>GET \/v1\/models<\/code>\u3002\u672c\u7f51\u5173\u652f\u6301\u8fd9\u4e2a\u7aef\u70b9\uff0c\u80fd\u76f4\u63a5\u5217\u51fa\u5168\u90e8\u6a21\u578b\uff0c\u5728\u641c\u7d22\u6846\u91cc\u52fe\u9009\u5373\u53ef\u3002<\/p>\n<div class=\"doc-note doc-note--tip\">\n    <strong>\ud83d\udccc \u6a21\u578b ID \u7684\u524d\u7f00\u662f\u6709\u610f\u4e49\u7684\uff1a<\/strong><br \/>\n    <code>global:<\/code> \u8d70\u56fd\u9645\u7248\u8d26\u53f7\uff0c<code>cn:<\/code> \u8d70\u56fd\u5185\u7248\u8d26\u53f7\uff0c\u7f51\u5173\u6309\u524d\u7f00\u81ea\u52a8\u9009\u53f7\u3002<br \/>\n    \u6a21\u578b\u603b\u6570\u662f\u56fa\u5b9a\u7684\uff0c\u4f46\u4e24\u4e2a\u524d\u7f00\u7684\u62c6\u5206\u6bcf\u6b21\u679a\u4e3e\u4f1a\u6709\u5c0f\u5e45\u6d6e\u52a8\uff0c\u6309 ID \u627e\u5c31\u884c\uff0c\u522b\u8bb0\u6570\u91cf\u3002\n<\/div>\n<h3>6.4 \u53ef\u9009\uff1a\u914d\u7f6e\u63a8\u7406\u7b49\u7ea7<\/h3>\n<p>dsh \u7684\u63a8\u7406\u7b49\u7ea7\u83dc\u5355\u53ea\u5bf9<strong>\u58f0\u660e\u4e86\u7b49\u7ea7<\/strong>\u7684\u6a21\u578b\u51fa\u73b0\uff0c\u624b\u52a8\u5f55\u5165\u7684\u6a21\u578b\u9ed8\u8ba4\u6ca1\u6709\u3002<br \/>\n\u9700\u8981\u5728\u8bbe\u7f6e\u9875\u9876\u90e8\u70b9\u300c\u6253\u5f00\u914d\u7f6e\u6587\u4ef6\u300d\uff08\u7b49\u4ef7\u4e8e\u7f16\u8f91 <code>~\/.dsh\/settings.yaml<\/code>\uff09\u624b\u52a8\u52a0\u4e0a\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>llm-pi-ai:\n  providers:\n    wb2api:\n      api: openai-completions\n      baseURL: https:\/\/api.example.com\/v1\n      models:\n        - id: global:deepseek-v4.1-flash\n          compat:\n            thinkingFormat: deepseek\n          reasoningEfforts:\n            off:\n            high: high\n            max: max<\/code><\/pre>\n<p>\u4e09\u4e2a\u7ec6\u8282\uff1a<\/p>\n<ul>\n<li><code>thinkingFormat: deepseek<\/code> \u8ba9 <code>off<\/code> \u771f\u7684\u53d1\u9001 <code>thinking: {type: disabled}<\/code>\u2014\u2014\u5426\u5219\u9ed8\u8ba4\u4f1a\u601d\u8003\u7684\u6a21\u578b\u505c\u4e0d\u4e0b\u6765\u3002<\/li>\n<li>\u53ea\u7559\u7a7a <code>off:<\/code> \u662f\u5408\u6cd5\u7684\uff1b\u5176\u4ed6\u7b49\u7ea7\u5fc5\u987b\u7ed9\u503c\uff0c\u503c\u5c31\u662f\u534f\u8bae\u4e0a <code>reasoning_effort<\/code> \u7684\u5199\u6cd5\u3002<\/li>\n<li>\u5192\u53f7\u540e\u4ec0\u4e48\u90fd\u4e0d\u5199\uff08\u4f8b\u5982 <code>supportsDeveloperRole:<\/code>\uff09<strong>\u4f1a\u88ab\u62d2\u7edd<\/strong>\uff0c\u4e0d\u662f\u88ab\u5ffd\u7565\u3002<\/li>\n<\/ul>\n<div class=\"doc-note doc-note--ok\">\n    \u2705 \u9002\u914d\u5668\u5728\u4e0b\u4e00\u6b21\u8bf7\u6c42\u65f6\u91cd\u65b0\u8bfb\u53d6\u914d\u7f6e\uff0c\u6539\u5b8c\u4e0d\u7528\u91cd\u542f dsh\u3002\n<\/div>\n<h3>6.5 \u517c\u5bb9\u6027\u5b9e\u6d4b\u7ed3\u679c<\/h3>\n<p>\u7528\u771f\u5b9e\u8bf7\u6c42\u9010\u9879\u9a8c\u8bc1\u8fc7\uff0c\u7ed3\u8bba\u662f<strong>\u4e0d\u9700\u8981\u989d\u5916\u517c\u5bb9\u5f00\u5173<\/strong>\uff1a<\/p>\n<table class=\"doc-table\">\n<tr>\n<th>\u6d4b\u8bd5\u9879<\/th>\n<th>\u7ed3\u679c<\/th>\n<\/tr>\n<tr>\n<td><code>POST \/v1\/chat\/completions<\/code> \u666e\u901a\u5bf9\u8bdd<\/td>\n<td>\u2705 \u6b63\u5e38\u8fd4\u56de<\/td>\n<\/tr>\n<tr>\n<td><code>role: \"developer\"<\/code> \u7cfb\u7edf\u63d0\u793a\u8bcd<\/td>\n<td>\u2705 \u63a5\u53d7<\/td>\n<\/tr>\n<tr>\n<td><code>max_completion_tokens<\/code> \u5b57\u6bb5<\/td>\n<td>\u2705 \u63a5\u53d7<\/td>\n<\/tr>\n<tr>\n<td>\u5de5\u5177\u8c03\u7528 \/ function calling<\/td>\n<td>\u2705 \u8fd4\u56de <code>finish_reason: \"tool_calls\"<\/code><\/td>\n<\/tr>\n<tr>\n<td>\u6d41\u5f0f SSE<\/td>\n<td>\u2705 \u9010\u5757\u63a8\u9001\uff0c\u542b\u601d\u7ef4\u94fe\u589e\u91cf<\/td>\n<\/tr>\n<tr>\n<td><code>GET \/v1\/models<\/code><\/td>\n<td>\u2705 \u8fd4\u56de\u5b8c\u6574\u6a21\u578b\u6e05\u5355<\/td>\n<\/tr>\n<\/table>\n<p>\u540e\u4e24\u9879\u662f\u91cd\u70b9\u3002dsh \u5bf9\u63a8\u7406\u6a21\u578b\u9ed8\u8ba4\u4f1a\u4ee5 <code>role: \"developer\"<\/code> \u53d1\u9001\u7cfb\u7edf\u63d0\u793a\u8bcd\u3001\u7528<br \/>\n<code>max_completion_tokens<\/code> \u8868\u793a\u8f93\u51fa\u4e0a\u9650\uff0c\u591a\u6570\u7f51\u5173\u4f1a\u62d2\u8fd9\u4e24\u4e2a\u5199\u6cd5\u3002\u672c\u7f51\u5173\u4e24\u6837\u90fd\u63a5\u53d7\uff0c<br \/>\n\u6240\u4ee5 <code>compat.supportsDeveloperRole<\/code> \u548c <code>compat.maxTokensField<\/code> \u90fd\u4e0d\u7528\u914d\u3002<\/p>\n<h3>6.6 \u5176\u4ed6\u5ba2\u6237\u7aef<\/h3>\n<p>\u540c\u4e00\u4e2a\u7f51\u5173\u4e5f\u80fd\u76f4\u63a5\u5582\u7ed9 Cursor\u3001Cline\u3001Roo\u3001Continue\u3001Codex CLI \u7b49\uff1a<\/p>\n<pre class=\"prettyprint linenums\" ><code>Base URL:  https:\/\/api.example.com\/v1\nAPI Key:   &lt;\u7b2c 1.2 \u6b65\u751f\u6210\u7684\u5bc6\u94a5&gt;\nModel:     global:deepseek-v4.1-flash   \uff08\u6216\u6e05\u5355\u91cc\u7684\u4efb\u610f\u4e00\u4e2a\uff09<\/code><\/pre>\n<\/div>\n<p><!-- ================= \u6587\u7ae0\u6b63\u6587\u7ed3\u675f ================= --><\/p>\n<\/article>\n<p><script>\n(function () {\n  document.querySelectorAll('.single-content .doc pre').forEach(function (pre) {\n    if (pre.parentElement && pre.parentElement.classList.contains('code-copy-wrap')) return;\n    var wrap = document.createElement('div');\n    wrap.className = 'code-copy-wrap';\n    pre.parentNode.insertBefore(wrap, pre);\n    wrap.appendChild(pre);\n    var btn = document.createElement('span');\n    btn.className = 'btn-copy';\n    btn.textContent = '\u590d\u5236';\n    btn.addEventListener('click', function () {\n      var text = pre.innerText;\n      var done = function () {\n        btn.textContent = '\u5df2\u590d\u5236';\n        setTimeout(function () { btn.textContent = '\u590d\u5236'; }, 1200);\n      };\n      if (navigator.clipboard && navigator.clipboard.writeText) {\n        navigator.clipboard.writeText(text).then(done, done);\n      } else {\n        var ta = document.createElement('textarea');\n        ta.value = text; document.body.appendChild(ta); ta.select();\n        try { document.execCommand('copy'); } catch (e) {}\n        document.body.removeChild(ta); done();\n      }\n    });\n    wrap.appendChild(btn);\n  });\n})();\n<\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u4ece\u96f6\u90e8\u7f72 workbuddy2api \u7f51\u5173\uff1aDocker \u7f16\u6392\u3001NPM \u53cd\u4ee3\u4e0e DeepSeek Harness \u63a5\u5165 \u4ece\u96f6\u90e8\u7f72 workbuddy2api \u7f51\u5173\uff1aDocker \u7f16\u6392\u3001NPM \u53cd\u4ee3\u4e0e DeepSeek Harness \u63a5\u5165 \u72ec\u7acb\u9884\u89c8\u7248 \u00b7 \u6b63\u6587\u5185\u5bb9\u4e0e\u300c\u7c98\u8d34\u7248\u300d\u5b8c\u5168\u4e00\u81f4 \u00b7 \u7c98\u8d34\u5230 WordPress \u65f6\u8bf7\u53ea\u590d\u5236\u4e0b\u65b9 &lt;div class=&#8221;doc&#8221;&#038;g [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[13,14],"tags":[29,20,51,33,52,53,36],"class_list":["post-134","post","type-post","status-publish","format-standard","hentry","category-server-ops","category-network-proxy","tag-deepseek-harness","tag-docker","tag-lets-encrypt","tag-nginx-proxy-manager","tag-openai-compatible","tag-workbuddy2api","tag-reverse-proxy"],"_links":{"self":[{"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/posts\/134","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=134"}],"version-history":[{"count":3,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/posts\/134\/revisions"}],"predecessor-version":[{"id":137,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=\/wp\/v2\/posts\/134\/revisions\/137"}],"wp:attachment":[{"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=134"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=134"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wp.jyy7998.tk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=134"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}