From b9d91473d197b3d65ad4f953acf02fbe65255892 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:06:34 +0100 Subject: [PATCH 01/13] feat(ui): enable insecure flag for catalog schema --- ui/schemas/catalogs.js | 1 + ui/schemas/softwares.js | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/schemas/catalogs.js b/ui/schemas/catalogs.js index d4d92ebb..1b2b4e35 100644 --- a/ui/schemas/catalogs.js +++ b/ui/schemas/catalogs.js @@ -168,6 +168,7 @@ NEWSCHEMA('Catalogs', function(schema) { builder.method('POST'); builder.url(decrypted.url); builder.json(payload); + builder.insecure(); if (decrypted.authentication) { builder.auth(decrypted.login, decrypted.password); } diff --git a/ui/schemas/softwares.js b/ui/schemas/softwares.js index 0cec528b..2db02dce 100644 --- a/ui/schemas/softwares.js +++ b/ui/schemas/softwares.js @@ -232,6 +232,7 @@ NEWSCHEMA('Softwares', function (schema) { builder.method('POST'); builder.url(decryptedSettings.url); builder.json(payload); + builder.insecure(); if (decryptedSettings.authentication) { builder.auth(decryptedSettings.login, decryptedSettings.password); } From 2ac6c4764249287d282546f74fc36ef27724c083 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:06:59 +0100 Subject: [PATCH 02/13] feat(ui): add code server image --- ui/public/img/{code-server.png => code_server.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/public/img/{code-server.png => code_server.png} (100%) diff --git a/ui/public/img/code-server.png b/ui/public/img/code_server.png similarity index 100% rename from ui/public/img/code-server.png rename to ui/public/img/code_server.png From ccab675beba56316c5939783b6737069aaae0231 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:07:17 +0100 Subject: [PATCH 03/13] feat(ui): include key param in variables_read API calls --- ui/public/forms/catalogs.html | 2 +- ui/public/forms/softwares.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/public/forms/catalogs.html b/ui/public/forms/catalogs.html index bad80e7c..9a5fad84 100644 --- a/ui/public/forms/catalogs.html +++ b/ui/public/forms/catalogs.html @@ -86,7 +86,7 @@ SET('common.form2', 'formvariable'); } else { - exports.tapi('variables_read/{0} ERROR'.format(id), { type: type, format: 'yaml' }, function(response) { + exports.tapi('variables_read/{0} ERROR'.format(id), { type: type, format: 'yaml', key: key}, function(response) { SET('formvariable @reset @hideloading', response); SET('common.form2', 'formvariable'); }); diff --git a/ui/public/forms/softwares.html b/ui/public/forms/softwares.html index c1b5534a..96727f86 100644 --- a/ui/public/forms/softwares.html +++ b/ui/public/forms/softwares.html @@ -69,7 +69,7 @@ SET('common.form2', 'formvariable'); } else { - exports.tapi('variables_read/{0} ERROR'.format(id), { type: type, format: 'yaml' }, function(response) { + exports.tapi('variables_read/{0} ERROR'.format(id), { type: type, format: 'yaml', key: key }, function(response) { SET('formvariable @reset @hideloading', response); SET('common.form2', 'formvariable'); }); From 14ee2d10660cf2660d49c64a1e0c89e580efedfc Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:07:54 +0100 Subject: [PATCH 04/13] fix(ansible): add software.instance fallback for nomad address Use software.instance as a fallback for nomad_primary_master_node when rendering the Traefik template. This prevents errors if the primary master variable is undefined. --- ansible/playbooks/saas/roles/traefik/templates/traefik.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/playbooks/saas/roles/traefik/templates/traefik.toml b/ansible/playbooks/saas/roles/traefik/templates/traefik.toml index a6bfce55..94137953 100644 --- a/ansible/playbooks/saas/roles/traefik/templates/traefik.toml +++ b/ansible/playbooks/saas/roles/traefik/templates/traefik.toml @@ -49,7 +49,7 @@ prefix = "traefik" exposedByDefault = false [providers.nomad.endpoint] - address = "https://{{ hostvars[nomad_primary_master_node]['ansible_ens3']['ipv4']['address'] | default('127.0.0.1') }}:4646" + address = "https://{{ hostvars[nomad_primary_master_node | default(software.instance)]['ansible_ens3']['ipv4']['address'] | default('127.0.0.1') }}:4646" token = "{{ lookup('simple-stack-ui', type='secret', key=inventory_hostname, subkey='nomad_traefik_token', missing='error') }}" [providers.nomad.endpoint.tls] ca = "/etc/ssl/simplestack/simplestack-ca.pem" From dfec968437cab9170345abe992931bb07626a46e Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:08:10 +0100 Subject: [PATCH 05/13] feat(traefik): add early exit when version unchanged --- ansible/playbooks/saas/roles/traefik/tasks/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ansible/playbooks/saas/roles/traefik/tasks/build.yml b/ansible/playbooks/saas/roles/traefik/tasks/build.yml index 9bf114b1..7421b506 100644 --- a/ansible/playbooks/saas/roles/traefik/tasks/build.yml +++ b/ansible/playbooks/saas/roles/traefik/tasks/build.yml @@ -8,3 +8,7 @@ image_name: "{{ image.name }}" image_labels: "{{ image.labels }}" image_build: "{{ image.build }}" + +- name: End playbook if no new version + ansible.builtin.meta: end_host + when: softwares[image.name] is defined and softwares[image.name] == image_version From b308d314d2fdda1bd8dade1e4d83fbe0d98e3cc8 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:09:22 +0100 Subject: [PATCH 06/13] feat(ansible): add simplestack_ui role with tasks, templates and variables --- .../roles/simplestack_ui/tasks/backup.yml | 1 + .../saas/roles/simplestack_ui/tasks/build.yml | 14 +++++ .../roles/simplestack_ui/tasks/destroy.yml | 10 +++ .../saas/roles/simplestack_ui/tasks/main.yml | 25 ++++++++ .../roles/simplestack_ui/tasks/restore.yml | 1 + .../roles/simplestack_ui/templates/nomad.hcl | 62 +++++++++++++++++++ .../saas/roles/simplestack_ui/vars/main.yml | 12 ++++ .../roles/simplestack_ui/vars/upstream.yml | 2 + 8 files changed, 127 insertions(+) create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/tasks/backup.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/tasks/build.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/tasks/destroy.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/tasks/main.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/tasks/restore.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/templates/nomad.hcl create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/vars/main.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ui/vars/upstream.yml diff --git a/ansible/playbooks/saas/roles/simplestack_ui/tasks/backup.yml b/ansible/playbooks/saas/roles/simplestack_ui/tasks/backup.yml new file mode 100644 index 00000000..73b314ff --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/tasks/backup.yml @@ -0,0 +1 @@ +--- \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ui/tasks/build.yml b/ansible/playbooks/saas/roles/simplestack_ui/tasks/build.yml new file mode 100644 index 00000000..7421b506 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/tasks/build.yml @@ -0,0 +1,14 @@ +--- +- name: Include upstream variables + ansible.builtin.include_vars: upstream.yml + +- name: Set custom variables + ansible.builtin.set_fact: + image_version: "{{ latest_version }}" + image_name: "{{ image.name }}" + image_labels: "{{ image.labels }}" + image_build: "{{ image.build }}" + +- name: End playbook if no new version + ansible.builtin.meta: end_host + when: softwares[image.name] is defined and softwares[image.name] == image_version diff --git a/ansible/playbooks/saas/roles/simplestack_ui/tasks/destroy.yml b/ansible/playbooks/saas/roles/simplestack_ui/tasks/destroy.yml new file mode 100644 index 00000000..ce77a12b --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/tasks/destroy.yml @@ -0,0 +1,10 @@ +--- +- name: Stop nomad job + ansible.builtin.include_role: + name: nomad + tasks_from: job_stop.yml + +- name: Remove software directory + ansible.builtin.file: + path: "{{ software_path }}" + state: absent diff --git a/ansible/playbooks/saas/roles/simplestack_ui/tasks/main.yml b/ansible/playbooks/saas/roles/simplestack_ui/tasks/main.yml new file mode 100644 index 00000000..e5adee3b --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: Create default directory + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: 1000 + group: 1000 + mode: "0755" + loop: + - "{{ software_path }}/databases" + +- name: Copy nomad job to destination + ansible.builtin.template: + src: nomad.hcl + dest: "/var/tmp/{{ domain }}.nomad" + owner: root + group: root + mode: '0600' + become: true + +- name: Run nomad job + ansible.builtin.include_role: + name: nomad + tasks_from: job_run.yml + diff --git a/ansible/playbooks/saas/roles/simplestack_ui/tasks/restore.yml b/ansible/playbooks/saas/roles/simplestack_ui/tasks/restore.yml new file mode 100644 index 00000000..73b314ff --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/tasks/restore.yml @@ -0,0 +1 @@ +--- \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ui/templates/nomad.hcl b/ansible/playbooks/saas/roles/simplestack_ui/templates/nomad.hcl new file mode 100644 index 00000000..872b79fd --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/templates/nomad.hcl @@ -0,0 +1,62 @@ +job "{{ domain }}" { + region = "{{ fact_instance.region }}" + datacenters = ["{{ fact_instance.datacenter }}"] + type = "service" + +{% if software.constraints is defined and software.constraints.location is defined %} + constraint { + attribute = "${meta.location}" + set_contains = "{{ software.constraints.location }}" + } +{% endif %} + +{% if software.constraints is defined and software.constraints.instance is defined %} + constraint { + attribute = "${meta.instance}" + set_contains = "{{ software.constraints.instance }}" + } +{% endif %} + + group "{{ domain }}" { + count = {{ software.scale | default(1) }} + + network { + port "http" { + to = 8000 + } + } + + service { + name = "{{ service_name }}" + port = "http" + provider = "nomad" + tags = [ + {{ lookup('template', '../../traefik/templates/traefik_tag.j2') | indent(8) }} + ] + } + + task "{{ domain }}-http" { + driver = "docker" + + env { + AUTH_SECRET = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='auth_secret', missing='error') }}" + AUTH_COOKIE = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='auth_cookie', missing='error') }}" + } + + user = "nodejs" + + config { + image = "ghcr.io/wiseflat/simple-stack-ui:v{{ softwares.simplestack_ui.version }}" + ports = ["http"] + volumes = [ + "{{ software_path }}/databases:/www/databases:rw" + ] + } + + resources { + cpu = {{ size[software.size].cpu }} + memory = {{ size[software.size].memory }} + } + } + } +} diff --git a/ansible/playbooks/saas/roles/simplestack_ui/vars/main.yml b/ansible/playbooks/saas/roles/simplestack_ui/vars/main.yml new file mode 100644 index 00000000..4e74c688 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/vars/main.yml @@ -0,0 +1,12 @@ +--- +image: + build: false + upstream: + source: github + user: wiseflat + repo: simple-stack + file: vVERSION + labels: {} + name: simplestack_ui + +traefik_x_robots_tag: noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ui/vars/upstream.yml b/ansible/playbooks/saas/roles/simplestack_ui/vars/upstream.yml new file mode 100644 index 00000000..7ba80d95 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ui/vars/upstream.yml @@ -0,0 +1,2 @@ +--- +latest_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ image.upstream.user }}/{{ image.upstream.repo }}/releases/latest', headers={'Accept': 'application/vnd.github+json', 'Authorization': 'Bearer ' + lookup('ansible.builtin.env', 'GITHUB_API_TOKEN') }) | from_json).get('tag_name') | replace('v', '') }}" \ No newline at end of file From 8c622f749e060fd473993ef09dd9613dad2fcd2f Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:10:59 +0100 Subject: [PATCH 07/13] feat(ansible): add simplestack_ansible role Add a new Ansible role `simplestack_ansible` with task files for backup, build, destroy, main and restore, a Nomad job template, and default variables including image metadata and upstream version lookup. --- .../simplestack_ansible/tasks/backup.yml | 1 + .../roles/simplestack_ansible/tasks/build.yml | 14 +++++ .../simplestack_ansible/tasks/destroy.yml | 10 +++ .../roles/simplestack_ansible/tasks/main.yml | 15 +++++ .../simplestack_ansible/tasks/restore.yml | 1 + .../simplestack_ansible/templates/nomad.hcl | 61 +++++++++++++++++++ .../roles/simplestack_ansible/vars/main.yml | 12 ++++ .../simplestack_ansible/vars/upstream.yml | 2 + 8 files changed, 116 insertions(+) create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/tasks/backup.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/tasks/build.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/tasks/destroy.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/tasks/main.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/tasks/restore.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/templates/nomad.hcl create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/vars/main.yml create mode 100644 ansible/playbooks/saas/roles/simplestack_ansible/vars/upstream.yml diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/tasks/backup.yml b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/backup.yml new file mode 100644 index 00000000..73b314ff --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/backup.yml @@ -0,0 +1 @@ +--- \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/tasks/build.yml b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/build.yml new file mode 100644 index 00000000..7421b506 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/build.yml @@ -0,0 +1,14 @@ +--- +- name: Include upstream variables + ansible.builtin.include_vars: upstream.yml + +- name: Set custom variables + ansible.builtin.set_fact: + image_version: "{{ latest_version }}" + image_name: "{{ image.name }}" + image_labels: "{{ image.labels }}" + image_build: "{{ image.build }}" + +- name: End playbook if no new version + ansible.builtin.meta: end_host + when: softwares[image.name] is defined and softwares[image.name] == image_version diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/tasks/destroy.yml b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/destroy.yml new file mode 100644 index 00000000..ce77a12b --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/destroy.yml @@ -0,0 +1,10 @@ +--- +- name: Stop nomad job + ansible.builtin.include_role: + name: nomad + tasks_from: job_stop.yml + +- name: Remove software directory + ansible.builtin.file: + path: "{{ software_path }}" + state: absent diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/tasks/main.yml b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/main.yml new file mode 100644 index 00000000..687e4f0a --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- name: Copy nomad job to destination + ansible.builtin.template: + src: nomad.hcl + dest: "/var/tmp/{{ domain }}.nomad" + owner: root + group: root + mode: '0600' + become: true + +- name: Run nomad job + ansible.builtin.include_role: + name: nomad + tasks_from: job_run.yml + diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/tasks/restore.yml b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/restore.yml new file mode 100644 index 00000000..73b314ff --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/tasks/restore.yml @@ -0,0 +1 @@ +--- \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/templates/nomad.hcl b/ansible/playbooks/saas/roles/simplestack_ansible/templates/nomad.hcl new file mode 100644 index 00000000..d0f81d66 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/templates/nomad.hcl @@ -0,0 +1,61 @@ +job "{{ domain }}" { + region = "{{ fact_instance.region }}" + datacenters = ["{{ fact_instance.datacenter }}"] + type = "service" + +{% if software.constraints is defined and software.constraints.location is defined %} + constraint { + attribute = "${meta.location}" + set_contains = "{{ software.constraints.location }}" + } +{% endif %} + +{% if software.constraints is defined and software.constraints.instance is defined %} + constraint { + attribute = "${meta.instance}" + set_contains = "{{ software.constraints.instance }}" + } +{% endif %} + + group "{{ domain }}" { + count = {{ software.scale | default(1) }} + + network { + port "http" { + to = 5001 + } + } + + service { + name = "{{ service_name }}" + port = "http" + provider = "nomad" + tags = [ + {{ lookup('template', '../../traefik/templates/traefik_tag.j2') | indent(8) }} + ] + } + + task "{{ domain }}-http" { + driver = "docker" + + env { + SIMPLE_STACK_UI_USER = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='user', missing='error') }}" + SIMPLE_STACK_UI_PASSWORD = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='password', missing='error') }}" + SIMPLE_STACK_UI_URL = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='url', missing='error') }}" + } + + config { + image = "ghcr.io/wiseflat/simple-stack-ansible:v{{ softwares.simplestack_ui.version }}" + ports = ["http"] + work_dir = "/ansible" + command = "ansible-rulebook" + args = ["-r", "rulebook.yml", "-i", "inventory.py", "-vvv"] + } + + resources { + cpu = {{ size[software.size].cpu }} + memory = {{ size[software.size].memory }} + } + } + } +} diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/vars/main.yml b/ansible/playbooks/saas/roles/simplestack_ansible/vars/main.yml new file mode 100644 index 00000000..307e3dfd --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/vars/main.yml @@ -0,0 +1,12 @@ +--- +image: + build: false + upstream: + source: github + user: wiseflat + repo: simple-stack + file: vVERSION + labels: {} + name: simplestack_ansible + +traefik_x_robots_tag: noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/simplestack_ansible/vars/upstream.yml b/ansible/playbooks/saas/roles/simplestack_ansible/vars/upstream.yml new file mode 100644 index 00000000..7ba80d95 --- /dev/null +++ b/ansible/playbooks/saas/roles/simplestack_ansible/vars/upstream.yml @@ -0,0 +1,2 @@ +--- +latest_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ image.upstream.user }}/{{ image.upstream.repo }}/releases/latest', headers={'Accept': 'application/vnd.github+json', 'Authorization': 'Bearer ' + lookup('ansible.builtin.env', 'GITHUB_API_TOKEN') }) | from_json).get('tag_name') | replace('v', '') }}" \ No newline at end of file From f51c12ef7f76c7ff0f9e6592d76930cb8092683a Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:13:28 +0100 Subject: [PATCH 08/13] feat(code_server): add code_server role with defaults and tasks --- .../saas/roles/code_server/defaults/main.yml | 4 + .../saas/roles/code_server/tasks/backup.yml | 1 + .../saas/roles/code_server/tasks/build.yml | 14 +++ .../saas/roles/code_server/tasks/destroy.yml | 0 .../saas/roles/code_server/tasks/main.yml | 33 +++++++ .../saas/roles/code_server/tasks/restore.yml | 1 + .../roles/code_server/templates/nomad.hcl | 92 +++++++++++++++++++ .../code_server/templates/traefik_tag.j2 | 38 ++++++++ .../saas/roles/code_server/vars/main.yml | 15 +++ .../saas/roles/code_server/vars/upstream.yml | 2 + 10 files changed, 200 insertions(+) create mode 100644 ansible/playbooks/saas/roles/code_server/defaults/main.yml create mode 100644 ansible/playbooks/saas/roles/code_server/tasks/backup.yml create mode 100644 ansible/playbooks/saas/roles/code_server/tasks/build.yml create mode 100644 ansible/playbooks/saas/roles/code_server/tasks/destroy.yml create mode 100644 ansible/playbooks/saas/roles/code_server/tasks/main.yml create mode 100644 ansible/playbooks/saas/roles/code_server/tasks/restore.yml create mode 100644 ansible/playbooks/saas/roles/code_server/templates/nomad.hcl create mode 100644 ansible/playbooks/saas/roles/code_server/templates/traefik_tag.j2 create mode 100644 ansible/playbooks/saas/roles/code_server/vars/main.yml create mode 100644 ansible/playbooks/saas/roles/code_server/vars/upstream.yml diff --git a/ansible/playbooks/saas/roles/code_server/defaults/main.yml b/ansible/playbooks/saas/roles/code_server/defaults/main.yml new file mode 100644 index 00000000..824bba67 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/defaults/main.yml @@ -0,0 +1,4 @@ +--- +timezone: Europe/Paris +password: changeme +sudo_password: changeme diff --git a/ansible/playbooks/saas/roles/code_server/tasks/backup.yml b/ansible/playbooks/saas/roles/code_server/tasks/backup.yml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/tasks/backup.yml @@ -0,0 +1 @@ +--- diff --git a/ansible/playbooks/saas/roles/code_server/tasks/build.yml b/ansible/playbooks/saas/roles/code_server/tasks/build.yml new file mode 100644 index 00000000..7421b506 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/tasks/build.yml @@ -0,0 +1,14 @@ +--- +- name: Include upstream variables + ansible.builtin.include_vars: upstream.yml + +- name: Set custom variables + ansible.builtin.set_fact: + image_version: "{{ latest_version }}" + image_name: "{{ image.name }}" + image_labels: "{{ image.labels }}" + image_build: "{{ image.build }}" + +- name: End playbook if no new version + ansible.builtin.meta: end_host + when: softwares[image.name] is defined and softwares[image.name] == image_version diff --git a/ansible/playbooks/saas/roles/code_server/tasks/destroy.yml b/ansible/playbooks/saas/roles/code_server/tasks/destroy.yml new file mode 100644 index 00000000..e69de29b diff --git a/ansible/playbooks/saas/roles/code_server/tasks/main.yml b/ansible/playbooks/saas/roles/code_server/tasks/main.yml new file mode 100644 index 00000000..6759f4e1 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/tasks/main.yml @@ -0,0 +1,33 @@ +--- +- name: Create default directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: 1000 + group: 1000 + mode: '0755' + loop: + - "{{ software_path }}/config/workspace" + - "{{ software_path }}/home/coder/.config/code-server" + - "{{ software_path }}/home/coder/.cache" + - "{{ software_path }}/home/coder/.config" + - "{{ software_path }}/home/coder/.local" + - "{{ software_path }}/home/coder/.ssh" + - "{{ software_path }}/projects" + +- debug: + msg: "{{ size}}" + +- name: Copy nomad job + ansible.builtin.template: + src: nomad.hcl + dest: "/var/tmp/{{ domain }}.nomad" + owner: root + group: root + mode: '0600' + +- name: Run nomad job + ansible.builtin.include_role: + name: nomad + tasks_from: job_run.yml + diff --git a/ansible/playbooks/saas/roles/code_server/tasks/restore.yml b/ansible/playbooks/saas/roles/code_server/tasks/restore.yml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/tasks/restore.yml @@ -0,0 +1 @@ +--- diff --git a/ansible/playbooks/saas/roles/code_server/templates/nomad.hcl b/ansible/playbooks/saas/roles/code_server/templates/nomad.hcl new file mode 100644 index 00000000..58f4cf3f --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/templates/nomad.hcl @@ -0,0 +1,92 @@ +job "{{ domain }}" { + region = "{{ fact_instance.region }}" + datacenters = ["{{ fact_instance.datacenter }}"] + type = "service" + +{% if software.constraints is defined and software.constraints.location is defined %} + constraint { + attribute = "${meta.location}" + set_contains = "{{ software.constraints.location }}" + } +{% endif %} + +{% if software.constraints is defined and software.constraints.instance is defined %} + constraint { + attribute = "${meta.instance}" + set_contains = "{{ software.constraints.instance }}" + } +{% endif %} + + group "{{ domain }}" { + count = {{ software.scale | default(1) }} + + network { + port "code_server" { + to = "8080" + } + port "http_dev" { + to = 8000 + } + } + + service { + name = "{{ service_name }}" + port = "code_server" + provider = "nomad" + tags = [ + {{ lookup('ansible.builtin.template', '../../traefik/templates/traefik_tag.j2') | indent(8) }} + ] + check { + type = "http" + path = "/" + interval = "10s" + timeout = "2s" + } + } + + service { + name = "{{ service_name }}-dev" + port = "http_dev" + provider = "nomad" + tags = [ + {{ lookup('ansible.builtin.template', 'templates/traefik_tag.j2', template_vars={'prefix': 'dev'}) | indent(8) }} + ] + } + + task "{{ software.domain }}-codeserver" { + + driver = "docker" + + env { + PUID = "1000" + PGID = "1000" + TZ = "{{ software.timezone | default(timezone) }}" + DOCKER_USER = "ubuntu" + PASSWORD= "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='password', missing='error') }}" + SUDO_PASSWORD = "{{ lookup('simple-stack-ui', type='secret', key=domain, subkey='password', missing='error') }}" + } + + config { + image = "codercom/code-server:{{ softwares.code_server.version }}-ubuntu" + + volumes = [ + "/usr/bin/docker:/usr/bin/docker", + "/var/run/docker.sock:/var/run/docker.sock", + "/usr/local/bin/terraform:/usr/local/bin/terraform", + "{{ software_path }}/config/workspace:/config/workspace", + "{{ software_path }}/home/coder/.cache:/home/coder/.cache", + "{{ software_path }}/home/coder/.config:/home/coder/.config", + "{{ software_path }}/home/coder/.cache:/home/coder/.local", + "{{ software_path }}/home/coder/.ssh:/home/coder/.ssh", + "{{ software_path }}/projects:/home/coder/projects", + ] + ports = ["code_server", "http_dev"] + } + + resources { + cpu = {{ size[software.size2 | default(software.size)].cpu }} + memory = {{ size[software.size2 | default(software.size)].memory }} + } + } + } +} diff --git a/ansible/playbooks/saas/roles/code_server/templates/traefik_tag.j2 b/ansible/playbooks/saas/roles/code_server/templates/traefik_tag.j2 new file mode 100644 index 00000000..d5d20835 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/templates/traefik_tag.j2 @@ -0,0 +1,38 @@ +{% if software.exposition is defined and software.exposition in ["public", "public-unmanaged"] %} +"fqdn=https://{{ prefix }}.{{ domain }}", +"traefik.enable=true", +"traefik.http.routers.{{ prefix }}-{{ service_name }}.tls=true", +"traefik.http.routers.{{ prefix }}-{{ service_name }}.tls.certresolver=myresolver", +"traefik.http.routers.{{ prefix }}-{{ service_name }}.tls.options=mintls12@file", +"traefik.http.routers.{{ prefix }}-{{ service_name }}.entrypoints=https", +"traefik.http.routers.{{ prefix }}-{{ service_name }}.rule=Host(`{{ prefix }}.{{ domain }}`)", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}.redirectscheme.scheme=https", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}.redirectscheme.permanent=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.customResponseHeaders.Strict-Transport-Security=max-age=63072000", +{% if software.traefik_x_robots_tag is defined %} +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.customResponseHeaders.X-Robots-Tag={{ software.traefik_x_robots_tag }}", +{% endif %} +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.frameDeny=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.browserXssFilter=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.contentTypeNosniff=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.stsIncludeSubdomains=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.stsPreload=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.stsSeconds=31536000", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.forceSTSHeader=true", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.accessControlMaxAge=15552000", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.customFrameOptionsValue=SAMEORIGIN", +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-headers.headers.accesscontrolalloworiginlist=*", +{% if software.ipfilter is defined %} +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-whistelist.IPAllowList.sourcerange={{ software.ipfilter | join(',') }}", +{% endif %} +{% if software.basic_auth is defined %} +"traefik.http.middlewares.{{ prefix }}-{{ service_name }}-basicauth.basicauth.users={{ software.basic_auth }}", +{% endif %} +{% if (software.ipfilter is defined and software.basic_auth is defined) %} +"traefik.http.routers.{{ prefix }}-{{ service_name }}.middlewares={{ prefix }}-{{ service_name }}-whistelist@nomad,{{ service_name }}-basicauth@nomad", +{% elif (software.ipfilter is defined) and software.basic_auth is not defined %} +"traefik.http.routers.{{ prefix }}-{{ service_name }}.middlewares={{ prefix }}-{{ service_name }}-whistelist@nomad", +{% elif (software.ipfilter is not defined and software.basic_auth is defined) %} +"traefik.http.routers.{{ prefix }}-{{ service_name }}.middlewares={{ prefix }}-{{ service_name }}-basicauth@nomad" +{% endif %} +{% endif %} \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/code_server/vars/main.yml b/ansible/playbooks/saas/roles/code_server/vars/main.yml new file mode 100644 index 00000000..d7c45e2b --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/vars/main.yml @@ -0,0 +1,15 @@ +--- +image: + build: false + upstream: + source: github + user: codercom + repo: code-server + type: release + format: tar.gz + file: code-server_VERSION_ARCH.FORMAT + os: linux + labels: {} + name: code_server + +traefik_x_robots_tag: noindex,nofollow,nosnippet,noarchive,notranslate,noimageindex \ No newline at end of file diff --git a/ansible/playbooks/saas/roles/code_server/vars/upstream.yml b/ansible/playbooks/saas/roles/code_server/vars/upstream.yml new file mode 100644 index 00000000..67952cb2 --- /dev/null +++ b/ansible/playbooks/saas/roles/code_server/vars/upstream.yml @@ -0,0 +1,2 @@ +--- +latest_version: "{{ (lookup('url', 'https://api.github.com/repos/{{ image.upstream.user }}/{{ image.upstream.repo }}/releases/latest', headers={'Accept': 'application/vnd.github+json', 'Authorization': 'Bearer ' + lookup('ansible.builtin.env', 'GITHUB_API_TOKEN')}) | from_json).get('tag_name') | replace('v', '') }}" \ No newline at end of file From 42686dbecf147e9e2dc215e96b6187c16851dae4 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:13:48 +0100 Subject: [PATCH 09/13] fix(ansible): use dynamic interface for nomad address --- ansible/playbooks/paas/roles/coredns/templates/Corefile.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/playbooks/paas/roles/coredns/templates/Corefile.j2 b/ansible/playbooks/paas/roles/coredns/templates/Corefile.j2 index 5cf6dbf6..9dd4e207 100644 --- a/ansible/playbooks/paas/roles/coredns/templates/Corefile.j2 +++ b/ansible/playbooks/paas/roles/coredns/templates/Corefile.j2 @@ -4,7 +4,7 @@ service.nomad.:1053 { #debug #log nomad { - address https://{{ hostvars[nomad_primary_master_node | default(inventory_hostname)]['ansible_ens3']['ipv4']['address'] }}:4646 + address https://{{ hostvars[nomad_primary_master_node | default(inventory_hostname)]['ansible_' + nomad_iface].ipv4.address | default('127.0.0.1') }}:4646 token {{ lookup('simple-stack-ui', type='secret', key=nomad_primary_master_node | default(inventory_hostname), subkey='nomad_management_token', missing='error') }} ttl 10 } From 95d12fab066722576bafcc3f0d2a2fd91d4704d0 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:14:09 +0100 Subject: [PATCH 10/13] refactor(ansible-ufw): remove no_log from ufw rule tasks --- ansible/playbooks/paas/roles/ansible-ufw/tasks/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/ansible/playbooks/paas/roles/ansible-ufw/tasks/config.yml b/ansible/playbooks/paas/roles/ansible-ufw/tasks/config.yml index 7b7c0ad1..6a02cbac 100644 --- a/ansible/playbooks/paas/roles/ansible-ufw/tasks/config.yml +++ b/ansible/playbooks/paas/roles/ansible-ufw/tasks/config.yml @@ -18,7 +18,6 @@ to_ip: "{{ item.to_ip | default(omit) }}" to_port: "{{ item.to_port | default(omit) }}" with_items: "{{ ufw_rules }}" - no_log: true - name: Config port/protocol/network custom rules community.general.ufw: @@ -39,7 +38,6 @@ to_ip: "{{ item.to_ip | default(omit) }}" to_port: "{{ item.to_port | default(omit) }}" with_items: "{{ ufw_custom_rules }}" - no_log: true - name: Config application rules community.general.ufw: From d3dfbbf2d907a493d97744bf4ce846cc4e457213 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:14:43 +0100 Subject: [PATCH 11/13] build(deps): bump cloud.terraform collection to 4.0.0 --- ansible/requirements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/requirements.yml b/ansible/requirements.yml index b6665d57..1e3d1860 100644 --- a/ansible/requirements.yml +++ b/ansible/requirements.yml @@ -1,7 +1,7 @@ --- collections: - name: cloud.terraform - version: 2.0.0 + version: 4.0.0 - name: community.general version: 10.2.0 - name: community.docker From 77928c44a0c20ad55876600df1da329fa0845985 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:14:57 +0100 Subject: [PATCH 12/13] build(docker): remove unused Simple Stack UI env args --- ansible/Dockerfile | 9 --------- 1 file changed, 9 deletions(-) diff --git a/ansible/Dockerfile b/ansible/Dockerfile index bba30aa0..d7e61f93 100644 --- a/ansible/Dockerfile +++ b/ansible/Dockerfile @@ -1,14 +1,5 @@ FROM ubuntu:25.04 -ARG SIMPLE_STACK_UI_URL -ENV SIMPLE_STACK_UI_URL=$SIMPLE_STACK_UI_URL - -ARG SIMPLE_STACK_UI_USER -ENV SIMPLE_STACK_UI_USER=$SIMPLE_STACK_UI_USER - -ARG SIMPLE_STACK_UI_PASSWORD -ENV SIMPLE_STACK_UI_PASSWORD=$SIMPLE_STACK_UI_PASSWORD - ARG JAVA_VERSION=21 RUN apt-get update && apt-get install --no-install-recommends -y \ From 2ca52295ea5a472dd53d28d2fcc330f0ff84c9b3 Mon Sep 17 00:00:00 2001 From: Mathieu Garcia Date: Wed, 29 Oct 2025 22:15:45 +0100 Subject: [PATCH 13/13] chore(config): simplify ansible inventory and enable SSH pipelining --- ansible/ansible.cfg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index f1792ec2..248df012 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -4,7 +4,7 @@ collections_path = ./collections deprecation_warnings = False host_key_checking = False interpreter_python = auto_silent -inventory = inventory.yml,inventory.py +inventory = inventory.py library = ./library action_plugins = ./plugins/action lookup_plugins = ./plugins/lookup @@ -15,11 +15,11 @@ warn = false fact_caching = jsonfile fact_caching_connection = tmp/facts -# callback_plugins = ./plugins/callback -# callbacks_enabled = http_callback - [ssh_connection] retries = 5 +scp_if_ssh = True +ssh_args = -F ssh/config +pipelining = True [url_lookup] timeout = 20.0