diff --git a/README.md b/README.md index 167118d..90c910f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ -# nsnm-debops -repository debops roles to deploy a portainer with letsencrypt and a docker-swarm \ No newline at end of file + cuyper__cuyper_ssl: yes + + certbot -v certonly --manual --preferred-challenges dns -d domain.com + + add the dns record to your dns + + Press Enter to continue. + + \ No newline at end of file diff --git a/defaults/main.yml b/defaults/main.yml index 797f720..8332593 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,18 +1,21 @@ --- -cuyper__config_file: '/cuyper/config.yml' -cuyper__vhosts_file: '/cuyper/vhosts.yml' +cuyper__cuyper_ssl: "no" +cuyper__portainer_certbot_enabled: "no" +cuyper__hostname: 'domain.com' + +cuyper__knownzones: + - 'zone.com' + +cuyper__volumes_path: /live/volumes +cuyper__compose_path: /live/instances +cuyper__config_path: /live/config cuyper__dns_clientId: 3213 cuyper__dns_accessToken: my_access_toke cuyper__dns_type: 'dnssimple' cuyper__dns_name: 'ns1' +cuyper__dns_domain: "{{ cuyper__hostname }}" -cuyper__gateway_hostname: 'domain.com' - -cuyper__portainer_storage_path: /live/storage/portainer/data -cuyper__portainer_docker_sock: /var/run/docker.sock -cuyper__portainer_hostName: hostnamePortainer -cuyper__portainer_url: https://url cuyper__portainer_username: bot cuyper__portainer_password: xyas8oas cuyper__portainer_endPointId: 2 @@ -25,10 +28,33 @@ cuyper__smtp_from: 'support' cuyper__apikey: dlsya8d9yashdp9asy0ud -cuyper__knownzones: - - 'zone.com' +## NOT RECOMMENDED TO CHANGE THIS VARS +cuyper__repo: ssh://git@git.nsnm.net:222/nsnm/cupyer-hook.git -cuyper__cuyper_storage_path: "/live/storage/cuyper-hook" -cuyper__cuyper_instance_path: "/live/instances/cuyper-hook" -cuyper__cuyper_config_file: "/live/storage/cuyper-hook/cuyper-hook.yml" -cuyper__cuyper_vhosts_file: "/live/storage/cuyper-hook/vhosts.yml" \ No newline at end of file +cuyper__cuyper_storage_path: "{{ cuyper__volumes_path }}/cuyper" +cuyper__cuyper_instance_path: "{{ cuyper__compose_path }}/cuyper" +cuyper__cuyper_config_path: "{{ cuyper__config_path }}/cuyper" + +cuyper__gateway_hostname: portainer +cuyper__portainer_instance_path: "{{ cuyper__compose_path }}/portainer" +cuyper__portainer_storage_path: "{{ cuyper__volumes_path }}/portainer/data" + +cuyper__portainer_docker_sock: /var/run/docker.sock +cuyper__portainer_url: http://{{ cuyper__gateway_hostname }}:9000 + +swarm_dummy_interface: swarm0 +swarm_dummy_address: 10.255.0.1/32 +swarm_advertise_addr: 10.255.0.1 + +swarm_manager_group: swarm_managers +swarm_worker_group: swarm_workers + +swarm_networks: + - name: loadbalance-network + driver: overlay + attachable: true + internal: false + - name: internal_network + driver: overlay + attachable: false + internal: true \ No newline at end of file diff --git a/files/daemon.json b/files/daemon.json new file mode 100644 index 0000000..d914f94 --- /dev/null +++ b/files/daemon.json @@ -0,0 +1,3 @@ +{ + "live-restore": false +} diff --git a/tasks/certbot.yml b/tasks/certbot.yml new file mode 100644 index 0000000..4ed7f00 --- /dev/null +++ b/tasks/certbot.yml @@ -0,0 +1,52 @@ +--- +- name: "check if cert {{ cuyper__gateway_hostname }} exists" + stat: + path: "{{ cuyper__cuyper_storage_path }}/letsencrypt/live/{{ cuyper__gateway_hostname }}/fullchain.pem" + register: fullchainpem + +- name: "create nginx without ssl /hooks (cuyper) /portainer on {{ cuyper__cuyper_storage_path }}/nginx/nginx-hooks.conf" + ansible.builtin.template: + src: templates/nginx-hooks.conf.j2 + dest: "{{ cuyper__cuyper_config_path }}/nginx/nginx-hooks.conf" + when: cuyper__cuyper_ssl != "yes" or not fullchainpem.stat.exists + +- name: "setting as absent / deleting {{ cuyper__cuyper_storage_path }}/nginx/nginx-hooks-ssl.conf" + ansible.builtin.file: + path: "{{ cuyper__cuyper_config_path }}/nginx/nginx-hooks-ssl.conf" + state: absent + when: cuyper__cuyper_ssl != "yes" or not fullchainpem.stat.exists + +- name: Restart loabalancer + community.docker.docker_compose_v2: + project_src: "{{ cuyper__cuyper_instance_path }}" + state: restarted + register: output + when: cuyper__cuyper_ssl == "yes" and not fullchainpem.stat.exists + +- name: Run a simple command (argv) + community.docker.docker_compose_v2_run: + service: loadbalancer + argv: + - certbot + - "-d" + - "{{ cuyper__hostname }}" + - "--agree-tos" + - "--non-interactive" + - "--nginx" + chdir: "{{ cuyper__cuyper_instance_path }}" + project_src: "{{ cuyper__cuyper_instance_path }}" + register: result + when: cuyper__cuyper_ssl == "yes" and not fullchainpem.stat.exists + + +- name: "create nginx w ssl /hooks (cuyper) /portainer on {{ cuyper__cuyper_storage_path }}/nginx/nginx-hooks-ssl.conf" + ansible.builtin.template: + src: templates/nginx-hooks-ssl.conf.j2 + dest: "{{ cuyper__cuyper_config_path }}/nginx/nginx-hooks-ssl.conf" + when: cuyper__cuyper_ssl == "yes" and fullchainpem.stat.exists + +- name: "setting as absent / deleting {{ cuyper__cuyper_storage_path }}/nginx/nginx-hooks.conf" + ansible.builtin.file: + path: "{{ cuyper__cuyper_config_path }}/nginx/nginx-hooks.conf" + state: absent + when: cuyper__cuyper_ssl == "yes" and fullchainpem.stat.exists \ No newline at end of file diff --git a/tasks/cuyper-hook.yml b/tasks/cuyper-hook.yml index 6f748cc..3b3ad87 100644 --- a/tasks/cuyper-hook.yml +++ b/tasks/cuyper-hook.yml @@ -1,39 +1,66 @@ -- name: Create cuyper directory +--- +- name: "Create cuyper {{ cuyper__cuyper_instance_path }}" ansible.builtin.file: path: "{{ cuyper__cuyper_instance_path }}" state: directory mode: '0755' -- name: Clone cuyper-hook repository +- name: "Create cuyper storage path {{ cuyper__cuyper_storage_path }}" + ansible.builtin.file: + path: "{{ cuyper__cuyper_storage_path }}" + state: directory + mode: '0755' + +- name: "Create cuyper config path {{ cuyper__cuyper_config_path }}" + ansible.builtin.file: + path: "{{ cuyper__cuyper_config_path }}" + state: directory + mode: '0755' + +- name: "Create cuyper config path {{ cuyper__cuyper_config_path }}/nginx" + ansible.builtin.file: + path: "{{ cuyper__cuyper_config_path }}/nginx" + state: directory + mode: '0755' + +- name: "Clone {{ cuyper__repo }} repository {{ cuyper__cuyper_instance_path }}/cuyper-hook/" ansible.builtin.git: - repo: ssh://git@git.nsnm.net:222/nsnm/cupyer-hook.git + repo: "{{ cuyper__repo }}" dest: "{{ cuyper__cuyper_instance_path }}/cuyper-hook/" version: main update: yes -- name: create env on cuyper-hook +- name: "create env on {{ cuyper__cuyper_instance_path }}/.env" ansible.builtin.template: src: templates/env.cuyper-hook.j2 dest: "{{ cuyper__cuyper_instance_path }}/.env" -- name: create nginx hook file on loadbalancer +- name: "create config {{ cuyper__cuyper_config_path }}/config.yml" ansible.builtin.template: - src: templates/config.cupyer-hook.j2 - dest: "{{ cuyper__cuyper_config_file }}" + src: templates/config.cuyper-hook.j2 + dest: "{{ cuyper__cuyper_config_path }}/config.yml" -- name: Touch vhosts +- name: "Touch vhosts {{ cuyper__cuyper_config_path }}/vhosts.yml" ansible.builtin.file: - path: "{{ cuyper__cuyper_vhosts_file }}" + path: "{{ cuyper__cuyper_config_path }}/vhosts.yml" state: touch -- name: Copy docker-compose.yml - ansible.builtin.copy: - src: templates/docker-compose.cupyer-hook.j2 - dest: "{{ cuyper__cuyper_instance_path }}docker-compose.yml" +- name: "create docker-compose.yml on {{ cuyper__cuyper_instance_path }}/docker-compose.yml" + ansible.builtin.template: + src: templates/docker-compose.cuyper-hook.j2 + dest: "{{ cuyper__cuyper_instance_path }}/docker-compose.yml" mode: '0644' -- name: Start cuyper service +- name: "Create portainer storage path on {{ cuyper__portainer_storage_path }}" + ansible.builtin.file: + path: "{{ cuyper__portainer_storage_path }}" + state: directory + mode: '0755' + +- name: "docker compose restarted cuyper/portainer/loadbalancer on {{ cuyper__cuyper_instance_path }}" community.docker.docker_compose_v2: project_src: "{{ cuyper__cuyper_instance_path }}" - state: present \ No newline at end of file + state: present + build: always + recreate: always \ No newline at end of file diff --git a/tasks/docker.yml b/tasks/docker.yml index 460ae1d..3142fb8 100644 --- a/tasks/docker.yml +++ b/tasks/docker.yml @@ -1,6 +1,146 @@ --- +- name: Create cuyper directory + ansible.builtin.file: + path: "/etc/docker" + state: directory + mode: '0755' + +- name: Install Docker and Docker Compose + block: + - name: Ensure Docker and Docker Compose plugin are installed + apt: + name: + - docker-cli + - docker.io + - docker-compose + state: present + update_cache: yes + + - name: Ensure Docker service is running and enabled on boot + service: + name: docker + state: started + enabled: yes + - name: Install Python Docker SDK via APT (Debian) + ansible.builtin.apt: + name: + - python3-docker + - python3-requests + state: present + update_cache: true + - name: Configure docker ansible.builtin.copy: src: "daemon.json" dest: "/etc/docker/daemon.json" - mode: '0644' \ No newline at end of file + mode: '0644' + + +- name: Initialize Swarm + community.docker.docker_swarm: + state: present + advertise_addr: "{{ swarm_advertise_addr }}" + listen_addr: "{{ swarm_advertise_addr }}:2377" + + +- name: Create Swarm overlay networks + community.docker.docker_network: + name: "{{ item.name }}" + driver: "{{ item.driver | default('overlay') }}" + attachable: "{{ item.attachable | default(false) }}" + internal: "{{ item.internal | default(false) }}" + scope: swarm + state: present + loop: "{{ swarm_networks }}" + + + + + + + + + + + + + +# - name: Get manager join token +# community.docker.docker_swarm_info: +# delegate_to: "{{ swarm_init_hostdahkasjhkla }}" +# run_once: true +# register: swarm_info + +# - name: Check local Swarm state +# community.docker.docker_swarm_info: +# register: local_swarm_info +# failed_when: false + +# - name: Join worker to Swarm +# community.docker.docker_swarm: +# state: join +# remote_addrs: +# - "{{ swarm_manager_endpoint }}" +# join_token: "{{ hostvars[swarm_init_host].swarm_info.swarm_facts.JoinTokens.Worker }}" +# advertise_addr: "{{ swarm_advertise_addr }}" +# listen_addr: "0.0.0.0:2377" +# when: +# - inventory_hostname in groups[swarm_worker_group] +# - not local_swarm_info.swarm_facts.IsSwarmActive | default(false) + +# - name: Join manager to Swarm +# community.docker.docker_swarm: +# state: join +# remote_addrs: +# - "{{ swarm_manager_endpoint }}" +# join_token: "{{ hostvars[swarm_init_host].swarm_info.swarm_facts.JoinTokens.Manager }}" +# advertise_addr: "{{ swarm_advertise_addr }}" +# listen_addr: "0.0.0.0:2377" +# when: +# - inventory_hostname in groups[swarm_manager_group] +# - not local_swarm_info.swarm_facts.IsSwarmActive | default(false) + +# - name: Create Swarm overlay networks +# community.docker.docker_network: +# name: "{{ item.name }}" +# driver: "{{ item.driver | default('overlay') }}" +# attachable: "{{ item.attachable | default(false) }}" +# internal: "{{ item.internal | default(false) }}" +# scope: swarm +# state: present +# loop: "{{ swarm_networks }}" + + + + + + + +# - name: Ensure Swarm ports are allowed +# ansible.posix.firewalld: +# port: "{{ item }}" +# permanent: true +# immediate: true +# state: enabled +# loop: +# - 2377/tcp +# - 7946/tcp +# - 7946/udp +# - 4789/udp +# when: +# - ansible_facts.services['firewalld.service'] is defined +# - ansible_facts.services['firewalld.service'].state == "running" + +# - name: Check Swarm state +# community.docker.docker_swarm_info: +# register: swarm_info +# failed_when: false + +# - name: Initialize Docker Swarm +# community.docker.docker_swarm: +# state: present +# advertise_addr: "{{ swarm_advertise_addr }}" +# listen_addr: "0.0.0.0:2377" +# when: not swarm_info.swarm_facts.IsSwarmActive | default(false) + +# when: my_custom_role__enabled | d(True) | bool diff --git a/tasks/dummyinterface.yml b/tasks/dummyinterface.yml new file mode 100644 index 0000000..0e85fb1 --- /dev/null +++ b/tasks/dummyinterface.yml @@ -0,0 +1,59 @@ +--- +- name: Install iproute2 + become: true + ansible.builtin.apt: + name: iproute2 + state: present + update_cache: true + +- name: Create dummy interface + become: true + ansible.builtin.command: + cmd: "ip link add {{ swarm_dummy_interface }} type dummy" + register: dummy_create + failed_when: + - dummy_create.rc != 0 + - "'File exists' not in dummy_create.stderr" + changed_when: dummy_create.rc == 0 + +- name: Configure dummy interface address + become: true + ansible.builtin.command: + cmd: >- + ip address replace {{ swarm_dummy_address }} + dev {{ swarm_dummy_interface }} + +- name: Enable dummy interface + become: true + ansible.builtin.command: + cmd: "ip link set {{ swarm_dummy_interface }} up" + +- name: Configure persistent dummy interface + become: true + ansible.builtin.copy: + dest: /etc/systemd/network/10-{{ swarm_dummy_interface }}.netdev + mode: "0644" + content: | + [NetDev] + Name={{ swarm_dummy_interface }} + Kind=dummy + +- name: Configure persistent dummy address + become: true + ansible.builtin.copy: + dest: /etc/systemd/network/10-{{ swarm_dummy_interface }}.network + mode: "0644" + content: | + [Match] + Name={{ swarm_dummy_interface }} + + [Network] + Address={{ swarm_dummy_address }} + notify: Restart systemd-networkd + +- name: Restart systemd-networkd + become: true + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + enabled: true \ No newline at end of file diff --git a/tasks/main.yml b/tasks/main.yml index 54173be..a233e03 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,18 +1,19 @@ --- +- name: Setup dummy interface + ansible.builtin.import_tasks: 'dummyinterface.yml' + - name: Setup docker & docker swarm ansible.builtin.import_tasks: 'docker.yml' - name: Setup cuyper-hook ansible.builtin.include_tasks: 'cuyper-hook.yml' -- name: Setup loadbalancer - ansible.builtin.import_tasks: 'loadbalancer.yml' +- name: Setup certbot + ansible.builtin.include_tasks: 'certbot.yml' + when: cuyper__portainer_certbot_enabled == "yes" -- name: reload loabalancer nginx - ansible.builtin.command: docker compose -f /live/instances/cuyper/docker-compose.yml exec loadbalancer service nginx reload - -- name: Run certbot - ansible.builtin.command: "docker compose -f /live/instances/cuyper/docker-compose.yml exec loadbalancer certbot -d '{{ cuyper__hostname }}' --agree-tos --non-interactive --nginx" - -- name: reload loabalancer nginx - ansible.builtin.command: docker compose -f /live/instances/cuyper/docker-compose.yml exec loadbalancer service nginx reload \ No newline at end of file +- name: Restart loabalancer + community.docker.docker_compose_v2: + project_src: "{{ cuyper__cuyper_instance_path }}" + state: restarted + register: output \ No newline at end of file diff --git a/tasks/portainer.yml b/tasks/portainer.yml index 1bb598f..b230193 100644 --- a/tasks/portainer.yml +++ b/tasks/portainer.yml @@ -1,16 +1,23 @@ ---- -- name: Create portainer paths - ansible.builtin.file: - path: "/live/instances/portainer" - state: directory - mode: '0755' +# --- -- name: create docker compose portainer file - ansible.builtin.template: - src: templates/docker-compose.portainer.j2 - dest: /live/storage/portainer/docker-compose.yml +# - name: Create portainer instance +# ansible.builtin.file: +# path: "{{ cuyper__portainer_instance_path }}" +# state: directory +# mode: '0755' -- name: Start portainer docker-compose - community.docker.docker_compose_v2: - project_src: "/live/instances/portainer" - state: present \ No newline at end of file +# - name: Create portainer storage path +# ansible.builtin.file: +# path: "{{ cuyper__portainer_storage_path }}" +# state: directory +# mode: '0755' + +# - name: create docker compose portainer file +# ansible.builtin.template: +# src: templates/docker-compose.portainer.j2 +# dest: "{{cuyper__portainer_instance_path}}/docker-compose.yml" + +# - name: Start portainer docker-compose +# community.docker.docker_compose_v2: +# project_src: "/live/instances/portainer" +# state: present \ No newline at end of file diff --git a/templates/config.cuyper-hook.j2 b/templates/config.cuyper-hook.j2 new file mode 100644 index 0000000..5c12b4b --- /dev/null +++ b/templates/config.cuyper-hook.j2 @@ -0,0 +1,28 @@ +--- + +dns: + clientId: {{ cuyper__dns_clientId }} + accessToken: {{ cuyper__dns_accessToken }} + type: {{ cuyper__dns_type }} + name: {{ cuyper__dns_name }} + domain: {{ cuyper__dns_domain }} + +gateway: {{ cuyper__gateway_hostname }} + +portainer: + hostName: "{{ cuyper__gateway_hostname }}" + url: "{{ cuyper__portainer_url }}" + user: "{{ cuyper__portainer_user }}" + password: "{{ cuyper__portainer_password }}" + endPointId: {{ cuyper__portainer_endPointId }} + +smtp: + hostName: {{ cuyper__smtp_hostName }} + port: {{ cuyper__smtp_port }} + user: {{ cuyper__smtp_user }} + pass: {{ cuyper__smtp_pass }} + from: {{ cuyper__smtp_from }} + +apikey: {{ cuyper__apikey }} + +knownzones: {{ cuyper__knownzones }} \ No newline at end of file diff --git a/templates/docker-compose.cuyper-hook.j2 b/templates/docker-compose.cuyper-hook.j2 index f5e7de4..a440682 100644 --- a/templates/docker-compose.cuyper-hook.j2 +++ b/templates/docker-compose.cuyper-hook.j2 @@ -2,16 +2,29 @@ services: loadbalancer: build: cuyper-hook/ restart: always + env_file: .env ports: - "80:80" - "443:443" volumes: - "{{ cuyper__cuyper_storage_path }}/letsencrypt:/etc/letsencrypt" - - "{{ cuyper__cuyper_storage_path }}/nginx:/etc/nginx/conf.d/" + - "{{ cuyper__cuyper_config_path }}/nginx:/etc/nginx/conf.d/" + - "{{ cuyper__cuyper_config_path }}:/config" networks: loadbalance-network: internal_network: + portainer: + image: portainer/portainer-ce:2.45.1 + restart: always + volumes: + - "{{ cuyper__portainer_storage_path }}:/data" + - "{{ cuyper__portainer_docker_sock }}:/var/run/docker.sock" + networks: + internal_network: + aliases: + - portainer + networks: loadbalance-network: external: true diff --git a/templates/env.cuyper-hook.j2 b/templates/env.cuyper-hook.j2 index 5d7b964..f860eb4 100644 --- a/templates/env.cuyper-hook.j2 +++ b/templates/env.cuyper-hook.j2 @@ -1,2 +1,3 @@ -CONFIG_FILE={{ cuyper__config_file }} -VHOSTS_FILE={{ cuyper__vhosts_file }} \ No newline at end of file +CONFIG_FILE=/config/config.yml +VHOSTS_FILE=/config/vhosts.yml +TEMPLATE_PATH=/app/templates \ No newline at end of file diff --git a/templates/nginx-hooks-ssl.conf.j2 b/templates/nginx-hooks-ssl.conf.j2 new file mode 100644 index 0000000..da5aaa8 --- /dev/null +++ b/templates/nginx-hooks-ssl.conf.j2 @@ -0,0 +1,42 @@ +server { + listen 80; + listen [::]:80; + + server_name {{ cuyper__dns_domain }}; + + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + + http2 on; + + server_name {{ cuyper__dns_domain }}; + + ssl_certificate /etc/letsencrypt/live/{{ cuyper__dns_domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ cuyper__dns_domain }}/privkey.pem; + + server_name {{ cuyper__dns_domain }}; + access_log /var/log/nginx/{{ cuyper__dns_domain }}.access.log; + error_log /var/log/nginx/{{ cuyper__dns_domain }}.error.log; + + location /hooks/ { + proxy_pass http://localhost:3000/; + proxy_set_header Host $http_host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + } + + location /portainer/ { + proxy_pass http://portainer:9000/; + proxy_set_header Host $http_host; + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + } +} \ No newline at end of file diff --git a/templates/nginx-hooks.conf.j2 b/templates/nginx-hooks.conf.j2 index e33f42c..709a73e 100644 --- a/templates/nginx-hooks.conf.j2 +++ b/templates/nginx-hooks.conf.j2 @@ -2,9 +2,9 @@ server { listen [::]:80; listen 80; - server_name {{ cuyper__hostname }}; - access_log /var/log/nginx/{{ cuyper__hostname }}.access.log; - error_log /var/log/nginx/{{ cuyper__hostname }}.error.log; + server_name {{ cuyper__dns_domain }}; + access_log /var/log/nginx/{{ cuyper__dns_domain }}.access.log; + error_log /var/log/nginx/{{ cuyper__dns_domain }}.error.log; client_max_body_size 128M;