Skip to main content

create resources, deploy code in prod and dev. supports hetzner and multipass

Project description

vpseasy

Install

pip install vpseasy
pub_keys = load_pub_keys()
print(f'{len(pub_keys)} key(s) found')
if pub_keys: print(pub_keys[0][:3] + '...')
4 key(s) found
ssh...

Cloud-init

multi_init() — local Multipass VMs (no UFW). vps_init() — production (UFW, fail2ban, Docker).

_vm = 'testvm'
mi = multi_init(_vm, docker=False)   # docker=False: skip install+reboot, much faster for local testing
print(mi.yaml)
ci = vps_init('demo-prod', pub_keys)
print(ci.yaml)
#cloud-config
hostname: demo-prod
preserve_hostname: false
packages:
- curl
- fail2ban
- unattended-upgrades
package_update: true
package_upgrade: true
disable_root: true
ssh_pwauth: false
users:
- name: deploy
  groups:
  - sudo
  shell: /bin/bash
  sudo:
  - ALL=(ALL) NOPASSWD:ALL
  ssh_authorized_keys:
  - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAcfGCEzt9TJzVOBmlzU4N8LvLKQxUQQ/mIikwArFO1K karthikrajgopal.laxminaarayanan@bain.com
  - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDM9kAjDCitQvl6oMLab4yK7LgylUV9lo/M/U8uqi5r8LPbo/qia7Oaj+Qo2LSs+Hsesr/mdKNjFle5DRb91k/Ns4GX2V8QyBcKwBK0Davjmj57X+4ZfnmC4HjZ/gzyYe1hV4Vjy4IBE/JSArSP3hI8tCAgN00tNLxJ5AJnYzdgfKpVa+zI54cdrJTPhko12mEyOih62xOWeHT16Y7jGPIaOzPo2YTFM+omwEm7TfyxuRLxaDN0d/ZxlKPi/+vBcuAOItrjA6DJ7lnYwk3cXubCKgHP3uc0MeBBX74S58zpoHlAp6XdePKOoBwam1/aYm+7zq+9GJyDGV25iD9bDwSn+0oNexJFRgQxwFdIwVUk4Iyq6OocUNLX8vrI1Qr6kXIchDVS922LGf+Z5aai89wqaxLLB+U4+dNTzb6zKMtQSMdGrgFZt0N5j/aMuRE5rkfWoeiCT8DmekuxA6NDaF76CYgcIsEaOsCTuNjwrpWBQnQ82r20tfegagT3Y38xBp9PLbFHbM45HkjAsOyT6QqXh8C6XofZJa/QL25uCTHQBBxZCVtYPccs6Sjh4u7zJ3cAH1E7tWgpzwFeBrgBMLIJ0Atwbp4fCqAm1XHyaLaeuVceFk3YXbCUK8DmN0FApkzlxpeCwHQ2ZLUOF5HExYSC2IoHYbYJl8oyhwG8Bwe9hQ== karthik.rajgopal@hotmail.com
  - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICOEkEHB+WPV42E9izurjWdTrBAHpgDxK5JcdzhkmN7T karthik.rajgopal@hotmail.com
  - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKu2Oi3apJsaZmO3dubl+8ZUA7ivXJdhzILPucA8F2ag karthik.rajgopal@hotmail.com
runcmd:
- curl -fsSL https://get.docker.com | sh
- usermod -aG docker deploy
- systemctl enable --now docker
- ufw default deny incoming
- ufw default allow outgoing
- ufw logging off
- ufw allow 22/tcp
- ufw --force enable
apt:
  conf: 'APT::Periodic::Update-Package-Lists "1";

    APT::Periodic::Download-Upgradeable-Packages "1";

    APT::Periodic::AutocleanInterval "7";

    APT::Periodic::Unattended-Upgrade "0";

    Unattended-Upgrade::Automatic-Reboot "false";

    '
write_files:
- path: /etc/logrotate.d/00-cloud-init-global
  owner: root:root
  permissions: '0644'
  content: "/var/log/*.log {\n    weekly\n    rotate 7\n    compress\n    su root adm\n    create\n    missingok\n}\n"
power_state:
  mode: reboot
  message: Rebooting
  timeout: 1
  condition: true

Local testing with Multipass

Requires Multipass installed. Pass cloud_init=mi directly to mp.launch(). Use docker=True in multi_init() if your app needs Docker pre-installed (adds ~2 min for install + reboot).

mp = Multipass()
mp.rm(_vm,purge=True)   # idempotent cleanup
vm = mp.launch(_vm, image='24.04', cpus=1, memory='1G', disk='10G', cloud_init=mi)
ip = mp.ip(vm.name)
print(f'VM at {ip}, key: {vm.key}')
deploy_mp(_vm, src='./myapp')
mp.rm(_vm)
Creating testvm  Configuring testvm  Starting testvm  Waiting for initialization to complete  

launch failed: The following errors occurred:
timed out waiting for initialization to complete

CalledProcessError: Command '['multipass', 'launch', '24.04', '-n', 'testvm', '-c', '1', '-m', '1G', '-d', '10G', '--cloud-init', '-']' returned non-zero exit status 2.
---------------------------------------------------------------------------
CalledProcessError                        Traceback (most recent call last)
Cell In[6], line 4
      1 #| eval: False
      2 mp = Multipass()
      3 mp.rm(_vm,purge=True)   # idempotent cleanup
----> 4 vm = mp.launch(_vm, image='24.04', cpus=1, memory='1G', disk='10G', cloud_init=mi)
      5 ip = mp.ip(vm.name)
      6 print(f'VM at {ip}, key: {vm.key}')
      7 deploy_mp(_vm, src='./myapp')

File ~/code/personal/orgs/vpseasy/vpseasy/core.py:29, in Multipass.launch(self, name, image, cpus, memory, disk, cloud_init, mounts)
     27 for hp, vp in (mounts or {}).items(): args += ['--mount', f'{hp}:{vp}']
     28 if cloud_init: args += ['--cloud-init', '-']
---> 29 subprocess.run(args, input=cloud_init.yaml if cloud_init else None, text=True, check=True)
     30 return AttrDict(name=name, key=cloud_init.key if cloud_init else None)

File ~/Library/Application Support/uv/python/cpython-3.13.1-macos-aarch64-none/lib/python3.13/subprocess.py:577, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    575     retcode = process.poll()
    576     if check and retcode:
--> 577         raise CalledProcessError(retcode, process.args,
    578                                  output=stdout, stderr=stderr)
    579 return CompletedProcess(process.args, retcode, stdout, stderr)

CalledProcessError: Command '['multipass', 'launch', '24.04', '-n', 'testvm', '-c', '1', '-m', '1G', '-d', '10G', '--cloud-init', '-']' returned non-zero exit status 2.

Provision on Hetzner

Set HCLOUD_TOKEN in your environment. vps_init() auto-generates an SSH key pair when pub_keys=None.

hz = Hetzner()                              # reads HCLOUD_TOKEN
ci = vps_init('myapp-prod', pub_keys)
svr = hz.create('myapp-prod', cloud_init=ci, ssh_keys=hz.key_names(), location='hel1')
print(f'Provisioning at {svr.ip}')

Deploy

wait_ssh() blocks until SSH is up. deploy() rsyncs your Compose stack and brings it up.

wait_ssh(svr.ip, tout=300)
assert chk_cloud_init(svr.ip) == 'done'
assert chk_docker(svr.ip)
deploy('./myapp', svr.ip)

Install agent skill

Copies SKILL.md to .agents/skills/vpseasy/ (project-local) and ~/.claude/skills/vpseasy/ (global Claude Code).

mv_skill_md(dry_run=True)   # preview; pass dry_run=False to actually install

API reference

Symbol Description
load_pub_keys(paths=None) Read ~/.ssh/id_*.pub → list of strings
gen_key(slug, key_dir=None) Generate ed25519 pair → AttrDict(key, pub, pub_str)
multi_init(hostname, pub_keys, ...) Multipass cloud-init YAML → AttrDict(yaml, key)
vps_init(hostname, pub_keys, ...) Production cloud-init YAML → AttrDict(yaml, key)
Multipass Launch / list / exec / delete local Ubuntu VMs
deploy_mp(name, src, path, build) Sync dir + docker compose up in Multipass VM
Hetzner Create / list / delete Hetzner Cloud servers
wait_ssh(host, u, k, tout) Poll until SSH accepts connections
chk_cloud_init(host, u, k) Return cloud-init status string
chk_docker(host, u, k) Verify Docker daemon running
run_ssh(host, *cmds, ...) Run commands over SSH
sync(host, src, path, ...) Rsync local dir to remote
deploy(host, src, path, ...) sync + docker compose up -d
mv_skill_md(dry_run, dir) Install agent SKILL.md

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vpseasy-0.0.2.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vpseasy-0.0.2-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

Details for the file vpseasy-0.0.2.tar.gz.

File metadata

  • Download URL: vpseasy-0.0.2.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for vpseasy-0.0.2.tar.gz
Algorithm Hash digest
SHA256 bf35e6542af9c66816e6bdfeda9d741da1fcc3573e353e25aefb0af7ebdee464
MD5 3bdc4da7af0e1d8ea402a6afc0e45088
BLAKE2b-256 2959c4f6866b73c47c529aa1c9233f7989665d6de69970069706f94927f8b904

See more details on using hashes here.

File details

Details for the file vpseasy-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: vpseasy-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 15.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for vpseasy-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4942f9cb95b90293f9429a2160c499dd8fac4bce1ee5950243b80ae21198f70b
MD5 78a10f24f7e65d5191fb14d192e63116
BLAKE2b-256 510a2bb12f21737a73b2569a71cee4078e966eb81ed18996b6e7cd765067b5cb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page