Author SHA1 Message Date
ztimson fd6e2b07dc Add install.sh
Code review / review (pull_request) Successful in 29s
2026-09-12 22:45:04 -04:00
ztimson 240119ee5f Merge pull request 'ztimson-patch-2' (#2) from ztimson-patch-2 into master
Reviewed-on: #2
2026-09-12 22:41:03 -04:00
ztimson 4817089056 Update public/logo.png
Code review / review (pull_request) Successful in 35s
2026-09-12 22:40:40 -04:00
ztimson 169ffad863 Upload files to "public" 2026-09-12 22:40:19 -04:00
ztimson 4bfecdeacc Add public/index.html 2026-09-12 22:39:16 -04:00
ztimson c84008cf5c Add src/server.mjs 2026-09-12 22:38:26 -04:00
ztimson e24ad685fe Add package.json 2026-09-12 22:36:53 -04:00
ztimson e08bc14634 Update README.md 2026-09-12 22:34:58 -04:00
ztimson 063a228134 Merge pull request 'Update README.md' (#1) from ztimson-patch-1 into master
Reviewed-on: #1
2026-09-12 22:32:41 -04:00
ztimson 15e0003958 Update README.md
Code review / review (pull_request) Successful in 30s
2026-09-12 22:32:32 -04:00
6 changed files with 282 additions and 77 deletions
+60 -75
View File
@@ -1,96 +1,77 @@
<!-- Header -->
<div id="top" align="center">
<br />
<!-- Logo -->
<img src="https://git.zakscode.com/repo-avatars/2b4ee6ba1f2e2618bf7694e4a52fb56d1d0ea6abafa2dcbe496ab786b86d5a76" alt="Logo" width="200" height="200">
<img src="https://git.zakscode.com/repo-avatars/8be45ee4b7c1acd45c73cfc7e9c189f139d69a1c9ec9aba29849ae6a8a0cfb40" alt="Logo" width="200" height="200">
<!-- Title -->
### Template
### Homecast
<!-- Description -->
Simple repository template
A homebrew Chromecast
<!-- Repo badges -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template/tags&query=$[0].name)](https://git.zakscode.com/ztimson/template/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template&query=open_pr_counter)](https://git.zakscode.com/ztimson/template/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/template&query=open_issues_count)](https://git.zakscode.com/ztimson/template/issues)
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version\&style=for-the-badge\&url=https://git.zakscode.com/api/v1/repos/ztimson/homecast/tags\&query=$\[0\].name)](https://git.zakscode.com/ztimson/homecast/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests\&style=for-the-badge\&url=https://git.zakscode.com/api/v1/repos/ztimson/homecast\&query=open_pr_counter)](https://git.zakscode.com/ztimson/homecast/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues\&style=for-the-badge\&url=https://git.zakscode.com/api/v1/repos/ztimson/homecast\&query=open_issues_count)](https://git.zakscode.com/ztimson/homecast/issues)
<!-- Links -->
---
---
<div>
<a href="https://git.zakscode.com/ztimson/template/wiki" target="_blank">Documentation</a>
• <a href="https://git.zakscode.com/ztimson/template/releases" target="_blank">Release Notes</a>
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
• <a href="https://git.zakscode.com/ztimson/template/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
<a href="https://git.zakscode.com/ztimson/homecast/wiki" target="_blank">Documentation</a>
• <a href="https://git.zakscode.com/ztimson/homecast/releases" target="_blank">Release Notes</a>
• <a href="https://git.zakscode.com/ztimson/homecast/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
• <a href="https://git.zakscode.com/ztimson/homecast/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
</div>
---
---
</div>
## Table of Contents
- [Template](#top)
- [About](#about)
- [Demo](#demo)
- [Built With](#built-with)
- [Setup](#setup)
- [Production](#production)
- [Development](#development)
- [License](#license)
* [Homecast](#top)
* [About](#about)
* [Built With](#built-with)
* [Setup](#setup)
* [Production](#production)
* [Development](#development)
* [License](#license)
## About
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Homecast is a homebrew, Chromecast-style streaming appliance built around a Raspberry Pi connected to a television.
### Demo
The goal is to provide a simple, self-hosted casting and media experience while retaining complete control over the underlying hardware and software.
Website: https://git.zakscode.com
Features include:
- Installs ontop of any Rasbian + Wayland OS
- Allows you to fully customize and use as your own media center
- Remote desktop into the pi via the browser on any device
- Mirrors to the TV
- Install the PWA via the browser to get the remote control app
- Use the share button built into any site / app to cast media to Homecast
- Custom screen savers, automatic background music, and AI harness integration
### Built With
[![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/)
[![Android](https://img.shields.io/badge/android-34A853?style=for-the-badge&logo=android&logoColor=ffffff)](https://www.android.com/)
[![Arduino](https://img.shields.io/badge/Arduino-00878F?style=for-the-badge&logo=arduino&logoColor=white)](https://www.arduino.cc/)
[![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![C](https://img.shields.io/badge/C-A8B9CC?style=for-the-badge&logo=c&logoColor=ffffff)](https://en.cppreference.com/w/c/language)
[![C++](https://img.shields.io/badge/C%2B%2B-00599C?style=for-the-badge&logo=cplusplus)](https://cplusplus.com/)
[![C#](https://img.shields.io/badge/C%23-239120?style=for-the-badge&logo=csharp)](https://dotnet.microsoft.com/)
[![CSS](https://img.shields.io/badge/CSS-1572B6?style=for-the-badge&logo=css3)](https://www.w3.org/Style/CSS/Overview.en.html)
[![Django](https://img.shields.io/badge/django-0C4B33?style=for-the-badge&logo=django)](https://www.djangoproject.com/)
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
[![Electron](https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white)](https://www.electronjs.org/)
[![Firebase](https://img.shields.io/badge/Firebase-FFFFFF?style=for-the-badge&logo=firebase)](https://firebase.google.com/)
[![Go](https://img.shields.io/badge/Go-00ADD8?style=for-the-badge&logo=go&logoColor=ffffff)](https://go.dev/)
[![GraphQL](https://img.shields.io/badge/GraphQL-E10098?style=for-the-badge&logo=graphql)](https://graphql.org/)
[![HTML](https://img.shields.io/badge/HTML-FFFFFF?style=for-the-badge&logo=html5)](https://developer.mozilla.org/en-US/docs/Glossary/HTML)
[![Java](https://img.shields.io/badge/Java-5382A1?style=for-the-badge&logo=coffeescript&logoColor=F8981D)](https://java.com/)
[![JavaScript](https://img.shields.io/badge/JavaScript-000000?style=for-the-badge&logo=javascript)](https://javascript.com/)
[![JQuery](https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery)](https://jquery.com )
[![Laravel](https://img.shields.io/badge/Laravel-6C6C6C?style=for-the-badge&logo=laravel)](https://laravel.com)
[![Linux](https://img.shields.io/badge/Linux-eeeeee?style=for-the-badge&logo=linux&logoColor=000000)](https://www.linux.org/)
[![Momentum](https://img.shields.io/badge/Momentum-000000?style=for-the-badge&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDIwMDEwOTA0Ly9FTiIgImh0dHA6Ly93d3cudzMub3JnL1RSLzIwMDEvUkVDLVNWRy0yMDAxMDkwNC9EVEQvc3ZnMTAuZHRkIj4KPHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI1MHB0IiBoZWlnaHQ9IjI1MHB0IiB2aWV3Qm94PSIwIDAgMjUwIDI1MCIgIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIG1lZXQiPgoJPHN0eWxlPgoJCUBtZWRpYSAocHJlZmVycy1jb2xvci1zY2hlbWU6IGxpZ2h0KSB7CgkJZyB7IGZpbGw6ICMwMDAwMDA7IH0KCQl9CgkJQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewoJCWcgeyBmaWxsOiAjZmZmZmZmOyB9CgkJfQoJPC9zdHlsZT4KCTxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsMjUwKSBzY2FsZSgwLjEsLTAuMSkiPgoJCTxwYXRoIGQ9Ik0xMTY1IDIyOTkgYy0yNDUgLTE4IC00NzAgLTEyMyAtNjUxIC0zMDMgLTExNyAtMTE4IC0yMDAgLTI1MCAtMjUyIC00MDEgLTcxIC0yMDkgLTcxIC00NjggLTEgLTY3OSAzNyAtMTA4IDExOCAtMjU0IDE3OCAtMzE5IGwyNSAtMjggLTEzMiAtMTMyIC0xMzIgLTEzMiA1MiAtNTMgNTMgLTUyIDEzMiAxMzIgMTMyIDEzMiAyOCAtMjUgYzQxIC0zOCAxMDkgLTgyIDE4OCAtMTIxIDE1MyAtNzcgMjkyIC0xMDkgNDcwIC0xMDkgMjkxIDAgNTM2IDEwMSA3NDEgMzA1IDE1MyAxNTQgMjQ0IDMyNCAyODkgNTQwIDIxIDEwMyAyMSAyOTkgLTEgNDAyIC0zMiAxNTkgLTEwNSAzMjQgLTE4NyA0MjYgbC00OSA2MSAxMjIgMTIyIDEyMyAxMjMgLTIyIDMxIGMtMTIgMTYgLTM2IDQwIC01MiA1MiBsLTMxIDIyIC0xMjEgLTEyMSAtMTIyIC0xMjEgLTYwIDQ2IGMtODEgNjEgLTI2MSAxNDcgLTM2NSAxNzMgLTEwNCAyNyAtMjM1IDM3IC0zNTUgMjl6IG0yNzQgLTE2NCBjMTAyIC0yMSAyMTcgLTY4IDI5NyAtMTIxIDEwOSAtNzIgMTA2IC02NiA0OSAtMTI0IGwtNTAgLTUwIC01MCA5IGMtODMgMTYgLTE2OSAtNSAtMzA1IC03NCBsLTc1IC0zOCA0NSAtMzggYzI1IC0yMiA1MyAtNDIgNjMgLTQ1IDExIC0zIDQ1IDYgNzcgMjAgNjggMzEgMTM1IDQ1IDE3MCAzNiAyMiAtNSAyNSAtMTIgMjggLTU2IDEyIC0yMDQgLTM2MCAtNjQ1IC02NzQgLTgwMCAtMTA3IC01MyAtMTgwIC02NiAtMjA4IC0zOCAtMjcgMjcgLTE4IDExMyAyMiAxOTggMTggMzcgMjkgNzMgMjUgODAgLTE0IDI0IC03NiA5NiAtODIgOTYgLTEyIDAgLTc4IC0xMzIgLTEwMSAtMTk5IC0xNiAtNDkgLTIxIC04NCAtMTkgLTE0OCBsMyAtODQgLTQxIC00MSBjLTUwIC01MCAtNTIgLTQ5IC0xMTkgNTggLTU1IDg4IC05OCAxOTYgLTExOSAyOTQgLTE5IDkxIC0xOSAyNzkgMCAzNjkgNzIgMzQzIDM0NyA2MjEgNjg4IDY5NSA4NSAxOSAyODkgMTkgMzc2IDF6IG01NjkgLTM5MyBjNjMgLTk1IDEwNSAtMTk1IDEyOCAtMzA1IDE4IC04OSAxOCAtMjc3IC0xIC0zNjcgLTczIC0zNDYgLTM0OSAtNjIyIC02OTUgLTY5NSAtOTEgLTE5IC0yNzggLTE5IC0zNjggMCAtMTMwIDI3IC0yNzggOTUgLTM2NyAxNjkgbC0zMCAyNSA0NSA0NSBjNDQgNDUgNDUgNDYgMTEwIDQ2IDE3MyAwIDM5MiAxMjQgNjI1IDM1NSAxMzAgMTI5IDIwNCAyMjIgMjc4IDM1MCA2MyAxMDkgODkgMTg1IDk0IDI4MCBsNSA4MCA1MSA1MyBjMjggMjggNTUgNTIgNTkgNTIgNSAwIDM1IC00MCA2NiAtODh6Ii8+CgkJPHBhdGggZD0iTTc1MiAxODM5IGMtMTk1IC05NyAtOTMgLTQzMyAyMzggLTc5MSA3MSAtNzcgNzIgLTc4IDEwMCAtNjMgMTUgOCA0MiAyNiA2MCA0MSBsMzEgMjcgLTY5IDcxIGMtMTc2IDE4MSAtMjk3IDM3NCAtMzE3IDUwOCAtMTQgOTIgMzIgMTEwIDE1OCA2NSAxMjAgLTQ0IDI2OCAtMTQ3IDQwOSAtMjg3IGw4OCAtODcgMTkgMjEgYzEwIDEyIDI4IDM2IDM5IDU1IGwyMSAzNCAtMjYgMzEgYy00NyA1NyAtMTk3IDE4NiAtMjg1IDI0NyAtMTk2IDEzNCAtMzYxIDE4MCAtNDY2IDEyOHoiLz4KCQk8cGF0aCBkPSJNMTAxMiAxNDk3IGMtMjkgLTI5IC01MiAtNTggLTUyIC02NiAwIC0xMyA1NCAtOTYgNjggLTEwNSA1IC0zIDQ0IDMwIDg2IDczIGw3OCA3OSAtNTMgMzYgYy0zMCAyMCAtNTggMzYgLTY0IDM2IC02IDAgLTM0IC0yNCAtNjMgLTUzeiIvPgoJCTxwYXRoIGQ9Ik0xNjcyIDExMzkgYy00NiAtNTYgLTQ5IC02OSAtMjggLTEwMiAyMyAtMzUgNTYgLTE0MyA1NiAtMTgxIDAgLTc4IC01OSAtODMgLTIwNiAtMjAgbC04MyAzNiAtNTEgLTQ0IGMtMjcgLTI0IC01MCAtNDYgLTUwIC01MCAwIC0xNCAxODYgLTk5IDI1MiAtMTE0IDIwMCAtNDkgMzE3IDc4IDI2MyAyODYgLTE2IDYzIC03MCAxODYgLTk3IDIyMyAtMTMgMTcgLTE3IDE0IC01NiAtMzR6Ii8+Cgk8L2c+Cjwvc3ZnPgo=)](https://git.zakscode.com/ztimson/momentum)
[![MongoDB](https://img.shields.io/badge/mongodb-000000?style=for-the-badge&logo=mongodb)](https://www.mongodb.com/)
[![MySQL](https://img.shields.io/badge/MySQL-4479A1?style=for-the-badge&logo=mysql&logoColor=ffffff)](https://www.mysql.com/)
[![Nest](https://img.shields.io/badge/nestjs-E0234E?style=for-the-badge&logo=nestjs)](https://nestjs.com/)
[![.NET](https://img.shields.io/badge/.NET-512BD4?style=for-the-badge&logo=dotnet)](https://dotnet.microsoft.com/)
[![Next](https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs)](https://nextjs.org/)
[![NGINX](https://img.shields.io/badge/NGINX-009639?style=for-the-badge&logo=nginx)](https://www.nginx.com/)
[![Node](https://img.shields.io/badge/Node.js-000000?style=for-the-badge&logo=nodedotjs)](https://nodejs.org/)
[![p5.js](https://img.shields.io/badge/p5.js-ed225d?style=for-the-badge&logo=p5dotjs&logoColor=white)](https://p5js.org/)
[![PHP](https://img.shields.io/badge/PHP-474A8A?style=for-the-badge&logo=php&logoColor=white)](https://www.php.net/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQl-212121?style=for-the-badge&logo=postgresql)](https://www.postgresql.org/)
[![Python](https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python)](https://www.python.org/)
[![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react)](https://reactjs.org/)
[![Redis](https://img.shields.io/badge/Redis-ffffff?style=for-the-badge&logo=redis)](https://redis.com/)
[![SASS](https://img.shields.io/badge/SASS-CC6699?style=for-the-badge&logo=sass&logoColor=ffffff)](https://sass-lang.com/)
[![Shell](https://img.shields.io/badge/Shell-000000?style=for-the-badge&logo=windowsterminal&logoColor=00ff00)](https://en.wikipedia.org/wiki/Shell_script)
[![SQL Server](https://img.shields.io/badge/SQL%20Server-CC2927?style=for-the-badge&logo=microsoftsqlserver)](https://www.microsoft.com/en-ca/sql-server)
[![SQLite](https://img.shields.io/badge/SQLITE-003B57?style=for-the-badge&logo=sqlite)](https://www.sqlite.org/index.html)
[![Svelte](https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte)](https://svelte.dev/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
[![Windows](https://img.shields.io/badge/Windows-0078D4?style=for-the-badge&logo=windows)](https://microsoft.com/windows)
[![Vite](https://img.shields.io/badge/vite-1b1b1b?style=for-the-badge&logo=vite)](https://vitejs.dev/)
[![Vue](https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs)](https://vuejs.org/)
[![Linux](https://img.shields.io/badge/Linux-eeeeee?style=for-the-badge\&logo=linux\&logoColor=000000)](https://www.linux.org/)
[![Node](https://img.shields.io/badge/Node.js-000000?style=for-the-badge\&logo=nodedotjs)](https://nodejs.org/)
[![JavaScript](https://img.shields.io/badge/JavaScript-000000?style=for-the-badge\&logo=javascript)](https://javascript.com/)
[![HTML](https://img.shields.io/badge/HTML-FFFFFF?style=for-the-badge\&logo=html5)](https://developer.mozilla.org/en-US/docs/Glossary/HTML)
[![CSS](https://img.shields.io/badge/CSS-1572B6?style=for-the-badge\&logo=css3)](https://www.w3.org/Style/CSS/Overview.en.html)
[![Raspberry Pi](https://img.shields.io/badge/Raspberry%20Pi-A22846?style=for-the-badge\&logo=raspberrypi\&logoColor=white)](https://www.raspberrypi.com/)
[![Wayland](https://img.shields.io/badge/Wayland-FFBC00?style=for-the-badge\&logo=wayland\&logoColor=black)](https://wayland.freedesktop.org/)
[![WebSocket](https://img.shields.io/badge/WebSocket-000000?style=for-the-badge)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
[![noVNC](https://img.shields.io/badge/noVNC-000000?style=for-the-badge)](https://novnc.com/)
## Setup
@@ -102,11 +83,14 @@ Website: https://git.zakscode.com
</summary>
#### Prerequisites
- [Docker](https://docs.docker.com/install/)
- Raspberry Pi 4/5 (2GB min/4GB ideal)
- Updated version of Raspberry Pi OS
#### Instructions
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/template:latest`
2. Open [http://localhost](http://localhost)
1. Install homecast: `curl -sL https://git.zakscode.com/ztimson/homecast/raw/branch/master/install.sh | sudo bash`
</details>
<details>
@@ -117,16 +101,17 @@ Website: https://git.zakscode.com
</summary>
#### Prerequisites
- [Node.js](https://nodejs.org/en/download)
* Node.js
#### Instructions
1. Install the dependencies: `npm install`
2. Start the Angular server: `npm run start`
3. Open [http://localhost:4200](http://localhost:4200)
1. Clone the repository.
2. Install the dependencies: `npm install`
3. Start the development server: `npm run start`
</details>
## License
Copyright © 2023 Zakary Timson | All Rights Reserved | Available under MIT Licensing
See the [license](./LICENSE) for more information.
Copyright © 2026 Zakary Timson | Available under MIT Licensing
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
# Fallback AP
curl -sL https://git.zakscode.com/ztimson/fallback-ap/raw/branch/master/install.sh | sudo bash
# VNC
sudo raspi-config nonint do_vnc 0
# Node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash
source ~/.bashrc
nvm install stable && nvm use stable
# Install Homecast
cd /tbd
git clone https://git.zakscode.com/ztimson/homecast.git .
npm i
mkdir -p certs
openssl req -x509 -newkey rsa:2048 \
-keyout certs/key.pem \
-out certs/cert.pem \
-days 3650 \
-nodes \
-subj "/CN=homecast"
# TODO: Create service
+17
View File
@@ -0,0 +1,17 @@
{
"name": "homecast",
"version": "0.1.0",
"description": "The Homebrew Chromecast",
"main": "src/server.mjs",
"scripts": {
"start": "node server.mjs"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@novnc/novnc": "^1.7.0",
"ws": "^8.21.3"
}
}
+46
View File
@@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Remote Desktop</title>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: #000;
}
#screen {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="screen"></div>
<script type="module">
import RFB from '/novnc/core/rfb.js';
const screen = document.querySelector('#screen');
const rfb = new RFB(
screen,
`wss://${location.host}/websockify`
);
rfb.addEventListener('connect', () => console.log('RFB connected'));
rfb.addEventListener('disconnect', event => console.log('RFB disconnected', event.detail));
rfb.scaleViewport = true;
rfb.resizeSession = false;
rfb.showDotCursor = true;
rfb.addEventListener('connect', () => console.log('Connected'));
rfb.addEventListener('disconnect', event => console.log('Disconnected', event.detail));
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+129
View File
@@ -0,0 +1,129 @@
import http from 'node:http';
import https from 'node:https';
import net from 'node:net';
import fs from 'node:fs';
import {readFile} from 'node:fs/promises';
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {WebSocketServer} from 'ws';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const NOVNC = path.join(__dirname, 'node_modules/@novnc/novnc');
const PORT = process.env['PORT'] || 3000;
const VNC_HOST = '127.0.0.1';
const VNC_PORT = 5900;
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.ico': 'image/x-icon',
'.wasm': 'application/wasm'
};
const server = https.createServer({
key: fs.readFileSync('./certs/key.pem'),
cert: fs.readFileSync('./certs/cert.pem')
}, async (req, res) => {
let file;
if (req.url === '/') file = path.join(__dirname, '../public/index.html');
else if (req.url.startsWith('/novnc/')) file = path.join(NOVNC, req.url.slice(7));
else {
res.writeHead(404);
res.end('Not found');
return;
}
try {
const data = await readFile(file);
const type = MIME_TYPES[path.extname(file)] || 'application/octet-stream';
res.writeHead(200, {'Content-Type': type});
res.end(data);
} catch (error) {
console.error('HTTP error:', error.message);
res.writeHead(404);
res.end('Not found');
}
});
const wss = new WebSocketServer({
server,
path: '/websockify',
handleProtocols: protocols => {
console.log('WebSocket protocols:', [...protocols]);
if (protocols.has('binary')) return 'binary';
return false;
}
});
wss.on('connection', ws => {
console.log('VNC client connected');
const socket = net.createConnection(VNC_PORT, VNC_HOST);
socket.on('connect', () => {
console.log(`Connected to VNC ${VNC_HOST}:${VNC_PORT}`);
});
socket.on('data', data => {
console.log(`VNC -> browser: ${data.length} bytes`);
if (data.length <= 64) {
console.log(` hex: ${data.toString('hex')}`);
console.log(` text: ${JSON.stringify(data.toString())}`);
}
if (ws.readyState === ws.OPEN) {
ws.send(data, {binary: true});
}
});
ws.on('message', (data, isBinary) => {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
console.log(`Browser -> VNC: ${buffer.length} bytes (${isBinary ? 'binary' : 'text'})`);
if (buffer.length <= 64) {
console.log(` hex: ${buffer.toString('hex')}`);
console.log(` text: ${JSON.stringify(buffer.toString())}`);
}
if (socket.writable) {
socket.write(buffer);
}
});
ws.on('error', error => {
console.error('WebSocket error:', error.message);
socket.destroy();
});
ws.on('close', (code, reason) => {
console.log(`Browser disconnected: ${code}${reason?.length ? ` ${reason}` : ''}`);
socket.destroy();
});
socket.on('error', error => {
console.error('VNC socket error:', error.message);
if (ws.readyState === ws.OPEN) ws.close();
});
socket.on('close', () => {
console.log('VNC socket closed');
if (ws.readyState === ws.OPEN) ws.close();
});
});
server.on('error', error => {
console.error('Server error:', error);
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`https://0.0.0.0:${PORT}`);
})