@weibaohui/dsh-git-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/client/bundle.js +2356 -0
- package/client/index.js +2342 -0
- package/cordis.patch.yml +11 -0
- package/package.json +70 -0
- package/server/dist/api/v1.js +2029 -0
- package/server/dist/authx/password.js +43 -0
- package/server/dist/authx/um.js +157 -0
- package/server/dist/conf.js +323 -0
- package/server/dist/context.js +701 -0
- package/server/dist/db/actions.js +202 -0
- package/server/dist/db/db.js +838 -0
- package/server/dist/dshapi.js +1416 -0
- package/server/dist/editorconfig.js +104 -0
- package/server/dist/gitx/git.js +580 -0
- package/server/dist/gitx/http.js +232 -0
- package/server/dist/gitx/paths.js +28 -0
- package/server/dist/gitx/service.js +251 -0
- package/server/dist/gosprintf.js +167 -0
- package/server/dist/hook.js +53 -0
- package/server/dist/index.js +112 -0
- package/server/dist/lfsx.js +265 -0
- package/server/dist/mailer.js +127 -0
- package/server/dist/markup.js +317 -0
- package/server/dist/metrics.js +18 -0
- package/server/dist/mirror.js +203 -0
- package/server/dist/multipart.js +33 -0
- package/server/dist/repox.js +99 -0
- package/server/dist/router.js +185 -0
- package/server/dist/serv.js +43 -0
- package/server/dist/server.js +382 -0
- package/server/dist/sshx/dispatch.js +160 -0
- package/server/dist/sshx/lfs-transfer.js +289 -0
- package/server/dist/sshx/server.js +107 -0
- package/server/dist/toolx.js +108 -0
- package/server/dist/twofactor.js +160 -0
- package/server/dist/webapi.js +377 -0
- package/server/dist/webhook.js +128 -0
- package/server/package.json +34 -0
- package/server/public/img/avatar_default.png +0 -0
- package/server/tsconfig.json +19 -0
- package/server/vendored-conf/app.ini +538 -0
- package/server/vendored-conf/gitignore/Actionscript +11 -0
- package/server/vendored-conf/gitignore/Ada +5 -0
- package/server/vendored-conf/gitignore/Agda +1 -0
- package/server/vendored-conf/gitignore/Android +32 -0
- package/server/vendored-conf/gitignore/Anjuta +3 -0
- package/server/vendored-conf/gitignore/AppEngine +2 -0
- package/server/vendored-conf/gitignore/AppceleratorTitanium +3 -0
- package/server/vendored-conf/gitignore/ArchLinuxPackages +13 -0
- package/server/vendored-conf/gitignore/Archives +27 -0
- package/server/vendored-conf/gitignore/Autotools +14 -0
- package/server/vendored-conf/gitignore/BricxCC +4 -0
- package/server/vendored-conf/gitignore/C +32 -0
- package/server/vendored-conf/gitignore/C Sharp +108 -0
- package/server/vendored-conf/gitignore/C++ +28 -0
- package/server/vendored-conf/gitignore/CFWheels +12 -0
- package/server/vendored-conf/gitignore/CMake +6 -0
- package/server/vendored-conf/gitignore/CUDA +6 -0
- package/server/vendored-conf/gitignore/CakePHP +13 -0
- package/server/vendored-conf/gitignore/ChefCookbook +9 -0
- package/server/vendored-conf/gitignore/Cloud9 +3 -0
- package/server/vendored-conf/gitignore/CodeIgniter +6 -0
- package/server/vendored-conf/gitignore/CodeKit +3 -0
- package/server/vendored-conf/gitignore/CommonLisp +3 -0
- package/server/vendored-conf/gitignore/Composer +6 -0
- package/server/vendored-conf/gitignore/Concrete5 +3 -0
- package/server/vendored-conf/gitignore/Coq +3 -0
- package/server/vendored-conf/gitignore/CraftCMS +3 -0
- package/server/vendored-conf/gitignore/DM +5 -0
- package/server/vendored-conf/gitignore/Dart +15 -0
- package/server/vendored-conf/gitignore/DartEditor +2 -0
- package/server/vendored-conf/gitignore/Delphi +57 -0
- package/server/vendored-conf/gitignore/Dreamweaver +3 -0
- package/server/vendored-conf/gitignore/Drupal +36 -0
- package/server/vendored-conf/gitignore/EPiServer +4 -0
- package/server/vendored-conf/gitignore/Eagle +36 -0
- package/server/vendored-conf/gitignore/Eclipse +39 -0
- package/server/vendored-conf/gitignore/EiffelStudio +2 -0
- package/server/vendored-conf/gitignore/Elisp +5 -0
- package/server/vendored-conf/gitignore/Elixir +4 -0
- package/server/vendored-conf/gitignore/Emacs +32 -0
- package/server/vendored-conf/gitignore/Ensime +4 -0
- package/server/vendored-conf/gitignore/Erlang +10 -0
- package/server/vendored-conf/gitignore/Espresso +1 -0
- package/server/vendored-conf/gitignore/ExpressionEngine +19 -0
- package/server/vendored-conf/gitignore/ExtJs +4 -0
- package/server/vendored-conf/gitignore/Fancy +2 -0
- package/server/vendored-conf/gitignore/Finale +13 -0
- package/server/vendored-conf/gitignore/FlexBuilder +3 -0
- package/server/vendored-conf/gitignore/ForceDotCom +4 -0
- package/server/vendored-conf/gitignore/FuelPHP +2 -0
- package/server/vendored-conf/gitignore/GWT +28 -0
- package/server/vendored-conf/gitignore/Gcov +5 -0
- package/server/vendored-conf/gitignore/GitBook +16 -0
- package/server/vendored-conf/gitignore/Go +24 -0
- package/server/vendored-conf/gitignore/Gradle +8 -0
- package/server/vendored-conf/gitignore/Grails +33 -0
- package/server/vendored-conf/gitignore/Haskell +16 -0
- package/server/vendored-conf/gitignore/IGORPro +5 -0
- package/server/vendored-conf/gitignore/IPythonNotebook +2 -0
- package/server/vendored-conf/gitignore/Idris +2 -0
- package/server/vendored-conf/gitignore/JDeveloper +13 -0
- package/server/vendored-conf/gitignore/Java +12 -0
- package/server/vendored-conf/gitignore/Jboss +19 -0
- package/server/vendored-conf/gitignore/Jekyll +3 -0
- package/server/vendored-conf/gitignore/JetBrains +46 -0
- package/server/vendored-conf/gitignore/Joomla +525 -0
- package/server/vendored-conf/gitignore/KDevelop4 +2 -0
- package/server/vendored-conf/gitignore/Kate +3 -0
- package/server/vendored-conf/gitignore/KiCAD +13 -0
- package/server/vendored-conf/gitignore/Kohana +2 -0
- package/server/vendored-conf/gitignore/LabVIEW +16 -0
- package/server/vendored-conf/gitignore/Laravel +4 -0
- package/server/vendored-conf/gitignore/Lazarus +30 -0
- package/server/vendored-conf/gitignore/Leiningen +12 -0
- package/server/vendored-conf/gitignore/LemonStand +21 -0
- package/server/vendored-conf/gitignore/LibreOffice +2 -0
- package/server/vendored-conf/gitignore/Lilypond +6 -0
- package/server/vendored-conf/gitignore/Linux +7 -0
- package/server/vendored-conf/gitignore/Lithium +2 -0
- package/server/vendored-conf/gitignore/Lua +41 -0
- package/server/vendored-conf/gitignore/LyX +4 -0
- package/server/vendored-conf/gitignore/Magento +126 -0
- package/server/vendored-conf/gitignore/Matlab +16 -0
- package/server/vendored-conf/gitignore/Maven +9 -0
- package/server/vendored-conf/gitignore/Mercurial +6 -0
- package/server/vendored-conf/gitignore/Mercury +13 -0
- package/server/vendored-conf/gitignore/MetaProgrammingSystem +16 -0
- package/server/vendored-conf/gitignore/MicrosoftOffice +10 -0
- package/server/vendored-conf/gitignore/ModelSim +23 -0
- package/server/vendored-conf/gitignore/Momentics +8 -0
- package/server/vendored-conf/gitignore/MonoDevelop +8 -0
- package/server/vendored-conf/gitignore/Nanoc +10 -0
- package/server/vendored-conf/gitignore/NetBeans +8 -0
- package/server/vendored-conf/gitignore/Nim +1 -0
- package/server/vendored-conf/gitignore/Ninja +2 -0
- package/server/vendored-conf/gitignore/Node +28 -0
- package/server/vendored-conf/gitignore/NotepadPP +2 -0
- package/server/vendored-conf/gitignore/OCaml +20 -0
- package/server/vendored-conf/gitignore/Objective-C +43 -0
- package/server/vendored-conf/gitignore/Opa +13 -0
- package/server/vendored-conf/gitignore/OpenCart +11 -0
- package/server/vendored-conf/gitignore/OracleForms +8 -0
- package/server/vendored-conf/gitignore/Packer +5 -0
- package/server/vendored-conf/gitignore/Perl +20 -0
- package/server/vendored-conf/gitignore/Phalcon +2 -0
- package/server/vendored-conf/gitignore/PhpStorm +63 -0
- package/server/vendored-conf/gitignore/PlayFramework +17 -0
- package/server/vendored-conf/gitignore/Plone +18 -0
- package/server/vendored-conf/gitignore/Prestashop +25 -0
- package/server/vendored-conf/gitignore/Processing +7 -0
- package/server/vendored-conf/gitignore/Python +58 -0
- package/server/vendored-conf/gitignore/Qooxdoo +5 -0
- package/server/vendored-conf/gitignore/Qt +34 -0
- package/server/vendored-conf/gitignore/R +16 -0
- package/server/vendored-conf/gitignore/ROS +47 -0
- package/server/vendored-conf/gitignore/Rails +35 -0
- package/server/vendored-conf/gitignore/Redcar +1 -0
- package/server/vendored-conf/gitignore/Redis +3 -0
- package/server/vendored-conf/gitignore/RhodesRhomobile +9 -0
- package/server/vendored-conf/gitignore/Ruby +36 -0
- package/server/vendored-conf/gitignore/Rust +11 -0
- package/server/vendored-conf/gitignore/SBT +9 -0
- package/server/vendored-conf/gitignore/SCons +2 -0
- package/server/vendored-conf/gitignore/SVN +1 -0
- package/server/vendored-conf/gitignore/Sass +2 -0
- package/server/vendored-conf/gitignore/Scala +17 -0
- package/server/vendored-conf/gitignore/Scrivener +7 -0
- package/server/vendored-conf/gitignore/Sdcc +8 -0
- package/server/vendored-conf/gitignore/SeamGen +26 -0
- package/server/vendored-conf/gitignore/SketchUp +1 -0
- package/server/vendored-conf/gitignore/SlickEdit +11 -0
- package/server/vendored-conf/gitignore/Stella +12 -0
- package/server/vendored-conf/gitignore/SublimeText +14 -0
- package/server/vendored-conf/gitignore/SugarCRM +25 -0
- package/server/vendored-conf/gitignore/Swift +43 -0
- package/server/vendored-conf/gitignore/Symfony +37 -0
- package/server/vendored-conf/gitignore/SymphonyCMS +6 -0
- package/server/vendored-conf/gitignore/SynopsysVCS +36 -0
- package/server/vendored-conf/gitignore/Tags +14 -0
- package/server/vendored-conf/gitignore/TeX +136 -0
- package/server/vendored-conf/gitignore/TextMate +3 -0
- package/server/vendored-conf/gitignore/Textpattern +11 -0
- package/server/vendored-conf/gitignore/TortoiseGit +2 -0
- package/server/vendored-conf/gitignore/TurboGears2 +20 -0
- package/server/vendored-conf/gitignore/Typo3 +20 -0
- package/server/vendored-conf/gitignore/Umbraco +17 -0
- package/server/vendored-conf/gitignore/Unity +21 -0
- package/server/vendored-conf/gitignore/UnrealEngine +76 -0
- package/server/vendored-conf/gitignore/VVVV +6 -0
- package/server/vendored-conf/gitignore/Vagrant +1 -0
- package/server/vendored-conf/gitignore/Vim +6 -0
- package/server/vendored-conf/gitignore/VirtualEnv +10 -0
- package/server/vendored-conf/gitignore/VisualStudio +217 -0
- package/server/vendored-conf/gitignore/VisualStudioCode +2 -0
- package/server/vendored-conf/gitignore/Waf +3 -0
- package/server/vendored-conf/gitignore/WebMethods +14 -0
- package/server/vendored-conf/gitignore/WebStorm +63 -0
- package/server/vendored-conf/gitignore/Windows +18 -0
- package/server/vendored-conf/gitignore/WordPress +18 -0
- package/server/vendored-conf/gitignore/Xcode +23 -0
- package/server/vendored-conf/gitignore/XilinxISE +57 -0
- package/server/vendored-conf/gitignore/Xojo +11 -0
- package/server/vendored-conf/gitignore/Yeoman +6 -0
- package/server/vendored-conf/gitignore/Yii +6 -0
- package/server/vendored-conf/gitignore/ZendFramework +20 -0
- package/server/vendored-conf/gitignore/Zephir +26 -0
- package/server/vendored-conf/gitignore/macOS +25 -0
- package/server/vendored-conf/label/Default +7 -0
- package/server/vendored-conf/license/Abstyles License +10 -0
- package/server/vendored-conf/license/Academic Free License v1.1 +22 -0
- package/server/vendored-conf/license/Academic Free License v1.2 +27 -0
- package/server/vendored-conf/license/Academic Free License v2.0 +43 -0
- package/server/vendored-conf/license/Academic Free License v2.1 +44 -0
- package/server/vendored-conf/license/Academic Free License v3.0 +42 -0
- package/server/vendored-conf/license/Affero General Public License v1.0 +84 -0
- package/server/vendored-conf/license/Apache License 1.0 +19 -0
- package/server/vendored-conf/license/Apache License 1.1 +20 -0
- package/server/vendored-conf/license/Apache License 2.0 +72 -0
- package/server/vendored-conf/license/Artistic License 1.0 +48 -0
- package/server/vendored-conf/license/Artistic License 2.0 +84 -0
- package/server/vendored-conf/license/BSD 2-clause License +8 -0
- package/server/vendored-conf/license/BSD 3-clause License +10 -0
- package/server/vendored-conf/license/BSD 4-clause License +13 -0
- package/server/vendored-conf/license/Creative Commons CC0 1.0 Universal +39 -0
- package/server/vendored-conf/license/Eclipse Public License 1.0 +72 -0
- package/server/vendored-conf/license/Educational Community License v1.0 +22 -0
- package/server/vendored-conf/license/Educational Community License v2.0 +97 -0
- package/server/vendored-conf/license/GNU Affero General Public License v3.0 +241 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.1 +118 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.2 +129 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.3 +148 -0
- package/server/vendored-conf/license/GNU General Public License v1.0 +99 -0
- package/server/vendored-conf/license/GNU General Public License v2.0 +116 -0
- package/server/vendored-conf/license/GNU General Public License v3.0 +231 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v2.1 +174 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v3.0 +70 -0
- package/server/vendored-conf/license/GNU Library General Public License v2.0 +173 -0
- package/server/vendored-conf/license/ISC license +6 -0
- package/server/vendored-conf/license/MIT License +8 -0
- package/server/vendored-conf/license/Mozilla Public License 1.0 +122 -0
- package/server/vendored-conf/license/Mozilla Public License 1.1 +142 -0
- package/server/vendored-conf/license/Mozilla Public License 2.0 +143 -0
- package/server/vendored-conf/readme/Default +3 -0
- package/src/dsh-host.js +168 -0
- package/src/index.js +682 -0
- package/src/runner.js +364 -0
package/src/runner.js
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @weibaohui/dsh-git-server — ts-gogs 子进程运行器(宿主无关,可直测)。
|
|
5
|
+
*
|
|
6
|
+
* 职责:把 vendor/ts-gogs(Gogs 的 TypeScript 平替,构建产物随包分发)作为
|
|
7
|
+
* 受管子进程跑起来:
|
|
8
|
+
* 1. 把配置渲染成 app.ini 写进 <dataDir>/custom/conf/app.ini(INSTALL_LOCK
|
|
9
|
+
* 直接置位,跳过安装向导);
|
|
10
|
+
* 2. spawn `node dist/index.js`(GOGS_CUSTOM 指向上面的 custom 目录);
|
|
11
|
+
* 3. 就绪探测:轮询 http://host:port/ 直到 2xx/3xx(首连前就绪,防
|
|
12
|
+
* ECONNRESET 噪音),超时判失败并带上最后一段 stderr;
|
|
13
|
+
* 4. 崩溃自动拉起(退避 2s→30s);配置指纹变化由上层先 stop 再 start。
|
|
14
|
+
*
|
|
15
|
+
* 账户单一来源 = user-management:用户库路径通过环境变量传给 ts-gogs(
|
|
16
|
+
* - user-management:DSH_UM_USERS_FILE 指向 user-management 的 users.json,
|
|
17
|
+
* git HTTP Basic 与网页登录接受 UM 用户名密码(映射到本库管理员);
|
|
18
|
+
* - gogs(默认):ts-gogs 自己的账号/令牌体系。首次启动用
|
|
19
|
+
* DSH_BOOTSTRAP_ADMIN 播种管理员(用户库为空时)。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { spawn } = require('node:child_process')
|
|
23
|
+
const fs = require('node:fs')
|
|
24
|
+
const path = require('node:path')
|
|
25
|
+
const crypto = require('node:crypto')
|
|
26
|
+
const os = require('node:os')
|
|
27
|
+
|
|
28
|
+
const PLUGIN_ID = 'dsh-git-server'
|
|
29
|
+
const SERVER_DIR = path.join(__dirname, '..', 'server')
|
|
30
|
+
|
|
31
|
+
const DEFAULTS = {
|
|
32
|
+
enabled: true,
|
|
33
|
+
host: '0.0.0.0', // 监听全网卡(局域网可访问);不再是设置项,写死
|
|
34
|
+
port: 3400,
|
|
35
|
+
dataDir: '', // 空 = dshHome()/dsh-git-server/data
|
|
36
|
+
adminPassword: '', // 播种的管理员密码(首次自动生成并持久化)
|
|
37
|
+
impersonateSecret: '', // 代理免登录用的会话铸造密钥(自动生成持久化)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const NUM_RANGES = { port: [1024, 65535] }
|
|
41
|
+
|
|
42
|
+
function dshHome() {
|
|
43
|
+
return process.env.DSH_HOME ? path.resolve(process.env.DSH_HOME) : path.join(require('node:os').homedir(), '.dsh')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function defaultDataDir() {
|
|
47
|
+
return path.join(dshHome(), PLUGIN_ID, 'data')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveDataDir(dir) {
|
|
51
|
+
if (!dir || typeof dir !== 'string') return defaultDataDir()
|
|
52
|
+
const expanded = dir.startsWith('~') ? path.join(dshHome(), dir.slice(1)) : dir
|
|
53
|
+
return path.isAbsolute(expanded) ? expanded : path.join(dshHome(), expanded)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function generateSecret() {
|
|
57
|
+
return crypto.randomBytes(24).toString('hex')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeConfig(raw) {
|
|
61
|
+
const cfg = { ...DEFAULTS, ...(raw || {}) }
|
|
62
|
+
cfg.enabled = !!cfg.enabled
|
|
63
|
+
cfg.host = String(cfg.host || DEFAULTS.host)
|
|
64
|
+
cfg.port = Math.max(NUM_RANGES.port[0], Math.min(NUM_RANGES.port[1], Math.floor(Number(cfg.port) || DEFAULTS.port)))
|
|
65
|
+
cfg.dataDir = resolveDataDir(cfg.dataDir)
|
|
66
|
+
cfg.adminPassword = String(cfg.adminPassword || '')
|
|
67
|
+
cfg.impersonateSecret = String(cfg.impersonateSecret || '')
|
|
68
|
+
return cfg
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sanitizePatch(patch, current) {
|
|
72
|
+
const out = {}
|
|
73
|
+
if (patch == null || typeof patch !== 'object') return out
|
|
74
|
+
if (typeof patch.enabled === 'boolean') out.enabled = patch.enabled
|
|
75
|
+
if (patch.port !== undefined) {
|
|
76
|
+
const n = Math.floor(Number(patch.port))
|
|
77
|
+
if (Number.isFinite(n) && n >= NUM_RANGES.port[0] && n <= NUM_RANGES.port[1]) out.port = n
|
|
78
|
+
}
|
|
79
|
+
if (typeof patch.dataDir === 'string') out.dataDir = patch.dataDir.trim()
|
|
80
|
+
if (typeof patch.adminPassword === 'string') out.adminPassword = patch.adminPassword
|
|
81
|
+
if (typeof patch.impersonateSecret === 'string') out.impersonateSecret = patch.impersonateSecret
|
|
82
|
+
// 防呆:改动这些字段必须真的有变化,否则 reconciler 会空转重建
|
|
83
|
+
for (const k of Object.keys(out)) if (current && current[k] === out[k]) delete out[k]
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function serverFingerprint(cfg) {
|
|
88
|
+
return crypto.createHash('sha256')
|
|
89
|
+
.update(JSON.stringify({ enabled: cfg.enabled, host: cfg.host, port: cfg.port, dataDir: cfg.dataDir, adminPassword: cfg.adminPassword, impersonateSecret: cfg.impersonateSecret, vendor: vendorStamp() }))
|
|
90
|
+
.digest('hex')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let _vendorStamp = null
|
|
94
|
+
function vendorStamp() {
|
|
95
|
+
if (_vendorStamp) return _vendorStamp
|
|
96
|
+
try {
|
|
97
|
+
const st = fs.statSync(path.join(SERVER_DIR, 'dist', 'index.js'))
|
|
98
|
+
_vendorStamp = `${st.mtimeMs}:${st.size}`
|
|
99
|
+
} catch {
|
|
100
|
+
_vendorStamp = 'missing'
|
|
101
|
+
}
|
|
102
|
+
return _vendorStamp
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 渲染并写入 app.ini(幂等;内容不变不写,避免无谓的 mtime 抖动)。 */
|
|
106
|
+
function writeAppIni(cfg) {
|
|
107
|
+
const customDir = path.join(cfg.dataDir, 'custom', 'conf')
|
|
108
|
+
fs.mkdirSync(customDir, { recursive: true })
|
|
109
|
+
fs.mkdirSync(path.join(cfg.dataDir, 'repositories'), { recursive: true })
|
|
110
|
+
fs.mkdirSync(path.join(cfg.dataDir, 'log'), { recursive: true })
|
|
111
|
+
const secretFile = path.join(cfg.dataDir, 'custom', 'secret-key.txt')
|
|
112
|
+
let secret = ''
|
|
113
|
+
try { secret = fs.readFileSync(secretFile, 'utf8').trim() } catch {}
|
|
114
|
+
if (!secret) {
|
|
115
|
+
secret = generateSecret()
|
|
116
|
+
fs.writeFileSync(secretFile, secret + '\n', { mode: 0o600 })
|
|
117
|
+
}
|
|
118
|
+
// dsh 变体:git 只走 HTTP(SSH 服务与系统 authorized_keys 全部移除)
|
|
119
|
+
const ini = [
|
|
120
|
+
`RUN_USER = ${os.userInfo().username}`,
|
|
121
|
+
'RUN_MODE = prod',
|
|
122
|
+
'',
|
|
123
|
+
'[app]',
|
|
124
|
+
'BRAND_NAME = dsh Git',
|
|
125
|
+
'',
|
|
126
|
+
'[auth]',
|
|
127
|
+
'DISABLE_REGISTRATION = true',
|
|
128
|
+
'',
|
|
129
|
+
'[server]',
|
|
130
|
+
`HTTP_ADDR = ${cfg.host}`,
|
|
131
|
+
`HTTP_PORT = ${cfg.port}`,
|
|
132
|
+
`EXTERNAL_URL = http://${cfg.host === '0.0.0.0' || cfg.host === '::' ? '127.0.0.1' : cfg.host}:${cfg.port}/`,
|
|
133
|
+
'DISABLE_SSH = true',
|
|
134
|
+
'DISABLE_GRAVATAR = true',
|
|
135
|
+
'',
|
|
136
|
+
'[repository]',
|
|
137
|
+
`ROOT = ${path.join(cfg.dataDir, 'repositories')}`,
|
|
138
|
+
'',
|
|
139
|
+
'[database]',
|
|
140
|
+
`PATH = ${path.join(cfg.dataDir, 'gogs.db')}`,
|
|
141
|
+
'',
|
|
142
|
+
'[security]',
|
|
143
|
+
'INSTALL_LOCK = true',
|
|
144
|
+
`SECRET_KEY = ${secret}`,
|
|
145
|
+
'',
|
|
146
|
+
'[log]',
|
|
147
|
+
'MODE = file',
|
|
148
|
+
'LEVEL = Info',
|
|
149
|
+
`ROOT_PATH = ${path.join(cfg.dataDir, 'log')}`,
|
|
150
|
+
'',
|
|
151
|
+
].join('\n')
|
|
152
|
+
const file = path.join(customDir, 'app.ini')
|
|
153
|
+
try {
|
|
154
|
+
if (fs.readFileSync(file, 'utf8') === ini) return file
|
|
155
|
+
} catch {}
|
|
156
|
+
fs.writeFileSync(file, ini, { mode: 0o600 })
|
|
157
|
+
return file
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let depsInstallInFlight = null
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 依赖自检 + 自动安装。`dsh plugin add` 对 link: 包不装依赖、pnpm 默认
|
|
164
|
+
* 还会拦 better-sqlite3 的构建脚本,所以启动前自检关键原生依赖,缺失就在
|
|
165
|
+
* 插件目录自动跑一次 `npm install --omit=dev`(npm 默认执行安装脚本,
|
|
166
|
+
* better-sqlite3 拿预编译产物)。幂等:并发调用共享同一 in-flight。
|
|
167
|
+
*/
|
|
168
|
+
function ensureDeps({ logger = () => {} } = {}) {
|
|
169
|
+
let probe
|
|
170
|
+
try {
|
|
171
|
+
const { createRequire } = require('node:module')
|
|
172
|
+
probe = createRequire(path.join(SERVER_DIR, 'dist', 'index.js'))
|
|
173
|
+
} catch {}
|
|
174
|
+
const critical = ['better-sqlite3', 'marked', 'ini', 'busboy']
|
|
175
|
+
const missing = []
|
|
176
|
+
for (const name of critical) {
|
|
177
|
+
try { probe && probe.resolve(name) } catch { missing.push(name) }
|
|
178
|
+
}
|
|
179
|
+
if (!missing.length) return { installed: false, missing: [] }
|
|
180
|
+
if (depsInstallInFlight) return depsInstallInFlight
|
|
181
|
+
logger(`检测到缺失依赖 ${missing.join(', ')},自动执行 npm install(首次可能需要 1-2 分钟)…`)
|
|
182
|
+
depsInstallInFlight = new Promise((resolve, reject) => {
|
|
183
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
184
|
+
const child = spawn(npm, ['install', '--omit=dev', '--no-audit', '--no-fund'], {
|
|
185
|
+
cwd: path.join(SERVER_DIR, '..', '..'),
|
|
186
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
187
|
+
})
|
|
188
|
+
let tail = ''
|
|
189
|
+
const collect = (d) => { tail = (tail + d.toString()).slice(-2000); logger(`[npm] ${d.toString().trimEnd()}`) }
|
|
190
|
+
child.stdout.on('data', collect)
|
|
191
|
+
child.stderr.on('data', collect)
|
|
192
|
+
const killer = setTimeout(() => child.kill('SIGKILL'), 300000)
|
|
193
|
+
child.once('exit', (code) => {
|
|
194
|
+
clearTimeout(killer)
|
|
195
|
+
depsInstallInFlight = null
|
|
196
|
+
// 装完复检
|
|
197
|
+
const still = []
|
|
198
|
+
for (const name of critical) {
|
|
199
|
+
try { probe && probe.resolve(name) } catch { still.push(name) }
|
|
200
|
+
}
|
|
201
|
+
if (code === 0 && !still.length) resolve({ installed: true, missing: [] })
|
|
202
|
+
else reject(new Error(`依赖自动安装失败(exit=${code}${still.length ? ',仍缺: ' + still.join(',') : ''})\n${tail.slice(-600)}`))
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
return depsInstallInFlight
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 按实例标签清理本数据目录的历史孤儿子进程(不同实例互不影响)。 */
|
|
209
|
+
function killOrphanChildren(tag, logger) {
|
|
210
|
+
try {
|
|
211
|
+
const marker = path.join(SERVER_DIR, 'dist', 'index.js')
|
|
212
|
+
const needle = tag ? `--dsh-instance=${tag}` : null
|
|
213
|
+
if (!needle) return
|
|
214
|
+
const out = require('node:child_process')
|
|
215
|
+
.execSync(`ps ax -o pid=,command= | grep -F '${marker}' | grep -v grep || true`, { shell: '/bin/bash' })
|
|
216
|
+
.toString()
|
|
217
|
+
for (const line of out.split('\n')) {
|
|
218
|
+
if (!line.includes(needle)) continue
|
|
219
|
+
const pid = parseInt(line.trim().split(/\s+/)[0], 10)
|
|
220
|
+
if (pid && pid !== process.pid) {
|
|
221
|
+
try { process.kill(pid, 'SIGTERM'); logger(`清理历史孤儿子进程 pid=${pid}`) } catch {}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch {}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** 定位 user-management 插件的 store 模块(dsh 服务注入)。
|
|
228
|
+
* 优先 web profile 的安装(软链指向真实仓库);找不到返回空串——server
|
|
229
|
+
* 侧据此回退本地兜底账号。DSH_UM_STORE_PATH 环境变量可显式覆盖。 */
|
|
230
|
+
function locateUmStore() {
|
|
231
|
+
if (process.env.DSH_UM_STORE_PATH) return process.env.DSH_UM_STORE_PATH
|
|
232
|
+
const candidates = [
|
|
233
|
+
path.join(dshHome(), 'profiles', 'web', 'node_modules', '@weibaohui', 'user-management', 'src', 'store.js'),
|
|
234
|
+
]
|
|
235
|
+
for (const c of candidates) {
|
|
236
|
+
try { fs.statSync(c); return c } catch {}
|
|
237
|
+
}
|
|
238
|
+
return ''
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** 实例标签:每个数据目录一个持久随机标签(<dataDir>/custom/instance-tag)。 */
|
|
242
|
+
function ensureInstanceTag(cfg) {
|
|
243
|
+
const file = path.join(cfg.dataDir, 'custom', 'instance-tag')
|
|
244
|
+
try { return fs.readFileSync(file, 'utf8').trim() } catch {}
|
|
245
|
+
const tag = crypto.randomBytes(8).toString('hex')
|
|
246
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
247
|
+
fs.writeFileSync(file, tag + '\n', { mode: 0o600 })
|
|
248
|
+
return tag
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 启动 ts-gogs 子进程。返回句柄 { pid, stop() }。
|
|
253
|
+
* 就绪前抛错(带 stderr 尾部);就绪判定 = GET / 返回任意状态(TS 服务
|
|
254
|
+
* 起来后即使 404/302 也代表监听器在线)。
|
|
255
|
+
*/
|
|
256
|
+
async function start(cfg, options = {}) {
|
|
257
|
+
const logger = options.logger || (() => {})
|
|
258
|
+
const instanceTag = ensureInstanceTag(cfg)
|
|
259
|
+
killOrphanChildren(instanceTag, logger)
|
|
260
|
+
await new Promise((r) => setTimeout(r, 600)) // 等端口释放
|
|
261
|
+
await ensureDeps({ logger })
|
|
262
|
+
// 偶发:子进程刚起来就收到未知来源的信号——按标签清理后重试一次
|
|
263
|
+
try {
|
|
264
|
+
return await spawnAndReady(cfg, { ...options, instanceTag })
|
|
265
|
+
} catch (e) {
|
|
266
|
+
if (!/进程退出|就绪探测超时/.test(String((e && e.message) || e))) throw e
|
|
267
|
+
logger('Git 服务器启动中断,清理后重试一次…')
|
|
268
|
+
killOrphanChildren(instanceTag, logger)
|
|
269
|
+
await new Promise((r) => setTimeout(r, 1200))
|
|
270
|
+
return spawnAndReady(cfg, { ...options, instanceTag })
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function spawnAndReady(cfg, { logger = () => {}, onExit, instanceTag } = {}) {
|
|
275
|
+
const appIni = writeAppIni(cfg)
|
|
276
|
+
const entry = path.join(SERVER_DIR, 'dist', 'index.js')
|
|
277
|
+
if (!fs.existsSync(entry)) throw new Error(`vendor/ts-gogs 构建产物缺失: ${entry}`)
|
|
278
|
+
const env = {
|
|
279
|
+
...process.env,
|
|
280
|
+
GOGS_CUSTOM: path.join(cfg.dataDir, 'custom'),
|
|
281
|
+
DSH_BOOTSTRAP_ADMIN: `root:${cfg.adminPassword || generateSecret()}`,
|
|
282
|
+
}
|
|
283
|
+
if (cfg.adminPassword) env.DSH_ADMIN_PASSWORD = cfg.adminPassword
|
|
284
|
+
if (cfg.impersonateSecret) env.DSH_IMPERSONATE_SECRET = cfg.impersonateSecret
|
|
285
|
+
// 账户单一来源:注入 user-management service(dsh profile 安装的 store 模块,
|
|
286
|
+
// 与 UM 网关同一代码路径);server 侧用它直接做登录校验,密码永不同步
|
|
287
|
+
env.DSH_UM_STORE_PATH = locateUmStore()
|
|
288
|
+
if (!env.DSH_HOME) env.DSH_HOME = dshHome()
|
|
289
|
+
|
|
290
|
+
const spawnArgs = instanceTag ? [entry, `--dsh-instance=${instanceTag}`] : [entry]
|
|
291
|
+
const child = spawn(process.execPath, spawnArgs, { cwd: SERVER_DIR, env, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
292
|
+
let stderrTail = ''
|
|
293
|
+
child.stderr.on('data', (d) => {
|
|
294
|
+
stderrTail = (stderrTail + d.toString()).slice(-4000)
|
|
295
|
+
logger(`[gogs] ${d.toString().trimEnd()}`)
|
|
296
|
+
})
|
|
297
|
+
child.stdout.on('data', (d) => logger(`[gogs] ${d.toString().trimEnd()}`))
|
|
298
|
+
|
|
299
|
+
const exited = new Promise((resolve) => child.once('exit', (code, sig) => resolve({ code, sig })))
|
|
300
|
+
|
|
301
|
+
// 就绪探测:最长 30s;进程先退则立即失败
|
|
302
|
+
const deadline = Date.now() + 30000
|
|
303
|
+
let ready = false
|
|
304
|
+
let lastErr = ''
|
|
305
|
+
while (Date.now() < deadline) {
|
|
306
|
+
const gone = await Promise.race([exited.then((e) => e || { code: 0 }), new Promise((r) => setTimeout(() => r(null), 500))])
|
|
307
|
+
if (gone) {
|
|
308
|
+
throw new Error(`ts-gogs 进程退出 code=${gone.code}\n${stderrTail.slice(-800)}`)
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const res = await fetch(`http://${cfg.host === '0.0.0.0' || cfg.host === '::' ? '127.0.0.1' : cfg.host}:${cfg.port}/`, { redirect: 'manual' })
|
|
312
|
+
if (res.status > 0) { ready = true; break }
|
|
313
|
+
} catch (e) {
|
|
314
|
+
lastErr = (e && e.message) || String(e)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!ready) {
|
|
318
|
+
child.kill('SIGTERM')
|
|
319
|
+
throw new Error(`ts-gogs 就绪探测超时(30s):${lastErr}\n${stderrTail.slice(-800)}`)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let stopping = false
|
|
323
|
+
const parentExit = () => { try { child.kill('SIGKILL') } catch {} }
|
|
324
|
+
process.once('exit', parentExit)
|
|
325
|
+
process.once('SIGTERM', parentExit)
|
|
326
|
+
process.once('SIGINT', parentExit)
|
|
327
|
+
const handle = {
|
|
328
|
+
pid: child.pid,
|
|
329
|
+
async stop() {
|
|
330
|
+
if (stopping) return
|
|
331
|
+
stopping = true
|
|
332
|
+
process.removeListener('exit', parentExit)
|
|
333
|
+
process.removeListener('SIGTERM', parentExit)
|
|
334
|
+
process.removeListener('SIGINT', parentExit)
|
|
335
|
+
child.removeAllListeners('exit')
|
|
336
|
+
child.kill('SIGTERM')
|
|
337
|
+
const t = setTimeout(() => { try { child.kill('SIGKILL') } catch {} }, 5000)
|
|
338
|
+
await new Promise((resolve) => child.once('exit', () => { clearTimeout(t); resolve() }))
|
|
339
|
+
},
|
|
340
|
+
onExit(cb) {
|
|
341
|
+
child.once('exit', (code, sig) => { if (!stopping && cb) cb({ code, sig, stderrTail }) })
|
|
342
|
+
},
|
|
343
|
+
appIni,
|
|
344
|
+
}
|
|
345
|
+
return handle
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
module.exports = {
|
|
349
|
+
ensureDeps,
|
|
350
|
+
locateUmStore,
|
|
351
|
+
PLUGIN_ID,
|
|
352
|
+
DEFAULTS,
|
|
353
|
+
NUM_RANGES,
|
|
354
|
+
SERVER_DIR,
|
|
355
|
+
dshHome,
|
|
356
|
+
defaultDataDir,
|
|
357
|
+
resolveDataDir,
|
|
358
|
+
normalizeConfig,
|
|
359
|
+
sanitizePatch,
|
|
360
|
+
serverFingerprint,
|
|
361
|
+
writeAppIni,
|
|
362
|
+
start,
|
|
363
|
+
generateSecret,
|
|
364
|
+
}
|