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"