$npx -y skills add popmechanic/VibesOS --skill deployDeploy Julian to an exe.xyz VM (new instance or update existing)
| 1 | # Deploy Julian |
| 2 | |
| 3 | Deploy Julian to an exe.xyz VM. Two paths: **provision** a new VM or **update** an existing one. The instance registry at `deploy/instances.json` tracks which VMs have been provisioned. |
| 4 | |
| 5 | ## Target VM |
| 6 | |
| 7 | Determine the target VM name: |
| 8 | |
| 9 | 1. If `$ARGUMENTS` is provided, use it as the VM name (e.g., `/julian:deploy screen-test`) |
| 10 | 2. If no arguments, derive from current git branch: `julian-<branch>` (e.g., branch `screen` → `julian-screen`) |
| 11 | 3. Strip any characters not valid in hostnames (keep alphanumeric and hyphens) |
| 12 | |
| 13 | **PRODUCTION SAFETY**: If the resolved VM name is exactly `julian` (the production instance), STOP and warn the user before proceeding. Only proceed after explicit confirmation. |
| 14 | |
| 15 | ## Routing: Provision or Update? |
| 16 | |
| 17 | Read `deploy/instances.json`. If the target VM name exists in the registry, run the **Update** path. Otherwise, run the **Provision** path. |
| 18 | |
| 19 | If `deploy/instances.json` doesn't exist, create it as `{}`. |
| 20 | |
| 21 | --- |
| 22 | |
| 23 | ## Path A: Provision (New VM) |
| 24 | |
| 25 | Full first-time setup. Run all steps in order. |
| 26 | |
| 27 | ### Pre-flight |
| 28 | |
| 29 | 1. Get current git branch: `git rev-parse --abbrev-ref HEAD` |
| 30 | 2. Pull Julian's changes locally: `git pull` (stop on merge conflicts) |
| 31 | 3. Check for uncommitted changes: `git status --porcelain` (warn but don't block) |
| 32 | 4. Push to GitHub: `git push` |
| 33 | 5. Print target: VM name and URL (`https://<vmname>.exe.xyz/`) |
| 34 | |
| 35 | #### OIDC Pre-flight |
| 36 | |
| 37 | Read the local `.env` file and check for `VITE_OIDC_AUTHORITY`: |
| 38 | |
| 39 | - **If present** (HTTPS URL): Extract the value for later. Proceed. |
| 40 | - **If missing or invalid**: STOP and guide the user: |
| 41 | - Option A: Run `/vibes:connect` to set up Connect + Pocket ID end-to-end |
| 42 | - Option B: Manually add `VITE_OIDC_AUTHORITY=https://studio.exe.xyz/auth` and `VITE_OIDC_CLIENT_ID=<id>` to `.env` |
| 43 | |
| 44 | ### Step P1: Create VM |
| 45 | |
| 46 | **IMPORTANT**: All SSH commands targeting the VM must include `-o StrictHostKeyChecking=accept-new`. |
| 47 | |
| 48 | ```bash |
| 49 | ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 <vmname>.exe.xyz echo ok |
| 50 | ``` |
| 51 | |
| 52 | If unreachable, create it: |
| 53 | |
| 54 | ```bash |
| 55 | ssh exe.dev new --name=<vmname> |
| 56 | ssh exe.dev share set-public <vmname> |
| 57 | ``` |
| 58 | |
| 59 | Wait for boot (up to 90 seconds): |
| 60 | |
| 61 | ```bash |
| 62 | for i in $(seq 1 9); do |
| 63 | ssh -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 <vmname>.exe.xyz echo ok && break |
| 64 | echo "Attempt $i failed, retrying in 10s..." |
| 65 | sleep 10 |
| 66 | done |
| 67 | ``` |
| 68 | |
| 69 | ### Step P2: Install system dependencies |
| 70 | |
| 71 | ```bash |
| 72 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "curl -fsSL https://bun.sh/install | bash && sudo apt-get update -qq && sudo apt-get install -y npm inotify-tools" |
| 73 | ``` |
| 74 | |
| 75 | ### Step P3: Set up directory structure |
| 76 | |
| 77 | ```bash |
| 78 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "sudo mkdir -p /opt/julian && sudo chown exedev:exedev /opt/julian && mkdir -p /home/exedev/mailbox" |
| 79 | ``` |
| 80 | |
| 81 | ### Step P4: Generate deploy key and clone repo |
| 82 | |
| 83 | Generate an SSH key for push access: |
| 84 | |
| 85 | ```bash |
| 86 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "ssh-keygen -t ed25519 -f ~/.ssh/julian-deploy -N '' -C '<vmname>-deploy'" |
| 87 | ``` |
| 88 | |
| 89 | Configure SSH to use it for GitHub: |
| 90 | |
| 91 | ```bash |
| 92 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "mkdir -p ~/.ssh && cat >> ~/.ssh/config << 'SSHEOF' |
| 93 | Host github.com |
| 94 | IdentityFile ~/.ssh/julian-deploy |
| 95 | StrictHostKeyChecking accept-new |
| 96 | SSHEOF" |
| 97 | ``` |
| 98 | |
| 99 | Add the deploy key to GitHub with write access: |
| 100 | |
| 101 | ```bash |
| 102 | DEPLOY_KEY=$(ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "cat ~/.ssh/julian-deploy.pub") |
| 103 | gh repo deploy-key add - --repo popmechanic/Julian --title "<vmname>-deploy" --allow-write <<< "$DEPLOY_KEY" |
| 104 | ``` |
| 105 | |
| 106 | If the key title already exists, skip — it's fine. |
| 107 | |
| 108 | Clone the repo and configure git identity: |
| 109 | |
| 110 | ```bash |
| 111 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "git clone git@github.com:popmechanic/Julian.git /opt/julian" |
| 112 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "cd /opt/julian && git config user.name 'Julian' && git config user.email 'julian@exe.xyz'" |
| 113 | ``` |
| 114 | |
| 115 | ### Step P5: Install dependencies |
| 116 | |
| 117 | ```bash |
| 118 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "cd /opt/julian && /home/exedev/.bun/bin/bun install" |
| 119 | ``` |
| 120 | |
| 121 | ### Step P6: Create .env |
| 122 | |
| 123 | Use the `VITE_OIDC_AUTHORITY` and `VITE_OIDC_CLIENT_ID` from pre-flight (do NOT hardcode): |
| 124 | |
| 125 | ```bash |
| 126 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "cat > /opt/julian/.env << 'ENVEOF' |
| 127 | VITE_OIDC_AUTHORITY=<value from local .env> |
| 128 | VITE_OIDC_CLIENT_ID=<value from local .env> |
| 129 | ALLOWED_ORIGIN=https://<vmname>.exe.xyz |
| 130 | ENVEOF" |
| 131 | ``` |
| 132 | |
| 133 | ### Step P6b: Configure Claude Code settings |
| 134 | |
| 135 | Enable Agent Teams (disabled by default) so Julian can spawn and manage agent teammates: |
| 136 | |
| 137 | ```bash |
| 138 | ssh -o StrictHostKeyChecking=accept-new <vmname>.exe.xyz "mkdir -p /home/exedev/.claude && cat > /home/exedev/.claude/settings.json << 'SETTINGSEOF' |
| 139 | { |
| 140 | "env": { |
| 141 | "CLAUDE |