4 Commits
0.4.0 ... 0.5.3

Author SHA1 Message Date
Morten Olsen
a08f9e1c91 chore: docker multi arch (#35) 2024-01-15 13:50:06 +01:00
Morten Olsen
e0c41d9220 fix: docker build (#34) 2024-01-14 13:04:11 +01:00
Morten Olsen
028b65587e fix: insecure tmp path (#33)
Fixes #13
2024-01-14 13:00:17 +01:00
Morten Olsen
7436b3439c feat: improved configuration (#31) 2024-01-14 12:49:54 +01:00
19 changed files with 73 additions and 33 deletions

View File

@@ -71,12 +71,24 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Retrieve version
run: |
echo "TAG_NAME=$(git describe --tag --abbrev=0) >> $GITHUB_OUTPUT
id: version
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
@@ -84,11 +96,16 @@ jobs:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |
latest latest
${{ steps.version.outputs.TAG_NAME }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
with: with:
context: . context: .
file: ./docker/Dockerfile file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
push: true push: true
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}

View File

@@ -27,6 +27,10 @@ COPY --from=builder /app/out/full/ .
RUN pnpm turbo run build --filter=@morten-olsen/mini-loader-server RUN pnpm turbo run build --filter=@morten-olsen/mini-loader-server
FROM base AS runner FROM base AS runner
ENV \
NODE_ENV=production \
DATA_DIR=/data \
CACHE_DIR=/cache
RUN apk add --no-cache jq curl RUN apk add --no-cache jq curl
WORKDIR /app WORKDIR /app
@@ -39,7 +43,7 @@ RUN chmod +x /entrypoint.sh
COPY --from=installer /app . COPY --from=installer /app .
EXPOSE 4500 EXPOSE 4500
VOLUME /app/data VOLUME /data
HEALTHCHECK \ HEALTHCHECK \
--interval=10s \ --interval=10s \

View File

@@ -7,6 +7,8 @@ GID=${GID:-1001}
addgroup --system --gid ${GID} nodejs && \ addgroup --system --gid ${GID} nodejs && \
adduser --system --uid ${UID} -G nodejs miniloader && \ adduser --system --uid ${UID} -G nodejs miniloader && \
mkdir -p /app/data mkdir -p ${DATA_DIR}
chown -R miniloader:nodejs /app/data mkdir -p ${CACHE_DIR}
chown -R miniloader:nodejs ${DATA_DIR}
chown -R miniloader:nodejs ${CACHE_DIR}
su miniloader -s /bin/sh -c "$CMD" su miniloader -s /bin/sh -c "$CMD"

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
import 'source-map-support/register.js'; import 'source-map-support/register.js';
import '../dist/esm/index.js'; import '../dist/esm/src/index.js';

View File

@@ -1,8 +1,8 @@
{ {
"name": "@morten-olsen/mini-loader-cli", "name": "@morten-olsen/mini-loader-cli",
"version": "1.0.0", "version": "1.0.0",
"main": "./dist/esm/index.js", "main": "./dist/esm/src/index.js",
"types": "./dist/esm/index.d.ts", "types": "./dist/esm/src/index.d.ts",
"license": "GPL-3.0", "license": "GPL-3.0",
"bin": { "bin": {
"mini-loader": "./bin/index.mjs" "mini-loader": "./bin/index.mjs"
@@ -16,11 +16,12 @@
], ],
"exports": { "exports": {
".": { ".": {
"import": "./dist/esm/index.js" "import": "./dist/esm/src/index.js"
} }
}, },
"dependencies": { "dependencies": {
"@morten-olsen/mini-loader-runner": "workspace:^", "@morten-olsen/mini-loader-runner": "workspace:^",
"@morten-olsen/mini-loader-server": "workspace:^",
"@rollup/plugin-auto-install": "^3.0.5", "@rollup/plugin-auto-install": "^3.0.5",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
@@ -40,7 +41,6 @@
}, },
"devDependencies": { "devDependencies": {
"@morten-olsen/mini-loader-configs": "workspace:^", "@morten-olsen/mini-loader-configs": "workspace:^",
"@morten-olsen/mini-loader-server": "workspace:^",
"@types/inquirer": "^9.0.7", "@types/inquirer": "^9.0.7",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },

View File

@@ -4,6 +4,7 @@ import { run as runLoad } from '@morten-olsen/mini-loader-runner';
import { bundle } from '../../bundler/bundler.js'; import { bundle } from '../../bundler/bundler.js';
import { step } from '../../utils/step.js'; import { step } from '../../utils/step.js';
import { readSecrets } from './local.utils.js'; import { readSecrets } from './local.utils.js';
import { Config } from '../../config/config.js';
const run = new Command('run'); const run = new Command('run');
@@ -12,6 +13,7 @@ run
.argument('script') .argument('script')
.action(async (script) => { .action(async (script) => {
const location = resolve(script); const location = resolve(script);
const config = new Config();
const { autoInstall } = run.opts(); const { autoInstall } = run.opts();
const secrets = await readSecrets(); const secrets = await readSecrets();
@@ -21,6 +23,7 @@ run
const { promise, emitter } = await runLoad({ const { promise, emitter } = await runLoad({
script: code, script: code,
secrets, secrets,
cacheLocation: config.cacheLocation,
}); });
emitter.addListener('message', (message) => { emitter.addListener('message', (message) => {
switch (message.type) { switch (message.type) {

View File

@@ -7,12 +7,13 @@ type ConfigValues = {
context?: string; context?: string;
}; };
const paths = envPaths('mini-loader');
class Config { class Config {
#location: string; #location: string;
#config?: ConfigValues; #config?: ConfigValues;
constructor() { constructor() {
const paths = envPaths('mini-loader');
this.#location = join(paths.config, 'config.json'); this.#location = join(paths.config, 'config.json');
if (existsSync(this.#location)) { if (existsSync(this.#location)) {
this.#config = JSON.parse(readFileSync(this.#location, 'utf-8')); this.#config = JSON.parse(readFileSync(this.#location, 'utf-8'));
@@ -23,6 +24,10 @@ class Config {
return this.#config?.context || 'default'; return this.#config?.context || 'default';
} }
public get cacheLocation() {
return join(paths.cache, this.context);
}
public setContext = (context: string) => { public setContext = (context: string) => {
this.#config = { this.#config = {
...(this.#config || {}), ...(this.#config || {}),

View File

@@ -5,10 +5,11 @@ type RunOptions = {
script: string; script: string;
input?: Buffer | string; input?: Buffer | string;
secrets?: Record<string, string>; secrets?: Record<string, string>;
cacheLocation: string;
}; };
const run = async ({ script, input, secrets }: RunOptions) => { const run = async ({ script, input, secrets, cacheLocation }: RunOptions) => {
const info = await setup({ script, input, secrets }); const info = await setup({ script, input, secrets, cacheLocation });
const worker = new Worker(info.scriptLocation, { const worker = new Worker(info.scriptLocation, {
stdin: false, stdin: false,

View File

@@ -1,5 +1,4 @@
import { join } from 'path'; import { join } from 'path';
import os from 'os';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { chmod, mkdir, rm, writeFile } from 'fs/promises'; import { chmod, mkdir, rm, writeFile } from 'fs/promises';
import { createServer } from 'net'; import { createServer } from 'net';
@@ -9,6 +8,7 @@ type SetupOptions = {
input?: Buffer | string; input?: Buffer | string;
script: string; script: string;
secrets?: Record<string, string>; secrets?: Record<string, string>;
cacheLocation: string;
}; };
type RunEvents = { type RunEvents = {
@@ -20,7 +20,7 @@ type RunEvents = {
const setup = async (options: SetupOptions) => { const setup = async (options: SetupOptions) => {
const { input, script, secrets } = options; const { input, script, secrets } = options;
const emitter = new EventEmitter<RunEvents>(); const emitter = new EventEmitter<RunEvents>();
const dataDir = join(os.tmpdir(), 'mini-loader', nanoid()); const dataDir = join(options.cacheLocation, nanoid());
await mkdir(dataDir, { recursive: true }); await mkdir(dataDir, { recursive: true });
await chmod(dataDir, 0o700); await chmod(dataDir, 0o700);

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node #!/usr/bin/env node
import 'source-map-support/register.js'; import 'source-map-support/register.js';
import '../dist/esm/index.js'; import '../dist/esm/src/index.js';

View File

@@ -2,8 +2,8 @@
"name": "@morten-olsen/mini-loader-server", "name": "@morten-olsen/mini-loader-server",
"version": "1.0.0", "version": "1.0.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"main": "./dist/esm/index.js", "main": "./dist/esm/src/index.js",
"types": "./dist/esm/index.d.ts", "types": "./dist/esm/src/index.d.ts",
"bin": { "bin": {
"mini-loader-server": "./bin/index.mjs" "mini-loader-server": "./bin/index.mjs"
}, },
@@ -16,7 +16,7 @@
], ],
"exports": { "exports": {
".": { ".": {
"import": "./dist/esm/index.js" "import": "./dist/esm/src/index.js"
} }
}, },
"devDependencies": { "devDependencies": {
@@ -32,6 +32,7 @@
"@trpc/server": "^10.45.0", "@trpc/server": "^10.45.0",
"commander": "^11.1.0", "commander": "^11.1.0",
"cron": "^3.1.6", "cron": "^3.1.6",
"env-paths": "^3.0.0",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"fastify": "^4.25.2", "fastify": "^4.25.2",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",

View File

@@ -20,10 +20,10 @@ class Auth {
#setup = async () => { #setup = async () => {
const { config } = this.#options; const { config } = this.#options;
const secretLocation = resolve(config.files.location, 'secret'); const secretLocation = resolve(config.files.data, 'secret');
let secret = ''; let secret = '';
await mkdir(config.files.data, { recursive: true });
if (!existsSync(secretLocation)) { if (!existsSync(secretLocation)) {
await mkdir(config.files.location, { recursive: true });
secret = nanoid(); secret = nanoid();
await writeFile(secretLocation, secret); await writeFile(secretLocation, secret);
} else { } else {

View File

@@ -3,7 +3,8 @@ import { Knex } from 'knex';
type Config = { type Config = {
database: Omit<Knex.Config, 'migrations'>; database: Omit<Knex.Config, 'migrations'>;
files: { files: {
location: string; data: string;
cache: string;
}; };
auth?: { auth?: {
oidc?: { oidc?: {

View File

@@ -62,7 +62,7 @@ class LoadRepo extends EventEmitter<LoadRepoEvents> {
const db = await database.instance; const db = await database.instance;
const id = options.id || nanoid(); const id = options.id || nanoid();
const script = createHash('sha256').update(options.script).digest('hex'); const script = createHash('sha256').update(options.script).digest('hex');
const scriptDir = resolve(this.#options.config.files.location, 'scripts'); const scriptDir = resolve(this.#options.config.files.data, 'scripts');
await mkdir(scriptDir, { recursive: true }); await mkdir(scriptDir, { recursive: true });
await writeFile(resolve(scriptDir, `${script}.js`), options.script); await writeFile(resolve(scriptDir, `${script}.js`), options.script);

View File

@@ -59,7 +59,7 @@ class RunnerInstance extends EventEmitter<RunnerInstanceEvents> {
const { runs, secrets } = repos; const { runs, secrets } = repos;
try { try {
const { script: scriptHash, input } = await runs.getById(id); const { script: scriptHash, input } = await runs.getById(id);
const scriptLocation = resolve(config.files.location, 'scripts', `${scriptHash}.js`); const scriptLocation = resolve(config.files.data, 'scripts', `${scriptHash}.js`);
const script = await readFile(scriptLocation, 'utf-8'); const script = await readFile(scriptLocation, 'utf-8');
const allSecrets = await secrets.getAll(); const allSecrets = await secrets.getAll();
await runs.started(id); await runs.started(id);
@@ -67,6 +67,7 @@ class RunnerInstance extends EventEmitter<RunnerInstanceEvents> {
script, script,
secrets: allSecrets, secrets: allSecrets,
input, input,
cacheLocation: config.files.cache,
}); });
this.#run = current; this.#run = current;
const { promise, emitter } = current; const { promise, emitter } = current;

View File

@@ -1,11 +1,14 @@
import { resolve } from 'path';
import envPaths from 'env-paths';
import { Database } from '../database/database.js'; import { Database } from '../database/database.js';
import { Repos } from '../repos/repos.js'; import { Repos } from '../repos/repos.js';
import { Runner } from '../runner/runner.js'; import { Runner } from '../runner/runner.js';
import { Config } from '../config/config.js'; import { Config } from '../config/config.js';
import { Auth } from '../auth/auth.js'; import { Auth } from '../auth/auth.js';
import { resolve } from 'path';
import { Scheduler } from '../scheduler/scheduler.js'; import { Scheduler } from '../scheduler/scheduler.js';
const paths = envPaths('mini-loader-server');
class Runtime { class Runtime {
#repos: Repos; #repos: Repos;
#runner: Runner; #runner: Runner;
@@ -41,12 +44,13 @@ class Runtime {
database: { database: {
client: 'sqlite3', client: 'sqlite3',
connection: { connection: {
filename: resolve(process.cwd(), 'data', 'database.sqlite'), filename: resolve(paths.data, 'database.sqlite'),
}, },
useNullAsDefault: true, useNullAsDefault: true,
}, },
files: { files: {
location: resolve(process.cwd(), 'data', 'files'), data: process.env.DATA_DIR || resolve(paths.data, 'data', 'files'),
cache: process.env.CACHE_DIR || resolve(paths.cache, 'data', 'cache'),
}, },
}); });

View File

@@ -1,3 +1,4 @@
import pkg from '../../package.json';
import { fastifyTRPCPlugin, FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify'; import { fastifyTRPCPlugin, FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify';
import fastify from 'fastify'; import fastify from 'fastify';
import { RootRouter, rootRouter } from '../router/router.js'; import { RootRouter, rootRouter } from '../router/router.js';
@@ -13,9 +14,6 @@ const createServer = async (runtime: Runtime) => {
level: 'warn', level: 'warn',
}, },
}); });
server.get('/', async () => {
return { hello: 'world' };
});
server.get('/health', async (req) => { server.get('/health', async (req) => {
let authorized = false; let authorized = false;
@@ -27,7 +25,7 @@ const createServer = async (runtime: Runtime) => {
authorized = true; authorized = true;
} }
} catch (error) {} } catch (error) {}
return { authorized, status: 'ok' }; return { authorized, status: 'ok', version: pkg.version };
}); });
server.register(fastifyTRPCPlugin, { server.register(fastifyTRPCPlugin, {

9
pnpm-lock.yaml generated
View File

@@ -39,6 +39,9 @@ importers:
'@morten-olsen/mini-loader-runner': '@morten-olsen/mini-loader-runner':
specifier: workspace:^ specifier: workspace:^
version: link:../runner version: link:../runner
'@morten-olsen/mini-loader-server':
specifier: workspace:^
version: link:../server
'@rollup/plugin-auto-install': '@rollup/plugin-auto-install':
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.0.5(rollup@4.9.4) version: 3.0.5(rollup@4.9.4)
@@ -91,9 +94,6 @@ importers:
'@morten-olsen/mini-loader-configs': '@morten-olsen/mini-loader-configs':
specifier: workspace:^ specifier: workspace:^
version: link:../configs version: link:../configs
'@morten-olsen/mini-loader-server':
specifier: workspace:^
version: link:../server
'@types/inquirer': '@types/inquirer':
specifier: ^9.0.7 specifier: ^9.0.7
version: 9.0.7 version: 9.0.7
@@ -176,6 +176,9 @@ importers:
cron: cron:
specifier: ^3.1.6 specifier: ^3.1.6
version: 3.1.6 version: 3.1.6
env-paths:
specifier: ^3.0.0
version: 3.0.0
eventemitter3: eventemitter3:
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1 version: 5.0.1