Browse Source

Env follow up (#231)

* Create the env file only if not empty

* Update

* Check some fs operation to async
Louis Lam 1 year ago
parent
commit
793a9de50d

+ 2 - 0
backend/dockge-server.ts

@@ -32,6 +32,8 @@ import User from "./models/user";
 import childProcessAsync from "promisify-child-process";
 import { Terminal } from "./terminal";
 
+import "dotenv/config";
+
 export class DockgeServer {
     app : Express;
     httpServer : http.Server;

+ 3 - 3
backend/socket-handlers/docker-socket-handler.ts

@@ -12,7 +12,7 @@ export class DockerSocketHandler extends SocketHandler {
         socket.on("deployStack", async (name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown, callback) => {
             try {
                 checkLogin(socket);
-                const stack = this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
+                const stack = await this.saveStack(socket, server, name, composeYAML, composeENV, isAdd);
                 await stack.deploy(socket);
                 server.sendStackList();
                 callback({
@@ -264,7 +264,7 @@ export class DockerSocketHandler extends SocketHandler {
         });
     }
 
-    saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Stack {
+    async saveStack(socket : DockgeSocket, server : DockgeServer, name : unknown, composeYAML : unknown, composeENV : unknown, isAdd : unknown) : Promise<Stack> {
         // Check types
         if (typeof(name) !== "string") {
             throw new ValidationError("Name must be a string");
@@ -280,7 +280,7 @@ export class DockerSocketHandler extends SocketHandler {
         }
 
         const stack = new Stack(server, name, composeYAML, composeENV, false);
-        stack.save(isAdd);
+        await stack.save(isAdd);
         return stack;
     }
 

+ 27 - 12
backend/stack.ts

@@ -1,8 +1,8 @@
 import { DockgeServer } from "./dockge-server";
-import fs from "fs";
+import fs, { promises as fsAsync } from "fs";
 import { log } from "./log";
 import yaml from "yaml";
-import { DockgeSocket, ValidationError } from "./util-server";
+import { DockgeSocket, fileExists, ValidationError } from "./util-server";
 import path from "path";
 import {
     COMBINED_TERMINAL_COLS,
@@ -99,6 +99,15 @@ export class Stack {
 
         // Check YAML format
         yaml.parse(this.composeYAML);
+
+        let lines = this.composeENV.split("\n");
+
+        // Check if the .env is able to pass docker-compose
+        // Prevent "setenv: The parameter is incorrect"
+        // It only happens when there is one line and it doesn't contain "="
+        if (lines.length === 1 && !lines[0].includes("=") && lines[0].length > 0) {
+            throw new ValidationError("Invalid .env format");
+        }
     }
 
     get composeYAML() : string {
@@ -146,29 +155,35 @@ export class Stack {
      * Save the stack to the disk
      * @param isAdd
      */
-    save(isAdd : boolean) {
+    async save(isAdd : boolean) {
         this.validate();
 
         let dir = this.path;
 
         // Check if the name is used if isAdd
         if (isAdd) {
-            if (fs.existsSync(dir)) {
+            if (await fileExists(dir)) {
                 throw new ValidationError("Stack name already exists");
             }
 
             // Create the stack folder
-            fs.mkdirSync(dir);
+            await fsAsync.mkdir(dir);
         } else {
-            if (!fs.existsSync(dir)) {
+            if (!await fileExists(dir)) {
                 throw new ValidationError("Stack not found");
             }
         }
 
         // Write or overwrite the compose.yaml
-        fs.writeFileSync(path.join(dir, this._composeFileName), this.composeYAML);
+        await fsAsync.writeFile(path.join(dir, this._composeFileName), this.composeYAML);
+
+        const envPath = path.join(dir, ".env");
+
         // Write or overwrite the .env
-        fs.writeFileSync(path.join(dir, ".env"), this.composeENV);
+        // If .env is not existing and the composeENV is empty, we don't need to write it
+        if (await fileExists(envPath) || this.composeENV.trim() !== "") {
+            await fsAsync.writeFile(envPath, this.composeENV);
+        }
     }
 
     async deploy(socket? : DockgeSocket) : Promise<number> {
@@ -188,7 +203,7 @@ export class Stack {
         }
 
         // Remove the stack folder
-        fs.rmSync(this.path, {
+        await fsAsync.rm(this.path, {
             recursive: true,
             force: true
         });
@@ -218,12 +233,12 @@ export class Stack {
             stackList = new Map<string, Stack>();
 
             // Scan the stacks directory, and get the stack list
-            let filenameList = fs.readdirSync(stacksDir);
+            let filenameList = await fsAsync.readdir(stacksDir);
 
             for (let filename of filenameList) {
                 try {
                     // Check if it is a directory
-                    let stat = fs.statSync(path.join(stacksDir, filename));
+                    let stat = await fsAsync.stat(path.join(stacksDir, filename));
                     if (!stat.isDirectory()) {
                         continue;
                     }
@@ -314,7 +329,7 @@ export class Stack {
         let dir = path.join(server.stacksDir, stackName);
 
         if (!skipFSOperations) {
-            if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
+            if (!await fileExists(dir) || !(await fsAsync.stat(dir)).isDirectory()) {
                 // Maybe it is a stack managed by docker compose directly
                 let stackList = await this.getStackList(server, true);
                 let stack = stackList.get(stackName);

+ 7 - 0
backend/util-server.ts

@@ -5,6 +5,7 @@ import { log } from "./log";
 import { ERROR_TYPE_VALIDATION } from "./util-common";
 import { R } from "redbean-node";
 import { verifyPassword } from "./password-hash";
+import fs from "fs";
 
 export interface JWTDecoded {
     username : string;
@@ -82,3 +83,9 @@ export async function doubleCheckPassword(socket : DockgeSocket, currentPassword
 
     return user;
 }
+
+export function fileExists(file : string) {
+    return fs.promises.access(file, fs.constants.F_OK)
+        .then(() => true)
+        .catch(() => false);
+}

+ 1 - 0
package.json

@@ -33,6 +33,7 @@
         "composerize": "~1.4.1",
         "croner": "~7.0.5",
         "dayjs": "~1.11.10",
+        "dotenv": "~16.3.1",
         "express": "~4.18.2",
         "express-static-gzip": "~2.1.7",
         "http-graceful-shutdown": "~3.1.13",

+ 8 - 0
pnpm-lock.yaml

@@ -32,6 +32,9 @@ dependencies:
   dayjs:
     specifier: ~1.11.10
     version: 1.11.10
+  dotenv:
+    specifier: ~16.3.1
+    version: 16.3.1
   express:
     specifier: ~4.18.2
     version: 4.18.2
@@ -2154,6 +2157,11 @@ packages:
       esutils: 2.0.3
     dev: true
 
+  /dotenv@16.3.1:
+    resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
+    engines: {node: '>=12'}
+    dev: false
+
   /eastasianwidth@0.2.0:
     resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
     dev: false