Michiu / marketing reference

Remote Devbox — Hetzner + Tailscale + Blink

Always-on remote development server for Quarb/Michiu projects. Work from Mac, iPad, or iPhone without the Mac needing to be awake. All compute lives on the server; your devices are just terminals.


Server facts

ItemValue
ProviderHetzner Cloud
PlanCX23 (2 vCPU, 4 GB RAM, 40 GB SSD)
Hostnameubuntu-4gb-nbg1-1
Public IP46.225.238.8
Tailscale IP100.112.135.68
Tailscale hostnameubuntu-4gb-nbg1-1.tail….ts.net
OSUbuntu 24.04
Usercasper
SSHTailscale-only (public port 22 closed)

Cost: ~€4.79/month (CX23 €3.99 + Hetzner backups €0.80). Tailscale personal = free.


Architecture

┌──────────────┐         ┌────────────────────┐
│   MacBook    │         │  iPad / iPhone      │
│   ssh/mosh   │         │  Blink Shell + Mosh │
└──────┬───────┘         └──────┬──────────────┘
       │                        │
       │      Tailscale (private mesh VPN)
       └────────────┬───────────┘
                    │
          ┌─────────▼──────────────────┐
          │  Hetzner CX23              │
          │  ubuntu-4gb-nbg1-1         │
          │                            │
          │  tmux sessions:            │
          │    michiu / quarb /        │
          │    ninja  / aimi           │
          │                            │
          │  Node 22 + pnpm            │
          │  Claude Code 2.x           │
          │  Supabase CLI              │
          │  ~/projects/               │
          │  ~/.secrets/               │
          └────────────────────────────┘
                    │
              GitHub (push/pull)
                    │
              Vercel (deploy target)

All code runs on the server. Mac/iPad/iPhone only render the terminal. Closing Blink does not kill anything — tmux keeps sessions alive indefinitely.


Current status (confirmed 2026-05-16)

  • Server provisioned and hardened
  • casper user created, SSH key auth only
  • UFW: SSH + Mosh only on Tailscale interface; public port 22 closed
  • fail2ban running
  • Tailscale authenticated (server + Mac)
  • Node 22.22.3, pnpm 11, Claude Code 2.1, Supabase CLI 2.98 installed
  • 4 tmux sessions pre-created: michiu / quarb / ninja / aimi
  • michiu-marketing + michiu-website cloned into ~/projects/michiu/
  • .env.local symlinked → ~/.secrets/env.michiu (17 env vars, chmod 600)
  • Claude Code memory copied to correct server slug (-home-casper-projects-michiu-michiu-marketing)

Daily workflow

Connect (any device)

mosh devbox               # Blink: reconnects instantly, survives sleep + roaming
# or
ssh casper@ubuntu-4gb-nbg1-1.ts.net   # if mosh not needed

Start a session

tmux attach -t michiu
cd ~/projects/michiu/michiu-marketing
git pull
claude

That's it. Claude Code loads CLAUDE.md + HANDOFF.md automatically. Pick up exactly where you left off.

End a session

Ctrl-a d       # detach from tmux — everything keeps running

Close Blink. The server stays up. No need to stop anything.


tmux reference

The server has 4 pre-created sessions. Attach to whichever project you're working on.

tmux ls                        # list all sessions + windows
tmux attach -t michiu          # attach to michiu session
tmux attach -t quarb           # attach to quarb session
tmux attach -d                 # force-detach any stale clients first

Inside tmux (prefix is Ctrl-a):

ActionKeys
DetachCtrl-a d
New windowCtrl-a c
Switch windowCtrl-a 0/1/2/3
Rename windowCtrl-a ,
Split horizontalCtrl-a |
Split verticalCtrl-a -
Navigate panesCtrl-a + arrow keys
Kill paneCtrl-a x

Blink Shell setup (iPad / iPhone)

  1. App Store → Blink Shell (~€20 one-time)

  2. Open Blink → type config

  3. Keys → New Key → generate a device-specific key (keep separate from Mac key)

  4. Add the public key to the server:

    # on server:
    echo "ssh-ed25519 AAAA... ipad" >> ~/.ssh/authorized_keys
    
  5. Hosts → New Host:

    • Alias: devbox
    • HostName: ubuntu-4gb-nbg1-1.tail….ts.net (your Tailscale hostname)
    • User: casper
    • Key: the device key you just generated
  6. Install Tailscale on the device and connect before using Blink.

Hardware keyboard tip: Remap Caps Lock → Control in iPad Settings → General → Keyboard → Hardware Keyboard. Makes tmux and vim usable.


Mosh vs SSH

Use mosh for daily work. Use ssh only for one-off quick commands from Mac.

MoshSSH
Survives iPad sleepYesNo
Survives WiFi → 4G switchYesNo
Works on spotty hotel WiFiYesNo
Reconnects automaticallyYesNo
Good for file transfers (scp)NoYes

Mosh requires UDP 60000–61000 open in UFW (already configured).


Common commands — michiu-marketing

cd ~/projects/michiu/michiu-marketing

# Development
pnpm dev                         # Next.js dev server on :3000
pnpm build                       # typecheck + production build
pnpm typecheck                   # tsc --noEmit only

# Database
supabase db push                 # apply pending migrations to live Supabase
supabase db push --include-all   # include all unapplied
supabase status                  # confirm linked project

# Testing
pnpm regress                     # review prompt regression suite
pnpm regress:dm                  # DM prompt regression suite

# Data
pnpm seed                        # seed mock data
pnpm import:google               # import Google reviews

# Inngest (local event bus)
pnpm dlx inngest-cli@latest dev  # local Inngest UI on :8288

# Claude Code
claude                           # start session (auto-loads CLAUDE.md + HANDOFF.md)
claude --continue                # continue last session without prompt

Secrets management

All secrets live in ~/.secrets/env.michiu (chmod 600), symlinked to .env.local in each repo.

# Verify symlink
ls -la ~/projects/michiu/michiu-marketing/.env.local
# → .env.local -> /home/casper/.secrets/env.michiu

# Edit a secret
nano ~/.secrets/env.michiu       # or vim

# Re-link if the symlink ever breaks
ln -sf ~/.secrets/env.michiu ~/projects/michiu/michiu-marketing/.env.local

When a secret changes (e.g. you rotate a key in Vercel):

  1. Update it in Vercel dashboard
  2. Update ~/.secrets/env.michiu on the server manually
  3. Restart the dev server if it's running (Ctrl-C then pnpm dev)

Never commit .env.local — it's gitignored and is a symlink anyway.

Current secret inventory

VariableRequiredNotes
NEXT_PUBLIC_SUPABASE_URLYesSupabase project URL
NEXT_PUBLIC_SUPABASE_ANON_KEYYesSupabase anon key
SUPABASE_SERVICE_ROLE_KEYYesServer-only
ADMIN_EMAIL_ALLOWLISTYescsprkrn@gmail.com
ANTHROPIC_API_KEYYesconsole.anthropic.com
ANTHROPIC_MODELYesclaude-sonnet-4-6
REPLY_SIGNATURE_POOLYesComma-separated staff names
FOOTBALL_DATA_API_KEYYesfootball-data.org v4
NEXT_PUBLIC_APP_URLDevhttp://localhost:3000
RESEND_API_KEYOptionalTransactional email
INNGEST_EVENT_KEY / INNGEST_SIGNING_KEYOptionalBlank = local dev mode
Meta, LinkedIn, WhatsApp, Google AdsPendingGated on platform approvals

Full spec in .env.example.


Claude Code on the server

Claude Code is installed globally (npm i -g @anthropic-ai/claude-code). Memory is project-scoped.

Memory slot: /home/casper/.claude/projects/-home-casper-projects-michiu-michiu-marketing/memory/

This is separate from the Mac slot (-Users-CasperMacbook-...). Both are now in sync — the Mac memory was copied to the server slot on 2026-05-16.

If memory ever gets out of sync (e.g. after a long gap where Mac sessions happened):

# On Mac — sync current memory to server
bash scripts/devbox/sync-to-server.sh

# On server — copy to the correct slot
NEW="-home-casper-projects-michiu-michiu-marketing"
OLD="-Users-CasperMacbook-APP-STUDIO-MICHIU-Code-michiu-marketing"
cp ~/.claude/projects/$OLD/memory/* ~/.claude/projects/$NEW/memory/

CLAUDE.md + lane files are in the repo and load automatically — no special setup needed.

Auth: Claude Code auth tokens live on the server. If they expire:

claude logout && claude login

Obsidian vault sync

The MICHIU vault lives on iCloud on the Mac. To read it on the server, mirror it to a private GitHub repo.

One-time setup (on Mac)

cd ~/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/MICHIU
git init
cat > .gitignore <<'EOF'
.obsidian/workspace.json
.obsidian/cache
.trash/
EOF
git add . && git commit -m "init: MICHIU vault"
git remote add origin git@github.com:quarbstudio/michiu-brand-vault.git
git push -u origin main

Create the repo first: gh repo create quarbstudio/michiu-brand-vault --private

Clone on server

mkdir -p ~/vaults
git clone git@github.com:quarbstudio/michiu-brand-vault.git ~/vaults/michiu

Sync workflow

Mac → server (after editing in Obsidian):

cd ~/Library/Mobile\ Documents/iCloud~md~obsidian/Documents/MICHIU
git add . && git commit -m "vault: $(date +%F)" && git push

Server (before a session that needs vault context):

cd ~/vaults/michiu && git pull

Tip: Install the Obsidian Git plugin in the vault to auto-commit + push every 10 minutes. Then git pull on the server is the only manual step.


Switching between ventures

The server has 4 tmux sessions. Use them to keep project contexts separate.

Ctrl-a d                          # detach current session
tmux attach -t quarb              # switch to Quarb projects
tmux attach -t ninja              # switch to Ninja project
tmux attach -t aimi               # switch to Aimi project

Each session can have multiple windows (Ctrl-a c) for different repos or tasks within the same venture.


Syncing secrets from Mac

When onboarding a new project or after rotating secrets, use the sync script:

# On Mac, from michiu-marketing repo root:
bash scripts/devbox/sync-to-server.sh

This copies ~/.secrets/ and Claude memory from Mac to server. Run it any time secrets change on the Mac side.


Maintenance

Weekly

sudo apt update && sudo apt upgrade -y
npm update -g @anthropic-ai/claude-code
pnpm add -g pnpm@latest

Monthly

sudo fail2ban-client status sshd    # check blocked IPs
df -h                                # disk usage (CX23 = 40 GB)
free -h                              # memory pressure

Upgrade path: If you consistently see memory pressure from multiple Claude Code sessions, upgrade to CX33 (8 GB RAM, ~€8/mo) in the Hetzner Cloud console — no data loss, live resize.

Backups

Enable in Hetzner Console → server → Backups (~€0.80/mo). Code is in GitHub. What isn't: ~/.secrets/, Claude auth tokens, tmux state. The backup covers all of these.


Troubleshooting

ProblemFix
"Connection refused" after firewall changeUse Hetzner web console (console.hetzner.cloud → server → Console) to fix UFW without SSH
Mosh hangs on connectsudo ufw allow 60000:61000/udp && sudo ufw reload
tmux session won't attachtmux attach -d -t michiu (force-detach stale client)
Claude Code auth expiredclaude logout && claude login
.env.local missingln -sf ~/.secrets/env.michiu ~/projects/michiu/michiu-marketing/.env.local
supabase not linkedsupabase login && supabase link --project-ref <ref> (ref in Supabase dashboard → Settings → General)
pnpm install fails ENOSPCpnpm store prune — or upgrade to CX33 if disk is full
nvm not found in new shellAdd to ~/.bashrc: export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
Server unreachable (Tailscale down)SSH via public IP temporarily: ssh casper@46.225.238.8 — then fix Tailscale
Tailscale not connected on deviceOpen Tailscale app, toggle off/on — or sudo tailscale up on server
git push fails (auth)ssh -T git@github.com — if it fails, re-add the server SSH key to GitHub

Security checklist

  • Root SSH login disabled (PermitRootLogin no)
  • Password auth disabled (PasswordAuthentication no)
  • UFW enabled: SSH + Mosh UDP 60000–61000 on Tailscale interface only
  • Public port 22 closed to internet
  • fail2ban running
  • Unattended upgrades enabled
  • Tailscale authenticated on server + Mac
  • Tailscale installed on iPad (required before Blink works)
  • Tailscale installed on iPhone (required before Blink works)
  • Separate SSH keys per device: Mac, iPad, iPhone
  • GitHub SSH key is devbox-specific (not reused from Mac)
  • ~/.secrets/ is chmod 700; each file chmod 600
  • .env.local is a symlink (not committed — confirmed in .gitignore)
  • Hetzner backups enabled

Quick reference cheatsheet

# ── Connect ──────────────────────────────────────────
mosh devbox                           # iPad/iPhone via Blink (recommended)
ssh casper@ubuntu-4gb-nbg1-1.ts.net  # Mac via Tailscale hostname
ssh casper@46.225.238.8              # Mac via public IP (fallback only)

# ── tmux ─────────────────────────────────────────────
tmux ls                               # list sessions
tmux attach -t michiu                 # attach to michiu
tmux attach -d                        # force-detach stale clients
Ctrl-a d                              # detach
Ctrl-a c / Ctrl-a 0/1/2/3            # new window / switch window
Ctrl-a | / Ctrl-a -                  # split horizontal / vertical
Ctrl-a ,                              # rename window

# ── Start a session ──────────────────────────────────
tmux attach -t michiu
cd ~/projects/michiu/michiu-marketing
git pull
claude

# ── michiu-marketing ─────────────────────────────────
pnpm dev                              # dev server :3000
pnpm build                            # typecheck + build
pnpm typecheck                        # typecheck only
pnpm regress                          # review prompt regression
supabase db push --include-all        # apply pending migrations
claude                                # Claude Code session

# ── Tailscale ────────────────────────────────────────
tailscale status                      # all devices + IPs
tailscale ip -4                       # this server's tailnet IP
sudo tailscale up                     # reconnect if disconnected

# ── Server health ────────────────────────────────────
htop                                  # processes + memory
df -h                                 # disk
free -h                               # RAM
sudo systemctl status fail2ban        # security daemon

# ── Vault sync ───────────────────────────────────────
cd ~/vaults/michiu && git pull        # pull latest vault from GitHub

Source: michiu-marketing/docs/platform/mobile-devbox-setup.md. To edit, change the file in the repo and redeploy.