nullpoint/deploy.py

169 lines
5.5 KiB
Python

#!/usr/bin/env python3
import os
import sys
import yaml
import subprocess
import json
import shutil
from datetime import datetime
import random
import tempfile
import secrets
import string
# List of adjectives for hostname generation
ADJECTIVES = [
# Scientific/Sci-fi
'quantum', 'atomic', 'plasma', 'fusion', 'ionic', 'magnetic', 'cosmic',
'stellar', 'nebular', 'pulsar', 'quasar', 'warp', 'phaser', 'hyper',
'temporal', 'spatial', 'dimensional', 'subspace', 'transwarp',
# Cool/Interesting
'abysmal', 'adamant', 'aerial', 'arcane', 'astral', 'azure', 'celestial',
'crimson', 'cryptic', 'crystalline', 'dormant', 'eerie', 'eldritch',
'ethereal', 'fractal', 'frozen', 'ghostly', 'gilded', 'singular',
'hollow', 'infernal', 'lunar', 'mystic', 'nebulous', 'obsidian',
'occult', 'prismatic', 'radiant', 'shadow', 'solar', 'spectral',
'stellar', 'sublime', 'titanic', 'twilight', 'void', 'volcanic'
]
NOUNS = [
# Star Trek
'enterprise', 'voyager', 'galaxy', 'intrepid', 'nova', 'warbird', 'falcon',
'aegis', 'nemesis', 'equinox', 'stargazer', 'challenger', 'discovery',
'prometheus', 'odyssey', 'daedalus', 'apollo', 'korolev', 'phoenix', 'orion',
# Space
'nebula', 'pulsar', 'quasar', 'nova', 'supernova', 'blackhole',
'wormhole', 'singularity', 'galaxy', 'void', 'rift', 'nexus',
# Cool Concepts
'abyss', 'aether', 'anomaly', 'artifact', 'beacon', 'cipher', 'crystal',
'echo', 'enigma', 'essence', 'fractal', 'horizon', 'infinity',
'paradox', 'phoenix', 'prism', 'spectrum', 'tesseract',
'vector', 'vertex', 'vortex', 'zenith'
]
def load_config(config_file):
"""Load and parse YAML config file."""
with open(config_file, 'r') as f:
return yaml.safe_load(f)
def check_prerequisites():
"""Check if required tools are installed."""
required_tools = ['hcloud', 'jq']
for tool in required_tools:
if not shutil.which(tool):
print(f"Error: {tool} not found. Please install it first.")
sys.exit(1)
def check_hetzner_token():
"""Check if HCLOUD_TOKEN is set."""
if not os.environ.get('HCLOUD_TOKEN'):
print("Error: HCLOUD_TOKEN environment variable not set")
sys.exit(1)
def generate_hostname():
"""Generate a unique hostname in adjective-noun-date-time format."""
adjective = random.choice(ADJECTIVES)
noun = random.choice(NOUNS)
timestamp = datetime.now().strftime('%y%m%d-%H%M')
return f"{adjective}-{noun}-{timestamp}"
def get_image_id():
"""Get the base image ID."""
cmd = [
'hcloud', 'image', 'list',
'--type=snapshot',
'--selector=name=fedora-coreos-nullpoint',
'--output', 'json'
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
images = json.loads(result.stdout)
if not images:
print("Error: Base image not found. Run build.py first.")
sys.exit(1)
return images[0]['id']
def generate_secure_passphrase(length=32):
"""Generate a secure random passphrase."""
alphabet = string.ascii_letters + string.digits + string.punctuation
return ''.join(secrets.choice(alphabet) for _ in range(length))
def create_server(config, hostname, image_id):
"""Create a new server."""
hetzner = config['hetzner']
# Generate cloud-init config for this specific server
cloud_init = {
'hostname': hostname,
'timezone': 'UTC',
'users': [
{
'name': 'admin',
'groups': ['wheel'],
'sudo': 'ALL=(ALL) NOPASSWD:ALL',
'ssh_authorized_keys': [config['admin_ssh_key']]
}
],
'package_update': True,
'package_upgrade': True
}
# Create temporary cloud-init config
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as f:
yaml.dump(cloud_init, f)
f.flush()
cmd = [
'hcloud', 'server', 'create',
'--name', hostname,
'--type', hetzner['server_type'],
'--datacenter', hetzner['datacenter'],
'--image', str(image_id),
'--ssh-key', hetzner['ssh_key_name'],
'--user-data-from-file', f.name
]
subprocess.run(cmd, check=True)
print(f"\nServer '{hostname}' created successfully!")
print("The LUKS passphrase has been saved to /root/luks-passphrase.txt on the server.")
print("Please save this passphrase securely - it will be needed if TPM+Tang unlock fails.")
print("\nYou can connect using: ssh admin@<server-ip>")
def get_server_ip(hostname):
"""Get the server's IP address."""
cmd = ['hcloud', 'server', 'ip', hostname]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return result.stdout.strip()
def main():
"""Main entry point."""
# Load config
if not os.path.exists('deploy-config.yaml'):
print("Error: deploy-config.yaml not found")
sys.exit(1)
config = load_config('deploy-config.yaml')
# Check prerequisites
check_prerequisites()
check_hetzner_token()
# Generate hostname
hostname = generate_hostname()
# Get image ID
image_id = get_image_id()
# Create server
print(f"Creating server '{hostname}'...")
create_server(config, hostname, image_id)
# Get server IP
server_ip = get_server_ip(hostname)
print(f"Server created! IP: {server_ip}")
print(f"You can connect using: ssh core@{server_ip}")
if __name__ == '__main__':
main()