368 lines
7.7 KiB
Markdown
368 lines
7.7 KiB
Markdown
**installer** for Ubuntu/Debian VPS
|
|
|
|
[debian ext2](https://github.com/leaningtech/webvm/releases/download/ext2_image/debian_mini_20230519_5022088024.ext2)
|
|
|
|
- install required packages
|
|
- create `/opt/webvm-aio`
|
|
- ask for:
|
|
- domain
|
|
- Let's Encrypt email
|
|
- image filename
|
|
- write:
|
|
- `.env`
|
|
- `docker-compose.caddy.yml`
|
|
- `docker-compose.webvm.yml`
|
|
- `Caddyfile`
|
|
- `nginx.conf`
|
|
- `bootstrap.sh`
|
|
- `deploy.sh`
|
|
- create the shared Docker network `web`
|
|
- start Caddy
|
|
- tell you where to upload your `.ext2` image
|
|
- optionally bootstrap immediately if the image is already present
|
|
|
|
---
|
|
|
|
## copy-paste installer
|
|
|
|
Run as sudo-capable user:
|
|
|
|
```bash
|
|
bash <(cat <<'EOF'
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="/opt/webvm-aio"
|
|
|
|
echo "==> WebVM AIO installer"
|
|
|
|
if ! command -v sudo >/dev/null 2>&1; then
|
|
echo "sudo is required"
|
|
exit 1
|
|
fi
|
|
|
|
read -rp "Domain (e.g. webvm.example.com): " WEBVM_DOMAIN
|
|
read -rp "Let's Encrypt email: " LETSENCRYPT_EMAIL
|
|
read -rp "Disk image filename (e.g. debian_mini_20230519_5022088024.ext2): " WEBVM_IMAGE
|
|
|
|
if [ -z "${WEBVM_DOMAIN}" ] || [ -z "${LETSENCRYPT_EMAIL}" ] || [ -z "${WEBVM_IMAGE}" ]; then
|
|
echo "All fields are required."
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Installing packages"
|
|
sudo apt update
|
|
sudo apt install -y git curl docker.io docker-compose-plugin nodejs npm python3
|
|
|
|
echo "==> Enabling docker"
|
|
sudo systemctl enable --now docker
|
|
|
|
echo "==> Creating layout at $ROOT_DIR"
|
|
sudo mkdir -p "$ROOT_DIR"
|
|
sudo chown -R "$USER":"$USER" "$ROOT_DIR"
|
|
|
|
mkdir -p "$ROOT_DIR"/{images,index,src}
|
|
mkdir -p "$ROOT_DIR/index/custom-disk-images"
|
|
|
|
cat > "$ROOT_DIR/.env" <<ENV
|
|
WEBVM_DOMAIN=$WEBVM_DOMAIN
|
|
WEBVM_IMAGE=$WEBVM_IMAGE
|
|
WEBVM_ROOT=$ROOT_DIR
|
|
LETSENCRYPT_EMAIL=$LETSENCRYPT_EMAIL
|
|
ENV
|
|
|
|
cat > "$ROOT_DIR/docker-compose.caddy.yml" <<'YAML'
|
|
version: "3.9"
|
|
|
|
services:
|
|
caddy:
|
|
image: lucaslorentz/caddy-docker-proxy:2.9-alpine
|
|
container_name: caddy
|
|
restart: unless-stopped
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
environment:
|
|
- CADDY_INGRESS_NETWORKS=web
|
|
networks:
|
|
- web
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
|
|
networks:
|
|
web:
|
|
external: true
|
|
|
|
volumes:
|
|
caddy_data:
|
|
caddy_config:
|
|
YAML
|
|
|
|
cat > "$ROOT_DIR/docker-compose.webvm.yml" <<'YAML'
|
|
version: "3.9"
|
|
|
|
services:
|
|
webvm:
|
|
image: nginx:alpine
|
|
container_name: webvm
|
|
restart: unless-stopped
|
|
networks:
|
|
- web
|
|
volumes:
|
|
- ./index:/usr/share/nginx/html:ro
|
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
labels:
|
|
caddy: ${WEBVM_DOMAIN}
|
|
caddy.reverse_proxy: "{{upstreams 80}}"
|
|
caddy.encode: zstd gzip
|
|
|
|
networks:
|
|
web:
|
|
external: true
|
|
YAML
|
|
|
|
cat > "$ROOT_DIR/Caddyfile" <<'CADDY'
|
|
{
|
|
email {$LETSENCRYPT_EMAIL}
|
|
}
|
|
CADDY
|
|
|
|
cat > "$ROOT_DIR/nginx.conf" <<'NGINX'
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
|
|
root /usr/share/nginx/html;
|
|
index index.html;
|
|
|
|
types {
|
|
application/wasm wasm;
|
|
}
|
|
|
|
add_header Cross-Origin-Opener-Policy same-origin always;
|
|
add_header Cross-Origin-Embedder-Policy require-corp always;
|
|
add_header Cross-Origin-Resource-Policy same-origin always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
|
|
location / {
|
|
try_files $uri $uri/ /index.html;
|
|
}
|
|
|
|
location /custom-disk-images/ {
|
|
autoindex off;
|
|
add_header Cache-Control "public, max-age=3600";
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ~* \.(wasm|bin|img|ext2)$ {
|
|
add_header Cache-Control "public, max-age=3600";
|
|
try_files $uri =404;
|
|
}
|
|
|
|
location ~* \.(js|mjs|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf)$ {
|
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
|
try_files $uri =404;
|
|
}
|
|
|
|
client_max_body_size 50m;
|
|
}
|
|
NGINX
|
|
|
|
cat > "$ROOT_DIR/bootstrap.sh" <<'BOOT'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="${WEBVM_ROOT:-/opt/webvm-aio}"
|
|
if [ -f "$ROOT_DIR/.env" ]; then
|
|
set -a
|
|
. "$ROOT_DIR/.env"
|
|
set +a
|
|
fi
|
|
|
|
SRC_DIR="$ROOT_DIR/src/webvm"
|
|
PUB_DIR="$ROOT_DIR/index"
|
|
IMG_SRC_DIR="$ROOT_DIR/images"
|
|
IMG_NAME="${WEBVM_IMAGE:-debian_mini_20230519_5022088024.ext2}"
|
|
IMG_SRC="$IMG_SRC_DIR/$IMG_NAME"
|
|
IMG_PUB_DIR="$PUB_DIR/custom-disk-images"
|
|
IMG_PUB="$IMG_PUB_DIR/$IMG_NAME"
|
|
|
|
echo "==> root: $ROOT_DIR"
|
|
echo "==> source: $SRC_DIR"
|
|
echo "==> publish: $PUB_DIR"
|
|
echo "==> image source: $IMG_SRC"
|
|
|
|
mkdir -p "$ROOT_DIR" "$ROOT_DIR/src" "$PUB_DIR" "$IMG_SRC_DIR" "$IMG_PUB_DIR"
|
|
|
|
if ! command -v docker >/dev/null 2>&1; then
|
|
echo "ERROR: docker is not installed"
|
|
exit 1
|
|
fi
|
|
|
|
if ! docker network inspect web >/dev/null 2>&1; then
|
|
echo "==> creating shared docker network: web"
|
|
docker network create web
|
|
fi
|
|
|
|
echo "==> starting caddy"
|
|
cd "$ROOT_DIR"
|
|
docker compose --env-file .env -f docker-compose.caddy.yml up -d
|
|
|
|
if [ ! -f "$IMG_SRC" ]; then
|
|
echo
|
|
echo "ERROR: missing disk image:"
|
|
echo " $IMG_SRC"
|
|
echo
|
|
echo "Upload your .ext2 image there, then rerun:"
|
|
echo " $ROOT_DIR/bootstrap.sh"
|
|
echo
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$SRC_DIR/.git" ]; then
|
|
echo "==> cloning webvm"
|
|
git clone https://github.com/leaningtech/webvm.git "$SRC_DIR"
|
|
else
|
|
echo "==> updating webvm"
|
|
git -C "$SRC_DIR" pull --ff-only
|
|
fi
|
|
|
|
cd "$SRC_DIR"
|
|
|
|
echo "==> installing dependencies"
|
|
npm install
|
|
|
|
CONFIG_FILE="$SRC_DIR/config_public_terminal.js"
|
|
|
|
echo "==> patching WebVM config"
|
|
python3 - <<PY
|
|
from pathlib import Path
|
|
import re
|
|
|
|
p = Path("$CONFIG_FILE")
|
|
s = p.read_text()
|
|
|
|
s = re.sub(
|
|
r'export const diskImageUrl\\s*=\\s*".*?";',
|
|
'export const diskImageUrl = "/custom-disk-images/$IMG_NAME";',
|
|
s
|
|
)
|
|
s = re.sub(
|
|
r'export const diskImageType\\s*=\\s*".*?";',
|
|
'export const diskImageType = "bytes";',
|
|
s
|
|
)
|
|
|
|
p.write_text(s)
|
|
print("patched", p)
|
|
PY
|
|
|
|
echo "==> building webvm"
|
|
npm install
|
|
npm run build
|
|
|
|
echo "==> publishing build"
|
|
rm -rf "$PUB_DIR"/*
|
|
mkdir -p "$IMG_PUB_DIR"
|
|
|
|
if [ -d "$SRC_DIR/dist" ]; then
|
|
cp -r "$SRC_DIR/dist/"* "$PUB_DIR"/
|
|
else
|
|
echo "ERROR: expected build output in $SRC_DIR/dist"
|
|
exit 1
|
|
fi
|
|
|
|
cp "$IMG_SRC" "$IMG_PUB"
|
|
|
|
echo "==> starting webvm"
|
|
cd "$ROOT_DIR"
|
|
docker compose --env-file .env -f docker-compose.webvm.yml up -d
|
|
|
|
echo
|
|
echo "Done."
|
|
echo "Site: https://${WEBVM_DOMAIN}"
|
|
echo "Image: https://${WEBVM_DOMAIN}/custom-disk-images/$IMG_NAME"
|
|
BOOT
|
|
|
|
cat > "$ROOT_DIR/deploy.sh" <<'DEPLOY'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
ROOT_DIR="/opt/webvm-aio"
|
|
cd "$ROOT_DIR"
|
|
./bootstrap.sh
|
|
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
DEPLOY
|
|
|
|
chmod +x "$ROOT_DIR/bootstrap.sh" "$ROOT_DIR/deploy.sh"
|
|
|
|
echo "==> Ensuring docker network exists"
|
|
docker network inspect web >/dev/null 2>&1 || docker network create web
|
|
|
|
echo "==> Starting Caddy"
|
|
cd "$ROOT_DIR"
|
|
docker compose --env-file .env -f docker-compose.caddy.yml up -d
|
|
|
|
echo
|
|
echo "==> Install complete"
|
|
echo
|
|
echo "Next steps:"
|
|
echo "1. Point DNS for $WEBVM_DOMAIN to this server."
|
|
echo "2. Upload your disk image to:"
|
|
echo " $ROOT_DIR/images/$WEBVM_IMAGE"
|
|
echo
|
|
echo "Example upload:"
|
|
echo " scp $WEBVM_IMAGE $USER@$(hostname -I | awk '{print $1}'):$ROOT_DIR/images/"
|
|
echo
|
|
read -rp "If the image is already present, bootstrap now? [y/N]: " DO_BOOTSTRAP
|
|
if [[ "${DO_BOOTSTRAP:-}" =~ ^[Yy]$ ]]; then
|
|
"$ROOT_DIR/bootstrap.sh"
|
|
else
|
|
echo
|
|
echo "When ready, run:"
|
|
echo " cd $ROOT_DIR && ./bootstrap.sh"
|
|
fi
|
|
EOF
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## After running it
|
|
|
|
### 1. Point DNS
|
|
Set `A` record for your domain to your VPS IP.
|
|
|
|
### 2. Upload the `.ext2` image
|
|
Example:
|
|
|
|
```bash
|
|
scp debian_mini_20230519_5022088024.ext2 user@your-server:/opt/webvm-aio/images/
|
|
```
|
|
|
|
### 3. Bootstrap
|
|
```bash
|
|
cd /opt/webvm-aio
|
|
./bootstrap.sh
|
|
```
|
|
|
|
### 4. Open
|
|
```text
|
|
https://your-domain
|
|
```
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
### If ports 80/443 are already used
|
|
Stop any existing web server before running Caddy:
|
|
|
|
```bash
|
|
sudo systemctl stop nginx
|
|
sudo systemctl disable nginx
|
|
```
|
|
|
|
or stop any existing container using 80/443.
|