commit the backend!!!! 🎉
This commit is contained in:
@@ -1,17 +1,15 @@
|
|||||||
# 💾 [Y2K Land](https://y2k.land/) [](https://y2k.land/) [](https://status.jrvs.io/785127956) [](https://github.com/jakejarvis/y2k/actions?query=workflow%3ADeploy)
|
# 💾 [Y2K Land](https://y2k.land/) [](https://y2k.land/) [](https://status.jrvs.io/785127956) [](https://github.com/jakejarvis/y2k/actions?query=workflow%3ADeploy)
|
||||||
|
|
||||||
Nostalgic time machine powered by on-demand Windows Me VMs, [my first website](https://jarv.is/y2k/), and quarantine boredom.
|
Nostalgic time machine powered by on-demand Windows Me® VMs, [my first website](https://jarv.is/y2k/), and quarantine boredom. 📟
|
||||||
|
|
||||||
The backend isn't quite ready to be open-sourced (read: it's still a fatally embarrassing ball of spaghetti) but will be moved here very soon! 🍝
|
[**📝 Read the blog post here.**](https://jarv.is/notes/y2k-land/)
|
||||||
|
|
||||||
[**Read the blog post here.**](https://jarv.is/notes/y2k-land/)
|
|
||||||
|
|
||||||
<p align="center"><a href="https://y2k.land/"><img width="600" src="screenshot.png"></a></p>
|
<p align="center"><a href="https://y2k.land/"><img width="600" src="screenshot.png"></a></p>
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Docker
|
- Docker
|
||||||
- [QEMU 4.x](https://www.qemu.org/) (target i386)
|
- [QEMU](https://www.qemu.org/) (target i386)
|
||||||
- [websocketd](https://github.com/joewalnes/websocketd)
|
- [websocketd](https://github.com/joewalnes/websocketd)
|
||||||
- [noVNC](https://github.com/novnc/noVNC)
|
- [noVNC](https://github.com/novnc/noVNC)
|
||||||
- [Cloudflare Workers](https://workers.cloudflare.com/) & [Argo Tunnel](https://www.cloudflare.com/products/argo-tunnel/)
|
- [Cloudflare Workers](https://workers.cloudflare.com/) & [Argo Tunnel](https://www.cloudflare.com/products/argo-tunnel/)
|
||||||
@@ -25,11 +23,11 @@ The backend isn't quite ready to be open-sourced (read: it's still a fatally emb
|
|||||||
|
|
||||||
## To-Do
|
## To-Do
|
||||||
|
|
||||||
|
- [x] **Commit backend scripts**
|
||||||
- [x] Sync user's mouse cursor/movements with VM
|
- [x] Sync user's mouse cursor/movements with VM
|
||||||
- [x] Error messages: no websockets, server down, etc.
|
- [x] Error messages: no websockets, server down, etc.
|
||||||
- [ ] Usage limits
|
- [ ] Usage limits
|
||||||
- [ ] Responsive browser sizing
|
- [ ] Responsive browser sizing
|
||||||
- [ ] **Commit backend scripts**
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# don't accidentally commit OS images (again...)
|
||||||
|
*.img
|
||||||
|
*.raw
|
||||||
|
*.qcow2
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
LABEL maintainer="Jake Jarvis <jake@jarv.is>"
|
||||||
|
LABEL repository="https://github.com/jakejarvis/y2k"
|
||||||
|
LABEL homepage="https://y2k.land/"
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# corrects the time inside the Windows VM, if tzdata is installed below
|
||||||
|
ENV TZ=America/New_York
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get -y upgrade \
|
||||||
|
&& apt-get -y --no-install-recommends install \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
qemu-system-x86 \
|
||||||
|
qemu-utils \
|
||||||
|
ruby \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# make sure everything's okay so far
|
||||||
|
RUN qemu-system-i386 --version \
|
||||||
|
&& ruby --version
|
||||||
|
|
||||||
|
# ----
|
||||||
|
# TODO: make *each container* a websockets server so we can load balance, etc.
|
||||||
|
|
||||||
|
# RUN wget https://github.com/joewalnes/websocketd/releases/download/v0.3.1/websocketd-0.3.1-linux_amd64.zip \
|
||||||
|
# && unzip websocketd-0.3.1-linux_amd64.zip \
|
||||||
|
# && chmod +x websocketd \
|
||||||
|
# && mv websocketd /usr/local/bin/
|
||||||
|
|
||||||
|
# RUN websocketd --version
|
||||||
|
|
||||||
|
# EXPOSE 80
|
||||||
|
# ----
|
||||||
|
|
||||||
|
# do everything as an unprivileged user :)
|
||||||
|
RUN useradd -m vm
|
||||||
|
|
||||||
|
# copy boot script and Windows HDD (must be at ./hdd/hdd.img)
|
||||||
|
COPY bin/boot.rb /usr/local/bin/boot-vm
|
||||||
|
COPY --chown=vm hdd/hdd.img /home/vm/hdd.img
|
||||||
|
|
||||||
|
# make double sure the boot script is executable & the hard drive was copied
|
||||||
|
RUN chmod +x /usr/local/bin/boot-vm \
|
||||||
|
&& ls -lah /home/vm
|
||||||
|
|
||||||
|
# bye bye root <3
|
||||||
|
USER vm
|
||||||
|
WORKDIR /home/vm
|
||||||
|
|
||||||
|
CMD ["boot-vm"]
|
||||||
Executable
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# encoding: BINARY
|
||||||
|
|
||||||
|
# This script starts a QEMU child process wearing a VNC sock and acts as
|
||||||
|
# middleman between the socket and stdin/out. Perfect for VNC clients that
|
||||||
|
# utilize binary websockets (ex: noVNC.js).
|
||||||
|
#
|
||||||
|
# Usage: ./boot.rb /root/images [/usr/local/bin/qemu-system-i386]
|
||||||
|
|
||||||
|
require "fileutils"
|
||||||
|
require "socket"
|
||||||
|
require "timeout"
|
||||||
|
|
||||||
|
# folder containing the OS's hdd.img, other instance files will also go here
|
||||||
|
# default for container is set, can be optionally overridden by first argument
|
||||||
|
base_path = ARGV[0] || "/home/vm"
|
||||||
|
|
||||||
|
# location of QEMU binary (`qemu-system-i386` here, or `-x86_64` for 64-bit)
|
||||||
|
# default for container is set, can be optionally overridden by second argument
|
||||||
|
qemu_path = ARGV[1] || "/usr/bin/qemu-system-i386"
|
||||||
|
|
||||||
|
# create a temporary directory for each instance from PID
|
||||||
|
# NOTE: not needed when containerized, everything's already ephemeral
|
||||||
|
# instance_dir = "/tmp/y2k.#{$$}"
|
||||||
|
|
||||||
|
# flush data immediately to stdout instead of buffering
|
||||||
|
# https://ruby-doc.org/core-2.7.0/IO.html#method-i-sync-3D
|
||||||
|
$stdout.sync = true
|
||||||
|
|
||||||
|
begin
|
||||||
|
# make the temp dir for our new instance & grab a fresh copy of the OS
|
||||||
|
# NOTE: not needed when containerized, everything's already ephemeral
|
||||||
|
# FileUtils.makedirs(instance_dir)
|
||||||
|
# FileUtils.cp(base_img, "#{instance_dir}/hdd.img")
|
||||||
|
|
||||||
|
# open a catch-all log file
|
||||||
|
log_file = File.open("#{base_path}/out.log", "w")
|
||||||
|
|
||||||
|
# start QEMU as a child process (TODO: put config somewhere more manageable)
|
||||||
|
qemu = spawn qemu_path,
|
||||||
|
"-hda", "#{base_path}/hdd.img",
|
||||||
|
"-cpu", "pentium3,enforce",
|
||||||
|
"-smp", "1",
|
||||||
|
"-m", "128",
|
||||||
|
"-net", "none",
|
||||||
|
"-serial", "none",
|
||||||
|
"-parallel", "none",
|
||||||
|
"-vga", "std",
|
||||||
|
"-usb",
|
||||||
|
"-device", "usb-tablet",
|
||||||
|
"-rtc", "base=localtime",
|
||||||
|
"-no-acpi",
|
||||||
|
"-no-reboot",
|
||||||
|
"-nographic",
|
||||||
|
"-vnc", "unix:#{base_path}/vnc.sock",
|
||||||
|
{ :in => :close, :out => log_file, :err => log_file }
|
||||||
|
|
||||||
|
# limit CPU usage of each VM (if host supports it)
|
||||||
|
# NOTE: setting --cpus with Docker makes this redundant
|
||||||
|
# if File.exist?("/usr/bin/cpulimit")
|
||||||
|
# cpulimit = spawn "/usr/bin/cpulimit",
|
||||||
|
# "--pid", "#{qemu}",
|
||||||
|
# "--limit", "90",
|
||||||
|
# { :in => :close, :out => log_file, :err => log_file }
|
||||||
|
# end
|
||||||
|
|
||||||
|
# wait until the VNC socket is created; only takes a fraction of a second (if
|
||||||
|
# the server load is low) but everything following this will freak the f*ck
|
||||||
|
# out if it's not there yet
|
||||||
|
Timeout.timeout(15) do
|
||||||
|
until File.exist?("#{base_path}/vnc.sock")
|
||||||
|
sleep 0.02
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# attach ourselves to the VM's VNC socket made by QEMU
|
||||||
|
sock = UNIXSocket.new("#{base_path}/vnc.sock")
|
||||||
|
|
||||||
|
# everything's all set up now, time to simply pass data between user and VM
|
||||||
|
while $stdin do
|
||||||
|
begin
|
||||||
|
# monitor the IO buffer for unprocessed data (read from both directions)
|
||||||
|
read, _, err = IO.select([$stdin, sock], nil)
|
||||||
|
|
||||||
|
# break out of loop if anything goes wrong, doesn't really matter what tbh
|
||||||
|
if err.any?
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
# pass input from user to VM
|
||||||
|
if read.include?($stdin)
|
||||||
|
data = $stdin.readpartial(4096)
|
||||||
|
sock.write(data) unless data.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# pass output from VM to user
|
||||||
|
if read.include?(sock)
|
||||||
|
data = sock.readpartial(4096)
|
||||||
|
$stdout.write(data) unless data.empty?
|
||||||
|
|
||||||
|
# send output immediately (see $stdout.sync above)
|
||||||
|
$stdout.flush
|
||||||
|
end
|
||||||
|
rescue EOFError
|
||||||
|
# we stopped receiving input from the user's end, so don't expect any more
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
# the user's done (or we crashed) so stop their personal VM; everything else
|
||||||
|
# will be deleted along with the Docker container
|
||||||
|
Process.kill(:SIGTERM, qemu) if qemu
|
||||||
|
|
||||||
|
# kill cpulimit if it didn't stop itself already
|
||||||
|
# Process.kill(:SIGTERM, cpulimit) if cpulimit
|
||||||
|
|
||||||
|
# ...and delete their hard drive, logs, etc.
|
||||||
|
# NOTE: not needed when containerized
|
||||||
|
# FileUtils.rm_rf(instance_dir)
|
||||||
|
end
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
# what a mess. https://stackoverflow.com/a/53183593
|
||||||
|
YOU_ARE_HERE="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
|
||||||
|
|
||||||
|
# container will be useless unless we bundle the actual OS
|
||||||
|
test -f "$YOU_ARE_HERE"/hdd/hdd.img
|
||||||
|
|
||||||
|
# this image is private on Docker Hub, make sure we're logged in
|
||||||
|
docker login
|
||||||
|
|
||||||
|
docker build -t jakejarvis/y2k:latest --no-cache "$YOU_ARE_HERE"
|
||||||
|
docker push jakejarvis/y2k:latest
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# /lib/systemd/system/y2k.service
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=y2k websockets
|
||||||
|
After=network.target
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1
|
||||||
|
User=root
|
||||||
|
ExecStart=/root/y2k/backend/server/socket.sh
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Executable
+63
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# you probably shouldn't just run this! ;)
|
||||||
|
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
#### install basic requirements ####
|
||||||
|
apt-get -y update
|
||||||
|
apt-get -y dist-upgrade
|
||||||
|
apt-get -y --no-install-recommends install \
|
||||||
|
apt-transport-https \
|
||||||
|
ca-certificates \
|
||||||
|
gnupg-agent \
|
||||||
|
software-properties-common \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
unzip
|
||||||
|
|
||||||
|
#### install papertrail logging ####
|
||||||
|
wget -qO - --header="X-Papertrail-Token: CHANGEMECHANGEMECHANGEME" \
|
||||||
|
https://papertrailapp.com/destinations/CHANGEME/setup.sh | bash
|
||||||
|
|
||||||
|
#### docker fixes ####
|
||||||
|
# sed -i 's/\(GRUB_CMDLINE_LINUX="\)"/\1cgroup_enable=memory swapaccount=1"/' /etc/default/grub
|
||||||
|
# update-grub
|
||||||
|
|
||||||
|
#### install Docker from official repository ####
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
|
||||||
|
apt-key fingerprint 0EBFCD88
|
||||||
|
add-apt-repository \
|
||||||
|
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(lsb_release -cs) \
|
||||||
|
stable"
|
||||||
|
apt-get update
|
||||||
|
apt-get install docker-ce docker-ce-cli containerd.io
|
||||||
|
|
||||||
|
#### install websocketd ####
|
||||||
|
wget https://github.com/joewalnes/websocketd/releases/download/v0.3.1/websocketd-0.3.1-linux_amd64.zip
|
||||||
|
unzip websocketd-0.3.1-linux_amd64.zip
|
||||||
|
chmod +x websocketd
|
||||||
|
mv websocketd /usr/bin/
|
||||||
|
|
||||||
|
#### install cloudflared ####
|
||||||
|
wget https://bin.equinox.io/c/VdrWdbjqyF/cloudflared-stable-linux-amd64.deb
|
||||||
|
dpkg -i cloudflared-stable-linux-amd64.deb
|
||||||
|
cloudflared update
|
||||||
|
cloudflared tunnel login
|
||||||
|
cloudflared service install
|
||||||
|
systemctl enable cloudflared
|
||||||
|
systemctl start cloudflared
|
||||||
|
|
||||||
|
#### clone repository ####
|
||||||
|
git clone https://github.com/jakejarvis/y2k.git /root/y2k
|
||||||
|
|
||||||
|
#### pull Docker image ####
|
||||||
|
docker login
|
||||||
|
docker pull jakejarvis/y2k:latest
|
||||||
|
|
||||||
|
#### enable & start service ####
|
||||||
|
cp /root/y2k/backend/server/example.service /lib/systemd/system/y2k.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable y2k
|
||||||
|
systemctl start y2k
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
/usr/bin/websocketd \
|
||||||
|
--port=80 \
|
||||||
|
--binary \
|
||||||
|
--header-ws="Sec-WebSocket-Protocol: binary" \
|
||||||
|
--origin=y2k.land,www.y2k.land,y2k.jakejarvis.workers.dev \
|
||||||
|
-- \
|
||||||
|
docker run \
|
||||||
|
--cpus 1 \
|
||||||
|
--memory 200m \
|
||||||
|
--network none \
|
||||||
|
--log-driver none \
|
||||||
|
--rm -i \
|
||||||
|
jakejarvis/y2k:latest
|
||||||
|
|
||||||
|
# NOTE: if not using Docker, the command is:
|
||||||
|
# /root/y2k/backend/bin/boot.rb /root/y2k/backend/hdd /usr/bin/qemu-system-i386
|
||||||
Reference in New Issue
Block a user