From fc59b4f9a5405dc5ba8aa45d1fc435e06ade0a74 Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Sat, 12 Apr 2025 06:50:12 +0200 Subject: [PATCH] feat: Initial code Signed-off-by: Julien Riou --- .gitignore | 10 ++ README.md | 73 ++++++++++- ansible/ansible.cfg | 8 ++ ansible/galene.yml | 6 + ansible/roles/certbot/tasks/main.yml | 17 +++ ansible/roles/galene/defaults/main.yml | 25 ++++ ansible/roles/galene/handlers/main.yml | 9 ++ ansible/roles/galene/meta/main.yml | 3 + ansible/roles/galene/tasks/main.yml | 116 ++++++++++++++++++ .../roles/galene/templates/galene.service.j2 | 19 +++ ansible/roles/golang/defaults/main.yml | 2 + ansible/roles/golang/tasks/main.yml | 7 ++ ansible/site.yml | 17 +++ tofu/.terraform.lock.hcl | 80 ++++++++++++ tofu/main.tf | 43 +++++++ tofu/provider.tf | 18 +++ tofu/templates/hosts.tpl | 4 + tofu/variables.tf | 22 ++++ 18 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 ansible/ansible.cfg create mode 100644 ansible/galene.yml create mode 100644 ansible/roles/certbot/tasks/main.yml create mode 100644 ansible/roles/galene/defaults/main.yml create mode 100644 ansible/roles/galene/handlers/main.yml create mode 100644 ansible/roles/galene/meta/main.yml create mode 100644 ansible/roles/galene/tasks/main.yml create mode 100644 ansible/roles/galene/templates/galene.service.j2 create mode 100644 ansible/roles/golang/defaults/main.yml create mode 100644 ansible/roles/golang/tasks/main.yml create mode 100644 ansible/site.yml create mode 100644 tofu/.terraform.lock.hcl create mode 100644 tofu/main.tf create mode 100644 tofu/provider.tf create mode 100644 tofu/templates/hosts.tpl create mode 100644 tofu/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0dbe0d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +ansible/venv +ansible/ssh_key +ansible/group_vars +tofu/clouds.yaml +tofu/ovh.conf +tofu/terraform.tfstate +tofu/terraform.tfstate.backup +tofu/*.tfvars +tofu/*.tfvars.json +tofu/.terraform diff --git a/README.md b/README.md index 59a41c5..acc00a0 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,74 @@ # galene-cloud -Run galene in the cloud using OpenTofu and Ansible \ No newline at end of file +Run galene in the cloud using OpenTofu and Ansible. + +# Requirements + +* Ansible (`ansible`) +* OpenTofu (`tofu`) +* Public Cloud project on [OVHcloud](https://www.ovhcloud.com) +* Domain zone on [OVHcloud](https://www.ovhcloud.com) + +# OpenTofu + +## Configuration + +### OpenStack provider + +> tofu/clouds.yaml + +The `clouds.yaml` file will automatically configure the openstack provider to +use your Public Cloud project. + +Go to the [OVHcloud Manager](https://www.ovh.com/manager/), then "Public Cloud" +section, then "Horizon", then "API access". In the drop down on the right, +select "OpenStack clouds.yaml File". + +### OVH provider + +> tofu/ovh.conf + +Follow the [First Steps with the OVHcloud +APIs](https://help.ovhcloud.com/csm/en-gb-api-getting-started-ovhcloud-api?id=kb_article_view&sysparm_article=KB0042784) +guide, section "Advanced usage: pair OVHcloud APIs with an application", to +generate the `ovh.conf` file. + +### Variables + +* **domain**: Name of the domain zone +* **hostname**: Name of the server (default: "galene") +* **openstack_image**: Name of the OpenStack image (default: "Debian 12") +* **openstack_flavor**: Name of the OpenStack flavor (default: "d2-2") + +Variables can be provided using files ending with `.tfvars` extension. See [the +documentation](https://opentofu.org/docs/language/values/variables/#variable-definitions-tfvars-files) +for more information. + +## Usage + +Change directory to "tofu": + +``` +cd tofu +``` + +### Start + +``` +tofu plan +tofu apply +``` + +Then follow the instructions for Ansible. + +### Stop + +``` +tofu destroy +``` + +# Ansible + +## Configuration + +## Usage diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000..82e8dbb --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,8 @@ +[defaults] +inventory = inventory/hosts +remote_user = debian +host_key_checking = False +private_key_file = ssh_key + +[ssh_connection] +pipelining = True diff --git a/ansible/galene.yml b/ansible/galene.yml new file mode 100644 index 0000000..1e06534 --- /dev/null +++ b/ansible/galene.yml @@ -0,0 +1,6 @@ +- name: Install galene + become: true + hosts: + - galene + roles: + - galene diff --git a/ansible/roles/certbot/tasks/main.yml b/ansible/roles/certbot/tasks/main.yml new file mode 100644 index 0000000..b7241ef --- /dev/null +++ b/ansible/roles/certbot/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: Check requirements + ansible.builtin.assert: + that: + - certbot_email is defined + - certbot_domain is defined + +- name: Install packages + ansible.builtin.package: + name: certbot + +- name: Request certificate + ansible.builtin.command: + cmd: >- + certbot certonly --standalone -n --agree-tos + --email {{ certbot_email }} -d {{ certbot_domain }} + creates: /etc/letsencrypt/live/{{ certbot_domain }}/fullchain.pem diff --git a/ansible/roles/galene/defaults/main.yml b/ansible/roles/galene/defaults/main.yml new file mode 100644 index 0000000..e467d44 --- /dev/null +++ b/ansible/roles/galene/defaults/main.yml @@ -0,0 +1,25 @@ +--- +galene_go_version: 1.24.1 +galene_version: galene-0.96.3 +galene_http_port: 443 +galene_turn: ":1194" +galene_user: galene +galene_group: galene +galene_base_directory: /var/lib/galene +galene_data_directory: "{{ galene_base_directory }}/data" +galene_groups_directory: "{{ galene_base_directory }}/groups" +galene_recording_directory: "{{ galene_base_directory }}/recordings" +galene_static_directory: "{{ galene_base_directory }}/static" +# galene_domain: + +# galene_config: +# canonicalHost: galene.example.org +galene_config: {} + +# galene_groups: +# example: +# users: +# bob: +# password: *** +# permissions: op +galene_groups: {} diff --git a/ansible/roles/galene/handlers/main.yml b/ansible/roles/galene/handlers/main.yml new file mode 100644 index 0000000..24198bf --- /dev/null +++ b/ansible/roles/galene/handlers/main.yml @@ -0,0 +1,9 @@ +--- +- name: Reload systemd + ansible.builtin.systemd_service: + daemon_reload: true + +- name: Restart galene + ansible.builtin.service: + name: galene + state: restarted diff --git a/ansible/roles/galene/meta/main.yml b/ansible/roles/galene/meta/main.yml new file mode 100644 index 0000000..affff51 --- /dev/null +++ b/ansible/roles/galene/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: golang diff --git a/ansible/roles/galene/tasks/main.yml b/ansible/roles/galene/tasks/main.yml new file mode 100644 index 0000000..e8570b4 --- /dev/null +++ b/ansible/roles/galene/tasks/main.yml @@ -0,0 +1,116 @@ +--- +# TODO: install in block +- name: Install requirements + ansible.builtin.package: + name: git + +- name: Clone source code + ansible.builtin.git: + repo: https://github.com/jech/galene + dest: /opt/galene + version: "{{ galene_version }}" + +- name: Compile + ansible.builtin.command: + chdir: /opt/galene + cmd: go build -ldflags='-s -w' + creates: /opt/galene/galene + environment: + CGO_ENABLED: "0" + PATH: /usr/local/go/bin + +- name: Install + ansible.builtin.copy: + remote_src: true + src: /opt/galene/galene + dest: /usr/local/bin/galene + owner: root + group: root + mode: "0755" +# TODO End of install in block + +- name: Create user + ansible.builtin.user: + name: "{{ galene_user }}" + system: true + password: '!' + home: "{{ galene_base_directory }}" + create_home: false + +- name: Create directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ galene_user }}" + group: "{{ galene_group }}" + mode: "0755" + loop: + - "{{ galene_base_directory }}" + - "{{ galene_data_directory }}" + - "{{ galene_groups_directory }}" + - "{{ galene_recording_directory }}" + - "{{ galene_static_directory }}" + +- name: Copy static directory + ansible.builtin.copy: + src: /opt/galene/static/ + dest: "{{ galene_static_directory }}/" + remote_src: true + mode: "0755" + owner: "{{ galene_user }}" + group: "{{ galene_group }}" + when: galene_static_directory != "/opt/galene/static" + +- name: Configure groups + ansible.builtin.copy: + content: "{{ item.value | to_json }}" + dest: "{{ galene_groups_directory }}/{{ item.key }}.json" + owner: "{{ galene_user }}" + group: "{{ galene_group }}" + mode: "0600" + loop: "{{ galene_groups | dict2items }}" + loop_control: + label: "{{ item.key }}" + notify: Restart galene + +- name: Create global configuration + ansible.builtin.copy: + content: "{{ galene_config | to_json }}" + dest: "{{ galene_data_directory }}/config.json" + owner: "{{ galene_user }}" + group: "{{ galene_group }}" + mode: "0600" + notify: Restart galene + +- name: Configure TLS certificates + when: galene_domain is defined + ansible.builtin.copy: + remote_src: true + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: "{{ galene_user }}" + group: "{{ galene_group }}" + loop: + - src: "/etc/letsencrypt/live/{{ galene_domain }}/fullchain.pem" + dest: "{{ galene_data_directory }}/cert.pem" + mode: "0644" + - src: "/etc/letsencrypt/live/{{ galene_domain }}/privkey.pem" + dest: "{{ galene_data_directory }}/key.pem" + mode: "0600" + +- name: Create service + ansible.builtin.template: + src: galene.service.j2 + dest: /etc/systemd/system/galene.service + mode: "0644" + owner: root + group: root + notify: + - Reload systemd + - Restart galene + +- name: Start service + ansible.builtin.service: + name: galene + state: started + enabled: true diff --git a/ansible/roles/galene/templates/galene.service.j2 b/ansible/roles/galene/templates/galene.service.j2 new file mode 100644 index 0000000..5a90399 --- /dev/null +++ b/ansible/roles/galene/templates/galene.service.j2 @@ -0,0 +1,19 @@ +{{ ansible_managed | comment }} + +[Unit] +Description=Galene +After=network.target + +[Service] +Type=simple +WorkingDirectory={{ galene_base_directory }} +User={{ galene_user }} +Group={{ galene_group }} +{% if galene_http_port < 1024 %} +AmbientCapabilities=CAP_NET_BIND_SERVICE +{% endif %} +ExecStart=/usr/local/bin/galene -http :{{ galene_http_port }} -data {{ galene_data_directory }} -groups {{ galene_groups_directory }} -recordings {{ galene_recording_directory }} -static {{ galene_static_directory }} -turn "{{ galene_turn }}" +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/golang/defaults/main.yml b/ansible/roles/golang/defaults/main.yml new file mode 100644 index 0000000..192473d --- /dev/null +++ b/ansible/roles/golang/defaults/main.yml @@ -0,0 +1,2 @@ +--- +golang_version: 1.24.1 diff --git a/ansible/roles/golang/tasks/main.yml b/ansible/roles/golang/tasks/main.yml new file mode 100644 index 0000000..6af3007 --- /dev/null +++ b/ansible/roles/golang/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- name: Install + ansible.builtin.unarchive: + src: "https://go.dev/dl/go{{ golang_version }}.linux-amd64.tar.gz" + dest: /usr/local + remote_src: true + creates: /usr/local/go diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..60a9b01 --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,17 @@ +- name: Pre install + become: true + hosts: + - galene + tasks: + - name: Update repositories + ansible.builtin.apt: + update_cache: true + upgrade: full + +- name: Install galene + become: true + hosts: + - galene + roles: + - certbot + - galene diff --git a/tofu/.terraform.lock.hcl b/tofu/.terraform.lock.hcl new file mode 100644 index 0000000..f2d171c --- /dev/null +++ b/tofu/.terraform.lock.hcl @@ -0,0 +1,80 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/hashicorp/local" { + version = "2.5.2" + hashes = [ + "h1:6lS+5A/4WFAqY3/RHWFRBSiFVLPRjvLaUgxPQvjXLHU=", + "zh:25b95b76ceaa62b5c95f6de2fa6e6242edbf51e7fc6c057b7f7101aa4081f64f", + "zh:3c974fdf6b42ca6f93309cf50951f345bfc5726ec6013b8832bcd3be0eb3429e", + "zh:5de843bf6d903f5cca97ce1061e2e06b6441985c68d013eabd738a9e4b828278", + "zh:86beead37c7b4f149a54d2ae633c99ff92159c748acea93ff0f3603d6b4c9f4f", + "zh:8e52e81d3dc50c3f79305d257da7fde7af634fed65e6ab5b8e214166784a720e", + "zh:9882f444c087c69559873b2d72eec406a40ede21acb5ac334d6563bf3a2387df", + "zh:a4484193d110da4a06c7bffc44cc6b61d3b5e881cd51df2a83fdda1a36ea25d2", + "zh:a53342426d173e29d8ee3106cb68abecdf4be301a3f6589e4e8d42015befa7da", + "zh:d25ef2aef6a9004363fc6db80305d30673fc1f7dd0b980d41d863b12dacd382a", + "zh:fa2d522fb323e2121f65b79709fd596514b293d816a1d969af8f72d108888e4c", + ] +} + +provider "registry.opentofu.org/hashicorp/tls" { + version = "4.0.6" + hashes = [ + "h1:EJoUGDo7L52Iu22cA1KCndJ9B1Rrfd75wyZzsScEnc0=", + "zh:4b53b372767e5068d9bbfc89199201c1ae4283dde2f0c301974f8abb4215791f", + "zh:5b4c308bd074c6d0bd560220e6ee10a9859ca9a1f29a59367b0477a740ff265e", + "zh:674dd6bc85597677e160ee601d88b21c5a974759a658769812d2904bd94bc042", + "zh:6ccc1c448349b56677ba66112aec7e0a58eb827f66209ca5f4077b81cce240fb", + "zh:8aa6e13a5d722b74230937ea21e8b4994e53340d95b5691cf6cf3518b9f38e6e", + "zh:8b27e55e4c7fa887774860113b95c8f7f68804b002fa47f0eb8e3a485997287e", + "zh:a430b5a3e8753d8f61784de49e538ac4abed19fb665fccd8a10b55402fe9f076", + "zh:b07c978c335ae9fc12f9c221629610775e4ae36691ed4e7ba258d275dd58a243", + "zh:bbec8cb1efc84ee3026c793956a4a4cd0ece20b89d2d4f7d954c68e7f6d596d0", + "zh:e684e247424188dc3b500a543b1a8046d1c0ec08c2a90aedca0c4f6bb56bedbd", + ] +} + +provider "registry.opentofu.org/ovh/ovh" { + version = "2.0.0" + constraints = "2.0.0" + hashes = [ + "h1:uNRSPKoEXESC8RwX1VRaLB/P7QAb0chWbXLq7WIJxA0=", + "zh:048777291c45fbb64abd4854effc6b2b8aa21d2af43ae67ee15b435fe1c8b6b9", + "zh:0b5c3aa40ee562d8d30bd2bea5885789a7863bdd5462b6bf14ba0cd41927bbb3", + "zh:2dee84d28915ce9e1d6f3bbf127baf433d539810be0b098220546c93d6b87196", + "zh:388598b1d8363a8eb5a853ec352a789acdc35e08cce3e41166826ad699e6cf43", + "zh:3ad775f75e4ee6037c8bfea1f689ee1d379fd2b4a72576618033c16715cfaf70", + "zh:45f7207ee1db02b376f7ab1a87e31cb9707f9ba73979f63309b1f7c8ea04b896", + "zh:4daa5b30e102213eb4e6907aee6044aefd95ec689e76014f234ee37454cb235b", + "zh:78477af1e004a3ea80dd9ce4cfa532f0e14758afaf2bdc7b6e90172d63532aad", + "zh:90ef8674f9cd7548f2c977508e754f714fafc071dea99a74c4a73da84ed012fa", + "zh:9839299f64ce383e49e1a0802e44f6e7feb3e2a8703b58d276e38f724e0ed094", + "zh:c6d9b66ee07e28e407f22d049ca3a38e00744fde80a0af5d7309c9991f646671", + "zh:cdc21d616b3a7eecf09b5681bfc774ebd483b769c64bf4de72d4a1222ef9b6be", + "zh:d1a790d02fd5c80007824145d38cc39c3f5849ee3a64a2f073f078971c058cf2", + "zh:e77438b905de97f8065646fe44ea9fd103abef08af7d8d87b52367ca78dc508f", + ] +} + +provider "registry.opentofu.org/terraform-provider-openstack/openstack" { + version = "3.0.0" + constraints = "3.0.0" + hashes = [ + "h1:sxq+0vFl4SfV9dlut5liwaH4FejyNIf4ClAjRgowxv8=", + "zh:01718f229597b34ed430236a230a407dacd6289543556d33910e151462e8cb8a", + "zh:2424c5347d35fe0de778d1c40dbe8d9b1278309c4d65cce31709e6fcbec139df", + "zh:2a785f9efd6d8c979031803dd78411a583bc0d9a572ddf5fb9e539cbbfc1ce43", + "zh:8ab18c7eb1fd04b34be75fcecfc461888bd37ff017973f46745abeabd21b3fda", + "zh:98e805318292b58d9692bfe6d3d82e0db0f8044e588a38b239309221198aa92d", + "zh:9a99c9801f96dc69e7c76c5ddc0e2800b77a333becacae530d7a3acd18855347", + "zh:c5aa6690c094be211d2700d7ea44ffe937763e4dd566506c87eb99d6d8330b52", + "zh:d06fc3a148a49aab059a1f08ceadbfe1a5c82c7b80b960169987603dddaaaf58", + "zh:d079da24a9f2cc0d6fec9616e7ebe994245a3d98da629f012069c26d650edb05", + "zh:d1d2b63dba9045a4ff3869c65ddcaf2703d993254b58b2a0230d067f5c036de2", + "zh:d2b9d09c47e7eca08091da825cdf0982dc30089ee401888ce8704b79c7636e95", + "zh:dd6be78f98772bfc1ebf022fc36e5d68ef0f165f9e87476430d4e0dc3f1cc57f", + "zh:e7743c11dd0c83a5c1905f04ffd490b4471941218b4c841b760223fcf416affd", + "zh:f234aefac77f4e9a2b05877a00d322ee165009713d79a8d8e19039c6c6f7cf1b", + ] +} diff --git a/tofu/main.tf b/tofu/main.tf new file mode 100644 index 0000000..5a91ee7 --- /dev/null +++ b/tofu/main.tf @@ -0,0 +1,43 @@ +resource "tls_private_key" "ssh_key" { + algorithm = "ED25519" +} + +resource "openstack_compute_keypair_v2" "ssh_key" { + name = replace(join(".", [var.hostname, var.domain]), ".", "-") + public_key = tls_private_key.ssh_key.public_key_openssh +} + +resource "local_file" "ansible_ssh_key" { + content = tls_private_key.ssh_key.private_key_openssh + filename = "../ansible/ssh_key" + directory_permission = "0755" + file_permission = "0600" +} + +resource "openstack_compute_instance_v2" "instance" { + name = join(".", [var.hostname, var.domain]) + image_name = var.openstack_image + flavor_name = var.openstack_flavor + key_pair = openstack_compute_keypair_v2.ssh_key.name + network { + name = "Ext-Net" + } +} + +resource "ovh_domain_zone_record" "subdomain" { + zone = var.domain + subdomain = var.hostname + fieldtype = "A" + ttl = 3600 + target = openstack_compute_instance_v2.instance.access_ip_v4 +} + +resource "local_file" "inventory" { + content = templatefile("templates/hosts.tpl", + { + ipaddr = openstack_compute_instance_v2.instance.access_ip_v4 + }) + filename = "../ansible/inventory/hosts" + file_permission = "0644" + directory_permission = "0755" +} diff --git a/tofu/provider.tf b/tofu/provider.tf new file mode 100644 index 0000000..cfa4aec --- /dev/null +++ b/tofu/provider.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.9.0" + required_providers { + openstack = { + source = "terraform-provider-openstack/openstack" + version = "3.0.0" + } + + ovh = { + source = "ovh/ovh" + version = "2.0.0" + } + } +} + +provider "openstack" { + cloud = "openstack" +} diff --git a/tofu/templates/hosts.tpl b/tofu/templates/hosts.tpl new file mode 100644 index 0000000..4fdfb7e --- /dev/null +++ b/tofu/templates/hosts.tpl @@ -0,0 +1,4 @@ +# Managed by Terraform + +[galene] +${ipaddr} diff --git a/tofu/variables.tf b/tofu/variables.tf new file mode 100644 index 0000000..fc184f9 --- /dev/null +++ b/tofu/variables.tf @@ -0,0 +1,22 @@ +variable "domain" { + description = "Name of the domain zone" + type = string +} + +variable "hostname" { + description = "Name of the server" + type = string + default = "galene" +} + +variable "openstack_image" { + description = "Name of the OpenStack image" + type = string + default = "Debian 12" +} + +variable "openstack_flavor" { + description = "Name of the OpenStack flavor" + type = string + default = "d2-2" +}