#!/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 secure passphrase passphrase = generate_secure_passphrase() # Generate cloud-init config for this specific server cloud_init = { 'hostname': hostname, 'timezone': 'UTC', 'users': config.get('users', []), 'package_update': True, 'package_upgrade': True, 'write_files': [ { 'path': '/root/luks-passphrase.txt', 'content': passphrase, 'permissions': '0600' } ], 'bootcmd': [ # Set passphrase before any LUKS operations f'export LUKS_PASSPHRASE="{passphrase}"' ] } # 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 passphrase for user to save print(f"\nIMPORTANT: Save this LUKS passphrase for {hostname}:") print(f"{passphrase}\n") print("This passphrase will be needed if TPM+Tang unlock fails.") print("It is also saved in /root/luks-passphrase.txt on the server.") 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()