Clankergit add .!

This commit is contained in:
usernames122
2025-09-13 20:53:31 +02:00
commit 734368103b
7 changed files with 1767 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
{
"name": "Existing Dockerfile",
"build": {
// Sets the run context to one level up instead of the .devcontainer folder.
"context": "..",
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "../Dockerfile"
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line to run commands after the container is created.
// "postCreateCommand": "cat /etc/os-release",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "devcontainer"
}

147
.gitignore vendored Normal file
View File

@@ -0,0 +1,147 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.*
!.env.example
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
.output
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Sveltekit cache directory
.svelte-kit/
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Firebase cache directory
.firebase/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Vite files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
.vite/
# ShittyServerless assets
localstorage/
config.js
# Config.js includes s3 login details, so it should not be committed to version control.

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM debian:stable
# Install Node.js (version 22)
RUN apt-get update && \
apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
EXPOSE 3000

9
config.sample.js Normal file
View File

@@ -0,0 +1,9 @@
var config = {
buckregion: 'local', // Use file-based storage if bucket region is set to 'local', else use S3
bucketname: 'functions',
accessKeyId: 'keyhere',
secretAccessKey: 'secretkeyhere',
endpoint: 's3.amazonaws.com' // Your S3 endpoint
};
module.exports = config

319
index.js Normal file
View File

@@ -0,0 +1,319 @@
const express = require('express')
const { spawn } = require('child_process');
const readline = require('readline');
const AWS = require('aws-sdk');
const config = require('./config.js');
const fs = require('fs');
const path = require('path');
const { get } = require('http');
const app = express();
const port = 3000;
// FILE FUNCS
// Configure S3 client if not using local
let s3;
if (config.buckregion !== 'local') { // assuming 'local' means local file system
s3 = new AWS.S3({
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
endpoint: config.endpoint,
s3ForcePathStyle: true, // needed for self-hosted S3 like MinIO or garage
});
} else {
console.log("!! Using local storage for files. This is not recommended for production.")
}
function getFileFromStorage(loc) {
return new Promise((resolve, reject) => {
if (config.buckregion === 'local') {
fs.readFile(path.join("localstorage", loc), { encoding: 'utf-8' }, (err, data) => {
if (err) return reject(err);
resolve(data);
});
} else {
// S3 access
const params = {
Bucket: config.bucketname,
Key: loc, // loc is the file key in S3
};
s3.getObject(params, (err, data) => {
if (err) return reject(err);
resolve(data.Body.toString('utf-8'));
});
}
});
}
function writeLocalFile(loc, data) {
return new Promise((resolve, reject) => {
fs.writeFile(loc, data, (err) => {
if (err) return reject(err);
resolve();
});
});
}
app.get('/', (req, res) => {
res.send(`<h1>ShittyServerless execution instance.</h1> If you are an end-user, contact site-admin for documentation.<br>
If you are a developer, deploy your serverless functions by placing files in the garage webui manually.`);
})
const exitCodeLookup = {
0: 200,
1: 500,
2: 403,
3: 401,
4: 404,
} // Lookup table to map exit codes to HTTP status codes
function execFileAsync(req,res, command, location, opts = {}) {
// opts: { timeoutMs: number } optional
const timeoutMs = opts.timeoutMs || Infinity; // default no timeout
let killTimer; // to hold timeout timer
return new Promise((resolve, reject) => {
let finished = false;
function onceFinish(err) {
if (finished) return;
finished = true;
try{clearTimeout(killTimer);console.log("Cleared timer")}catch(e){}
if (err) {
try { res.status(500).end(); } catch (e) {}
reject(err);
} else {
resolve();
}
}
(async () => {
try {
// --- prepare temp file (keeps your original logic) ---
const tempDir = '/tmp';
const fileName = path.basename(location[0]);
const tempFilePath = path.join(tempDir, fileName);
const contents = await getFileFromStorage(location[0]);
await writeLocalFile(tempFilePath, contents);
// --- spawn process ---
const child = spawn(command, [tempFilePath]);
// make sure we DONT close stdin (we need to send data to the child for it to have reactiveness)
child.on('error', (err) => {
console.error('child process error', err);
onceFinish(new Error('Failed to spawn child process: ' + err.message));
});
// safety timeout to avoid indefinite hangs
killTimer = setTimeout(() => {
if (finished) return;
console.error(`child timeout after ${timeoutMs}ms — killing pid ${child.pid}`);
try { child.kill('SIGKILL'); } catch (e) {}
onceFinish(new Error(`Child process timeout after ${timeoutMs}ms`));
}, timeoutMs);
// --- streaming header parser ---
let isReadingHeaders = true;
let headerBuf = ''; // string buffer for header parsing
let headers = { 'Content-Type': 'text/plain' };
let statusCode = 200;
function parseHeadersAndFlush(remainder) {
const lines = headerBuf.split(/\r?\n/);
for (const line of lines) {
if (!line.trim()) continue;
// if line looks like "Key: value"
const idx = line.indexOf(':');
if (idx > -1) {
const key = line.slice(0, idx).trim();
const val = line.slice(idx + 1).trim();
// treat status header specially if you want
if (key.toLowerCase() === 'status') {
const parsed = parseInt(val, 10);
if (!Number.isNaN(parsed)) statusCode = parsed;
} else {
headers[key] = val;
}
}
}
// Send headers and write remainder
try {
res.writeHead(statusCode, headers);
} catch (e) {
console.warn('res.writeHead failed', e);
}
if (remainder && remainder.length) {
res.write(remainder);
}
}
// We will listen on stdout data and look for the token.
child.stdout.on('data', (chunk) => {
if (finished) return;
const str = chunk.toString('utf8');
if (isReadingHeaders) {
headerBuf += str;
// Look for the token on its own line anywhere in headerBuf
const token = '\n!!STARTBODY';
let index = headerBuf.indexOf(token);
// also allow token at very start (no preceding newline)
if (index === -1 && headerBuf.indexOf('!!STARTBODY') === 0) {
index = 0;
}
if (headerBuf.endsWith("!!RECVDATA")) {
console.log("Child is requesting data from us...");
child.stdin.write(JSON.stringify({
method: req.method,
headers: req.headers,
query: req.query,
params: req.params
}));
child.stdin.write("\n"); // newline to signal end of JSON, because child most likely uses input() or readline()
headerBuf = headerBuf.slice(0, headerBuf.length - "!!RECVDATA".length); // remove the token, to avoid cluttering headerspace
return; // wait for more data
}
if (index !== -1) {
// header portion is everything before token (strip a preceding newline)
let headerPart = headerBuf.slice(0, index);
// remainder after token
let remainder = headerBuf.slice(index + token.length);
// If token was at start without the leading \n, we've included token; handle that case
if (headerPart.startsWith('\n')) headerPart = headerPart.slice(1);
// parse headerPart lines
headerBuf = headerPart;
parseHeadersAndFlush(remainder);
// switch mode
isReadingHeaders = false;
} else {
// if no token yet, keep buffering — but to avoid unbounded growth, cap buffer size
const MAX_HEADER_BUF = 64 * 1024; // 64KB
if (headerBuf.length > MAX_HEADER_BUF) {
// give up and treat whole buffer as headers
parseHeadersAndFlush('');
isReadingHeaders = false;
}
}
} else {
// already reading body — stream straight to response
res.write(str);
}
});
// also forward stderr to console (and optionally to response)
child.stderr.on('data', (data) => {
console.error(`stderr: ${data.toString()}`);
});
child.on('close', (code) => {
// If we never left header mode, send a helpful message
if (isReadingHeaders) {
try {
res.writeHead(200, headers);
res.write("No body content. Double check that the child process emits the delimiter `!!STARTBODY` followed by a newline.\n");
} catch (e) {
console.warn('writeHead on close failed', e);
}
}
console.log(`child process exited with code ${code}`);
const mappedStatus = exitCodeLookup[code] || 500;
console.log(`Mapping exit code ${code} to HTTP status ${mappedStatus}`);
try { res.status(mappedStatus);
res.end(); } catch (e) {console.log(e)}
// Remove temp file
try {
fs.unlink(tempFilePath, (err) => {
if (err) {
console.warn('Failed to remove temp file', tempFilePath, err);
} else {
console.log('Temp file removed', tempFilePath);
}
});
} catch (e) {
console.warn('unlink failed', e);
}
onceFinish(null,code);
});
} catch (err) {
onceFinish(err);
}
})();
});
}
const lookup = {
sh: 'bash',
py: 'python3',
js: 'node',
}
app.get('/*path', async (req, res) => {
const filePath = req.path.replace(/^\/+/, ''); // remove leading slash(s)
console.log(`[request] filePath="${filePath}"`);
if (!filePath) {
return res.status(400).send('No file requested');
}
// default timeout (ms) for execFileAsync; tune as needed
const execOpts = { timeoutMs: 30_000 };
try {
if (filePath.includes('.')) {
const ext = filePath.split('.').pop();
console.log(`[lookup] requested ext="${ext}"`);
if (lookup[ext]) {
await execFileAsync(req,res, lookup[ext], [filePath], execOpts);
return; // response handled by execFileAsync
} else {
console.log(`[lookup] no handler for extension "${ext}"`);
}
}
// Brute-force check (ensure fs checks the correct cwd or use absolute paths)
let found = false;
for (const ext in lookup) {
const candidate = filePath + '.' + ext;
console.log(`[bruteforce] checking ${candidate}`);
try{
await getFileFromStorage(candidate);
console.log(`[bruteforce] found ${candidate} -> handler ${lookup[ext]}`);
await execFileAsync(req,res, lookup[ext], [candidate], execOpts);
found = true;
break;
} catch {continue;}
}
if (!found) {
if (!res.headersSent) {
res.status(404).send("No matching file found for serverless execution");
} else {
res.end();
}
return;
}
} catch (err) {
console.error('[execFileAsync] error:', err && err.stack ? err.stack : err);
// If execFileAsync already sent headers/body, don't try to send another response
if (!res.headersSent) {
res.status(500).send("Serverless function failed to execute");
} else {
try { res.end(); } catch (e) {}
}
return;
}
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

1236
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "shittyserverless",
"version": "1.0.0",
"description": "Shitty serverless framework cuz openfaas is restrictive",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "node index.js"
},
"repository": {
"type": "git",
"url": "https://hazzy.nonamesoft.xyz/ChattedRooms/shittyserverless.git"
},
"author": "ChattedRooms",
"license": "UNLICENSED",
"dependencies": {
"aws-sdk": "^2.1692.0",
"express": "^5.1.0"
}
}