feat: Initial code

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-04-12 06:50:12 +02:00
parent 0e18170cdf
commit fc59b4f9a5
Signed by: jriou
GPG key ID: 9A099EDA51316854
18 changed files with 478 additions and 1 deletions

10
.gitignore vendored Normal file
View file

@ -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

View file

@ -1,3 +1,74 @@
# galene-cloud # galene-cloud
Run galene in the cloud using OpenTofu and Ansible 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

8
ansible/ansible.cfg Normal file
View file

@ -0,0 +1,8 @@
[defaults]
inventory = inventory/hosts
remote_user = debian
host_key_checking = False
private_key_file = ssh_key
[ssh_connection]
pipelining = True

6
ansible/galene.yml Normal file
View file

@ -0,0 +1,6 @@
- name: Install galene
become: true
hosts:
- galene
roles:
- galene

View file

@ -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

View file

@ -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: {}

View file

@ -0,0 +1,9 @@
---
- name: Reload systemd
ansible.builtin.systemd_service:
daemon_reload: true
- name: Restart galene
ansible.builtin.service:
name: galene
state: restarted

View file

@ -0,0 +1,3 @@
---
dependencies:
- role: golang

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,2 @@
---
golang_version: 1.24.1

View file

@ -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

17
ansible/site.yml Normal file
View file

@ -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

80
tofu/.terraform.lock.hcl generated Normal file
View file

@ -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",
]
}

43
tofu/main.tf Normal file
View file

@ -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"
}

18
tofu/provider.tf Normal file
View file

@ -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"
}

4
tofu/templates/hosts.tpl Normal file
View file

@ -0,0 +1,4 @@
# Managed by Terraform
[galene]
${ipaddr}

22
tofu/variables.tf Normal file
View file

@ -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"
}