From d21250ce666710414cc00479721bd4931dd5c514 Mon Sep 17 00:00:00 2001 From: "Harry (hjbdev)" Date: Fri, 28 Mar 2025 13:56:07 +0000 Subject: [PATCH] Hetzner Service, DTOs, provision script --- app/Data/ServerProviders/Image.php | 13 ++ app/Data/ServerProviders/Location.php | 13 ++ app/Data/ServerProviders/ServerType.php | 22 ++++ app/Models/Service.php | 6 + app/Models/Slice.php | 6 +- .../ServerProviders/HetznerService.php | 89 +++++++++++++ .../ServerProviders/ServerProviderService.php | 24 ++++ provision.sh | 121 ++++++++++++++++++ resources/js/pages/Dashboard.vue | 7 +- 9 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 app/Data/ServerProviders/Image.php create mode 100644 app/Data/ServerProviders/Location.php create mode 100644 app/Data/ServerProviders/ServerType.php create mode 100644 app/Services/ServerProviders/HetznerService.php create mode 100644 app/Services/ServerProviders/ServerProviderService.php create mode 100644 provision.sh diff --git a/app/Data/ServerProviders/Image.php b/app/Data/ServerProviders/Image.php new file mode 100644 index 0000000..78450ab --- /dev/null +++ b/app/Data/ServerProviders/Image.php @@ -0,0 +1,13 @@ +belongsTo(Server::class); } + + public function slices(): HasMany + { + return $this->hasMany(Slice::class); + } } diff --git a/app/Models/Slice.php b/app/Models/Slice.php index e92d800..89f1ac4 100644 --- a/app/Models/Slice.php +++ b/app/Models/Slice.php @@ -3,8 +3,12 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; class Slice extends Model { - // + public function service(): BelongsTo + { + return $this->belongsTo(Service::class); + } } diff --git a/app/Services/ServerProviders/HetznerService.php b/app/Services/ServerProviders/HetznerService.php new file mode 100644 index 0000000..4e1ac9d --- /dev/null +++ b/app/Services/ServerProviders/HetznerService.php @@ -0,0 +1,89 @@ +connector = new HetznerConnector; + } + + public function createServer( + string $name, + string $serverType, + string $location, + string $image, + ): bool { + return false; + } + + public function listServerTypes(): Collection + { + $response = $this->connector->send(new ListServerTypesRequest); + + if ($response->status() !== 200) { + throw new Exception('Failed to fetch server types from Hetzner'); + } + + return collect($response->json('server_types'))->where('deprecated', false)->where('architecture', 'x86')->map(function ($serverType) { + return new ServerType( + id: $serverType['id'], + name: $serverType['name'], + cores: $serverType['cores'], + memory: $serverType['memory'] * 1024, + disk: $serverType['disk'], + priceMonthly: $serverType['prices'][0]['monthly']['gross'] ?? 0, + priceHourly: $serverType['prices'][0]['hourly']['gross'] ?? 0, + ); + }); + } + + public function listLocations(): Collection + { + $response = $this->connector->send(new ListLocationsRequest); + + if ($response->status() !== 200) { + throw new Exception('Failed to fetch locations from Hetzner'); + } + + return collect($response->json('locations'))->map(function ($location) { + return new Location( + id: $location['id'], + name: $location['name'], + country: $location['country'], + city: $location['city'], + ); + }); + } + + public function listImages(): Collection + { + $response = $this->connector->send(new ListImagesRequest( + architecture: 'x86', + )); + + if ($response->status() !== 200) { + throw new Exception('Failed to fetch images from Hetzner'); + } + + return collect($response->json('images'))->where('os_flavor', 'ubuntu')->map(function ($image) { + return new Image( + id: $image['id'], + name: $image['description'], + osFlavor: $image['os_flavor'], + osVersion: $image['os_version'], + ); + }); + } +} diff --git a/app/Services/ServerProviders/ServerProviderService.php b/app/Services/ServerProviders/ServerProviderService.php new file mode 100644 index 0000000..93297b7 --- /dev/null +++ b/app/Services/ServerProviders/ServerProviderService.php @@ -0,0 +1,24 @@ +/dev/null 2>&1; do + echo "Waiting: dpkg/lock is locked..." + sleep 5 + done + while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; do + echo "Waiting: dpkg/lock-frontend is locked..." + sleep 5 + done + while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do + echo "Waiting: lists/lock is locked..." + sleep 5 + done + if [ -f /var/log/unattended-upgrades/unattended-upgrades.log ]; then + while fuser /var/log/unattended-upgrades/unattended-upgrades.log >/dev/null 2>&1; do + echo "Waiting: unattended-upgrades is locked..." + sleep 5 + done + fi +} + +apt_wait + +# Make sure we're up to date +export DEBIAN_FRONTEND=noninteractive +apt update +apt_wait +apt upgrade -y +apt_wait +apt install unzip curl fail2ban ufw -y + +# No password logins +sed -i "/PasswordAuthentication yes/d" /etc/ssh/sshd_config +echo "" | sudo tee -a /etc/ssh/sshd_config +echo "" | sudo tee -a /etc/ssh/sshd_config +echo "PasswordAuthentication no" | sudo tee -a /etc/ssh/sshd_config + +# Restart SSH +ssh-keygen -A service ssh restart + +# UTC +ln -sf /usr/share/zoneinfo/UTC /etc/localtime + +# Create The Root SSH Directory If Necessary +if [ ! -d /root/.ssh ]; then + mkdir -p /root/.ssh + touch /root/.ssh/authorized_keys +fi + +# Setup Keystone User +useradd keystone +mkdir -p /home/keystone/.ssh +mkdir -p /home/keystone/.keystone +adduser keystone sudo + +# Setup Bash For Keystone User +chsh -s /bin/bash keystone +cp /root/.profile /home/keystone/.profile +cp /root/.bashrc /home/keystone/.bashrc + +# Set The Sudo Password For Keystone +PASSWORD=$(mkpasswd [!sudo_password!]) +usermod --password $PASSWORD keystone + +# Build Formatted Keys & Copy Keys To Keystone +cat >/root/.ssh/authorized_keys </etc/apt/apt.conf.d/50unattended-upgrades </etc/apt/apt.conf.d/10periodic <
The plan:
-
Organisations have projects
-
Projects have environments
-
Environments have servers
+
Organisations have applications
+
Applications have environments
Servers have services
-
Servers can have services
Services have slices, this could be a database table or a client in a websocket server
+
Environments have SLICES
Also servers need to be provisioned
Users have SSH keys
Keystone has its own SSH keys