Skip to content

Commit 8b37d61

Browse files
Copilotdebloperparthsidpara
authored
refactor(API): convert API server from Node.js to Deno with standalone executable compilation (#89)
BREAKING CHANGES --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: debloper <[email protected]> Co-authored-by: Parth <[email protected]> Co-authored-by: Soumya Deb <[email protected]>
1 parent 68e221c commit 8b37d61

File tree

7 files changed

+317
-12
lines changed

7 files changed

+317
-12
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: Build and Release PiOSK Binaries
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
actions: read
12+
13+
jobs:
14+
build:
15+
name: Build for ${{ matrix.os }}
16+
runs-on: ${{ matrix.runner }}
17+
strategy:
18+
matrix:
19+
include:
20+
# Linux x86_64
21+
- os: linux-x64
22+
runner: ubuntu-latest
23+
target: x86_64-unknown-linux-gnu
24+
output: piosk-linux-x64
25+
# Linux ARM64 (Raspberry Pi)
26+
- os: linux-arm64
27+
runner: ubuntu-latest
28+
target: aarch64-unknown-linux-gnu
29+
output: piosk-linux-arm64
30+
# Windows x86_64
31+
- os: windows-x64
32+
runner: windows-latest
33+
target: x86_64-pc-windows-msvc
34+
output: piosk-windows-x64.exe
35+
# macOS x86_64
36+
- os: macos-x64
37+
runner: macos-13
38+
target: x86_64-apple-darwin
39+
output: piosk-macos-x64
40+
# macOS ARM64 (Apple Silicon)
41+
- os: macos-arm64
42+
runner: macos-14
43+
target: aarch64-apple-darwin
44+
output: piosk-macos-arm64
45+
46+
steps:
47+
- name: Checkout code
48+
uses: actions/checkout@v4
49+
50+
- name: Setup Deno
51+
uses: denoland/setup-deno@v2
52+
with:
53+
deno-version: v2.x
54+
55+
- name: Compile binary
56+
run: |
57+
deno compile \
58+
--allow-net \
59+
--allow-read \
60+
--allow-write \
61+
--allow-run \
62+
--allow-env \
63+
--target ${{ matrix.target }} \
64+
--output ${{ matrix.output }} \
65+
index.ts
66+
67+
- name: Archive binary (Unix)
68+
if: runner.os != 'Windows'
69+
run: |
70+
tar -czf ${{ matrix.output }}.tar.gz ${{ matrix.output }}
71+
72+
- name: Archive binary (Windows)
73+
if: runner.os == 'Windows'
74+
run: |
75+
7z a ${{ matrix.output }}.zip ${{ matrix.output }}
76+
77+
- name: Upload binary as artifact
78+
uses: actions/upload-artifact@v4
79+
with:
80+
name: ${{ matrix.output }}
81+
path: |
82+
${{ matrix.output }}.tar.gz
83+
${{ matrix.output }}.zip
84+
if-no-files-found: ignore
85+
86+
release:
87+
name: Create Release
88+
needs: build
89+
runs-on: ubuntu-latest
90+
if: startsWith(github.ref, 'refs/tags/v')
91+
92+
steps:
93+
- name: Checkout code
94+
uses: actions/checkout@v4
95+
96+
- name: Download all artifacts
97+
uses: actions/download-artifact@v4
98+
with:
99+
path: ./artifacts
100+
101+
- name: Create Release
102+
uses: softprops/action-gh-release@v2
103+
with:
104+
files: ./artifacts/*/*.{tar.gz,zip}
105+
draft: false
106+
prerelease: false
107+
generate_release_notes: true
108+
env:
109+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
config.json
22
node_modules/
33
package-lock.json
4+
5+
# Deno compiled binaries
6+
piosk
7+
piosk.exe
8+
piosk-*
9+
10+
# Test files
11+
test.sh

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ That's when I realized... maybe there are other people (or future me) who'd also
3939
Either open terminal on the Raspberry Pi's desktop environment, or remote login to it; and run the following command:
4040

4141
```bash
42-
curl -sSL https://raw.githubusercontent.com/debloper/piosk/main/scripts/setup.sh | sudo bash -
42+
curl -sSL https://raw.githubusercontent.com/debloper/piosk/d586dfa833187df34de8e8345b85c8d27be8bdc9/scripts/setup.sh | sudo bash -
4343
```
4444

4545
That's it[^2].
@@ -64,7 +64,7 @@ That's it[^2].
6464
> Try these at your own risk; if you know what you're doing. Misconfiguration(s) may break the setup.
6565
6666
1. The PiOSK repo is cloned to `/opt/piosk`
67-
2. You can change the dashboard port from `index.js`
67+
2. You can change the dashboard port by setting the `PORT` from `index.js`
6868
3. You can change the per-page timeout from `scripts/switcher.sh`
6969
4. You can change browser behavior (e.g. no full screen) from `scripts/runner.sh`
7070
5. Some changes can be applied without rebooting, but rebooting is simpler

deno.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "0.1.0",
3+
"exports": "./index.ts",
4+
"tasks": {
5+
"start": "deno run --allow-net --allow-read --allow-write --allow-run --allow-env index.ts",
6+
"compile": "deno compile --allow-net --allow-read --allow-write --allow-run --allow-env --output piosk index.ts"
7+
},
8+
"compilerOptions": {
9+
"strict": true,
10+
"useUnknownInCatchVariables": false
11+
}
12+
}

index.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const CONFIG_FILE = "./config.json";
2+
3+
async function readConfig(): Promise<string> {
4+
try {
5+
return await Deno.readTextFile(CONFIG_FILE);
6+
} catch (error) {
7+
if (error instanceof Deno.errors.NotFound) {
8+
// Return sample config if config.json doesn't exist
9+
return await Deno.readTextFile("./config.json.sample");
10+
}
11+
throw error;
12+
}
13+
}
14+
15+
async function writeConfig(configData: string): Promise<void> {
16+
await Deno.writeTextFile(CONFIG_FILE, configData);
17+
}
18+
19+
async function rebootSystem(): Promise<void> {
20+
try {
21+
// In test mode, don't actually reboot
22+
if (Deno.env.get("PIOSK_TEST_MODE") === "true") {
23+
console.log("Test mode: Would reboot system");
24+
return;
25+
}
26+
27+
const command = new Deno.Command("reboot", {
28+
stdout: "piped",
29+
stderr: "piped"
30+
});
31+
await command.output();
32+
} catch (error) {
33+
console.error("Reboot command failed:", error);
34+
throw error;
35+
}
36+
}
37+
38+
async function serveStaticFile(pathname: string): Promise<Response> {
39+
let filePath: string;
40+
41+
if (pathname === "/" || pathname === "/index.html") {
42+
filePath = "./web/index.html";
43+
} else {
44+
// Remove leading slash and serve from web directory
45+
filePath = `./web${pathname}`;
46+
}
47+
48+
try {
49+
const file = await Deno.readFile(filePath);
50+
51+
// Determine content type
52+
let contentType = "text/plain";
53+
if (filePath.endsWith(".html")) {
54+
contentType = "text/html";
55+
} else if (filePath.endsWith(".js")) {
56+
contentType = "application/javascript";
57+
} else if (filePath.endsWith(".css")) {
58+
contentType = "text/css";
59+
} else if (filePath.endsWith(".json")) {
60+
contentType = "application/json";
61+
}
62+
63+
return new Response(file, {
64+
headers: { "Content-Type": contentType },
65+
});
66+
} catch (error) {
67+
if (error instanceof Deno.errors.NotFound) {
68+
return new Response("Not Found", { status: 404 });
69+
}
70+
console.error("Error serving file:", error);
71+
return new Response("Internal Server Error", { status: 500 });
72+
}
73+
}
74+
75+
async function handler(req: Request): Promise<Response> {
76+
const url = new URL(req.url);
77+
78+
// Handle API endpoints
79+
if (url.pathname === "/config") {
80+
if (req.method === "GET") {
81+
try {
82+
const config = await readConfig();
83+
return new Response(config, {
84+
headers: { "Content-Type": "application/json" },
85+
});
86+
} catch (error) {
87+
console.error("Error reading config:", error);
88+
return new Response("Could not read config.", { status: 500 });
89+
}
90+
}
91+
92+
if (req.method === "POST") {
93+
try {
94+
const configData = await req.text();
95+
96+
// Validate JSON
97+
JSON.parse(configData);
98+
99+
await writeConfig(JSON.stringify(JSON.parse(configData), null, " "));
100+
101+
// Reboot system
102+
try {
103+
await rebootSystem();
104+
return new Response("New config applied; rebooting for changes to take effect...", { status: 200 });
105+
} catch (rebootError) {
106+
console.error("Reboot error:", rebootError);
107+
return new Response("Could not reboot to apply config. Retry or reboot manually.", { status: 500 });
108+
}
109+
} catch (error) {
110+
console.error("Error saving config:", error);
111+
return new Response("Could not save config.", { status: 500 });
112+
}
113+
}
114+
}
115+
116+
// Handle static files
117+
return await serveStaticFile(url.pathname);
118+
}
119+
120+
const port = parseInt(Deno.env.get("PORT") || "80");
121+
console.log(`PiOSK Deno server starting on port ${port}...`);
122+
123+
Deno.serve({ port }, handler);

0 commit comments

Comments
 (0)