diff --git a/app/api/system/shutdown/route.ts b/app/api/system/shutdown/route.ts
new file mode 100644
index 0000000..52cfd21
--- /dev/null
+++ b/app/api/system/shutdown/route.ts
@@ -0,0 +1,17 @@
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(req: NextRequest) {
+ const token = req.cookies.get("token")?.value;
+ if (!token) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const res = await fetch("http://localhost:3001/system/shutdown", {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ return NextResponse.json(await res.json(), { status: res.status });
+}
diff --git a/app/components/serverMenu.tsx b/app/components/serverMenu.tsx
index a9e909f..f6f2465 100644
--- a/app/components/serverMenu.tsx
+++ b/app/components/serverMenu.tsx
@@ -81,6 +81,13 @@ export default function ControlPanel({ onClose }: { onClose: () => void }) {
showToast(res.ok ? "Rebooting..." : "Reboot failed", res.ok);
}
+ async function handleShutdown() {
+ if (!confirm("Shut down the server? You will need physical access to turn it back on."))
+ return;
+ const res = await fetch("/api/system/shutdown", { method: "POST" });
+ showToast(res.ok ? "Shutting down..." : "Shutdown failed", res.ok);
+ }
+
const isLoading = (service: string, action: string) =>
loading[`${service}-${action}`] !== undefined;
@@ -203,6 +210,22 @@ export default function ControlPanel({ onClose }: { onClose: () => void }) {
+
+
+
+ Shut down server
+
+
+ Powers off the machine
+
+
+
+
{/* Toast */}
{toast && (
diff --git a/app/globals.css b/app/globals.css
index 2f1c2c7..ccc522c 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,13 +1,20 @@
@import "tailwindcss";
+
:root {
--background: #f9fafb;
--foreground: #111827;
+ --font-dm-sans: "DM Sans", sans-serif;
+ --font-playfair: "Playfair Display", serif;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
+
+ /* Link Tailwind to the Next.js Font Variables */
+ --font-sans: var(--font-dm-sans), ui-sans-serif, system-ui;
+ --font-serif: var(--font-playfair), ui-serif, Georgia;
}
body {
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..f123c7e
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1774709303,
+ "narHash": "sha256-D3Q07BbIA2KnTcSXIqqu9P586uWxN74zNoCH3h2ESHg=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "8110df5ad7abf5d4c0f6fb0f8f978390e77f9685",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
index b1b93ff..d149fc6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -17,20 +17,8 @@
pkgs = import nixpkgs { inherit system; };
in
{
- packages.default = pkgs.buildNpmPackage {
- pname = "server-dash";
- version = "0.1.0";
- src = ./.;
- npmDepsHash = "sha256-jzVH/DKNE6m+RowHku7h3brC6T+a6xjl2SKSXiTmLgM=";
- buildPhase = ''
- npm run build
- '';
- installPhase = ''
- mkdir -p $out/.next
- cp -r .next/standalone/. $out/
- cp -r .next/static $out/.next/static
- cp -r public $out/public
- '';
+ devShells.default = pkgs.mkShell {
+ buildInputs = with pkgs; [ nodejs ];
};
}
)
@@ -67,8 +55,11 @@
Type = "simple";
User = "server-dash";
Group = "server-dash";
+ ExecStartPre = "${pkgs.bash}/bin/bash -c 'test -f ${config.services.server-dash.package}/server.js || (echo \"Build not found, run npm run deploy first\" && exit 1)'";
+ WorkingDirectory = config.services.server-dash.package;
ExecStart = "${pkgs.nodejs}/bin/node ${config.services.server-dash.package}/server.js";
- Restart = "always";
+ Restart = "on-failure";
+ RestartSec = "10s";
EnvironmentFile = "/var/lib/server-dash/.env";
Environment = [
"PORT=3000"
diff --git a/package.json b/package.json
index cd0c397..9d56d2e 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"build": "next build",
"start": "next start",
"lint": "eslint",
- "deploy": "npm run build && sudo mkdir -p /var/lib/server-dash/build && sudo cp -r .next/standalone/. /var/lib/server-dash/build/ && sudo mkdir -p /var/lib/server-dash/build/.next && sudo cp -r .next/static /var/lib/server-dash/build/.next/static && sudo cp -r public /var/lib/server-dash/build/public && sudo cp .env /var/lib/server-dash/.env && sudo chown -R server-dash:server-dash /var/lib/server-dash && sudo systemctl restart server-dash"
+ "deploy": "npm run build && sudo mkdir -p /var/lib/server-dash/build/.next && sudo cp -r .next/standalone/. /var/lib/server-dash/build/ && sudo cp -r .next/static /var/lib/server-dash/build/.next/static && sudo cp -r public /var/lib/server-dash/build/public && sudo cp .env /var/lib/server-dash/.env && sudo chown -R server-dash:server-dash /var/lib/server-dash && sudo systemctl restart server-dash"
},
"dependencies": {
"next": "16.2.1",