From 3486f90bd36fd80f7dbfc9c709f5d6cdfd4a92a5 Mon Sep 17 00:00:00 2001 From: Daan <20974756+daanschenkel@users.noreply.github.com> Date: Thu, 8 Aug 2024 20:38:45 +0200 Subject: [PATCH 1/2] Remove unnecessary extra test --- .github/workflows/test.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 22e125f..63a1907 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,9 +2,6 @@ name: Tests on: workflow_dispatch: - push: - branches: - - main pull_request: branches: - main From cbea4af4a5eeceb945cb21b0caa11d265292ee74 Mon Sep 17 00:00:00 2001 From: Daan <20974756+daanschenkel@users.noreply.github.com> Date: Thu, 8 Aug 2024 22:29:29 +0200 Subject: [PATCH 2/2] Add webui --- .env.example | 3 +- .gitignore | 3 +- README.md | 1 + package-lock.json | 71 +++++++++++++++++++++++++++++++++++++++++++++-- package.json | 7 +++-- src/anzip.d.ts | 29 +++++++++++++++++++ src/http.ts | 5 ++-- src/index.ts | 34 ++++++++++++++++++++++- src/ui.ts | 34 +++++++++++++++++++++++ src/utils.ts | 7 +++-- tsconfig.json | 3 +- 11 files changed, 184 insertions(+), 13 deletions(-) create mode 100644 src/anzip.d.ts create mode 100644 src/ui.ts diff --git a/.env.example b/.env.example index 949d1c0..430e8be 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ HEYA=true #Used to check if the .env file is loaded, THIS IS REQUIRED! PORT=7999 #The port LTS runs on. +UI_PORT=7998 #The port the WebUI runs on. ENABLE_WEBUI=true #Enables the WebUI, where you can manage your buckets and files. The UI is available at / WELCOME_MESSAGE=LittleTinyStorage #The message that is shown when visiting / @@ -11,7 +12,7 @@ DELETE_BUCKETS_WHEN_ENV_REMOVED=false #DANGEROUS DANGEROUS DATA LOSS DANGEROUS A CORS_ALLOWED_ORIGINS=* #The origins that are allowed to access the API, seperated by a comma API_KEY=pleasehackme #The api key is used to authenticate with the API -BUCKETS=bucket1,bucket2 #The buckets that are created, seperated by a comma (yes, you can edit this after initialization) +BUCKETS=bucket1,bucket2 #The buckets that are created, seperated by a comma (yes, you can edit this after initialization) #Per bucket configuration diff --git a/.gitignore b/.gitignore index a18337b..8d48e02 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ .env data/ out -dist/ \ No newline at end of file +dist/ +web/ \ No newline at end of file diff --git a/README.md b/README.md index 25809b4..31be0cb 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ A little tiny file storage server, for the people that use S3 when they really d - Directory listings - Store on your own server - Completely configured using environment variables +- A web interface for managing your buckets and files ## Setup diff --git a/package-lock.json b/package-lock.json index 56febe6..ce55881 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,13 +6,16 @@ "": { "name": "littletinystorage", "dependencies": { + "anzip": "^0.2.0", "dotenv": "^16.4.5", "jose": "^5.6.3", - "kleur": "^4.1.5" + "kleur": "^4.1.5", + "mime": "^4.0.4" }, "devDependencies": { "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^22.1.0" + "@types/node": "^22.1.0", + "@types/yauzl": "^2.10.3" } }, "node_modules/@types/jsonwebtoken": { @@ -33,6 +36,34 @@ "undici-types": "~6.13.0" } }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/anzip": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/anzip/-/anzip-0.2.0.tgz", + "integrity": "sha512-NS4U6nsesdxZg+kRksuzK0PIhW1mMzdW9LFiVecciZJURdKXma2OWr0w77/pvGN94GSdGDHIHmohwRwCQLaqWw==", + "dependencies": { + "yauzl": "^2.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -44,6 +75,14 @@ "url": "https://dotenvx.com" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/jose": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", @@ -60,11 +99,39 @@ "node": ">=6" } }, + "node_modules/mime": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, "node_modules/undici-types": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", "dev": true + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } } } } diff --git a/package.json b/package.json index 91db404..f44207b 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,11 @@ "author": "dandandev", "type": "module", "dependencies": { + "anzip": "^0.2.0", "dotenv": "^16.4.5", "jose": "^5.6.3", - "kleur": "^4.1.5" + "kleur": "^4.1.5", + "mime": "^4.0.4" }, "scripts": { "build:windows": "bun build dist/index.js --compile --outfile out/littletinystorage-win --minify --sourcemap --target bun-windows-x64", @@ -21,6 +23,7 @@ }, "devDependencies": { "@types/jsonwebtoken": "^9.0.6", - "@types/node": "^22.1.0" + "@types/node": "^22.1.0", + "@types/yauzl": "^2.10.3" } } diff --git a/src/anzip.d.ts b/src/anzip.d.ts new file mode 100644 index 0000000..fdf87ee --- /dev/null +++ b/src/anzip.d.ts @@ -0,0 +1,29 @@ +declare module "anzip" { + interface Options { + pattern?: RegExp; + disableSave?: boolean; + outputContent?: boolean; + entryHandler?: Promise; + outputPath?: string; + flattenPath?: boolean; + disableOutput?: boolean; + rules?: Options[]; + } + + interface File { + name: string; + directory: string; + saved: boolean; + content: Buffer; + error: Error; + } + + interface Result { + duration: number; + files: File[]; + } + + function anzip(filename: string, opts?: Options): Promise; + + export = anzip; +} diff --git a/src/http.ts b/src/http.ts index 987fb43..b3f8197 100644 --- a/src/http.ts +++ b/src/http.ts @@ -26,9 +26,10 @@ const directoryTemplate = ` export function createServer( requestListener: http.RequestListener, - port: number + port: number, + name: string ) { - console.log(`🚀 LTS is running on port ${port}`); + console.log(`🚀 ${name} is running on port ${port}`); return http.createServer(requestListener).listen(port); } diff --git a/src/index.ts b/src/index.ts index b4017ad..e539e52 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ import * as path from "path"; import * as https from "https"; import { buckets, dataDir } from "./utils.js"; import { createServer, requestListener } from "./http.js"; +import { requestListener as uiRequestListener } from "./ui.js"; +import anzip from "anzip"; //Initial setup if (!process.env.HEYA) { @@ -32,6 +34,34 @@ if (!process.env.HEYA) { } } +//WebUI +if (process.env.ENABLE_WEBUI === "true") { + const webExists = fs.existsSync("./web"); + if (!webExists) { + console.log("Downloading web interface..."); + //delete old zip + if (fs.existsSync("./webui.zip")) fs.unlinkSync("./webui.zip"); + fetch( + "https://github.com/dandanthedev/littletinystorage-webui/releases/latest/download/build.zip" + ) + .then((res) => res.arrayBuffer()) + .then((buffer) => { + fs.writeFileSync("./webui.zip", Buffer.from(buffer)); + console.log("Web interface downloaded, extracting..."); + anzip("webui.zip").then(() => { + console.log( + "Web interface extracted, deleting zip file and renaming build folder..." + ); + fs.unlinkSync("./webui.zip"); + setTimeout(() => { + //todo: find better solution + fs.renameSync("./build", "./web"); + }, 100); + }); + }); + } +} + //Create data directory if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir); @@ -75,5 +105,7 @@ if (process.env.DELETE_BUCKETS_WHEN_ENV_REMOVED === "true") { } const port = parseInt(process.env.PORT ?? "7999"); +const uiPort = parseInt(process.env.UI_PORT ?? "7998"); -createServer(requestListener, port); +createServer(requestListener, port, "LTS"); +createServer(uiRequestListener, uiPort, "WebUI"); diff --git a/src/ui.ts b/src/ui.ts new file mode 100644 index 0000000..8b8c785 --- /dev/null +++ b/src/ui.ts @@ -0,0 +1,34 @@ +import { IncomingMessage, ServerResponse } from "http"; +import * as fs from "fs"; +import * as path from "path"; +import * as mime from "mime"; +import { resp } from "./utils.js"; + +export const requestListener = async function ( + req: IncomingMessage, + res: ServerResponse +) { + if (!req.url) return; + //send files from ./web directory + let fileName = req.url.replace("/", ""); + if (!fileName) fileName = "index.html"; + + console.log(fileName); + + const exts = ["js", "css", "png"]; + + if ( + fileName && + !fileName.endsWith(".html") && + !exts.map((ext) => fileName.endsWith("." + ext)).includes(true) //most cursed thing i've ever seen but it works + ) { + fileName = fileName + ".html"; + if (!fs.existsSync(path.join("./web", fileName))) return resp(res, 404); + } + + if (fileName) { + const file = fs.createReadStream(path.join("./web", fileName)); + const mimeType = mime.default.getType(fileName); + return resp(res, 200, file, "file", mimeType ?? undefined); + } +}; diff --git a/src/utils.ts b/src/utils.ts index 247925e..40c8879 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -11,19 +11,20 @@ export function resp( res: ServerResponse, status: number, body?: ResponseBody, - type?: ResponseType + type?: ResponseType, + mimeType?: string ) { if (typeof body === "object" && !(body instanceof ReadStream)) body = JSON.stringify(body); - if (type) { + if (type && !mimeType) { if (type === "json") res.setHeader("Content-Type", "application/json"); if (type === "html") res.setHeader("Content-Type", "text/html"); if (type === "file") res.setHeader("Content-Type", "application/octet-stream"); } + if (mimeType) res.setHeader("Content-Type", mimeType); res.writeHead(status); - if (body instanceof ReadStream) { body.pipe(res); } else { diff --git a/tsconfig.json b/tsconfig.json index 11961da..8fc1dca 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "allowImportingTsExtensions": false, "lib": ["ES2021.String"], "moduleDetection": "force", - "removeComments": true + "removeComments": true, + "allowSyntheticDefaultImports": true } }