hfs 0.1.6 → 0.26.2
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/LICENSE.txt +674 -0
- package/README.md +102 -8
- package/admin/assets/index.dcc78777.css +1 -0
- package/admin/assets/index.f056db34.js +282 -0
- package/admin/assets/sha512.3c0e384c.js +8 -0
- package/admin/index.html +17 -0
- package/admin/logo.svg +36 -0
- package/frontend/assets/index.55c710c2.js +85 -0
- package/frontend/assets/index.ee805a6c.css +1 -0
- package/frontend/assets/sha512.634b743e.js +8 -0
- package/frontend/fontello.css +77 -0
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +18 -0
- package/package.json +93 -28
- package/plugins/antibrute/plugin.js +38 -0
- package/plugins/download-counter/plugin.js +47 -0
- package/plugins/download-counter/public/hits.js +5 -0
- package/plugins/updater-disabled/plugin.js +44 -0
- package/plugins/vhosting/plugin.js +42 -0
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +130 -0
- package/src/api.file_list.js +103 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +102 -0
- package/src/api.plugins.js +125 -0
- package/src/api.vfs.js +164 -0
- package/src/apiMiddleware.js +136 -0
- package/src/block.js +33 -0
- package/src/commands.js +105 -0
- package/src/config.js +172 -0
- package/src/connections.js +57 -0
- package/src/const.js +83 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +48 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +102 -0
- package/src/index.js +53 -0
- package/src/listen.js +226 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +154 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +176 -0
- package/src/plugins.js +338 -0
- package/src/serveFile.js +104 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +29 -0
- package/src/throttler.js +91 -0
- package/src/update.js +69 -0
- package/src/util-files.js +141 -0
- package/src/util-generators.js +30 -0
- package/src/util-http.js +30 -0
- package/src/vfs.js +227 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +69 -0
- package/.npmignore +0 -19
- package/admin-server.js +0 -212
- package/cli.js +0 -33
- package/file-server.js +0 -100
- package/lib/common.js +0 -10
- package/lib/extending.js +0 -158
- package/lib/mime.js +0 -19
- package/lib/misc.js +0 -75
- package/lib/serving.js +0 -81
- package/lib/vfs.js +0 -403
- package/main.js +0 -24
- package/note.txt +0 -104
- package/speedtest.js +0 -21
- package/static/backend.css +0 -14
- package/static/backend.html +0 -32
- package/static/backend.js +0 -694
- package/static/extending.js +0 -187
- package/static/frontend.css +0 -29
- package/static/frontend.html +0 -23
- package/static/frontend.js +0 -230
- package/static/icons/files/archive.png +0 -0
- package/static/icons/files/audio.png +0 -0
- package/static/icons/files/file.png +0 -0
- package/static/icons/files/folder.png +0 -0
- package/static/icons/files/image.png +0 -0
- package/static/icons/files/link.png +0 -0
- package/static/icons/files/video.png +0 -0
- package/static/jquery.js +0 -4
- package/static/jquery.rule-1.0.2.js +0 -273
- package/static/misc.js +0 -194
- package/static/tpl.js +0 -17
- package/todo.txt +0 -25
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.frontEndApis = void 0;
|
|
28
|
+
const api_file_list_1 = require("./api.file_list");
|
|
29
|
+
const api_auth = __importStar(require("./api.auth"));
|
|
30
|
+
const config_1 = require("./config");
|
|
31
|
+
const customHeader = (0, config_1.defineConfig)('custom_header');
|
|
32
|
+
exports.frontEndApis = {
|
|
33
|
+
file_list: api_file_list_1.file_list,
|
|
34
|
+
...api_auth,
|
|
35
|
+
config() {
|
|
36
|
+
return Object.fromEntries([customHeader].map(x => [x.key(), x.get()]));
|
|
37
|
+
}
|
|
38
|
+
};
|
package/src/github.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.searchPlugins = exports.getFolder2repo = exports.readOnlinePlugin = exports.getRepoInfo = exports.downloadPlugin = void 0;
|
|
7
|
+
const events_1 = __importDefault(require("./events"));
|
|
8
|
+
const misc_1 = require("./misc");
|
|
9
|
+
const plugins_1 = require("./plugins");
|
|
10
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
11
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
12
|
+
const DIST_ROOT = 'dist/';
|
|
13
|
+
const downloading = {};
|
|
14
|
+
function downloadProgress(id, status) {
|
|
15
|
+
if (status === undefined)
|
|
16
|
+
delete downloading[id];
|
|
17
|
+
else
|
|
18
|
+
downloading[id] = status;
|
|
19
|
+
events_1.default.emit('pluginDownload_' + id, status);
|
|
20
|
+
}
|
|
21
|
+
async function downloadPlugin(repo, branch = '', overwrite) {
|
|
22
|
+
if (downloading[repo])
|
|
23
|
+
return new apiMiddleware_1.ApiError(409, "already downloading");
|
|
24
|
+
downloadProgress(repo, true);
|
|
25
|
+
const rec = await getRepoInfo(repo);
|
|
26
|
+
if (!branch)
|
|
27
|
+
branch = rec.default_branch;
|
|
28
|
+
const short = repo.split('/')[1]; // second part, repo without the owner
|
|
29
|
+
const folder2repo = getFolder2repo();
|
|
30
|
+
const folder = overwrite ? lodash_1.default.findKey(folder2repo, x => x === repo) // use existing folder
|
|
31
|
+
: short in folder2repo ? repo.replace('/', '-') // longer form only if another plugin is using short form
|
|
32
|
+
: short;
|
|
33
|
+
const installPath = plugins_1.PATH + '/' + folder;
|
|
34
|
+
const GITHUB_ZIP_ROOT = short + '-' + branch; // GitHub puts everything within this folder
|
|
35
|
+
const rootWithinZip = GITHUB_ZIP_ROOT + '/' + DIST_ROOT;
|
|
36
|
+
const stream = await (0, misc_1.httpsStream)(`https://github.com/${repo}/archive/refs/heads/${branch}.zip`);
|
|
37
|
+
await (0, misc_1.unzip)(stream, path => path.startsWith(rootWithinZip) && installPath + '/' + path.slice(rootWithinZip.length));
|
|
38
|
+
downloadProgress(repo, undefined);
|
|
39
|
+
await (0, plugins_1.rescan)(); // workaround: for some reason, operations are not triggering the rescan of the watched folder. Let's invoke it.
|
|
40
|
+
return folder;
|
|
41
|
+
}
|
|
42
|
+
exports.downloadPlugin = downloadPlugin;
|
|
43
|
+
function getRepoInfo(id) {
|
|
44
|
+
return apiGithub('repos/' + id);
|
|
45
|
+
}
|
|
46
|
+
exports.getRepoInfo = getRepoInfo;
|
|
47
|
+
async function readOnlinePlugin(repoInfo, branch = '') {
|
|
48
|
+
const url = `https://raw.githubusercontent.com/${repoInfo.full_name}/${branch || repoInfo.default_branch}/${DIST_ROOT}plugin.js`;
|
|
49
|
+
const res = await (0, misc_1.httpsString)(url);
|
|
50
|
+
if (!res.ok)
|
|
51
|
+
throw res.statusCode;
|
|
52
|
+
const pl = (0, plugins_1.parsePluginSource)(repoInfo.full_name, res.body); // use 'repo' as 'id' client-side
|
|
53
|
+
pl.branch = branch || undefined;
|
|
54
|
+
return pl;
|
|
55
|
+
}
|
|
56
|
+
exports.readOnlinePlugin = readOnlinePlugin;
|
|
57
|
+
function getFolder2repo() {
|
|
58
|
+
const ret = Object.fromEntries((0, plugins_1.getAvailablePlugins)().map(x => [x.id, x.repo]));
|
|
59
|
+
Object.assign(ret, Object.fromEntries((0, plugins_1.mapPlugins)(x => [x.id, x.getData().repo])));
|
|
60
|
+
return ret;
|
|
61
|
+
}
|
|
62
|
+
exports.getFolder2repo = getFolder2repo;
|
|
63
|
+
async function apiGithub(uri) {
|
|
64
|
+
const res = await (0, misc_1.httpsString)('https://api.github.com/' + uri, {
|
|
65
|
+
headers: {
|
|
66
|
+
'User-Agent': 'HFS',
|
|
67
|
+
Accept: 'application/vnd.github.v3+json',
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
throw res.statusCode;
|
|
72
|
+
return JSON.parse(res.body);
|
|
73
|
+
}
|
|
74
|
+
async function* searchPlugins(text) {
|
|
75
|
+
var _a;
|
|
76
|
+
const res = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
|
|
77
|
+
for (const it of res.items) {
|
|
78
|
+
const repo = it.full_name;
|
|
79
|
+
let pl = await readOnlinePlugin(it);
|
|
80
|
+
if (!pl.apiRequired)
|
|
81
|
+
continue; // mandatory field
|
|
82
|
+
if (pl.badApi) { // we try other branches (starting with 'api')
|
|
83
|
+
const branches = (await apiGithub('repos/' + it.full_name + '/branches'))
|
|
84
|
+
.map((x) => x.name).filter((x) => x.startsWith('api')).sort();
|
|
85
|
+
for (const branch of branches) {
|
|
86
|
+
pl = await readOnlinePlugin(it, branch);
|
|
87
|
+
if (!pl.apiRequired)
|
|
88
|
+
pl.badApi = '-';
|
|
89
|
+
if (!pl.badApi)
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (pl.badApi)
|
|
94
|
+
continue;
|
|
95
|
+
Object.assign(pl, {
|
|
96
|
+
downloading: downloading[repo],
|
|
97
|
+
license: (_a = it.license) === null || _a === void 0 ? void 0 : _a.spdx_id,
|
|
98
|
+
}, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count']));
|
|
99
|
+
yield pl;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.searchPlugins = searchPlugins;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.app = void 0;
|
|
9
|
+
const koa_1 = __importDefault(require("koa"));
|
|
10
|
+
const koa_mount_1 = __importDefault(require("koa-mount"));
|
|
11
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
12
|
+
const const_1 = require("./const");
|
|
13
|
+
const frontEndApis_1 = require("./frontEndApis");
|
|
14
|
+
const log_1 = require("./log");
|
|
15
|
+
const plugins_1 = require("./plugins");
|
|
16
|
+
const throttler_1 = require("./throttler");
|
|
17
|
+
const middlewares_1 = require("./middlewares");
|
|
18
|
+
require("./listen");
|
|
19
|
+
require("./commands");
|
|
20
|
+
const adminApis_1 = require("./adminApis");
|
|
21
|
+
const config_1 = require("./config");
|
|
22
|
+
const assert_1 = require("assert");
|
|
23
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
24
|
+
(0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints
|
|
25
|
+
const keys = ['hfs-keys-test'];
|
|
26
|
+
exports.app = new koa_1.default({ keys });
|
|
27
|
+
exports.app.use(middlewares_1.someSecurity)
|
|
28
|
+
.use((0, middlewares_1.sessions)(exports.app))
|
|
29
|
+
.use(middlewares_1.prepareState)
|
|
30
|
+
.use(middlewares_1.headRequests)
|
|
31
|
+
.use((0, log_1.log)())
|
|
32
|
+
.use(throttler_1.throttler)
|
|
33
|
+
.use(middlewares_1.gzipper)
|
|
34
|
+
.use((0, plugins_1.pluginsMiddleware)())
|
|
35
|
+
.use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
|
|
36
|
+
.use(middlewares_1.serveGuiAndSharedFiles)
|
|
37
|
+
.on('error', errorHandler);
|
|
38
|
+
function errorHandler(err) {
|
|
39
|
+
const { code } = err;
|
|
40
|
+
if (const_1.DEV && code === 'ENOENT' && err.path.endsWith('sockjs-node'))
|
|
41
|
+
return; // spam out dev stuff
|
|
42
|
+
if (code === 'ECANCELED' || code === 'ECONNRESET' || code === 'ECONNABORTED' || code === 'EPIPE')
|
|
43
|
+
return; // someone interrupted, don't care
|
|
44
|
+
console.error('server error', err);
|
|
45
|
+
}
|
|
46
|
+
process.on('uncaughtException', err => {
|
|
47
|
+
if (err.syscall !== 'watch')
|
|
48
|
+
console.error(err);
|
|
49
|
+
});
|
|
50
|
+
(0, config_1.defineConfig)('proxies', 0).sub(n => {
|
|
51
|
+
exports.app.proxy = n > 0;
|
|
52
|
+
exports.app.maxIpsCount = n;
|
|
53
|
+
});
|
package/src/listen.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.getUrls = exports.getStatus = exports.httpsPortCfg = exports.portCfg = void 0;
|
|
31
|
+
const http = __importStar(require("http"));
|
|
32
|
+
const config_1 = require("./config");
|
|
33
|
+
const index_1 = require("./index");
|
|
34
|
+
const https = __importStar(require("https"));
|
|
35
|
+
const watchLoad_1 = require("./watchLoad");
|
|
36
|
+
const os_1 = require("os");
|
|
37
|
+
const connections_1 = require("./connections");
|
|
38
|
+
const open_1 = __importDefault(require("open"));
|
|
39
|
+
const misc_1 = require("./misc");
|
|
40
|
+
const const_1 = require("./const");
|
|
41
|
+
const find_process_1 = __importDefault(require("find-process"));
|
|
42
|
+
const perm_1 = require("./perm");
|
|
43
|
+
let httpSrv;
|
|
44
|
+
let httpsSrv;
|
|
45
|
+
const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
|
|
46
|
+
exports.portCfg = (0, config_1.defineConfig)('port', 80);
|
|
47
|
+
exports.portCfg.sub(async (port) => {
|
|
48
|
+
while (!index_1.app)
|
|
49
|
+
await (0, misc_1.wait)(100);
|
|
50
|
+
stopServer(httpSrv).then();
|
|
51
|
+
httpSrv = Object.assign(http.createServer(index_1.app.callback()), { name: 'http' });
|
|
52
|
+
port = await startServer(httpSrv, { port });
|
|
53
|
+
if (!port)
|
|
54
|
+
return;
|
|
55
|
+
httpSrv.on('connection', connections_1.newConnection);
|
|
56
|
+
printUrls(port, 'http');
|
|
57
|
+
if (openBrowserAtStart.get())
|
|
58
|
+
(0, open_1.default)('http://localhost' + (port === 80 ? '' : ':' + port) + const_1.ADMIN_URI, { wait: true }).catch(e => {
|
|
59
|
+
console.debug(String(e));
|
|
60
|
+
console.warn("cannot launch browser on this machine >PLEASE< open your browser and reach one of these (you may need a different address)", ...Object.values(getUrls()).flat().map(x => '\n - ' + x + const_1.ADMIN_URI));
|
|
61
|
+
if (!(0, perm_1.anyAccountCanLoginAdmin)())
|
|
62
|
+
console.log(`HINT: you can enter command: create-admin YOUR_PASSWORD`);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
66
|
+
var _a, _b;
|
|
67
|
+
stopServer(httpsSrv).then();
|
|
68
|
+
let port = exports.httpsPortCfg.get();
|
|
69
|
+
try {
|
|
70
|
+
while (!index_1.app)
|
|
71
|
+
await (0, misc_1.wait)(100);
|
|
72
|
+
httpsSrv = Object.assign(https.createServer(port < 0 ? {} : { key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https', error: undefined });
|
|
73
|
+
if (port >= 0) {
|
|
74
|
+
const namesForOutput = { cert: 'certificate', private_key: 'private key' };
|
|
75
|
+
const missing = (_a = httpsNeeds.find(x => !x.get())) === null || _a === void 0 ? void 0 : _a.key();
|
|
76
|
+
if (missing)
|
|
77
|
+
return httpsSrv.error = "missing " + namesForOutput[missing];
|
|
78
|
+
const cantRead = (_b = httpsNeeds.find(x => !httpsOptions[x.key()])) === null || _b === void 0 ? void 0 : _b.key();
|
|
79
|
+
if (cantRead)
|
|
80
|
+
return httpsSrv.error = "cannot read " + namesForOutput[cantRead];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
httpsSrv.error = "bad private key or certificate";
|
|
85
|
+
console.log("failed to create https server: check your private key and certificate", String(e));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
port = await startServer(httpsSrv, { port });
|
|
89
|
+
if (!port)
|
|
90
|
+
return;
|
|
91
|
+
httpsSrv.on('connection', connections_1.newConnection);
|
|
92
|
+
printUrls(port, 'https');
|
|
93
|
+
});
|
|
94
|
+
const cert = (0, config_1.defineConfig)('cert');
|
|
95
|
+
const privateKey = (0, config_1.defineConfig)('private_key');
|
|
96
|
+
const httpsNeeds = [cert, privateKey];
|
|
97
|
+
const httpsOptions = { cert: '', private_key: '' };
|
|
98
|
+
for (const cfg of httpsNeeds) {
|
|
99
|
+
let unwatch;
|
|
100
|
+
cfg.sub(async (v) => {
|
|
101
|
+
unwatch === null || unwatch === void 0 ? void 0 : unwatch();
|
|
102
|
+
const k = cfg.key();
|
|
103
|
+
httpsOptions[k] = v;
|
|
104
|
+
if (!v || v.includes('\n'))
|
|
105
|
+
return considerHttps();
|
|
106
|
+
// v is a path
|
|
107
|
+
httpsOptions[k] = '';
|
|
108
|
+
unwatch = (0, watchLoad_1.watchLoad)(v, data => {
|
|
109
|
+
httpsOptions[k] = data;
|
|
110
|
+
considerHttps();
|
|
111
|
+
}).unwatch;
|
|
112
|
+
await considerHttps();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
exports.httpsPortCfg = (0, config_1.defineConfig)('https_port', -1);
|
|
116
|
+
exports.httpsPortCfg.sub(considerHttps);
|
|
117
|
+
function startServer(srv, { port, host }) {
|
|
118
|
+
return new Promise(async (resolve) => {
|
|
119
|
+
try {
|
|
120
|
+
if (port < 0 || !host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
|
|
121
|
+
return resolve(0);
|
|
122
|
+
port = await listen(host);
|
|
123
|
+
if (port)
|
|
124
|
+
console.log(srv.name, "serving on", host || "any network", ':', port);
|
|
125
|
+
resolve(port);
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
srv.error = String(e);
|
|
129
|
+
console.error(srv.name, "couldn't listen on port", port, srv.error);
|
|
130
|
+
resolve(0);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
async function testIpV4() {
|
|
134
|
+
const res = await listen('0.0.0.0');
|
|
135
|
+
await new Promise(res => srv.close(res));
|
|
136
|
+
return res > 0;
|
|
137
|
+
}
|
|
138
|
+
function listen(host) {
|
|
139
|
+
return new Promise(async (resolve, reject) => {
|
|
140
|
+
srv.listen({ port, host }, () => {
|
|
141
|
+
const ad = srv.address();
|
|
142
|
+
if (!ad)
|
|
143
|
+
return reject('no address');
|
|
144
|
+
if (typeof ad === 'string') {
|
|
145
|
+
srv.close();
|
|
146
|
+
return reject('type of socket not supported');
|
|
147
|
+
}
|
|
148
|
+
resolve(ad.port);
|
|
149
|
+
}).on('error', async (e) => {
|
|
150
|
+
srv.error = String(e);
|
|
151
|
+
srv.busy = undefined;
|
|
152
|
+
const { code } = e;
|
|
153
|
+
if (code === 'EADDRINUSE') {
|
|
154
|
+
srv.busy = (0, find_process_1.default)('port', port).then(res => { var _a; return ((_a = res === null || res === void 0 ? void 0 : res[0]) === null || _a === void 0 ? void 0 : _a.name) || ''; }, () => '');
|
|
155
|
+
srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`;
|
|
156
|
+
}
|
|
157
|
+
console.error(srv.name, srv.error);
|
|
158
|
+
const k = (srv === httpSrv ? exports.portCfg : exports.httpsPortCfg).key();
|
|
159
|
+
console.log(` >> try specifying a different port, enter this command: config ${k} 8011`);
|
|
160
|
+
resolve(0);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function stopServer(srv) {
|
|
166
|
+
return new Promise(resolve => {
|
|
167
|
+
if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
|
|
168
|
+
return resolve(null);
|
|
169
|
+
const ad = srv.address();
|
|
170
|
+
if (ad && typeof ad !== 'string')
|
|
171
|
+
console.log("stopped port", ad.port);
|
|
172
|
+
srv.close(err => {
|
|
173
|
+
if (err && err.code !== 'ERR_SERVER_NOT_RUNNING')
|
|
174
|
+
console.debug("failed to stop server", String(err));
|
|
175
|
+
resolve(err);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function getStatus() {
|
|
180
|
+
return {
|
|
181
|
+
httpSrv,
|
|
182
|
+
httpsSrv,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
exports.getStatus = getStatus;
|
|
186
|
+
const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
|
|
187
|
+
function getUrls() {
|
|
188
|
+
return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
|
|
189
|
+
var _a;
|
|
190
|
+
if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
|
|
191
|
+
return false;
|
|
192
|
+
const port = (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
|
|
193
|
+
const appendPort = port === (srv.name === 'https' ? 443 : 80) ? '' : ':' + port;
|
|
194
|
+
const urls = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).map(([name, nets]) => nets && !ignore.test(name) && nets.map(net => {
|
|
195
|
+
if (net.internal)
|
|
196
|
+
return;
|
|
197
|
+
let { address } = net;
|
|
198
|
+
if (address.includes(':'))
|
|
199
|
+
address = '[' + address + ']';
|
|
200
|
+
return srv.name + '://' + address + appendPort;
|
|
201
|
+
})).flat());
|
|
202
|
+
return urls.length && [srv.name, urls];
|
|
203
|
+
})));
|
|
204
|
+
}
|
|
205
|
+
exports.getUrls = getUrls;
|
|
206
|
+
function printUrls(port, proto) {
|
|
207
|
+
if (!port)
|
|
208
|
+
return;
|
|
209
|
+
for (const [name, nets] of Object.entries((0, os_1.networkInterfaces)())) {
|
|
210
|
+
if (!nets)
|
|
211
|
+
continue;
|
|
212
|
+
const filteredNets = nets.filter(n => !n.internal);
|
|
213
|
+
if (!filteredNets.length || ignore.test(name))
|
|
214
|
+
continue;
|
|
215
|
+
console.log('network', name);
|
|
216
|
+
for (const net of nets) {
|
|
217
|
+
if (net.internal)
|
|
218
|
+
continue;
|
|
219
|
+
const appendPort = port === (proto === 'https' ? 443 : 80) ? '' : ':' + port;
|
|
220
|
+
let { address } = net;
|
|
221
|
+
if (address.includes(':'))
|
|
222
|
+
address = '[' + address + ']';
|
|
223
|
+
console.log('-', proto + '://' + address + appendPort);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
package/src/log.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This file is part of HFS - Copyright 2021-2022, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.log = exports.loggers = void 0;
|
|
31
|
+
const config_1 = require("./config");
|
|
32
|
+
const fs_1 = require("fs");
|
|
33
|
+
const util = __importStar(require("util"));
|
|
34
|
+
const promises_1 = require("fs/promises");
|
|
35
|
+
const const_1 = require("./const");
|
|
36
|
+
const events_1 = __importDefault(require("./events"));
|
|
37
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
38
|
+
const path_1 = require("path");
|
|
39
|
+
const perm_1 = require("./perm");
|
|
40
|
+
class Logger {
|
|
41
|
+
constructor(name) {
|
|
42
|
+
this.name = name;
|
|
43
|
+
this.path = '';
|
|
44
|
+
}
|
|
45
|
+
async setPath(path) {
|
|
46
|
+
var _a;
|
|
47
|
+
this.path = path;
|
|
48
|
+
(_a = this.stream) === null || _a === void 0 ? void 0 : _a.end();
|
|
49
|
+
this.last = undefined;
|
|
50
|
+
if (!path)
|
|
51
|
+
return this.stream = undefined;
|
|
52
|
+
try {
|
|
53
|
+
const stats = await (0, promises_1.stat)(path);
|
|
54
|
+
this.last = stats.mtime || stats.ctime;
|
|
55
|
+
}
|
|
56
|
+
catch (_b) {
|
|
57
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(path), { recursive: true })
|
|
58
|
+
.catch(() => console.log("cannot create folder for", path));
|
|
59
|
+
}
|
|
60
|
+
this.reopen();
|
|
61
|
+
}
|
|
62
|
+
reopen() {
|
|
63
|
+
return this.stream = (0, fs_1.createWriteStream)(this.path, { flags: 'a' })
|
|
64
|
+
.on('error', () => this.stream = undefined);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// we'll have names same as config keys. These are used also by the get_log api.
|
|
68
|
+
const accessLogger = new Logger('log');
|
|
69
|
+
const accessErrorLog = new Logger('error_log');
|
|
70
|
+
exports.loggers = [accessLogger, accessErrorLog];
|
|
71
|
+
(0, config_1.defineConfig)('log', 'logs/access.log').sub(path => {
|
|
72
|
+
console.debug('access log file: ' + (path || 'disabled'));
|
|
73
|
+
accessLogger.setPath(path);
|
|
74
|
+
});
|
|
75
|
+
const errorLogFile = (0, config_1.defineConfig)(accessErrorLog.name, 'logs/access-error.log');
|
|
76
|
+
errorLogFile.sub(path => {
|
|
77
|
+
console.debug('access error log: ' + (path || 'disabled'));
|
|
78
|
+
accessErrorLog.setPath(path);
|
|
79
|
+
});
|
|
80
|
+
const logRotation = (0, config_1.defineConfig)('log_rotation', 'weekly');
|
|
81
|
+
function log() {
|
|
82
|
+
const debounce = lodash_1.default.debounce(cb => cb(), 1000);
|
|
83
|
+
return async (ctx, next) => {
|
|
84
|
+
var _a;
|
|
85
|
+
await next();
|
|
86
|
+
const isError = ctx.status >= 400;
|
|
87
|
+
const logger = isError && accessErrorLog || accessLogger;
|
|
88
|
+
const rotate = (_a = logRotation.get()) === null || _a === void 0 ? void 0 : _a[0];
|
|
89
|
+
let { stream, last, path } = logger;
|
|
90
|
+
if (!stream)
|
|
91
|
+
return;
|
|
92
|
+
const now = new Date();
|
|
93
|
+
const a = now.toString().split(' ');
|
|
94
|
+
logger.last = now;
|
|
95
|
+
if (rotate && last) { // rotation enabled and a file exists?
|
|
96
|
+
const passed = Number(now) - Number(last)
|
|
97
|
+
- 3600000; // be pessimistic and count a possible DST change
|
|
98
|
+
if (rotate === 'm' && (passed >= 31 * const_1.DAY || now.getMonth() !== last.getMonth())
|
|
99
|
+
|| rotate === 'd' && (passed >= const_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
|
|
100
|
+
|| rotate === 'w' && (passed >= 7 * const_1.DAY || now.getDay() < last.getDay())) {
|
|
101
|
+
stream.end();
|
|
102
|
+
const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
|
|
103
|
+
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
|
|
104
|
+
(0, fs_1.renameSync)(path, path + '-' + postfix);
|
|
105
|
+
}
|
|
106
|
+
catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
|
|
107
|
+
console.error(e);
|
|
108
|
+
}
|
|
109
|
+
stream = logger.reopen(); // keep variable updated
|
|
110
|
+
if (!stream)
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s\n'; // Apache's Common Log Format
|
|
115
|
+
const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + a[5].slice(3);
|
|
116
|
+
const user = (0, perm_1.getCurrentUsername)(ctx);
|
|
117
|
+
events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status', 'length']), { user, ts: now, uri: ctx.path }));
|
|
118
|
+
console.debug(ctx.status, ctx.method, ctx.path);
|
|
119
|
+
debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
|
|
120
|
+
(0, promises_1.stat)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
|
|
121
|
+
stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, ctx.path, ctx.req.httpVersion, ctx.status, ctx.length ? ctx.length.toString() : '-'));
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
exports.log = log;
|
|
125
|
+
function doubleDigit(n) {
|
|
126
|
+
return n > 9 ? n : '0' + n;
|
|
127
|
+
}
|
|
128
|
+
// dump console.error to file
|
|
129
|
+
const debugLogFile = (0, fs_1.createWriteStream)('debug.log', { flags: 'a' });
|
|
130
|
+
debugLogFile.on('open', () => {
|
|
131
|
+
const was = console.error;
|
|
132
|
+
console.error = function (...args) {
|
|
133
|
+
was.apply(this, args);
|
|
134
|
+
const params = args.map(x => typeof x === 'string' ? x : JSON.stringify(x)).join(' ');
|
|
135
|
+
debugLogFile.write(new Date().toLocaleString() + ': ' + params + '\n');
|
|
136
|
+
};
|
|
137
|
+
}).on('error', () => console.log("cannot create debug.log"));
|