Compare commits

..

6 Commits

Author SHA1 Message Date
4400ba5dd6 WIP 2023-12-10 20:29:14 +08:00
2b428e139c Merge branch 'master' into build-windows
# Conflicts:
#	pnpm-lock.yaml
2023-12-10 13:23:15 +08:00
587d2dcaca Fix URLs with env 2023-12-10 13:17:04 +08:00
5f1f3593fd Add pnpm run dev 2023-12-10 13:16:40 +08:00
e570374a48 WIP 2023-12-10 13:07:24 +08:00
316c566c76 Update dependencies 2023-12-10 02:39:39 +08:00
9 changed files with 785 additions and 486 deletions

View File

@ -194,39 +194,6 @@ export class DockgeServer {
// Create Socket.io
this.io = new socketIO.Server(this.httpServer, {
cors,
allowRequest: (req, callback) => {
let isOriginValid = true;
const bypass = isDev;
if (!bypass) {
let host = req.headers.host;
// If this is set, it means the request is from the browser
let origin = req.headers.origin;
// If this is from the browser, check if the origin is allowed
if (origin) {
try {
let originURL = new URL(origin);
if (host !== originURL.host) {
isOriginValid = false;
log.error("auth", `Origin (${origin}) does not match host (${host}), IP: ${req.socket.remoteAddress}`);
}
} catch (e) {
// Invalid origin url, probably not from browser
isOriginValid = false;
log.error("auth", `Invalid origin url (${origin}), IP: ${req.socket.remoteAddress}`);
}
} else {
log.info("auth", `Origin is not set, IP: ${req.socket.remoteAddress}`);
}
} else {
log.debug("auth", "Origin check is bypassed");
}
callback(null, isOriginValid);
}
});
this.io.on("connection", async (socket: Socket) => {
@ -611,35 +578,4 @@ export class DockgeServer {
finalFunction() {
log.info("server", "Graceful shutdown successful!");
}
/**
* Force connected sockets of a user to refresh and disconnect.
* Used for resetting password.
* @param {string} userID
* @param {string?} currentSocketID
*/
disconnectAllSocketClients(userID: number, currentSocketID? : string) {
for (const rawSocket of this.io.sockets.sockets.values()) {
let socket = rawSocket as DockgeSocket;
if (socket.userID === userID && socket.id !== currentSocketID) {
try {
socket.emit("refresh");
socket.disconnect();
} catch (e) {
}
}
}
}
isSSL() {
return this.config.sslKey && this.config.sslCert;
}
getLocalWebSocketURL() {
const protocol = this.isSSL() ? "wss" : "ws";
const host = this.config.hostname || "localhost";
return `${protocol}://${host}:${this.config.port}`;
}
}

View File

@ -211,8 +211,6 @@ export class MainSocketHandler extends SocketHandler {
let user = await doubleCheckPassword(socket, password.currentPassword);
await user.resetPassword(password.newPassword);
server.disconnectAllSocketClients(user.id, socket.id);
callback({
ok: true,
msg: "Password has been updated successfully.",
@ -282,18 +280,6 @@ export class MainSocketHandler extends SocketHandler {
}
}
});
// Disconnect all other socket clients of the user
socket.on("disconnectOtherSocketClients", async () => {
try {
checkLogin(socket);
server.disconnectAllSocketClients(socket.userID, socket.id);
} catch (e) {
if (e instanceof Error) {
log.warn("disconnectOtherSocketClients", e.message);
}
}
});
}
async login(username : string, password : string) : Promise<User | null> {

View File

@ -21,11 +21,6 @@ export interface LooseObject {
[key: string]: any
}
export interface BaseRes {
ok: boolean;
msg?: string;
}
let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes();

92
extra/build-windows.ts Normal file
View File

@ -0,0 +1,92 @@
import fsAsync from "fs/promises";
import unzipper from "unzipper";
import stream from "node:stream";
import { fileExists } from "../backend/util-server";
const version = process.env.VERSION;
if (!version) {
console.error("VERSION env not set");
process.exit(1);
}
const output = `./private/build/dockgen-${version}-win-x64.zip`;
if (await fileExists(output)) {
console.error(`${output} already exists`);
process.exit(1);
}
console.log(`Building ${output}`);
const nodeVersion = "18.17.1";
const buildPath = "./private/build/windows";
const nodePath = `${buildPath}/node`;
const nodeTempPath = `${buildPath}/node-v${nodeVersion}-win-x64`;
const corePath = `${buildPath}/core`;
// Clear
await fsAsync.rm(`${buildPath}/dockge-${version}`, {
recursive: true,
force: true
});
await fsAsync.rm(corePath, {
recursive: true,
force: true
});
// mkdir
await fsAsync.mkdir(buildPath, {
recursive: true
});
// Download Node.js if not exists
// Download,pipe to unzipper and extract to nodePath
if (!await fileExists(nodePath)) {
console.log(`Downloading Node.js ${nodeVersion}`);
try {
await download(`https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-win-x64.zip`);
// Rename folder
await fsAsync.rename(nodeTempPath, nodePath);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
process.exit(1);
}
} else {
console.log(`Node.js ${nodeVersion} already exists, skipping download`);
}
// Download Dockge from GitHub
console.log(`Downloading Dockge ${version} from GitHub`);
try {
await download(`https://github.com/louislam/dockge/archive/refs/tags/${version}.zip`);
// Rename folder
await fsAsync.rename(`${buildPath}/dockge-${version}`, corePath);
} catch (e) {
if (e instanceof Error) {
console.error(e.message);
}
process.exit(1);
}
function download(url : string) {
return new Promise((resolve, reject) => {
fetch(url).then((res) => {
if (res.body) {
// @ts-ignore
stream.Readable.fromWeb(res.body)
.pipe(unzipper.Extract({
path: buildPath,
}))
.on("close", resolve);
} else {
reject(new Error(`Unable to download ${url}`));
}
});
});
}

View File

@ -4,8 +4,6 @@ import readline from "readline";
import { User } from "../backend/models/user";
import { DockgeServer } from "../backend/dockge-server";
import { log } from "../backend/log";
import { io } from "socket.io-client";
import { BaseRes } from "../backend/util-common";
console.log("== Dockge Reset Password Tool ==");
@ -14,10 +12,11 @@ const rl = readline.createInterface({
output: process.stdout
});
const server = new DockgeServer();
export const main = async () => {
const server = new DockgeServer();
// Check if
console.log("Connecting the database");
try {
await Database.init(server);
@ -48,16 +47,12 @@ export const main = async () => {
// Reset all sessions by reset jwt secret
await server.initJWTSecret();
console.log("Password reset successfully.");
// Disconnect all other socket clients of the user
await disconnectAllSocketClients(user.username, password);
break;
} else {
console.log("Passwords do not match, please try again.");
}
}
console.log("Password reset successfully.");
}
} catch (e) {
if (e instanceof Error) {
@ -84,47 +79,6 @@ function question(question : string) : Promise<string> {
});
}
function disconnectAllSocketClients(username : string, password : string) : Promise<void> {
return new Promise((resolve) => {
const url = server.getLocalWebSocketURL();
console.log("Connecting to " + url + " to disconnect all other socket clients");
// Disconnect all socket connections
const socket = io(url, {
transports: [ "websocket" ],
reconnection: false,
timeout: 5000,
});
socket.on("connect", () => {
socket.emit("login", {
username,
password,
}, (res : BaseRes) => {
if (res.ok) {
console.log("Logged in.");
socket.emit("disconnectOtherSocketClients");
} else {
console.warn("Login failed.");
console.warn("Please restart the server to disconnect all sessions.");
}
socket.close();
});
});
socket.on("connect_error", function () {
// The localWebSocketURL is not guaranteed to be working for some complicated Uptime Kuma setup
// Ask the user to restart the server manually
console.warn("Failed to connect to " + url);
console.warn("Please restart the server to disconnect all sessions manually.");
resolve();
});
socket.on("disconnect", () => {
resolve();
});
});
}
if (!process.env.TEST_BACKEND) {
main();
}

View File

@ -202,10 +202,6 @@ export default defineComponent({
}
}
});
socket.on("refresh", () => {
location.reload();
});
},
/**

View File

@ -299,12 +299,12 @@ export default {
computed: {
urls() {
if (!this.jsonConfig["x-dockge"] || !this.jsonConfig["x-dockge"].urls || !Array.isArray(this.jsonConfig["x-dockge"].urls)) {
if (!this.envsubstJSONConfig["x-dockge"] || !this.envsubstJSONConfig["x-dockge"].urls || !Array.isArray(this.envsubstJSONConfig["x-dockge"].urls)) {
return [];
}
let urls = [];
for (const url of this.jsonConfig["x-dockge"].urls) {
for (const url of this.envsubstJSONConfig["x-dockge"].urls) {
let display;
try {
let obj = new URL(url);

View File

@ -10,13 +10,15 @@
"lint": "eslint \"**/*.{ts,vue}\"",
"check-ts": "tsc --noEmit",
"start": "tsx ./backend/index.ts",
"dev": "concurrently -k -r \"wait-on tcp:5000 && pnpm run dev:backend \" \"pnpm run dev:frontend\"",
"dev:backend": "cross-env NODE_ENV=development tsx watch --inspect ./backend/index.ts",
"dev:frontend": "cross-env NODE_ENV=development vite --host --config ./frontend/vite.config.ts",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && npm run build:docker",
"release-final": "tsx ./extra/test-docker.ts && tsx extra/update-version.ts && pnpm run build:frontend && pnpm run build:docker",
"build:frontend": "vite build --config ./frontend/vite.config.ts",
"build:docker-base": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:base -f ./docker/Base.Dockerfile . --push",
"build:docker": "node ./extra/env2arg.js docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:latest -t louislam/dockge:1 -t louislam/dockge:$VERSION --target release -f ./docker/Dockerfile . --push",
"build:docker-nightly": "pnpm run build:frontend && docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:nightly --target nightly -f ./docker/Dockerfile . --push",
"build:windows": "tsx ./extra/build-windows.ts",
"build:healthcheck": "docker buildx build -f docker/BuildHealthCheck.Dockerfile --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/dockge:build-healthcheck . --push",
"start-docker": "docker run --rm -p 5001:5001 --name dockge louislam/dockge:latest",
"mark-as-nightly": "tsx ./extra/mark-as-nightly.ts",
@ -24,7 +26,7 @@
"reset-password": "tsx ./extra/reset-password.ts"
},
"dependencies": {
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.11",
"@homebridge/node-pty-prebuilt-multiarch": "~0.11.12",
"@inventage/envsubst": "^0.16.0",
"@louislam/sqlite3": "~15.1.6",
"bcryptjs": "~2.4.3",
@ -42,7 +44,8 @@
"jwt-decode": "~3.1.2",
"knex": "~2.5.1",
"limiter-es6-compat": "~2.1.2",
"mysql2": "~3.6.3",
"mysql2": "~3.6.5",
"node-windows": "1.0.0-beta.8",
"promisify-child-process": "~4.1.2",
"redbean-node": "~0.3.3",
"socket.io": "~4.7.2",
@ -55,21 +58,23 @@
},
"devDependencies": {
"@actions/github": "^6.0.0",
"@fontsource/jetbrains-mono": "^5.0.17",
"@fontsource/jetbrains-mono": "^5.0.18",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3",
"@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.9",
"@types/bootstrap": "~5.2.10",
"@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21",
"@types/jsonwebtoken": "~9.0.5",
"@types/unzipper": "^0.10.9",
"@typescript-eslint/eslint-plugin": "~6.8.0",
"@typescript-eslint/parser": "~6.8.0",
"@vitejs/plugin-vue": "~4.5.0",
"@vitejs/plugin-vue": "~4.5.2",
"bootstrap": "5.3.2",
"bootstrap-vue-next": "~0.14.10",
"concurrently": "^8.2.2",
"cross-env": "~7.0.3",
"eslint": "~8.50.0",
"eslint-plugin-jsdoc": "~46.8.2",
@ -78,15 +83,17 @@
"sass": "~1.68.0",
"typescript": "~5.2.2",
"unplugin-vue-components": "~0.25.2",
"vite": "~5.0.0",
"unzipper": "^0.10.14",
"vite": "~5.0.7",
"vite-plugin-compression": "~0.5.1",
"vue": "~3.3.8",
"vue": "~3.3.11",
"vue-eslint-parser": "~9.3.2",
"vue-i18n": "~9.5.0",
"vue-prism-editor": "2.0.0-alpha.2",
"vue-qrcode": "~2.2.0",
"vue-router": "~4.2.5",
"vue-toastification": "2.0.0-rc.5",
"wait-on": "^7.2.0",
"xterm": "5.4.0-beta.37",
"xterm-addon-web-links": "~0.9.0"
}

1011
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff