@yunuseon/kayra 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/ansible/.ansible-lint +13 -0
- package/ansible/ansible.cfg +12 -0
- package/ansible/playbooks/adopt.yml +35 -0
- package/ansible/playbooks/app.yml +21 -0
- package/ansible/playbooks/apps-prune.yml +30 -0
- package/ansible/playbooks/edge.yml +41 -0
- package/ansible/playbooks/nas.yml +47 -0
- package/ansible/playbooks/platform.yml +62 -0
- package/ansible/requirements.yml +7 -0
- package/ansible/roles/app/defaults/main.yml +9 -0
- package/ansible/roles/app/tasks/main.yml +107 -0
- package/ansible/roles/app/templates/app.env.j2 +9 -0
- package/ansible/roles/app/templates/casc-app.yaml.j2 +68 -0
- package/ansible/roles/backup-check/files/backup-check.service +9 -0
- package/ansible/roles/backup-check/files/backup-check.timer +10 -0
- package/ansible/roles/backup-check/tasks/main.yml +59 -0
- package/ansible/roles/backup-check/templates/backup-check.sh.j2 +117 -0
- package/ansible/roles/common/handlers/main.yml +16 -0
- package/ansible/roles/common/tasks/main.yml +100 -0
- package/ansible/roles/compose-stacks/tasks/main.yml +74 -0
- package/ansible/roles/docker/defaults/main.yml +3 -0
- package/ansible/roles/docker/files/buildkit-rootless +23 -0
- package/ansible/roles/docker/handlers/main.yml +12 -0
- package/ansible/roles/docker/tasks/main.yml +82 -0
- package/ansible/roles/edge-proxy/files/docker-compose.yml +13 -0
- package/ansible/roles/edge-proxy/handlers/main.yml +6 -0
- package/ansible/roles/edge-proxy/tasks/main.yml +34 -0
- package/ansible/roles/edge-proxy/templates/Caddyfile.j2 +25 -0
- package/ansible/roles/forgejo-config/tasks/main.yml +292 -0
- package/ansible/roles/platform/defaults/main.yml +76 -0
- package/ansible/roles/platform/tasks/main.yml +298 -0
- package/ansible/roles/platform/templates/authelia-users.yml.j2 +17 -0
- package/ansible/roles/platform/templates/platform.env.j2 +41 -0
- package/ansible/roles/restic-backup/files/docker-compose.postgres.yml +27 -0
- package/ansible/roles/restic-backup/files/docker-compose.yml +12 -0
- package/ansible/roles/restic-backup/handlers/main.yml +7 -0
- package/ansible/roles/restic-backup/tasks/main.yml +121 -0
- package/ansible/roles/restic-backup/templates/backup.env.j2 +9 -0
- package/ansible/roles/restic-backup/templates/backup.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/docker-compose.override.yml.j2 +26 -0
- package/ansible/roles/restic-backup/templates/follow-restore-test.sh.j2 +28 -0
- package/ansible/roles/restic-backup/templates/restore-test.sh.j2 +51 -0
- package/ansible/roles/restic-backup/templates/root.crontab.j2 +6 -0
- package/ansible/roles/restic-backup/templates/ssh_config.j2 +12 -0
- package/ansible/roles/wireguard/handlers/main.yml +9 -0
- package/ansible/roles/wireguard/tasks/main.yml +33 -0
- package/ansible/roles/wireguard/templates/wg0.conf.j2 +12 -0
- package/bin/kayra.js +29 -0
- package/cli/dist/brand.js +41 -0
- package/cli/dist/cli.js +21 -0
- package/cli/dist/commands/doctor.js +79 -0
- package/cli/dist/commands/server.js +215 -0
- package/cli/dist/commands/status.js +43 -0
- package/cli/dist/fleet.js +134 -0
- package/cli/dist/interpret.js +98 -0
- package/cli/dist/load.js +49 -0
- package/cli/dist/main.js +94 -0
- package/cli/dist/plan.js +31 -0
- package/cli/dist/result.js +5 -0
- package/cloud-init.yml.tpl +14 -0
- package/compose/authelia/configuration.yml +138 -0
- package/compose/buildkit/buildkitd.toml +14 -0
- package/compose/docker-compose.acme-dns.yml +12 -0
- package/compose/docker-compose.ci.yml +156 -0
- package/compose/docker-compose.yml +83 -0
- package/compose/jenkins/Dockerfile +40 -0
- package/compose/jenkins/casc/00-platform.yaml +158 -0
- package/compose/jenkins/plugins.txt +97 -0
- package/compose/jenkins-agent/Dockerfile +24 -0
- package/compose/traefik/dynamic/authelia.yml +18 -0
- package/compose/traefik/dynamic/middlewares.yml +14 -0
- package/compose/traefik/dynamic/ratelimit.yml +21 -0
- package/compose/traefik/dynamic/tls.yml +9 -0
- package/compose/traefik/traefik.yml +46 -0
- package/hosts.conf +6 -0
- package/package.json +73 -4
- package/scripts/local/app-manifests.sh +26 -0
- package/scripts/local/authelia-dev-init.sh +58 -0
- package/scripts/local/certs.sh +55 -0
- package/scripts/local/home-image.sh +55 -0
- package/scripts/local/home-reach.sh +41 -0
- package/scripts/local/require-main.sh +38 -0
- package/scripts/local/setup-hosts.sh +30 -0
- package/scripts/server/app-purge +26 -0
- package/scripts/server/app-remove +30 -0
- package/scripts/server/jenkins-api +28 -0
- package/scripts/server/jenkins-app-apply +36 -0
- package/scripts/server/jenkins-reload +12 -0
- package/terraform/modules/hcloud-firewall/main.tf +54 -0
- package/terraform/modules/hcloud-firewall/outputs.tf +9 -0
- package/terraform/modules/hcloud-firewall/variables.tf +22 -0
- package/terraform/modules/hcloud-server/main.tf +62 -0
- package/terraform/modules/hcloud-server/outputs.tf +19 -0
- package/terraform/modules/hcloud-server/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/hcloud-server/variables.tf +50 -0
- package/terraform/modules/multipass-instance/main.tf +67 -0
- package/terraform/modules/multipass-instance/outputs.tf +19 -0
- package/terraform/modules/multipass-instance/templates/inventory.ini.tpl +5 -0
- package/terraform/modules/multipass-instance/variables.tf +39 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# app-remove <app> — take an app off this server, keeping its data.
|
|
4
|
+
#
|
|
5
|
+
# Stops its containers and removes everything the platform set up for it:
|
|
6
|
+
# JCasC fragment, secret files, .env, the Jenkins folder with its jobs and
|
|
7
|
+
# history, the release records (so Rollback no longer offers it). Its images
|
|
8
|
+
# are collected by the next registry GC, since the app is no longer registered.
|
|
9
|
+
#
|
|
10
|
+
# Volumes stay: dropping a line from server.yml must never silently destroy a
|
|
11
|
+
# database. `app-purge <app>` deletes them deliberately.
|
|
12
|
+
#
|
|
13
|
+
# Run by `kayra apps` for every registered app missing from server.yml.
|
|
14
|
+
# Installed by the platform role at <platform_dir>/bin. Run as root.
|
|
15
|
+
# =============================================================================
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
app="${1:?Usage: app-remove <app>}"
|
|
19
|
+
platform_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
20
|
+
apps_dir="$(sed -n 's/^APPS_DIR=//p' "${platform_dir}/.env")"
|
|
21
|
+
|
|
22
|
+
docker compose -p "$app" down --remove-orphans
|
|
23
|
+
rm -rf "${platform_dir}/jenkins/casc/10-${app}.yaml" \
|
|
24
|
+
"${platform_dir}/jenkins-secrets/${app}" \
|
|
25
|
+
"${apps_dir:?}/${app}"
|
|
26
|
+
# Without its fragment the reload drops the app's credentials.
|
|
27
|
+
"${platform_dir}/bin/jenkins-reload"
|
|
28
|
+
"${platform_dir}/bin/jenkins-api" POST "/job/${app}/doDelete" -o /dev/null || true
|
|
29
|
+
(cd "$platform_dir" && docker compose exec -T jenkins-controller rm -rf "/var/jenkins_home/releases/${app}")
|
|
30
|
+
echo "Removed ${app}. Its volumes are kept — app-purge ${app} deletes them."
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# jenkins-api <METHOD> <path> [curl args…] — one authenticated call to the
|
|
4
|
+
# Jenkins API, from inside the controller (admin credentials from its env,
|
|
5
|
+
# CSRF crumb fetched in the same session). Prints the response body.
|
|
6
|
+
#
|
|
7
|
+
# jenkins-api GET '/api/json?tree=jobs[name]'
|
|
8
|
+
# jenkins-api POST /configuration-as-code/reload
|
|
9
|
+
#
|
|
10
|
+
# Installed by the platform role at <platform_dir>/bin.
|
|
11
|
+
# =============================================================================
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
method="${1:?Usage: jenkins-api <METHOD> <path> [curl args…]}"
|
|
15
|
+
path="${2:?}"
|
|
16
|
+
shift 2
|
|
17
|
+
|
|
18
|
+
cd "$(dirname "$0")/.."
|
|
19
|
+
docker compose exec -T jenkins-controller sh -ec '
|
|
20
|
+
jar=$(mktemp)
|
|
21
|
+
trap "rm -f $jar" EXIT
|
|
22
|
+
auth="${JENKINS_ADMIN_USER}:${JENKINS_ADMIN_PASSWORD}"
|
|
23
|
+
base=http://127.0.0.1:8080
|
|
24
|
+
crumb=$(curl -sf -c "$jar" -u "$auth" \
|
|
25
|
+
"$base/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)")
|
|
26
|
+
method="$1"; path="$2"; shift 2
|
|
27
|
+
curl -sf -b "$jar" -u "$auth" -H "$crumb" -X "$method" "$base$path" "$@"
|
|
28
|
+
' sh "$method" "$path" "$@"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# jenkins-app-apply <app> — the one way an app registers with Jenkins.
|
|
4
|
+
#
|
|
5
|
+
# Reads the app's secrets as KEY=VALUE lines on stdin, writes each to
|
|
6
|
+
# jenkins-secrets/<app>/<KEY> (where its 10-<app>.yaml reads them via
|
|
7
|
+
# ${readFile:/run/app-secrets/<app>/<KEY>}), then runs jenkins-reload.
|
|
8
|
+
#
|
|
9
|
+
# Installed by the platform role at <platform_dir>/bin. Run as root.
|
|
10
|
+
# =============================================================================
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
app="${1:?Usage: jenkins-app-apply <app> < secrets.env}"
|
|
14
|
+
platform_dir="$(cd "$(dirname "$0")/.." && pwd)"
|
|
15
|
+
secrets_dir="${platform_dir}/jenkins-secrets/${app}"
|
|
16
|
+
|
|
17
|
+
# Staged next to the target and swapped in whole, so keys the app no longer
|
|
18
|
+
# declares disappear instead of lingering.
|
|
19
|
+
staging="${secrets_dir}.new"
|
|
20
|
+
rm -rf "$staging"
|
|
21
|
+
mkdir -p "$staging"
|
|
22
|
+
trap 'rm -rf "$staging"' EXIT
|
|
23
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
24
|
+
case "$line" in ''|'#'*) continue ;; esac
|
|
25
|
+
printf '%s' "${line#*=}" > "${staging}/${line%%=*}"
|
|
26
|
+
done
|
|
27
|
+
rm -rf "$secrets_dir"
|
|
28
|
+
mv "$staging" "$secrets_dir"
|
|
29
|
+
trap - EXIT
|
|
30
|
+
# The controller runs as uid 1000 and reads these through a read-only mount.
|
|
31
|
+
chown -R 1000:1000 "$secrets_dir"
|
|
32
|
+
find "$secrets_dir" -type f -exec chmod 0400 {} +
|
|
33
|
+
chmod 0500 "$secrets_dir"
|
|
34
|
+
|
|
35
|
+
"${platform_dir}/bin/jenkins-reload"
|
|
36
|
+
echo "Applied Jenkins config for ${app}."
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# jenkins-reload — re-apply every JCasC fragment in jenkins/casc/ in place.
|
|
4
|
+
#
|
|
5
|
+
# Reload, not restart: Jenkins is shared, and recreating the controller for one
|
|
6
|
+
# config change would kill every app's running builds.
|
|
7
|
+
#
|
|
8
|
+
# Installed by the platform role at <platform_dir>/bin.
|
|
9
|
+
# =============================================================================
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
"$(dirname "$0")/jenkins-api" POST /configuration-as-code/reload -o /dev/null
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Hetzner Cloud Firewall Module
|
|
3
|
+
# Creates a firewall with standard web server rules.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
|
|
6
|
+
terraform {
|
|
7
|
+
required_providers {
|
|
8
|
+
hcloud = {
|
|
9
|
+
source = "hetznercloud/hcloud"
|
|
10
|
+
version = "~> 1.45"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
resource "hcloud_firewall" "this" {
|
|
16
|
+
name = var.name
|
|
17
|
+
|
|
18
|
+
# SSH
|
|
19
|
+
rule {
|
|
20
|
+
direction = "in"
|
|
21
|
+
protocol = "tcp"
|
|
22
|
+
port = "22"
|
|
23
|
+
source_ips = var.ssh_allowed_ips
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# HTTP
|
|
27
|
+
rule {
|
|
28
|
+
direction = "in"
|
|
29
|
+
protocol = "tcp"
|
|
30
|
+
port = "80"
|
|
31
|
+
source_ips = ["0.0.0.0/0", "::/0"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# HTTPS
|
|
35
|
+
rule {
|
|
36
|
+
direction = "in"
|
|
37
|
+
protocol = "tcp"
|
|
38
|
+
port = "443"
|
|
39
|
+
source_ips = ["0.0.0.0/0", "::/0"]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Anything else the server's kind needs open to the world (e.g. WireGuard).
|
|
43
|
+
dynamic "rule" {
|
|
44
|
+
for_each = var.extra_rules
|
|
45
|
+
content {
|
|
46
|
+
direction = "in"
|
|
47
|
+
protocol = rule.value.protocol
|
|
48
|
+
port = rule.value.port
|
|
49
|
+
source_ips = ["0.0.0.0/0", "::/0"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
labels = var.labels
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
variable "name" {
|
|
2
|
+
description = "Firewall name"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "ssh_allowed_ips" {
|
|
7
|
+
description = "CIDR ranges allowed to SSH (default: anywhere)"
|
|
8
|
+
type = list(string)
|
|
9
|
+
default = ["0.0.0.0/0", "::/0"]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "extra_rules" {
|
|
13
|
+
description = "Additional inbound rules open to everyone, e.g. [{protocol = \"udp\", port = \"51820\"}]"
|
|
14
|
+
type = list(object({ protocol = string, port = string }))
|
|
15
|
+
default = []
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
variable "labels" {
|
|
19
|
+
description = "Labels to attach to the firewall"
|
|
20
|
+
type = map(string)
|
|
21
|
+
default = {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Hetzner Cloud Server Module
|
|
3
|
+
# Provisions a VPS with SSH key and cloud-init.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
|
|
6
|
+
terraform {
|
|
7
|
+
required_providers {
|
|
8
|
+
hcloud = {
|
|
9
|
+
source = "hetznercloud/hcloud"
|
|
10
|
+
version = "~> 1.45"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# SSH key
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
resource "hcloud_ssh_key" "deploy" {
|
|
19
|
+
name = "${var.name}-deploy"
|
|
20
|
+
public_key = var.ssh_public_key
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Server
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
resource "hcloud_server" "this" {
|
|
27
|
+
name = var.name
|
|
28
|
+
server_type = var.server_type
|
|
29
|
+
image = var.image
|
|
30
|
+
location = var.location
|
|
31
|
+
ssh_keys = [hcloud_ssh_key.deploy.id]
|
|
32
|
+
|
|
33
|
+
firewall_ids = var.firewall_ids
|
|
34
|
+
|
|
35
|
+
user_data = templatefile("${path.module}/../../../cloud-init.yml.tpl", {
|
|
36
|
+
hostname = var.name
|
|
37
|
+
ssh_pubkey = var.ssh_public_key
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
labels = var.labels
|
|
41
|
+
|
|
42
|
+
# cloud-init runs once, at creation, and Hetzner replaces the server for any
|
|
43
|
+
# change to these. Editing the template (or adopting a server that was never
|
|
44
|
+
# created by us) must not destroy it; Ansible converges existing servers.
|
|
45
|
+
lifecycle {
|
|
46
|
+
ignore_changes = [user_data, ssh_keys, image]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# Ansible inventory file
|
|
52
|
+
# ---------------------------------------------------------------------------
|
|
53
|
+
resource "local_file" "ansible_inventory" {
|
|
54
|
+
content = templatefile("${path.module}/templates/inventory.ini.tpl", {
|
|
55
|
+
hostname = var.name
|
|
56
|
+
ssh_host = hcloud_server.this.ipv4_address
|
|
57
|
+
ssh_port = 22
|
|
58
|
+
ssh_user = "deploy"
|
|
59
|
+
ssh_key_path = var.ssh_private_key_path
|
|
60
|
+
})
|
|
61
|
+
filename = var.inventory_path
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
output "ipv4_address" {
|
|
2
|
+
description = "Public IPv4 address of the server"
|
|
3
|
+
value = hcloud_server.this.ipv4_address
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "server_id" {
|
|
7
|
+
description = "Hetzner server ID"
|
|
8
|
+
value = hcloud_server.this.id
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "server_name" {
|
|
12
|
+
description = "Server name"
|
|
13
|
+
value = hcloud_server.this.name
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "inventory_path" {
|
|
17
|
+
description = "Path to the generated Ansible inventory"
|
|
18
|
+
value = local_file.ansible_inventory.filename
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
variable "name" {
|
|
2
|
+
description = "Server name"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "server_type" {
|
|
7
|
+
description = "Hetzner server type (e.g., cx22, cx32)"
|
|
8
|
+
type = string
|
|
9
|
+
default = "cx22"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "image" {
|
|
13
|
+
description = "OS image"
|
|
14
|
+
type = string
|
|
15
|
+
default = "ubuntu-24.04"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
variable "location" {
|
|
19
|
+
description = "Hetzner datacenter location"
|
|
20
|
+
type = string
|
|
21
|
+
default = "nbg1"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
variable "ssh_public_key" {
|
|
25
|
+
description = "SSH public key for the deploy user"
|
|
26
|
+
type = string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
variable "ssh_private_key_path" {
|
|
30
|
+
description = "Path to the SSH private key (for inventory generation)"
|
|
31
|
+
type = string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
variable "firewall_ids" {
|
|
35
|
+
description = "List of firewall IDs to attach"
|
|
36
|
+
type = list(number)
|
|
37
|
+
default = []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
variable "labels" {
|
|
41
|
+
description = "Labels to attach to the server"
|
|
42
|
+
type = map(string)
|
|
43
|
+
default = {}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
variable "inventory_path" {
|
|
47
|
+
description = "Path to write the Ansible inventory file"
|
|
48
|
+
type = string
|
|
49
|
+
default = "./inventory.ini"
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Multipass Instance Module
|
|
3
|
+
# Provisions a local VM via Multipass with the same cloud-init as production.
|
|
4
|
+
# Generates a dedicated, disposable SSH keypair for the instance — unlike
|
|
5
|
+
# the hcloud-server module, which expects the operator's own persistent key.
|
|
6
|
+
# =============================================================================
|
|
7
|
+
|
|
8
|
+
terraform {
|
|
9
|
+
required_providers {
|
|
10
|
+
multipass = {
|
|
11
|
+
source = "todoroff/multipass"
|
|
12
|
+
}
|
|
13
|
+
tls = {
|
|
14
|
+
source = "hashicorp/tls"
|
|
15
|
+
}
|
|
16
|
+
local = {
|
|
17
|
+
source = "hashicorp/local"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
resource "tls_private_key" "deploy" {
|
|
23
|
+
algorithm = "ED25519"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
resource "local_sensitive_file" "private_key" {
|
|
27
|
+
content = tls_private_key.deploy.private_key_openssh
|
|
28
|
+
filename = "${var.ssh_key_dir}/${var.name}_ed25519"
|
|
29
|
+
file_permission = "0600"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
resource "local_file" "public_key" {
|
|
33
|
+
content = tls_private_key.deploy.public_key_openssh
|
|
34
|
+
filename = "${var.ssh_key_dir}/${var.name}_ed25519.pub"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Instance
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
resource "multipass_instance" "this" {
|
|
41
|
+
name = var.name
|
|
42
|
+
image = var.image
|
|
43
|
+
cpus = var.cpus
|
|
44
|
+
memory = var.memory
|
|
45
|
+
disk = var.disk
|
|
46
|
+
|
|
47
|
+
cloud_init = templatefile("${path.module}/../../../cloud-init.yml.tpl", {
|
|
48
|
+
hostname = var.name
|
|
49
|
+
ssh_pubkey = tls_private_key.deploy.public_key_openssh
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
wait_for_cloud_init = true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Ansible inventory file
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
resource "local_file" "ansible_inventory" {
|
|
59
|
+
content = templatefile("${path.module}/templates/inventory.ini.tpl", {
|
|
60
|
+
hostname = var.name
|
|
61
|
+
ssh_host = multipass_instance.this.ipv4[0]
|
|
62
|
+
ssh_port = 22
|
|
63
|
+
ssh_user = "deploy"
|
|
64
|
+
ssh_key_path = local_sensitive_file.private_key.filename
|
|
65
|
+
})
|
|
66
|
+
filename = var.inventory_path
|
|
67
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
output "ipv4_address" {
|
|
2
|
+
description = "Primary IPv4 address of the instance"
|
|
3
|
+
value = multipass_instance.this.ipv4[0]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "instance_name" {
|
|
7
|
+
description = "Multipass instance name"
|
|
8
|
+
value = multipass_instance.this.name
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "inventory_path" {
|
|
12
|
+
description = "Path to the generated Ansible inventory"
|
|
13
|
+
value = local_file.ansible_inventory.filename
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "ssh_private_key_path" {
|
|
17
|
+
description = "Path to the generated SSH private key"
|
|
18
|
+
value = local_sensitive_file.private_key.filename
|
|
19
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
variable "name" {
|
|
2
|
+
description = "Instance name"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "image" {
|
|
7
|
+
description = "Multipass image alias"
|
|
8
|
+
type = string
|
|
9
|
+
default = "24.04"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
variable "cpus" {
|
|
13
|
+
description = "Virtual CPU count"
|
|
14
|
+
type = number
|
|
15
|
+
default = 2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
variable "memory" {
|
|
19
|
+
description = "Memory size (e.g. 4G)"
|
|
20
|
+
type = string
|
|
21
|
+
default = "2G"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
variable "disk" {
|
|
25
|
+
description = "Disk size (e.g. 20G)"
|
|
26
|
+
type = string
|
|
27
|
+
default = "10G"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
variable "ssh_key_dir" {
|
|
31
|
+
description = "Directory to write the generated SSH keypair into"
|
|
32
|
+
type = string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
variable "inventory_path" {
|
|
36
|
+
description = "Path to write the Ansible inventory file"
|
|
37
|
+
type = string
|
|
38
|
+
default = "./inventory.ini"
|
|
39
|
+
}
|