Compare commits

...
10 Commits
Author SHA1 Message Date
selimaj-dev 8cf75ce1d5 Merge pull request #17 from orus-dev/small-changes
Small changes
2026-02-15 08:41:52 -08:00
selimaj-dev f700ccb855 Remove deployment 2026-02-15 17:40:24 +01:00
selimaj-dev e4060d0fba Reversed items direction #16 2026-02-15 17:23:04 +01:00
selimaj-dev d4b9b8c75b Updated script 2026-02-14 22:40:08 +01:00
selimaj-dev 52fc6c8831 Update script 2026-02-14 22:38:03 +01:00
selimaj-dev efcbc58272 Fixed Application error after going to monitoring 2026-02-14 22:36:53 +01:00
selimaj-dev 6fb54da4bf Temporarily using root port as node port 2026-02-14 16:47:34 +01:00
selimaj-dev 1331e36177 Structured libs 2026-02-14 16:42:11 +01:00
selimaj-dev a7e29867b8 Port 2026-02-14 16:01:02 +01:00
selimaj-dev ed9d76798e Maybe fixed cookie problem 2026-02-14 15:58:36 +01:00
26 changed files with 188 additions and 83 deletions
+5 -2
View File
@@ -26,6 +26,11 @@ else
echo "DOCKSHIP_LITE=false" > .env
fi
# Prompt for port
read -p "Enter the port DockShip should run on [3000]: " PORT
PORT=${PORT:-3000}
echo "PORT=$PORT" >> .env
# Prompt for ORIGIN type
echo ""
echo "Choose the origin IP for DockShip:"
@@ -33,8 +38,6 @@ echo "1) Local IP (Recommended)"
echo "2) Public IP (Unsafe)"
read -p "Enter choice [1/2]: " ip_choice
PORT=3000 # Default port
if [[ "$ip_choice" == "1" ]]; then
# Get local IP (first non-loopback IP)
LOCAL_IP=$(hostname -I | awk '{print $1}')
-2
View File
@@ -33,9 +33,7 @@ export async function GET(request: NextRequest) {
const c = await cookies();
c.set("github_access", accessToken, {
httpOnly: true,
path: "/",
secure: process.env.NODE_ENV === "production",
});
return NextResponse.redirect(`${process.env.ORIGIN}/overview`);
+4 -4
View File
@@ -1,9 +1,9 @@
import { NextRequest, NextResponse } from "next/server";
import { version } from "@/../package.json";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { read } from "@/app/api/file";
import { NodeJsonTemplate } from "../types";
import { read } from "@/lib/server/file";
import { NodeJsonTemplate } from "../../../lib/server/types";
import osName from "os-name";
export async function GET(req: NextRequest): Promise<NextResponse> {
@@ -12,7 +12,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+4 -4
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { getDocker, removeContainer } from "@/core/docker";
import { Docker } from "@/lib/types";
@@ -10,7 +10,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
@@ -30,14 +30,14 @@ export async function DELETE(req: NextRequest): Promise<NextResponse> {
if (!id) {
return NextResponse.json(
{ message: "Invalid containerId param" },
{ status: StatusCodes.BAD_REQUEST }
{ status: StatusCodes.BAD_REQUEST },
);
}
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+5 -5
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../../auth";
import testAuth from "../../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { startContainer, stopContainer } from "@/core/docker";
@@ -10,14 +10,14 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
if (!id) {
return NextResponse.json(
{ message: "Invalid containerId param" },
{ status: StatusCodes.BAD_REQUEST }
{ status: StatusCodes.BAD_REQUEST },
);
}
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
@@ -35,14 +35,14 @@ export async function DELETE(req: NextRequest): Promise<NextResponse> {
if (!id) {
return NextResponse.json(
{ message: "Invalid containerId param" },
{ status: StatusCodes.BAD_REQUEST }
{ status: StatusCodes.BAD_REQUEST },
);
}
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+3 -3
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../../auth";
import testAuth from "../../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { getContainerStats } from "@/core/docker";
@@ -10,14 +10,14 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (!id) {
return NextResponse.json(
{ message: "Invalid containerId param" },
{ status: StatusCodes.BAD_REQUEST }
{ status: StatusCodes.BAD_REQUEST },
);
}
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+31 -2
View File
@@ -1,7 +1,11 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { deployApp, toggleDeployment } from "@/core/deployment";
import {
deployApp,
removeDeployment,
toggleDeployment,
} from "@/core/deployment";
import { Application, Port } from "@/lib/types";
export async function POST(req: NextRequest): Promise<NextResponse> {
@@ -59,3 +63,28 @@ export async function PUT(req: NextRequest): Promise<NextResponse> {
message: "ok",
});
}
export async function DELETE(req: NextRequest): Promise<NextResponse> {
const deployId = req.nextUrl.searchParams.get("deployId");
const auth = await testAuth(req);
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED },
);
}
if (!deployId) {
return NextResponse.json(
{ message: "Body missing deployId" },
{ status: StatusCodes.BAD_REQUEST },
);
}
await removeDeployment(deployId);
return NextResponse.json({
message: "ok",
});
}
+3 -3
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { getDeployments } from "@/core/deployment";
@@ -10,14 +10,14 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
if (!apps)
return NextResponse.json(
{ message: "Body should be Application[]" },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
const deployments = await getDeployments(apps);
+2 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import { getAllContainerLogs } from "@/core/docker";
@@ -9,7 +9,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import si from "systeminformation";
import { average } from "@/lib/format";
@@ -93,7 +93,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import testAuth from "../auth";
import testAuth from "../../../lib/server/auth";
import { StatusCodes } from "http-status-codes";
import si from "systeminformation";
import { NodeLiveData } from "@/lib/types";
@@ -10,7 +10,7 @@ export async function GET(req: NextRequest): Promise<NextResponse> {
if (auth) {
return NextResponse.json(
{ message: auth },
{ status: StatusCodes.UNAUTHORIZED }
{ status: StatusCodes.UNAUTHORIZED },
);
}
+1 -1
View File
@@ -48,7 +48,7 @@ export default function ApplicationsPage() {
</RegisterAppDialog>
</div>
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{apps.map((app) => (
{apps.toReversed().map((app) => (
<Card key={app.id}>
<CardHeader>
<CardTitle>{app.name}</CardTitle>
+12 -3
View File
@@ -11,7 +11,11 @@ import {
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Plus, Play, Pause, Square, RotateCcw } from "lucide-react";
import { getDeployments, toggleDeployment } from "@/lib/dockship/deploy";
import {
getDeployments,
removeDeployment,
toggleDeployment,
} from "@/lib/dockship/deploy";
import { getApps } from "@/lib/dockship/application";
import { cn } from "@/lib/utils";
import DeployAppDialog from "@/components/dialogs/DeployApp";
@@ -53,6 +57,11 @@ export default function DeploymentsPage() {
await toggleDeployment(deployId, action);
};
const remove = async (deployId: string) => {
console.log(`Removing deployment ${deployId}`);
await removeDeployment(deployId);
};
return (
<DashboardLayout
title="Deployments"
@@ -74,7 +83,7 @@ export default function DeploymentsPage() {
</DeployAppDialog>
</div>
<div className="divide-y divide-border flex flex-col gap-5">
<div className="divide-y divide-border flex flex-col-reverse gap-5">
{deployments.map((dep) => {
return (
<Card key={`${dep.image}-${dep.container ?? "latest"}`}>
@@ -138,7 +147,7 @@ export default function DeploymentsPage() {
<RotateCcw className="w-3 h-3" />
</Button>
</DeployAppDialog>
<RemoveDialog remove={() => {}} />
<RemoveDialog remove={() => remove(dep.container)} />
</div>
</div>
</CardContent>
+1 -1
View File
@@ -41,7 +41,7 @@ export default function MonitoringPage() {
// Live nodes polling every 3 seconds
const { value: liveData } = useAsyncInterval(
"liveNodes",
"liveNodeUsage",
{ cpuUsage: 0, ramUsage: 0 },
async () => {
const nodes = await getLiveNodes();
+1 -1
View File
@@ -192,7 +192,7 @@ export default function OverviewPage() {
</CardHeader>
<CardContent className="overflow-x-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 min-w-75">
{nodes.map((node, i) => (
{nodes.toReversed().map((node, i) => (
<div key={i} className="border border-border p-4 bg-secondary/30">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-3 gap-2">
<div className="flex items-center gap-2">
+8 -4
View File
@@ -1,5 +1,7 @@
"use server";
import { read, write } from "@/app/api/file";
import testAuth from "@/lib/server/auth";
import { read, write } from "@/lib/server/file";
import { NodeJsonTemplate } from "@/lib/server/types";
import axios from "axios";
import { createHash } from "crypto";
import { cookies } from "next/headers";
@@ -20,7 +22,7 @@ export async function getSession(): Promise<
const tokens: Record<string, string> = await read(
() => ({}),
"data",
"sessions.json"
"sessions.json",
);
const tokenHash = createHash("sha256").update(accessToken).digest("hex");
@@ -36,7 +38,7 @@ export async function getSession(): Promise<
// Test the token
const users = Object.keys(
await read<Users>(() => ({}), "data", "users.json")
await read<Users>(() => ({}), "data", "users.json"),
);
if (!users.includes(String(userId))) {
@@ -44,8 +46,10 @@ export async function getSession(): Promise<
await write(
{ [userId]: { roles: ["admin"] } } as Users, // First user is always an admin
"data",
"users.json"
"users.json",
);
const { key } = await read(NodeJsonTemplate, "data", "node.json");
console.log(`Node key: ${key}\ncwd: ${process.cwd()}`);
} else {
throw new Error("Invalid user");
}
+19
View File
@@ -9,6 +9,7 @@ import pLimit from "p-limit";
import { randomUUID } from "crypto";
import { cloneRepo } from "./git";
import tar from "tar-fs";
import { read, write } from "@/lib/server/file";
const docker = new Docker();
@@ -180,3 +181,21 @@ export async function toggleDeployment(
console.error(`Failed to ${action} container ${containerId}:`, err);
}
}
export async function removeDeployment(deployId: string) {
const apps = await read(() => [] as Application[], "data", "apps.json");
const app = apps.find((a) => a.deployments.includes(deployId));
if (app) {
app.deployments = app.deployments.filter((id) => id !== deployId);
await write(apps, "data", "apps.json");
}
try {
const container = docker.getContainer(deployId);
await container.stop();
await container.remove({ force: true });
await docker.pruneImages();
} catch (err) {
console.error(`Failed to remove container ${deployId}:`, err);
}
}
+3 -3
View File
@@ -2,7 +2,7 @@
import { verifySession } from "../../core/auth/session";
import { Env, EnvVariable, Application } from "@/lib/types";
import { read, write } from "@/app/api/file";
import { read, write } from "@/lib/server/file";
import { randomUUID } from "crypto";
export async function getApps(): Promise<Application[]> {
@@ -19,7 +19,7 @@ export async function getApp(appId: string): Promise<Application | undefined> {
}
return (await read<Application[]>(() => [], "data", "apps.json")).find(
(app) => app.id === appId
(app) => app.id === appId,
);
}
@@ -86,7 +86,7 @@ export async function getEnv(): Promise<Record<string, Env>> {
export async function setEnv(
appId: string,
variables: Record<string, EnvVariable>
variables: Record<string, EnvVariable>,
) {
if (await verifySession()) {
throw new Error("Unauthorized");
+27 -5
View File
@@ -16,9 +16,13 @@ export async function getDeployments(): Promise<
nodes.map(
async (n) =>
await (
await axios.post(`http://${n.ip}:3000/api/get-deployments`, apps, {
headers: { Authorization: `ApiKey ${n.key}` },
})
await axios.post(
`http://${n.ip}:${process.env.PORT}/api/get-deployments`,
apps,
{
headers: { Authorization: `ApiKey ${n.key}` },
},
)
).data.deployments.map((d: any) => ({ ...d, nodeId: n.node_id })),
),
)
@@ -42,7 +46,7 @@ export async function deployApp(
const deployId = await (
await axios.post(
`http://${node.ip}:3000/api/deploy`,
`http://${node.ip}:${process.env.PORT}/api/deploy`,
{ name, app, ports },
{
headers: { Authorization: `ApiKey ${node.key}` },
@@ -67,7 +71,7 @@ export async function toggleDeployment(
nodes.map(
async (node) =>
await axios.put(
`http://${node.ip}:3000/api/deploy`,
`http://${node.ip}:${process.env.PORT}/api/deploy`,
{ deployId, action },
{
headers: { Authorization: `ApiKey ${node.key}` },
@@ -78,3 +82,21 @@ export async function toggleDeployment(
return "ok";
}
export async function removeDeployment(deployId: string) {
const nodes = await getNodes();
await Promise.all(
nodes.map(
async (node) =>
await axios.delete(
`http://${node.ip}:${process.env.PORT}/api/deploy?deployId=${deployId}`,
{
headers: { Authorization: `ApiKey ${node.key}` },
},
),
),
);
return "ok";
}
+34 -25
View File
@@ -11,18 +11,21 @@ export async function getDocker(nodes: Node[]): Promise<Docker[]> {
return await Promise.all(
nodes.map(async (node) => {
const data = await (
await axios.get(`http://${node.ip}:3000/api/container/`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
await axios.get(
`http://${node.ip}:${process.env.PORT}/api/container/`,
{
headers: { Authorization: `ApiKey ${node.key}` },
},
)
).data;
return { ...node, containers: data.containers, version: data.version };
})
}),
);
}
export async function getContainerStats(
containerId: string
containerId: string,
): Promise<SimpleStats | undefined> {
const nodes = await getNodes();
@@ -31,12 +34,15 @@ export async function getContainerStats(
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/container/stats`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data?.stats
)
await axios.get(
`http://${n.ip}:${process.env.PORT}/api/container/stats`,
{
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
},
)
).data?.stats,
),
)
).find((s) => s);
}
@@ -48,14 +54,14 @@ export async function startContainer(containerId: string) {
async (n) =>
await (
await axios.post(
`http://${n.ip}:3000/api/container/start`,
`http://${n.ip}:${process.env.PORT}/api/container/start`,
{ containerId },
{
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
}
},
)
).data
).data,
);
}
@@ -65,11 +71,14 @@ export async function stopContainer(containerId: string) {
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container/start`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
await axios.delete(
`http://${n.ip}:${process.env.PORT}/api/container/start`,
{
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
},
)
).data,
);
}
@@ -79,11 +88,11 @@ export async function removeContainer(containerId: string) {
nodes.forEach(
async (n) =>
await (
await axios.delete(`http://${n.ip}:3000/api/container`, {
await axios.delete(`http://${n.ip}:${process.env.PORT}/api/container`, {
headers: { Authorization: `ApiKey ${n.key}` },
params: { containerId },
})
).data
).data,
);
}
@@ -95,16 +104,16 @@ export async function getAllContainerLogs(): Promise<Log[]> {
nodes.map(
async (n) =>
await (
await axios.get(`http://${n.ip}:3000/api/logs`, {
await axios.get(`http://${n.ip}:${process.env.PORT}/api/logs`, {
headers: { Authorization: `ApiKey ${n.key}` },
})
).data
)
).data,
),
)
)
.flat()
.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
);
}
+2 -2
View File
@@ -33,7 +33,7 @@ export async function getMetrics(): Promise<
acc.out += m.out;
return acc;
},
{ cpu: 0, memory: 0, in: 0, out: 0 }
{ cpu: 0, memory: 0, in: 0, out: 0 },
);
averaged.push({
@@ -58,7 +58,7 @@ export async function getNodeMetrics(node: Node): Promise<
}[]
> {
const data = await (
await axios.get(`http://${node.ip}:3000/api/metrics`, {
await axios.get(`http://${node.ip}:${process.env.PORT}/api/metrics`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
).data;
+4 -4
View File
@@ -1,6 +1,6 @@
"use server";
import { read, write } from "@/app/api/file";
import { read, write } from "@/lib/server/file";
import { Node, NodeLiveData } from "@/lib/types";
import axios from "axios";
import { verifySession } from "../../core/auth/session";
@@ -26,11 +26,11 @@ export async function getLiveNodes(): Promise<(NodeLiveData & Node)[]> {
nodes.map(async (node) => ({
...node,
liveData: await (
await axios.get(`http://${node.ip}:3000/api/status`, {
await axios.get(`http://${node.ip}:${process.env.PORT}/api/status`, {
headers: { Authorization: `ApiKey ${node.key}` },
})
).data.liveData,
}))
})),
);
}
@@ -38,7 +38,7 @@ export async function authNode(ip: string, key: string) {
if (await verifySession()) return;
const data = await (
await axios.get(`http://${ip}:3000/api/auth`, {
await axios.get(`http://${ip}:${process.env.PORT}/api/auth`, {
headers: { Authorization: `ApiKey ${key}` },
})
).data;
@@ -1,9 +1,9 @@
import { read } from "@/app/api/file";
import { NodeJsonTemplate } from "@/app/api/types";
import { read } from "@/lib/server/file";
import { NodeJsonTemplate } from "@/lib/server/types";
import { NextRequest } from "next/server";
export default async function testAuth(
req: NextRequest
req: NextRequest,
): Promise<null | string> {
const auth = req.headers
.get("Authorization")
+12
View File
@@ -0,0 +1,12 @@
echo "Pulling latest changes from GitHub..."
git pull
echo "Installing dependencies..."
npm install
echo "Building the application..."
npm run build
echo "restarting DockShip service..."
sudo systemctl restart dockship