https://accounts.hetzner.com/login rM2!23 -- TODO: Save ssh key file to lifeOS I hereby confirm that if I lose my recovery key, Hetzner Online will send me a written replacement key in the mail/post. Hetzner Online will not use any other method to send me a replacement. Enable 2FA Your recovery key allows you to deactivate two-factor authentication on your account if you are unable to log in with your one-time password. LOGIN: K0285092926 KEY: baYj8qYBE%vbA5QU https://oracle-one.slack.com/archives/C0ABND4D6H5/p1772500406113429 https://kasm.invixiom.com Kasm Admin: admin@kasm.local / UCTOQDZiUhN8U Kasm User: user@kasm.local / 8QVbpuz9YDpzy DB Password: A7lZYaiCjO0z76fNVxTY Manager Token: 0RQKXaG8u0ylmBkX2bfJ Service Token: bn4S7ykHMR18skYQASVA -- Release 2 New database passwords: DB_PROD_PASSWORD="UCTOQDZiUhN8U" DB_DEV_PASSWORD="8QVbpuz9YDpzy" old suprabase passwords: SUPABASE_URL=https://ezvbghpbpvsqcpvkxnnb.supabase.co SUPABASE_KEY=sb_secret_05dhOv3FGgFW4ZBw7PtJlw_DyEzIQiZ pg_dump "postgresql://postgres:YOUR_PASSWORD@db.ezvbghpbpvsqcpvkxnnb.supabase.co:5432/postgres" -f lifeos_prod_backup.sql reate a new project, then a server with these specs: Type: CX42 (8 vCPU / 16GB RAM / 160GB SSD) - CX32 is not enough once Kasm is running OS: Ubuntu 22.04 Location: Nuremberg or Falkenstein (Germany) SSH key: add yours during setup Firewall: create one with these inbound rules: TCP 22 (SSH) TCP 80 (HTTP) TCP 443 (HTTPS) TCP 8443 (Kasm internal, temporary during setup) Note the server IP when it provisions. defiant-01 firewall-1 ------------------------ SSH Key ---------------------------- PS C:\> ssh-keygen -t ed25519 -C "hetzner" Generating public/private ed25519 key pair. Enter file in which to save the key (C:\Users\Michael Dombaugh/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in C:\Users\Michael Dombaugh/.ssh/id_ed25519 Your public key has been saved in C:\Users\Michael Dombaugh/.ssh/id_ed25519.pub The key fingerprint is: SHA256:L2W7OhQ8feZ8Z6dbt5qzLdcjnuuhG6ssP045zRNHUss hetzner The key's randomart image is: +--[ED25519 256]--+ | . | | o . | | . . . E | | + . = | | Soo* . | | .++.= . +| | ..+o= o +=| | .+o..=+*++| | =B+++OX+.| +----[SHA256]-----+ PS C:\> cat "$env:USERPROFILE\.ssh\id_ed25519.pub" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILskGQEduefKgUeyjZV1uIJnYjtQ9G7pP594fxPiGVA6 hetzner Domain: invixiom.com kasm.invixiom.com -> YOUR_SERVER_IP files.invixiom.com -> YOUR_SERVER_IP api.invixiom.com -> YOUR_SERVER_IP code.invixiom.com -> YOUR_SERVER_IP 46.225.166.142 In Hostinger, go to Domains > your domain > DNS / Nameservers > DNS Records. Create 4 A records: Type | Name | Points to | TTL -----|------- |----------------|---- A | kasm | 46.225.166.142 | 300 A | files | 46.225.166.142 | 300 A | lifeos-api | 46.225.166.142 | 300 A | code | 46.225.166.142 | 300 DELETED: A ftp 0 46.202.196.44 1800 A @ 0 46.202.196.44 1800 nslookup kasm.invixiom.com 8.8.8.8 nslookup files.invixiom.com ssh root@46.225.166.142 -------------------------------- -- set up user -------------------------------- apt update && apt upgrade -y useradd -m -s /bin/bash michael usermod -aG sudo michael mkdir -p /home/michael/.ssh cp ~/.ssh/authorized_keys /home/michael/.ssh/ chown -R michael:michael /home/michael/.ssh -------------------------------- -- harden SSH -------------------------------- sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config systemctl restart ssh -------------------------------- -- Install Docker -------------------------------- curl -fsSL https://get.docker.com | sh usermod -aG docker mic apt install -y docker-compose-plugin docker --version docker compose version -- output -- root@defiant-01:~# docker --version Docker version 29.2.1, build a5c7197 root@defiant-01:~# docker compose version Docker Compose version v5.0.2 root@defiant-01:~# -------------------------------- -- Create stack directory -------------------------------- mkdir -p /home/michael/stack/{caddy,nextcloud,api,redis} cd /home/michael/stack -------------------------------- -- Create the Caddyfile: -------------------------------- cat > /home/michael/stack/caddy/Caddyfile << 'EOF' files.invixiom.com { reverse_proxy nextcloud:80 } lifeos-api.invixiom.com { reverse_proxy fastapi:8000 } kasm.invixiom.com { reverse_proxy localhost:8443 { transport http { tls_insecure_skip_verify } } } code.invixiom.com { reverse_proxy code-server:8080 } EOF -------------------------------- -- Create Fast API: -------------------------------- cat > /home/michael/stack/api/Dockerfile << 'EOF' FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] EOF cat > /home/michael/stack/api/requirements.txt << 'EOF' fastapi uvicorn sqlalchemy psycopg2-binary redis celery EOF mkdir -p /home/michael/stack/api/app cat > /home/michael/stack/api/app/main.py << 'EOF' from fastapi import FastAPI app = FastAPI() @app.get("/health") def health(): return {"status": "ok"} EOF cat > /home/michael/stack/api/app/celery.py << 'EOF' from celery import Celery import os celery = Celery(__name__, broker=os.getenv("REDIS_URL")) EOF cat > /home/michael/stack/docker-compose.yml << 'EOF' version: "3.9" networks: web: external: false volumes: nextcloud_data: postgres_data: redis_data: caddy_data: caddy_config: services: caddy: image: caddy:2-alpine container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config networks: [web] postgres: image: postgres:16-alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss POSTGRES_DB: lifeos volumes: - postgres_data:/var/lib/postgresql/data networks: [web] redis: image: redis:7-alpine container_name: redis restart: unless-stopped volumes: - redis_data:/data networks: [web] nextcloud: image: nextcloud:27-apache container_name: nextcloud restart: unless-stopped environment: NEXTCLOUD_ADMIN_USER: michael NEXTCLOUD_ADMIN_PASSWORD: Ch4ng3Th1sP4ss NEXTCLOUD_TRUSTED_DOMAINS: files.invixiom.com POSTGRES_HOST: postgres POSTGRES_DB: nextcloud POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss volumes: - nextcloud_data:/var/www/html depends_on: [postgres] networks: [web] fastapi: build: ./api container_name: fastapi restart: unless-stopped environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] celery: build: ./api container_name: celery restart: unless-stopped command: celery -A app.celery worker --loglevel=info environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] EOF -- part II cat > /home/michael/stack/docker-compose.yml << 'EOF' version: "3.9" networks: web: external: false volumes: nextcloud_data: postgres_data: redis_data: caddy_data: caddy_config: services: caddy: image: caddy:2-alpine container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./caddy/Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config networks: [web] postgres: image: postgres:16-alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss POSTGRES_DB: lifeos volumes: - postgres_data:/var/lib/postgresql/data networks: [web] redis: image: redis:7-alpine container_name: redis restart: unless-stopped volumes: - redis_data:/data networks: [web] nextcloud: image: nextcloud:27-apache container_name: nextcloud restart: unless-stopped environment: NEXTCLOUD_ADMIN_USER: michael NEXTCLOUD_ADMIN_PASSWORD: Ch4ng3Th1sP4ss NEXTCLOUD_TRUSTED_DOMAINS: files.invixiom.com POSTGRES_HOST: postgres POSTGRES_DB: nextcloud POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss volumes: - nextcloud_data:/var/www/html depends_on: [postgres] networks: [web] fastapi: build: ./api container_name: fastapi restart: unless-stopped environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] celery: build: ./api container_name: celery restart: unless-stopped command: celery -A app.celery worker --loglevel=info environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] EOF -- part II review the files: root@defiant-01:~# find /home/michael/stack -type f -- output -- /home/michael/stack/api/app/main.py /home/michael/stack/api/app/celery.py /home/michael/stack/api/requirements.txt /home/michael/stack/api/Dockerfile /home/michael/stack/caddy/Caddyfile root@defiant-01:~# -------------------------------- -- Start the Stack -------------------------------- cd /home/michael/stack docker compose up -d -------------------------------- -- All containers built and created. -- Verify they're all running: -------------------------------- docker compose ps -------------------------------- -- Next step is Kasm -------------------------------- -- check curl -s https://kasm-static-content.s3.amazonaws.com/latest | head -20 cd /tmp rm kasm_release_1.18.0.tar.gz curl -O https://kasm-static-content.s3.amazonaws.com/kasm_release_1.18.0.09f70a.tar.gz tar -xf kasm_release_1.18.0.09f70a.tar.gz # sudo bash kasm_release/install.sh --accept-eula sudo bash kasm_release/install.sh --accept-eula --proxy-port 8443 y #2 -- 2) 8GB Installation Complete Kasm UI Login Credentials ------------------------------------ username: admin@kasm.local password: UCTOQDZiUhN8U ------------------------------------ username: user@kasm.local password: 8QVbpuz9YDpzy ------------------------------------ Kasm Database Credentials ------------------------------------ username: kasmapp password: A7lZYaiCjO0z76fNVxTY ------------------------------------ Kasm Manager Token ------------------------------------ password: 0RQKXaG8u0ylmBkX2bfJ ------------------------------------ Service Registration Token ------------------------------------ password: bn4S7ykHMR18skYQASVA ------------------------------------ Kasm Admin: admin@kasm.local / UCTOQDZiUhN8U Kasm User: user@kasm.local / 8QVbpuz9YDpzy DB Password: A7lZYaiCjO0z76fNVxTY Manager Token: 0RQKXaG8u0ylmBkX2bfJ Service Token: bn4S7ykHMR18skYQASVA ------------------------------------ Test it directly first before going through Caddy: ------------------------------------ Open your browser and go to https://46.225.166.142:8443 You'll get a certificate warning - that's expected, click through it. You should see the Kasm login page. Log in with admin@kasm.local and the admin password above. Once confirmed working, we'll finalize the Caddy routing so it's accessible at kasm.invixiom.com with a proper SSL cert. ------------------------------------ Now let's get it routing through kasm.invixiom.com with proper SSL via Caddy. ------------------------------------ cd /home/michael/stack docker compose restart caddy https://kasm.invixiom.com This page isn’t working kasm.invixiom.com is currently unable to handle this request. HTTP ERROR 502 ------------------------------------ Troubleshooting ------------------------------------ cd /home/michael/stack docker logs caddy --tail 30 ------------------------------------ The problem is clear: Caddy runs inside Docker, so localhost:8443 refers to inside the container, not the host where Kasm is running. Fix the Caddyfile to use the Docker host gateway IP: ------------------------------------ cat > /home/michael/stack/caddy/Caddyfile << 'EOF' files.invixiom.com { reverse_proxy nextcloud:80 } lifeos-api.invixiom.com { reverse_proxy fastapi:8000 } kasm.invixiom.com { reverse_proxy https://172.17.0.1:8443 { transport http { tls_insecure_skip_verify } } } code.invixiom.com { reverse_proxy code-server:8080 } EOF ------------------------------------ Restart ------------------------------------ docker compose restart caddy ------------------------------------ Setup Browser ------------------------------------ In Kasm, go to Admin > Workspaces > Registry - this shows available workspace images to install. Find and install one of these to start: Brave Browser - just a browser, lightest option, good for your corporate bypass use case # Ubuntu Desktop - full Linux desktop with Firefox included -- Selected Ubuntu Noble (Desktop) Click the install icon next to whichever you want. It will pull the Docker image which takes a few minutes. Once installed, click Workspaces in the top nav, then launch it. You should get a full browser or desktop session streaming in your browser tab. ----------------------------- Fix Hanging Desktop ----------------------------- cat > /home/michael/stack/caddy/Caddyfile << 'EOF' files.invixiom.com { reverse_proxy nextcloud:80 } lifeos-api.invixiom.com { reverse_proxy fastapi:8000 } kasm.invixiom.com { reverse_proxy https://172.17.0.1:8443 { transport http { tls_insecure_skip_verify } header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-For {remote_host} header_up X-Forwarded-Proto {scheme} } } code.invixiom.com { reverse_proxy code-server:8080 } EOF cd /home/michael/stack docker compose restart caddy -- work around https://46.225.166.142:8443 --------------------------------- fix Ngix/ Kasm streaming issue --------------------------------- apt install -y nginx cd /home/michael/stack docker compose stop caddy -------------------------------- create the Nginx config for Kasm: -------------------------------- cat > /etc/nginx/sites-available/kasm << 'EOF' server { listen 80; server_name kasm.invixiom.com files.invixiom.com lifeos-api.invixiom.com code.invixiom.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name kasm.invixiom.com; ssl_certificate /etc/nginx/ssl/invixiom.crt; ssl_certificate_key /etc/nginx/ssl/invixiom.key; location / { proxy_pass https://127.0.0.1:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_ssl_verify off; proxy_read_timeout 1800s; proxy_send_timeout 1800s; } } EOF ------------------------------- we need SSL certs. Run certbot ------------------------------- apt install -y certbot python3-certbot-nginx mkdir -p /etc/nginx/ssl certbot certonly --standalone -d kasm.invixiom.com -d files.invixiom.com -d lifeos-api.invixiom.com -d code.invixiom.com --agree-tos --email michael.dombaugh@gmail.com --non-interactive ------------------------------- update the Nginx config to use the real cert paths ------------------------------- cat > /etc/nginx/sites-available/invixiom << 'EOF' server { listen 80; server_name kasm.invixiom.com files.invixiom.com lifeos-api.invixiom.com code.invixiom.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name kasm.invixiom.com; ssl_certificate /etc/letsencrypt/live/kasm.invixiom.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/kasm.invixiom.com/privkey.pem; location / { proxy_pass https://127.0.0.1:8443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_ssl_verify off; proxy_read_timeout 1800s; proxy_send_timeout 1800s; } } server { listen 443 ssl; server_name files.invixiom.com; ssl_certificate /etc/letsencrypt/live/kasm.invixiom.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/kasm.invixiom.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl; server_name lifeos-api.invixiom.com; ssl_certificate /etc/letsencrypt/live/kasm.invixiom.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/kasm.invixiom.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } server { listen 443 ssl; server_name code.invixiom.com; ssl_certificate /etc/letsencrypt/live/kasm.invixiom.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/kasm.invixiom.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8081; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; } } EOF ----------------------------------- Enable it and start Nginx: ----------------------------------- ln -s /etc/nginx/sites-available/invixiom /etc/nginx/sites-enabled/ rm -f /etc/nginx/sites-enabled/default nginx -t systemctl start nginx ----------------------------------- Now fix the Docker stack - Nextcloud needs to expose port 8080 to the host so Nginx can reach it. Update the docker-compose.yml: ----------------------------------- cd /home/michael/stack cat > /home/michael/stack/docker-compose.yml << 'EOF' networks: web: external: false volumes: nextcloud_data: postgres_data: redis_data: services: postgres: image: postgres:16-alpine container_name: postgres restart: unless-stopped environment: POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss POSTGRES_DB: lifeos volumes: - postgres_data:/var/lib/postgresql/data networks: [web] redis: image: redis:7-alpine container_name: redis restart: unless-stopped networks: [web] nextcloud: image: nextcloud:27-apache container_name: nextcloud restart: unless-stopped ports: - "8080:80" environment: NEXTCLOUD_ADMIN_USER: michael NEXTCLOUD_ADMIN_PASSWORD: Ch4ng3Th1sP4ss NEXTCLOUD_TRUSTED_DOMAINS: files.invixiom.com POSTGRES_HOST: postgres POSTGRES_DB: nextcloud POSTGRES_USER: michael POSTGRES_PASSWORD: Ch4ng3Th1sP4ss volumes: - nextcloud_data:/var/www/html depends_on: [postgres] networks: [web] fastapi: build: ./api container_name: fastapi restart: unless-stopped ports: - "8000:8000" environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] celery: build: ./api container_name: celery restart: unless-stopped command: celery -A app.celery worker --loglevel=info environment: DATABASE_URL: postgresql://michael:Ch4ng3Th1sP4ss@postgres:5432/lifeos REDIS_URL: redis://redis:6379 depends_on: [postgres, redis] networks: [web] EOF docker compose up -d cd /home/michael/stack docker stop caddy docker rm caddy docker compose up -d systemctl restart nginx ss -tlnp | grep -E '80|8080|8000|443|8443' tail -f /opt/kasm/1.18.0/log/nginx/access_json.log & tail -f /var/log/nginx/error.log & https://claude.ai/chat/6b61f9c2-89a4-4c08-97ed-f3ee30a9f4cb sed -i '/proxy_send_timeout 1800s;/a\ proxy_buffering off;' /etc/nginx/sites-available/invixiom nginx -t && systemctl reload nginx grep -r "kasm" /etc/nginx/ 2>/dev/null ls /etc/nginx/sites-enabled/ ls /etc/nginx/conf.d/ nginx -T 2>&1 | grep -A 30 "kasm" docker exec -it $(docker ps --filter name=kasm_api -q) cat /opt/kasm/current/conf/app/api.app.config.yaml 2>/dev/null || \ docker exec -it $(docker ps --format '{{.Names}}' | grep api | head -1) env | grep -i upstream docker ps --format "table {{.Names}}\t{{.Ports}}" Zone Name: default Allow Origin Domain: kasm.invixiom.com Upstream Auth Address: kasm.invixiom.com Proxy Hostname: kasm.invixiom.com Proxy Path: desktop Proxy Port: 443 docker exec kasm_proxy cat /etc/nginx/conf.d/upstream_proxy.conf docker exec kasm_proxy cat /etc/nginx/conf.d/upstream_proxy.conf docker exec kasm_api cat /opt/kasm/current/conf/app/api.app.config.yaml | grep -A 20 "zone" docker exec kasm_proxy find / -name "*.conf" -path "*/nginx/*" 2>/dev/null docker exec kasm_api find /opt -name "*.yaml" -o -name "*.yml" 2>/dev/null | head -20 Kasm Admin: admin@kasm.local / UCTOQDZiUhN8U Kasm User: user@kasm.local / 8QVbpuz9YDpzy DB Password: A7lZYaiCjO0z76fNVxTY Manager Token: 0RQKXaG8u0ylmBkX2bfJ Service Token: bn4S7ykHMR18skYQASVA nano /etc/nginx/sites-available/invixiom ``` In the kasm.invixiom.com server block, add this line inside the `location /` block: ``` proxy_cookie_path /api /; docker logs kasm_api --since 10s 2>&1 | tail -20 docker logs kasm_proxy --since 10s 2>&1 | grep "desktop\|403"