**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" < "$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 - < 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.