├── frontend
├── .npmrc
├── .prettierignore
├── src
│ ├── lib
│ │ ├── index.ts
│ │ └── schemas.ts
│ ├── routes
│ │ ├── +layout.svelte
│ │ ├── api
│ │ │ ├── status
│ │ │ │ └── [job_name]
│ │ │ │ │ └── [job_id]
│ │ │ │ │ └── +server.ts
│ │ │ └── submit
│ │ │ │ └── +server.ts
│ │ └── +page.svelte
│ ├── app.css
│ ├── app.d.ts
│ └── app.html
├── static
│ └── favicon.png
├── postcss.config.js
├── vite.config.ts
├── .prettierrc
├── .gitignore
├── tailwind.config.js
├── tsconfig.json
├── svelte.config.js
├── eslint.config.js
├── README.md
├── package.json
└── pnpm-lock.yaml
├── .gitignore
├── executor-rust
├── templates
│ └── rust
│ │ ├── src
│ │ └── main.rs
│ │ ├── Cargo.lock
│ │ └── Cargo.toml
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
└── src
│ └── main.rs
├── public
└── screenshot.png
├── .gitmodules
├── compose.yaml
├── backend
├── src
│ ├── jobs
│ │ ├── mod.rs
│ │ ├── rust.rs
│ │ └── job.rs
│ ├── error.rs
│ └── main.rs
├── Cargo.toml
├── Dockerfile
└── Cargo.lock
├── README.md
├── LICENSE
└── infrastructure
└── jobs
├── backend.job.hcl
└── traefik.job.hcl
/frontend/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target
2 | .env
3 |
4 | # MAC
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/executor-rust/templates/rust/src/main.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | println!("Hello, world!");
3 | }
4 |
--------------------------------------------------------------------------------
/public/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unknown/executor/main/public/screenshot.png
--------------------------------------------------------------------------------
/frontend/.prettierignore:
--------------------------------------------------------------------------------
1 | # Package Managers
2 | package-lock.json
3 | pnpm-lock.yaml
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "nomad-rs"]
2 | path = nomad-rs
3 | url = git@github.com:unknown/nomad-rs.git
4 |
--------------------------------------------------------------------------------
/frontend/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | // place files you want to import through the `$lib` alias in this folder.
2 |
--------------------------------------------------------------------------------
/frontend/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unknown/executor/main/frontend/static/favicon.png
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | };
7 |
--------------------------------------------------------------------------------
/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | backend:
3 | build:
4 | context: backend
5 | executor-rust:
6 | build:
7 | context: executor-rust
8 |
--------------------------------------------------------------------------------
/frontend/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/backend/src/jobs/mod.rs:
--------------------------------------------------------------------------------
1 | mod job;
2 | mod rust;
3 |
4 | pub use job::get_job_output;
5 | pub use job::Job;
6 | pub use job::JobOutput;
7 | pub use rust::RustJob;
8 |
--------------------------------------------------------------------------------
/frontend/src/app.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | body {
7 | @apply antialiased;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite';
2 | import { defineConfig } from 'vite';
3 |
4 | export default defineConfig({
5 | plugins: [sveltekit()]
6 | });
7 |
--------------------------------------------------------------------------------
/executor-rust/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "executor-rust"
7 | version = "0.1.0"
8 |
--------------------------------------------------------------------------------
/executor-rust/templates/rust/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "rust"
7 | version = "0.1.0"
8 |
--------------------------------------------------------------------------------
/executor-rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "executor-rust"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 |
--------------------------------------------------------------------------------
/executor-rust/templates/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 |
--------------------------------------------------------------------------------
/frontend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "none",
4 | "printWidth": 100,
5 | "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
6 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | /.svelte-kit
7 | /build
8 |
9 | # OS
10 | .DS_Store
11 | Thumbs.db
12 |
13 | # Env
14 | .env
15 | .env.*
16 | !.env.example
17 | !.env.test
18 |
19 | # Vite
20 | vite.config.js.timestamp-*
21 | vite.config.ts.timestamp-*
22 |
--------------------------------------------------------------------------------
/backend/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | #[derive(Error, Debug)]
4 | pub enum ExecutionError {
5 | #[error(transparent)]
6 | NomadError(nomad_rs::NomadError),
7 | #[error("Invalid response: {0}")]
8 | InvalidResponse(String),
9 | #[error("Job timed out")]
10 | TimeoutError(),
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | import defaultTheme from 'tailwindcss/defaultTheme';
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | export default {
5 | content: ['./src/**/*.{html,js,svelte,ts}'],
6 | theme: {
7 | extend: {
8 | fontFamily: {
9 | sans: ['Inter Variable', ...defaultTheme.fontFamily.sans]
10 | }
11 | }
12 | },
13 | plugins: []
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 | %sveltekit.body%
11 |
12 |
13 |
--------------------------------------------------------------------------------
/frontend/src/routes/api/status/[job_name]/[job_id]/+server.ts:
--------------------------------------------------------------------------------
1 | import { EXECUTOR_BASE_URL } from '$env/static/private';
2 | import type { RequestHandler } from './$types';
3 | import { json } from '@sveltejs/kit';
4 |
5 | export const GET: RequestHandler = async ({ params }) => {
6 | const response = await fetch(
7 | `${EXECUTOR_BASE_URL}/execution-output/${params.job_name}/${params.job_id}`,
8 | { method: 'GET' }
9 | );
10 | return json(await response.json());
11 | };
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Executor
2 |
3 | Executor is a code playground that runs code from the browser, similar to [Go Playground](https://go.dev/play/) and [Rust Playground](https://play.rust-lang.org/). It compiles and runs submitted code and returns the output. Under the hood, Executor uses [Nomad](https://www.nomadproject.io/) to run the code in a Docker container.
4 |
5 |
6 |
7 |
8 |
9 | executor.dmo.ooo
10 |
11 |
12 |
--------------------------------------------------------------------------------
/frontend/src/routes/api/submit/+server.ts:
--------------------------------------------------------------------------------
1 | import { json } from '@sveltejs/kit';
2 | import type { RequestHandler } from './$types';
3 | import { EXECUTOR_BASE_URL } from '$env/static/private';
4 |
5 | export const POST: RequestHandler = async ({ request }) => {
6 | const body = await request.json();
7 | const response = await fetch(`${EXECUTOR_BASE_URL}/execute-rust`, {
8 | method: 'POST',
9 | headers: {
10 | 'Content-Type': 'application/json'
11 | },
12 | body: JSON.stringify(body)
13 | });
14 | return json(await response.json());
15 | };
16 |
--------------------------------------------------------------------------------
/backend/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "backend"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | async-trait = "0.1.80"
10 | axum = "0.7.5"
11 | # nomad-rs = { path = "../nomad-rs" }
12 | nomad-rs = "0.0.1-beta.3"
13 | serde = { version = "1.0.203", features = ["derive"] }
14 | serde_json = "1.0.117"
15 | thiserror = "1.0.61"
16 | tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
17 | uuid = { version = "1.8.0", features = ["v4"] }
18 |
--------------------------------------------------------------------------------
/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # based off of https://verzettelung.com/22/12/29/
2 | FROM rust:1.66 as build
3 |
4 | # create empty project
5 | RUN cargo new app --lib --vcs=none
6 | WORKDIR /app
7 |
8 | # copy manifests
9 | COPY ./Cargo.lock ./Cargo.toml ./
10 |
11 | # build dependencies to cache them
12 | RUN cargo build --release --lib
13 |
14 | # copy source files
15 | COPY ./src ./src
16 |
17 | # update lib.rs timestamp and build release target
18 | RUN touch src/lib.rs && cargo build --release
19 |
20 | # final base
21 | FROM debian:bullseye-slim
22 |
23 | # copy build artifact
24 | COPY --from=build /app/target/release/backend .
25 |
26 | # set entrypoint to run our binary
27 | ENTRYPOINT [ "./backend" ]
28 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true,
12 | "moduleResolution": "bundler"
13 | }
14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
16 | //
17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
18 | // from the referenced tsconfig.json - TypeScript does not merge them in
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-auto';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors
7 | // for more information about preprocessors
8 | preprocess: vitePreprocess(),
9 |
10 | kit: {
11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
14 | adapter: adapter()
15 | }
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/executor-rust/Dockerfile:
--------------------------------------------------------------------------------
1 | # based off of https://verzettelung.com/22/12/29/
2 | FROM rust:1.66-slim as build
3 |
4 | # create empty project
5 | RUN cargo new app --lib --vcs=none
6 | WORKDIR /app
7 |
8 | # copy manifests
9 | COPY ./Cargo.lock ./Cargo.toml ./
10 |
11 | # build dependencies to cache them
12 | RUN cargo build --release --lib
13 |
14 | # copy source files
15 | COPY ./src ./src
16 |
17 | # update lib.rs timestamp and build release target
18 | RUN touch src/lib.rs && cargo build --release
19 |
20 | # final base
21 | FROM rust:1.66-slim
22 |
23 | # copy templates
24 | COPY ./templates ./templates
25 |
26 | # copy build artifact
27 | COPY --from=build /app/target/release/executor-rust .
28 |
29 | # set entrypoint to run our binary
30 | ENTRYPOINT [ "./executor-rust" ]
31 |
--------------------------------------------------------------------------------
/frontend/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js';
2 | import ts from 'typescript-eslint';
3 | import svelte from 'eslint-plugin-svelte';
4 | import prettier from 'eslint-config-prettier';
5 | import globals from 'globals';
6 |
7 | /** @type {import('eslint').Linter.FlatConfig[]} */
8 | export default [
9 | js.configs.recommended,
10 | ...ts.configs.recommended,
11 | ...svelte.configs['flat/recommended'],
12 | prettier,
13 | ...svelte.configs['flat/prettier'],
14 | {
15 | languageOptions: {
16 | globals: {
17 | ...globals.browser,
18 | ...globals.node
19 | }
20 | }
21 | },
22 | {
23 | files: ['**/*.svelte'],
24 | languageOptions: {
25 | parserOptions: {
26 | parser: ts.parser
27 | }
28 | }
29 | },
30 | {
31 | ignores: ['build/', '.svelte-kit/', 'dist/']
32 | }
33 | ];
34 |
--------------------------------------------------------------------------------
/frontend/src/lib/schemas.ts:
--------------------------------------------------------------------------------
1 | import { z } from 'zod';
2 |
3 | export const submitSchema = z.discriminatedUnion('status', [
4 | z.object({
5 | status: z.literal('Success'),
6 | job_id: z.string(),
7 | job_name: z.string()
8 | }),
9 | z.object({
10 | status: z.literal('Error'),
11 | error: z.string()
12 | })
13 | ]);
14 |
15 | export type SubmitResponse = z.infer;
16 |
17 | export const executionOutputSchema = z.discriminatedUnion('status', [
18 | z.object({
19 | status: z.literal('Success'),
20 | output: z.object({
21 | pending: z.boolean(),
22 | stdout: z.string(),
23 | stderr: z.string()
24 | })
25 | }),
26 | z.object({
27 | status: z.literal('Error'),
28 | error: z.string()
29 | })
30 | ]);
31 |
32 | export type ExecutionOutputResponse = z.infer;
33 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # create-svelte
2 |
3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
4 |
5 | ## Creating a project
6 |
7 | If you're seeing this, you've probably already done this step. Congrats!
8 |
9 | ```bash
10 | # create a new project in the current directory
11 | npm create svelte@latest
12 |
13 | # create a new project in my-app
14 | npm create svelte@latest my-app
15 | ```
16 |
17 | ## Developing
18 |
19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
20 |
21 | ```bash
22 | npm run dev
23 |
24 | # or start the server and open the app in a new browser tab
25 | npm run dev -- --open
26 | ```
27 |
28 | ## Building
29 |
30 | To create a production version of your app:
31 |
32 | ```bash
33 | npm run build
34 | ```
35 |
36 | You can preview the production build with `npm run preview`.
37 |
38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 David Mo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/executor-rust/src/main.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | io,
3 | path::Path,
4 | process::{self, Command, Output},
5 | };
6 |
7 | fn compile_program() -> io::Result