mirror of
https://github.com/System-End/spaces.git
synced 2026-04-19 15:28:27 +00:00
spaces stuff
This commit is contained in:
parent
a4815f03a1
commit
153ae3dec5
10 changed files with 312 additions and 20 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -2,7 +2,8 @@
|
|||
node_modules
|
||||
dist
|
||||
build
|
||||
AGENTS.md
|
||||
.github
|
||||
.vscode
|
||||
.DS_Store
|
||||
.claude
|
||||
.claude
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ RUN apt-get update && apt-get install -y \
|
|||
software-properties-common \
|
||||
supervisor \
|
||||
nginx \
|
||||
iptables \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Docker Engine for Docker-in-Docker
|
||||
|
|
@ -54,7 +55,7 @@ nodaemon=true
|
|||
user=root
|
||||
|
||||
[program:dockerd]
|
||||
command=dockerd --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376 --storage-driver=overlay2
|
||||
command=dockerd --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2376 --storage-driver=overlay2 --iptables=true --ip-forward=true --ip-masq=true
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/dockerd.err.log
|
||||
|
|
@ -91,7 +92,7 @@ EOF
|
|||
|
||||
# Copy and setup startup script
|
||||
COPY start.sh /app/start.sh
|
||||
RUN chmod +x /app/start.sh
|
||||
RUN sed -i 's/\r$//' /app/start.sh && chmod +x /app/start.sh
|
||||
|
||||
# Expose ports
|
||||
EXPOSE 80 3000 5173
|
||||
|
|
|
|||
6
client/package-lock.json
generated
6
client/package-lock.json
generated
|
|
@ -637,6 +637,7 @@
|
|||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
|
||||
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
|
|
@ -658,6 +659,7 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
|
||||
"integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.18",
|
||||
|
|
@ -1039,7 +1041,8 @@
|
|||
"version": "3.53.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.53.1.tgz",
|
||||
"integrity": "sha512-Q4/hHkktZogGhN5iqxqSi9sjEVoe/NbIxX4hXEHoasTxj+TxEQVAq66LnDMdAZxjmsodkoI5F3slqsS68U7FNw==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"svelte-hmr": {
|
||||
"version": "0.15.0",
|
||||
|
|
@ -1053,6 +1056,7 @@
|
|||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.3.tgz",
|
||||
"integrity": "sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.15.9",
|
||||
"fsevents": "~2.3.2",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export const API_BASE = 'https://t0080w08wcockgs44ws8w880.b.selfhosted.hackclub.com/api/v1';
|
||||
export const API_BASE = 'http://localhost:2593/api/v1';
|
||||
|
||||
export const ERROR_MESSAGES = {
|
||||
NETWORK_ERROR: 'Network error. Please try again.',
|
||||
|
|
|
|||
|
|
@ -173,6 +173,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteSpace(spaceId) {
|
||||
if (!confirm('Are you sure you want to delete this space? This action cannot be undone.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionLoading[spaceId] = 'deleting';
|
||||
actionError[spaceId] = '';
|
||||
actionLoading = actionLoading;
|
||||
actionError = actionError;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/spaces/delete/${spaceId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': authorization,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
await loadSpaces();
|
||||
} else {
|
||||
actionError[spaceId] = data.error || 'Failed to delete space';
|
||||
actionError = actionError;
|
||||
}
|
||||
} catch (err) {
|
||||
actionError[spaceId] = ERROR_MESSAGES.NETWORK_ERROR;
|
||||
actionError = actionError;
|
||||
} finally {
|
||||
delete actionLoading[spaceId];
|
||||
actionLoading = actionLoading;
|
||||
}
|
||||
}
|
||||
|
||||
function handleSignOut() {
|
||||
dispatch('signout');
|
||||
}
|
||||
|
|
@ -389,6 +424,12 @@
|
|||
>
|
||||
↻
|
||||
</button>
|
||||
<button
|
||||
class="action-btn delete"
|
||||
on:click={() => deleteSpace(space.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
93
code-server-setup.sh
Normal file
93
code-server-setup.sh
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#!/bin/bash
|
||||
|
||||
# runs inside of created code-server containers, installs essential dev tools by default
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting development environment setup..."
|
||||
|
||||
echo "Updating package manager..."
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
echo "Installing essential system tools..."
|
||||
sudo apt install -y \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
nano \
|
||||
htop \
|
||||
tree \
|
||||
unzip \
|
||||
zip \
|
||||
build-essential \
|
||||
software-properties-common \
|
||||
apt-transport-https \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
lsb-release
|
||||
|
||||
echo "🐍 Setting up Python environment..."
|
||||
sudo apt install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
python3-dev \
|
||||
python3-setuptools
|
||||
|
||||
python3 -m pip install --user pipx
|
||||
python3 -m pipx ensurepath
|
||||
|
||||
echo "📗 Setting up Node.js..."
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||
sudo apt install -y nodejs
|
||||
|
||||
echo "🧶 Installing Yarn..."
|
||||
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
|
||||
sudo apt update && sudo apt install -y yarn
|
||||
|
||||
echo "☕ Setting up Java..."
|
||||
sudo apt install -y openjdk-17-jdk openjdk-17-jre
|
||||
|
||||
echo "🔵 Setting up Go..."
|
||||
GO_VERSION="1.21.4"
|
||||
wget "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz"
|
||||
sudo rm -rf /usr/local/go
|
||||
sudo tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz"
|
||||
rm "go${GO_VERSION}.linux-amd64.tar.gz"
|
||||
|
||||
echo "🦀 Setting up Rust..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
|
||||
echo "💎 Setting up Ruby..."
|
||||
sudo apt install -y ruby-full
|
||||
|
||||
echo "🐘 Setting up PHP..."
|
||||
sudo apt install -y \
|
||||
php \
|
||||
php-cli \
|
||||
php-common \
|
||||
php-curl \
|
||||
php-json \
|
||||
php-mbstring \
|
||||
php-xml \
|
||||
php-zip
|
||||
|
||||
curl -sS https://getcomposer.org/installer | php
|
||||
sudo mv composer.phar /usr/local/bin/composer
|
||||
|
||||
# Database tools
|
||||
echo "🗄️ Setting up database tools..."
|
||||
sudo apt install -y \
|
||||
sqlite3 \
|
||||
postgresql-client \
|
||||
mysql-client
|
||||
|
||||
|
||||
|
||||
# Final cleanup
|
||||
echo "🧹 Cleaning up..."
|
||||
sudo apt autoremove -y
|
||||
sudo apt autoclean
|
||||
|
||||
48
nginx.conf
48
nginx.conf
|
|
@ -25,14 +25,14 @@ http {
|
|||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Port forwarding - matches /space/8080, /space/3001, etc.
|
||||
# Routes to localhost ports (containers running in Docker-in-Docker)
|
||||
location ~ ^/space/(\d+)(/.*)?$ {
|
||||
# Port forwarding - matches /8080/workspace, /3001/file.txt, etc.
|
||||
# Routes to localhost ports (e.g., /44831/workspace -> localhost:44831/44831/workspace)
|
||||
location ~ ^/(\d+)(/.*)?$ {
|
||||
set $port $1;
|
||||
set $path $2;
|
||||
set $fullpath $uri;
|
||||
set $target 127.0.0.1:$port;
|
||||
proxy_pass http://$target$path;
|
||||
proxy_set_header Host localhost;
|
||||
proxy_pass http://$target$fullpath$is_args$args;
|
||||
proxy_set_header Host localhost:$port;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
|
@ -47,14 +47,45 @@ http {
|
|||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Additional headers for better compatibility
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
proxy_set_header X-Forwarded-Server $host;
|
||||
proxy_set_header X-Forwarded-Host localhost:$port;
|
||||
proxy_set_header X-Forwarded-Server localhost;
|
||||
proxy_set_header X-Forwarded-Port $port;
|
||||
proxy_buffering off;
|
||||
|
||||
# Handle authentication responses properly
|
||||
proxy_intercept_errors off;
|
||||
}
|
||||
|
||||
# Port forwarding - matches /space/8080, /space/3001, etc.
|
||||
# Routes to localhost ports (containers running in Docker-in-Docker)
|
||||
location ~ ^/space/(\d+)(/.*)?$ {
|
||||
set $port $1;
|
||||
set $path $2;
|
||||
set $target 127.0.0.1:$port;
|
||||
proxy_pass http://$target$path$is_args$args;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Pass through authentication headers for HTTP Basic Auth
|
||||
proxy_pass_header Authorization;
|
||||
proxy_set_header Authorization $http_authorization;
|
||||
|
||||
# WebSocket support for development servers
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $http_connection;
|
||||
|
||||
# Additional headers for better compatibility
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 86400;
|
||||
|
||||
# Handle authentication responses properly
|
||||
proxy_intercept_errors off;
|
||||
}
|
||||
|
||||
# Frontend fallback - must be last
|
||||
location / {
|
||||
proxy_pass http://frontend;
|
||||
proxy_set_header Host $host;
|
||||
|
|
@ -66,5 +97,6 @@ http {
|
|||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,8 @@ import {
|
|||
startContainer,
|
||||
stopContainer,
|
||||
getContainerStatus,
|
||||
getUserSpaces
|
||||
getUserSpaces,
|
||||
deleteSpace
|
||||
} from "../../utils/spaces.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
|
@ -84,4 +85,17 @@ router.get("/list", async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.delete("/delete/:spaceId", async (req, res) => {
|
||||
const { spaceId } = req.params;
|
||||
const authorization = req.headers.authorization;
|
||||
|
||||
try {
|
||||
const result = await deleteSpace(spaceId, authorization);
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
const statusCode = err.statusCode || (err.message.includes("required") || err.message.includes("Missing") || err.message.includes("Invalid authorization") ? 400 : 500);
|
||||
res.status(statusCode).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ import Docker from "dockerode";
|
|||
import getPort from "get-port";
|
||||
import pg from "./db.js";
|
||||
import { getUser } from "./user.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const docker = new Docker();
|
||||
|
||||
|
|
@ -9,7 +15,10 @@ const containerConfigs = {
|
|||
"code-server": {
|
||||
image: "linuxserver/code-server",
|
||||
port: "8443/tcp",
|
||||
env: (password) => [`PASSWORD=${password}`],
|
||||
env: (password, port) => [
|
||||
`PASSWORD=${password}`,
|
||||
`DEFAULT_WORKSPACE=/config/workspace`
|
||||
],
|
||||
description: "VS Code Server"
|
||||
},
|
||||
"blender": {
|
||||
|
|
@ -56,15 +65,48 @@ export const createContainer = async (password, type, authorization) => {
|
|||
|
||||
const container = await docker.createContainer({
|
||||
Image: config.image,
|
||||
Env: config.env(password),
|
||||
Env: config.env(password, port),
|
||||
ExposedPorts: { [config.port]: {} },
|
||||
HostConfig: {
|
||||
PortBindings: { [config.port]: [{ HostPort: `${port}` }] },
|
||||
NetworkMode: "bridge",
|
||||
Dns: ["8.8.8.8", "8.8.4.4", "1.1.1.1"],
|
||||
PublishAllPorts: false,
|
||||
RestartPolicy: { Name: "unless-stopped" }
|
||||
},
|
||||
});
|
||||
|
||||
await container.start();
|
||||
|
||||
if (type.toLowerCase() === "code-server") {
|
||||
try {
|
||||
console.log("Running setup script for code-server container...");
|
||||
const setupScriptPath = path.join(__dirname, "../../code-server-setup.sh");
|
||||
const setupScript = fs.readFileSync(setupScriptPath, "utf8");
|
||||
|
||||
const exec = await container.exec({
|
||||
Cmd: ["bash", "-c", `cat > /tmp/setup.sh << 'EOF'\n${setupScript}\nEOF && chmod +x /tmp/setup.sh && /tmp/setup.sh`],
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
});
|
||||
|
||||
const stream = await exec.start();
|
||||
stream.pipe(process.stdout);
|
||||
|
||||
console.log("Setup script executed successfully");
|
||||
} catch (setupErr) {
|
||||
console.error("Failed to run setup script (container will still be created):", setupErr);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.DOCKER === 'false') {
|
||||
console.log("Non-Docker environment detected, adjusting access URL");
|
||||
var access_url = `${process.env.SERVER_URL}:${port}`;
|
||||
} else {
|
||||
console.log("Docker environment detected, using standard access URL");
|
||||
var access_url = `${process.env.SERVER_URL}/space/${port}/`;
|
||||
}
|
||||
|
||||
const [newSpace] = await pg('spaces')
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
|
|
@ -73,13 +115,10 @@ export const createContainer = async (password, type, authorization) => {
|
|||
description: config.description,
|
||||
image: config.image,
|
||||
port,
|
||||
access_url: `${process.env.SERVER_URL}/space/${port}/`
|
||||
access_url: access_url
|
||||
})
|
||||
.returning(['id', 'container_id', 'type', 'description', 'image', 'port', 'access_url']);
|
||||
|
||||
if (process.env.DOCKER === 'false') {
|
||||
newSpace.access_url = `http://${process.env.SERVER_URL}:${newSpace.port}`;
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Container created successfully",
|
||||
|
|
@ -297,3 +336,63 @@ export const getSpacesByUserId = async (userId) => {
|
|||
throw new Error("Failed to get spaces for user");
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteSpace = async (spaceId, authorization) => {
|
||||
if (!spaceId) {
|
||||
throw new Error("Space ID is required");
|
||||
}
|
||||
|
||||
if (!authorization) {
|
||||
throw new Error("Missing authorization token");
|
||||
}
|
||||
|
||||
const user = await getUser(authorization);
|
||||
if (!user) {
|
||||
throw new Error("Invalid authorization token");
|
||||
}
|
||||
|
||||
try {
|
||||
const space = await pg('spaces')
|
||||
.where('id', spaceId)
|
||||
.where('user_id', user.id)
|
||||
.first();
|
||||
|
||||
if (!space) {
|
||||
const error = new Error("Space not found or not owned by user");
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const container = docker.getContainer(space.container_id);
|
||||
|
||||
try {
|
||||
await container.inspect();
|
||||
await container.stop();
|
||||
} catch (err) {
|
||||
console.log("Container already stopped or doesn't exist, continuing with deletion");
|
||||
}
|
||||
|
||||
try {
|
||||
await container.remove();
|
||||
} catch (err) {
|
||||
console.error("Failed to remove container:", err);
|
||||
}
|
||||
|
||||
await pg('spaces')
|
||||
.where('id', spaceId)
|
||||
.delete();
|
||||
|
||||
return {
|
||||
message: "Space deleted successfully",
|
||||
spaceId: space.id,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Error deleting space:", err);
|
||||
|
||||
if (err.statusCode === 404) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
throw new Error("Failed to delete space");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
start.sh
7
start.sh
|
|
@ -1,5 +1,12 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Enable IP forwarding for nested containers
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
|
||||
# Configure iptables for NAT to allow nested containers internet access
|
||||
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE 2>/dev/null || true
|
||||
iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o docker0 -j MASQUERADE 2>/dev/null || true
|
||||
|
||||
if docker info >/dev/null 2>&1; then
|
||||
echo "Docker is available."
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue