diff --git a/infrastructure/.gitignore b/infrastructure/.gitignore deleted file mode 100644 index 3d787a2..0000000 --- a/infrastructure/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/bin/ -/node_modules/ -.local/ \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/assets/20auto-upgrades b/infrastructure/docker-cluster/ansible/assets/20auto-upgrades new file mode 100644 index 0000000..7587020 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/assets/20auto-upgrades @@ -0,0 +1,4 @@ +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; +APT::Periodic::Unattended-Upgrade "1"; \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/assets/50unattended-upgrades b/infrastructure/docker-cluster/ansible/assets/50unattended-upgrades new file mode 100644 index 0000000..76dd90b --- /dev/null +++ b/infrastructure/docker-cluster/ansible/assets/50unattended-upgrades @@ -0,0 +1,9 @@ +Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-security"; +}; +Unattended-Upgrade::Package-Blacklist { +}; +Unattended-Upgrade::DevRelease "false"; +Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "false"; \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/group_vars/all.yml b/infrastructure/docker-cluster/ansible/group_vars/all.yml new file mode 100644 index 0000000..78c8f40 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/group_vars/all.yml @@ -0,0 +1,16 @@ +admin_user: 'admin' +# Passwords are generated by this command: ansible all -i localhost, -m debug -a 'msg={{ "" | password_hash("sha512", "") }}' +admin_password: '$6$rounds=656000$C43bVkou378rvtx8$J1KJZ6uw/KnLGc7nFSixAdsNDKUaSw.SGA38ZtZHwHB8eB5a/rRm1qJ8QR/wIxxylLJ0i7ByaW9CAjpbDYaQH/' # 7V#4kW9RvJfmxAnJV8zWbLrp%%SXRJ#kvGkowR98TY # salt=C43bVkou378rvtx8 +cephadm_password: '$6$rounds=656000$nE9mLm8AbEshiHr7$9HkqmR2Fr.0gKXwzml7CPhaWY9Ul4t0hdHiFldAI8ShISp6yB0NvehbTDnCKnfBO5UTL591IsNKda54WMa6MS.' # fM5$kZP4d!%$ZocBkhj85q^2GuwwN35YHxS!neC3AM # salt=nE9mLm8AbEshiHr7 +ssh_port: 22 +cephfs_name: "shared-fs" +ceph_osd_device: "/dev/sdb" +public_interface: 'eth0' +private_interface: 'enp7s0' +authorized_keys: + - 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtYTptTN9ggoy0aUKXmxaPKpclEn86jM7s5UtTw1JJI' +hetzner_api_key: "wqwAS4LINKOxgkyeECrHFOq1MRiOhFe236UyNBjs7fIhmOcKrXCmuncVPyt0S7Ac" + +ceph_volume: /mnt/cephfs +main_domain: avicenna.hamburg +traefik_public_net: traefik_public \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/inventory.ini b/infrastructure/docker-cluster/ansible/inventory.ini new file mode 100644 index 0000000..9d9f87f --- /dev/null +++ b/infrastructure/docker-cluster/ansible/inventory.ini @@ -0,0 +1,10 @@ +[all:children] +managers +workers + +[managers] +manager-node-1 ansible_host=188.245.52.124 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key +manager-node-2 ansible_host=116.202.18.42 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key +manager-node-3 ansible_host=167.235.227.234 ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=./.local/secure/private_key + +[workers] diff --git a/infrastructure/docker-cluster/ansible/playbook.yml b/infrastructure/docker-cluster/ansible/playbook.yml new file mode 100644 index 0000000..724ced4 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/playbook.yml @@ -0,0 +1,48 @@ +--- +# - name: Nodes initialisieren und härten +# hosts: all +# become: true + +# roles: +# - role: common +# tags: common +# - role: ssh_hardening +# tags: ssh +# - role: ufw_firewall +# tags: firewall +# - role: fail2ban +# tags: fail2ban +# handlers: +# - name: restart sshd +# ansible.builtin.service: +# name: ssh +# state: restarted +# - name: restart fail2ban +# ansible.builtin.service: +# name: fail2ban +# state: restarted +# - name: Setup Ceph Cluster and CephFS +# hosts: all +# become: true +# roles: +# - role: ceph_setup + +# - name: Docker Swarm initialisieren +# hosts: all +# become: true +# roles: +# - role: docker_swarm + +- name: Infrastruktur Dienste bereitstellen + hosts: all + gather_facts: true + roles: + - traefik + # - authentik + # # - dockge + - portainer + # - signoz + # - signoz-infra + # - leantime + - kestra + - hyperdx \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/ceph_setup/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/ceph_setup/tasks/main.yml new file mode 100644 index 0000000..91f7252 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/ceph_setup/tasks/main.yml @@ -0,0 +1,93 @@ +--- +- name: CEPH | Private IP des ersten Managers ermitteln + ansible.builtin.set_fact: + ceph_bootstrap_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}" + when: inventory_hostname == groups['managers'][0] + +- name: CEPH | Cluster auf dem ersten Manager initialisieren (Bootstrap) + ansible.builtin.command: + cmd: "cephadm bootstrap --mon-ip {{ ceph_bootstrap_ip }}" + creates: /etc/ceph/ceph.conf + when: inventory_hostname == groups['managers'][0] + +- name: CEPH | Öffentlichen SSH-Schlüssel von cephadm abrufen + ansible.builtin.command: "cephadm shell -- ceph cephadm get-pub-key" + register: cephadm_pub_key + changed_when: false + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | Öffentlichen Schlüssel von cephadm auf allen Knoten für root verteilen + ansible.posix.authorized_key: + user: root + key: "{{ hostvars[groups['managers'][0]]['cephadm_pub_key'].stdout }}" + state: present + key_options: 'no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty' + +- name: CEPH | Andere Knoten zum Ceph-Cluster hinzufügen + ansible.builtin.command: + cmd: "ceph orch host add {{ item }} {{ hostvars[item]['ansible_' + private_interface]['ipv4']['address'] }}" + loop: "{{ groups['all'] }}" + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | Prüfen, ob bereits OSDs (Speichergeräte) vorhanden sind + ansible.builtin.command: "ceph osd ls" + register: existing_osds + changed_when: false + failed_when: false + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | Spezifische Festplatte ({{ ceph_osd_device }}) auf jedem Knoten als OSD hinzufügen + ansible.builtin.command: "ceph orch daemon add osd {{ item }}:{{ ceph_osd_device }}" + loop: "{{ groups['all'] }}" + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + when: existing_osds.stdout | length == 0 + +- name: CEPH | Prüfen, ob CephFS bereits existiert + ansible.builtin.command: "ceph fs ls -f json" + register: cephfs_list + changed_when: false + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | CephFS Pools und Dateisystem erstellen, falls nicht vorhanden + block: + - name: Metadaten-Pool für CephFS erstellen + ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_metadata" + - name: Daten-Pool für CephFS erstellen + ansible.builtin.command: "ceph osd pool create {{ cephfs_name }}_data" + - name: CephFS-Dateisystem erstellen + ansible.builtin.command: "ceph fs new {{ cephfs_name }} {{ cephfs_name }}_metadata {{ cephfs_name }}_data" + when: cephfs_list.stdout | from_json | length == 0 + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | Metadaten-Server (MDS) für CephFS starten + ansible.builtin.command: "ceph orch apply mds {{ cephfs_name }} --placement=2" + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + when: cephfs_list.stdout | from_json | length == 0 + +- name: CEPH | Ceph Admin-Schlüssel für das Mounten abrufen + ansible.builtin.command: "ceph auth get-key client.admin" + register: ceph_admin_key + changed_when: false + delegate_to: "{{ groups['managers'][0] }}" + run_once: true + +- name: CEPH | Mount-Punkt für CephFS erstellen + ansible.builtin.file: + path: /mnt/cephfs + state: directory + mode: '0755' + +- name: CEPH | CephFS auf allen Knoten mounten (und in /etc/fstab eintragen) + ansible.posix.mount: + path: /mnt/cephfs + src: "{{ hostvars[groups['managers'][0]]['ceph_bootstrap_ip'] }}:/" + fstype: ceph + opts: "name=admin,secret={{ ceph_admin_key.stdout }}" + state: mounted \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/common/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/common/tasks/main.yml new file mode 100644 index 0000000..71a3802 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/common/tasks/main.yml @@ -0,0 +1,113 @@ +--- +- name: COMMON | Systempakete aktualisieren und upgraden + ansible.builtin.apt: + update_cache: true + upgrade: dist + autoremove: true + autoclean: true + +- name: COMMON | Notwendige Pakete installieren + ansible.builtin.apt: + name: + - ufw + - fail2ban + - unattended-upgrades + - apt-listchanges + - docker-ce + - python3-pip + - chrony + - lvm2 + - cephadm + - ceph-common + state: present + +- name: COMMON | Chrony Dienst starten und aktivieren + ansible.builtin.service: + name: chronyd + state: started + enabled: true + +- name: COMMON | Docker Dienst starten und aktivieren + ansible.builtin.service: + name: docker + state: started + enabled: true + +- name: COMMON | Einen dedizierten Admin-Benutzer erstellen + ansible.builtin.user: + name: "{{ admin_user }}" + password: "{{ admin_password}}" + shell: /bin/bash + groups: sudo,docker + append: true + state: present + +- name: COMMON | SSH-Schlüssel für den Admin-Benutzer einrichten + ansible.posix.authorized_key: + user: "{{ admin_user }}" + key: "{{ item }}" + state: present + with_items: "{{ authorized_keys }}" + +- name: COMMON | Ensure group "cephadm" exists + group: + name: cephadm + state: present + +- name: COMMON | cephadm-Benutzer erstellen + ansible.builtin.user: + name: "cephadm" + password: "{{ cephadm_password }}" + shell: /bin/bash + groups: sudo,docker + append: yes + state: present + +- name: COMMON | .ssh Verzeichnis für cephadm-Benutzer erstellen + ansible.builtin.file: + path: /home/cephadm/.ssh + state: directory + +- name: COMMON | Passwortloses Sudo für cephadm-Benutzer erlauben + ansible.builtin.copy: + dest: "/etc/sudoers.d/91-cephadm-nopasswd" + content: "cephadm ALL=(ALL) NOPASSWD: ALL" + mode: '0440' + validate: 'visudo -cf %s' + +- name: COMMON | ed25519 SSH-Schlüssel für cephadm-Benutzer generieren (nur auf dem ersten Manager) + community.crypto.openssh_keypair: + path: /home/cephadm/.ssh/id_ed25519 + type: ed25519 + owner: cephadm + group: cephadm + mode: '0600' + when: inventory_hostname == groups['managers'][0] + +- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm abrufen + ansible.builtin.slurp: + src: /home/cephadm/.ssh/id_ed25519.pub + register: cephadm_ssh_pub_key + when: inventory_hostname == groups['managers'][0] + +- name: COMMON | Öffentlichen SSH-Schlüssel von cephadm auf allen Knoten verteilen + ansible.posix.authorized_key: + user: cephadm + key: "{{ hostvars[groups['managers'][0]]['cephadm_ssh_pub_key']['content'] | b64decode }}" + state: present + +- name: COMMON | Automatische Sicherheitsupdates konfigurieren + ansible.builtin.copy: + src: assets/50unattended-upgrades + dest: /etc/apt/apt.conf.d/50unattended-upgrades + owner: root + group: root + mode: '0644' + +- name: COMMON | Periodische Auto-Updates aktivieren + ansible.builtin.copy: + src: assets/20auto-upgrades + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: '0644' \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/docker_swarm/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/docker_swarm/tasks/main.yml new file mode 100644 index 0000000..f18b250 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/docker_swarm/tasks/main.yml @@ -0,0 +1,58 @@ +--- +- name: SWARM | Ensure Docker SDK for Python is installed + ansible.builtin.apt: + name: python3-docker + state: present + +- name: SWARM | Get interface IP address for the manager + ansible.builtin.set_fact: + manager_ip: "{{ hostvars[inventory_hostname]['ansible_' + private_interface]['ipv4']['address'] }}" + when: inventory_hostname == groups['managers'][0] + +- name: SWARM | Initialize the Docker Swarm + community.docker.docker_swarm: + state: present + advertise_addr: "{{ manager_ip }}" + when: inventory_hostname == groups['managers'][0] + register: swarm_init_result + +- name: SWARM | Get the join tokens + community.docker.docker_swarm_info: + register: swarm_info + when: inventory_hostname == groups['managers'][0] + +- name: SWARM | Verify that join tokens were fetched + ansible.builtin.assert: + that: + - swarm_info is defined + - swarm_info.swarm_facts is defined + - swarm_info.swarm_facts.JoinTokens.Manager is defined + - swarm_info.swarm_facts.JoinTokens.Worker is defined + fail_msg: "Konnte die Join-Tokens vom Swarm Manager nicht abrufen. Ist der Swarm korrekt initialisiert?" + success_msg: "Join-Tokens erfolgreich abgerufen." + when: inventory_hostname == groups['managers'][0] + +- name: SWARM | Join manager nodes to the Swarm + community.docker.docker_swarm: + state: join + remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ] + join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Manager'] }}" + when: inventory_hostname in groups['managers'] + +- name: SWARM | Join worker nodes to the Swarm + community.docker.docker_swarm: + state: join + remote_addrs: [ "{{ hostvars[groups['managers'][0]]['manager_ip'] }}:2377" ] + join_token: "{{ hostvars[groups['managers'][0]]['swarm_info']['swarm_facts']['JoinTokens']['Worker'] }}" + when: inventory_hostname in groups['workers'] + +- name: SWARM | Verify Swarm Cluster State (run on manager) + ansible.builtin.command: docker node ls + register: swarm_nodes + changed_when: false + when: inventory_hostname == groups['managers'][0] + +- name: SWARM | Display cluster state + ansible.builtin.debug: + msg: "{{ swarm_nodes.stdout_lines }}" + when: inventory_hostname == groups['managers'][0] \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/fail2ban/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/fail2ban/tasks/main.yml new file mode 100644 index 0000000..275505d --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/fail2ban/tasks/main.yml @@ -0,0 +1,9 @@ +--- +- name: FAIL2BAN | Eine lokale Jail-Konfiguration erstellen + ansible.builtin.template: + src: jail.local.j2 + dest: /etc/fail2ban/jail.local + owner: root + group: root + mode: '0644' + notify: restart fail2ban \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/fail2ban/templates/jail.local.j2 b/infrastructure/docker-cluster/ansible/roles/fail2ban/templates/jail.local.j2 new file mode 100644 index 0000000..0039dca --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/fail2ban/templates/jail.local.j2 @@ -0,0 +1,8 @@ +[DEFAULT] +bantime = 1h +findtime = 10m +maxretry = 5 + +[sshd] +enabled = true +port = {{ ssh_port }} \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/hyperdx/files/config.xml b/infrastructure/docker-cluster/ansible/roles/hyperdx/files/config.xml new file mode 100644 index 0000000..84eca4b --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/hyperdx/files/config.xml @@ -0,0 +1,175 @@ + + + + debug + true + + + + + 0.0.0.0 + 8123 + 9000 + ch-server + 9009 + + 4096 + 64 + 100 + 8589934592 + 5368709120 + + /var/lib/clickhouse/ + /var/lib/clickhouse/tmp/ + /var/lib/clickhouse/user_files/ + + + + users.xml + + + + default + default + UTC + false + + + + /metrics + 9363 + true + true + true + true + + + + + system + query_log
+ 7500 +
+ + + + system + metric_log
+ 7500 + 1000 +
+ + + + system + asynchronous_metric_log
+ + 7000 +
+ + + + + + engine MergeTree + partition by toYYYYMM(finish_date) + order by (finish_date, finish_time_us, trace_id) + + system + opentelemetry_span_log
+ 7500 +
+ + + + + system + crash_log
+ + + 1000 +
+ + + + system + processors_profile_log
+ + toYYYYMM(event_date) + 7500 +
+ + + + system + part_log
+ toYYYYMM(event_date) + 7500 +
+ + + + system + trace_log
+ + toYYYYMM(event_date) + 7500 +
+ + + + system + query_thread_log
+ toYYYYMM(event_date) + 7500 +
+ + + + system + query_views_log
+ toYYYYMM(event_date) + 7500 +
+ + + + + + ch-server + 9000 + + + + + + + /clickhouse/task_queue/ddl + + + /var/lib/clickhouse/format_schemas/ +
diff --git a/infrastructure/docker-cluster/ansible/roles/hyperdx/files/users.xml b/infrastructure/docker-cluster/ansible/roles/hyperdx/files/users.xml new file mode 100644 index 0000000..80c8d4f --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/hyperdx/files/users.xml @@ -0,0 +1,51 @@ + + + + + 10000000000 + 0 + in_order + 1 + + + + + + + default + + ::/0 + + default + + + api + default + + ::/0 + + default + + + worker + default + + ::/0 + + default + + + + + + + 3600 + 0 + 0 + 0 + 0 + 0 + + + + diff --git a/infrastructure/docker-cluster/ansible/roles/hyperdx/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/hyperdx/tasks/main.yml new file mode 100644 index 0000000..e0e3e34 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/hyperdx/tasks/main.yml @@ -0,0 +1,46 @@ +--- +- name: HYPERDX | Verzeichnisse erstellen + ansible.builtin.file: + path: "/mnt/cephfs/hyperdx/{{ item.path }}" + state: directory + owner: "{{ item.uid }}" + group: "{{ item.gid }}" + mode: '0755' + recurse: yes + loop: + - { path: 'mongo', uid: 999, gid: 999 } # MongoDB Standard + - { path: 'clickhouse/data', uid: 101, gid: 101 } # ClickHouse Standard + - { path: 'clickhouse/logs', uid: 101, gid: 101 } + - { path: 'clickhouse/config', uid: 101, gid: 101 } + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: HYPERDX | ClickHouse Konfiguration kopieren + ansible.builtin.copy: + src: "{{ item }}" + dest: "/mnt/cephfs/hyperdx/clickhouse/config/" + owner: 101 + group: 101 + mode: '0644' + loop: + - files/config.xml # Lokal in deinem Ansible Repo + - files/users.xml + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: HYPERDX | Generate Compose file + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: '{{ data_dir }}/hyperdx.yml' + mode: 0644 + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: HYPERDX | Deploy stack + community.docker.docker_stack: + state: present + name: hyperdx + compose: + - '{{ data_dir }}/hyperdx.yml' + delegate_to: "{{ groups['managers'][0] }}" + run_once: true diff --git a/infrastructure/docker-cluster/ansible/roles/hyperdx/templates/docker-compose.yml.j2 b/infrastructure/docker-cluster/ansible/roles/hyperdx/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..9fc8b24 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/hyperdx/templates/docker-compose.yml.j2 @@ -0,0 +1,100 @@ +version: '3.9' + +services: + db: + image: mongo:5.0.14-focal + volumes: + - "{{ data_dir }}/mongo:/data/db" + networks: + - internal + deploy: + mode: replicated + replicas: 1 + # placement: + # constraints: [node.role == worker] # DBs besser auf Workern lassen wenn möglich + + otel-collector: + image: "clickhouse/clickstack-otel-collector:2" + environment: + CLICKHOUSE_ENDPOINT: 'tcp://ch-server:9000?dial_timeout=10s' + HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE: "default" + HYPERDX_LOG_LEVEL: "{{ hyperdx_log_level | default('info') }}" + OPAMP_SERVER_URL: 'http://app:{{ hyperdx_opamp_port | default(4320) }}' + # Ingestion Ports direkt via Swarm Mesh exponieren für Performance + ports: + - "4317:4317" # OTLP gRPC + - "4318:4318" # OTLP HTTP + # - "8888:8888" # Metrics (optional) + networks: + - internal + - shared-observability + - traefik_public + deploy: + mode: replicated + replicas: 1 + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik_public" + - "traefik.http.routers.otel-collector.rule=Host(`{{ otlp_domain }}`)" + - "traefik.http.routers.otel-collector.entrypoints=https" + - "traefik.http.routers.otel-collector.tls.certresolver=main" + - "traefik.http.services.otel-collector.loadbalancer.server.port=4318" + + app: + image: "hyperdx/hyperdx:2" + environment: + # URLs anpassen für Traefik Erreichbarkeit + FRONTEND_URL: "https://{{ hdx_domain }}" + HYPERDX_APP_URL: "https://{{ hdx_domain }}" + + HYPERDX_API_KEY: "{{ hyperdx_api_key }}" + HYPERDX_API_PORT: "{{ hyperdx_api_port | default(8000) }}" + HYPERDX_APP_PORT: "{{ hyperdx_app_port | default(8080) }}" + HYPERDX_LOG_LEVEL: "{{ hyperdx_log_level | default('info') }}" + MINER_API_URL: 'http://miner:5123' # Falls miner benötigt wird (in original compose nicht definiert?) + MONGO_URI: 'mongodb://db:27017/hyperdx' + SERVER_URL: "http://127.0.0.1:{{ hyperdx_api_port | default(8000) }}" + OPAMP_PORT: "{{ hyperdx_opamp_port | default(4320) }}" + OTEL_EXPORTER_OTLP_ENDPOINT: 'http://otel-collector:4318' + OTEL_SERVICE_NAME: 'hdx-oss-app' + USAGE_STATS_ENABLED: "{{ usage_stats_enabled | default('false') }}" + # Clickhouse Connection String (Default User/Pass from Clickhouse Image) + DEFAULT_CONNECTIONS: >- + [{"name":"Local ClickHouse","host":"http://ch-server:8123","username":"default","password":""}] + DEFAULT_SOURCES: '{{ hyperdx_default_sources | to_json }}' + networks: + - internal + - traefik_public + deploy: + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik_public" + - "traefik.http.routers.hyperdx.rule=Host(`{{ subdomain }}.{{ main_domain }}`)" + - "traefik.http.routers.hyperdx.entrypoints=https" + - "traefik.http.routers.hyperdx.tls.certresolver=main" + - "traefik.http.services.hyperdx.loadbalancer.server.port={{ hyperdx_app_port | default(8080) }}" + + ch-server: + image: clickhouse/clickhouse-server:25.6-alpine + environment: + CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1 + volumes: + - "{{ data_dir }}/clickhouse/config/config.xml:/etc/clickhouse-server/config.xml" + - "{{ data_dir }}/clickhouse/config/users.xml:/etc/clickhouse-server/users.xml" + - "{{ data_dir }}/clickhouse/data:/var/lib/clickhouse" + - "{{ data_dir }}/clickhouse/logs:/var/log/clickhouse-server" + deploy: + mode: replicated + replicas: 1 + # placement: + # constraints: [node.role == worker] + networks: + - internal + +networks: + internal: + driver: overlay + traefik_public: + external: true + shared-observability: + external: true \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/hyperdx/vars/main.yml b/infrastructure/docker-cluster/ansible/roles/hyperdx/vars/main.yml new file mode 100644 index 0000000..cb95a6e --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/hyperdx/vars/main.yml @@ -0,0 +1,107 @@ +data_dir: "{{ ceph_volume }}/hyperdx" +subdomain: "hdx" + +hdx_domain: "{{ subdomain }}.{{ main_domain }}" +otlp_domain: "otlp.{{ main_domain }}" + +# Generiere einen sicheren Key: `openssl rand -hex 16` +hyperdx_api_key: "2bb00477e073d6e2b72e310c39785fa1" +hyperdx_api_port: 8000 +hyperdx_app_port: 8080 +hyperdx_log_level: "info" +hyperdx_opamp_port: 4320 + +usage_stats_enabled: "false" + +# vars/main.yml + +# ... (deine anderen Variablen) ... + +# Definition der Datenquellen für das Frontend +hyperdx_default_sources: + - name: "Logs" + kind: "log" + from: + databaseName: "default" + tableName: "otel_logs" + timestampValueExpression: "TimestampTime" + displayedTimestampValueExpression: "Timestamp" + implicitColumnExpression: "Body" + serviceNameExpression: "ServiceName" + bodyExpression: "Body" + eventAttributesExpression: "LogAttributes" + resourceAttributesExpression: "ResourceAttributes" + defaultTableSelectExpression: "Timestamp,ServiceName,SeverityText,Body" + severityTextExpression: "SeverityText" + traceIdExpression: "TraceId" + spanIdExpression: "SpanId" + connection: "Local ClickHouse" + traceSourceId: "Traces" + sessionSourceId: "Sessions" + metricSourceId: "Metrics" + + - name: "Traces" + kind: "trace" + from: + databaseName: "default" + tableName: "otel_traces" + timestampValueExpression: "Timestamp" + displayedTimestampValueExpression: "Timestamp" + implicitColumnExpression: "SpanName" + serviceNameExpression: "ServiceName" + bodyExpression: "SpanName" + eventAttributesExpression: "SpanAttributes" + resourceAttributesExpression: "ResourceAttributes" + defaultTableSelectExpression: "Timestamp,ServiceName,StatusCode,round(Duration/1e6),SpanName" + traceIdExpression: "TraceId" + spanIdExpression: "SpanId" + durationExpression: "Duration" + durationPrecision: 9 + parentSpanIdExpression: "ParentSpanId" + spanNameExpression: "SpanName" + spanKindExpression: "SpanKind" + statusCodeExpression: "StatusCode" + statusMessageExpression: "StatusMessage" + connection: "Local ClickHouse" + logSourceId: "Logs" + sessionSourceId: "Sessions" + metricSourceId: "Metrics" + + - name: "Metrics" + kind: "metric" + from: + databaseName: "default" + tableName: "" + timestampValueExpression: "TimeUnix" + resourceAttributesExpression: "ResourceAttributes" + metricTables: + gauge: "otel_metrics_gauge" + histogram: "otel_metrics_histogram" + sum: "otel_metrics_sum" + _id: "682586a8b1f81924e628e808" + id: "682586a8b1f81924e628e808" + connection: "Local ClickHouse" + logSourceId: "Logs" + traceSourceId: "Traces" + sessionSourceId: "Sessions" + + - name: "Sessions" + kind: "session" + from: + databaseName: "default" + tableName: "hyperdx_sessions" + timestampValueExpression: "TimestampTime" + displayedTimestampValueExpression: "Timestamp" + implicitColumnExpression: "Body" + serviceNameExpression: "ServiceName" + bodyExpression: "Body" + eventAttributesExpression: "LogAttributes" + resourceAttributesExpression: "ResourceAttributes" + defaultTableSelectExpression: "Timestamp,ServiceName,SeverityText,Body" + severityTextExpression: "SeverityText" + traceIdExpression: "TraceId" + spanIdExpression: "SpanId" + connection: "Local ClickHouse" + logSourceId: "Logs" + traceSourceId: "Traces" + metricSourceId: "Metrics" \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/kestra/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/kestra/tasks/main.yml new file mode 100644 index 0000000..fba5cbe --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/kestra/tasks/main.yml @@ -0,0 +1,47 @@ +--- +- name: KESTRA | Ensure data directory + ansible.builtin.file: + path: '{{ data_dir }}/data/data' + state: directory + mode: '0755' + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: KESTRA | Ensure db directory + ansible.builtin.file: + path: '{{ data_dir }}/data/db' + state: directory + mode: '0755' + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: KESTRA | Konfigurationsdatei für tmpfiles.d erstellen + ansible.builtin.copy: + content: "d /tmp/kestra-wd 0755 root root -" + dest: /etc/tmpfiles.d/kestra-wd.conf + owner: root + group: root + mode: '0644' + +- name: KESTRA | Create Kestra working directory + ansible.builtin.file: + path: /tmp/kestra-wd + state: directory + mode: '0755' + +- name: KESTRA | Generate Compose file + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: '{{ data_dir }}/kestra.yml' + mode: 0644 + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: KESTRA | Deploy stack + community.docker.docker_stack: + state: present + name: kestra + compose: + - /mnt/cephfs/kestra/kestra.yml + delegate_to: "{{ groups['managers'][0] }}" + run_once: true \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/kestra/templates/docker-compose.yml.j2 b/infrastructure/docker-cluster/ansible/roles/kestra/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..6f39b1d --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/kestra/templates/docker-compose.yml.j2 @@ -0,0 +1,92 @@ +networks: + internal: + {{ traefik_public_net }}: + external: true + +services: + postgres: + image: postgres:17 + volumes: + - {{ data_dir }}/data/db:/var/lib/postgresql/data + environment: + POSTGRES_DB: kestra + POSTGRES_USER: kestra + POSTGRES_PASSWORD: k3str4 + healthcheck: + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] + interval: 30s + timeout: 10s + retries: 10 + networks: + - internal + deploy: + mode: replicated + replicas: 1 + + kestra: + image: kestra/kestra:v0.24.2 + entrypoint: /bin/bash + # Note that this is meant for development only. Refer to the documentation for production deployments of Kestra which runs without a root user. + user: "root" + command: + - -c + - /app/kestra server standalone --worker-thread=128 + volumes: + - {{ data_dir }}/data/data:/app/storage + - /var/run/docker.sock:/var/run/docker.sock + - /tmp/kestra-wd:/tmp/kestra-wd + environment: + KESTRA_CONFIGURATION: | + datasources: + postgres: + url: jdbc:postgresql://postgres:5432/kestra + driverClassName: org.postgresql.Driver + username: kestra + password: k3str4 + kestra: + tutorialFlows: + enabled: false + traces: + root: DEFAULT + micronaut: + metrics: + export: + otlp: + enabled: true + url: http://signoz_otel-collector:4318/v1/metrics + otel: + traces: + exporter: otlp + exporter: + otlp: + endpoint: http://signoz_otel-collector:4318 + server: + basic-auth: + username: ma@coachhamburg.com + password: "igyozi9B87yTeiQ6z2sbe8Y4aQLJV58jdaCNu" + repository: + type: postgres + storage: + type: local + local: + base-path: "/app/storage" + queue: + type: postgres + tasks: + tmp-dir: + path: /tmp/kestra-wd/tmp + url: http://localhost:8080/ + networks: + - {{ traefik_public_net }} + - internal + deploy: + mode: replicated + replicas: 1 + labels: + - "traefik.enable=true" + - "traefik.swarm.network={{ traefik_public_net }}" + - "traefik.http.routers.kestra.rule=Host(`{{ subdomain }}.{{ main_domain }}`)" + - "traefik.http.routers.kestra.entrypoints=https" + - "traefik.http.routers.kestra.tls=true" + - "traefik.http.routers.kestra.tls.certresolver=main" + - "traefik.http.services.kestra.loadbalancer.server.port=8080" diff --git a/infrastructure/docker-cluster/ansible/roles/kestra/vars/main.yml b/infrastructure/docker-cluster/ansible/roles/kestra/vars/main.yml new file mode 100644 index 0000000..269e7f6 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/kestra/vars/main.yml @@ -0,0 +1,2 @@ +subdomain: workflow +data_dir: "{{ ceph_volume }}/kestra" \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/portainer/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/portainer/tasks/main.yml new file mode 100644 index 0000000..ece4462 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/portainer/tasks/main.yml @@ -0,0 +1,25 @@ +--- +- name: PORTAINER | Ensure data directories + ansible.builtin.file: + path: '{{ data_dir }}/data' + state: directory + mode: '0755' + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: PORTAINER | Generate Compose file + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: '{{ data_dir }}/portainer.yml' + mode: 0644 + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: PORTAINER | Deploy stack + community.docker.docker_stack: + state: present + name: portainer + compose: + - '{{ data_dir }}/portainer.yml' + delegate_to: "{{ groups['managers'][0] }}" + run_once: true diff --git a/infrastructure/docker-cluster/ansible/roles/portainer/templates/docker-compose.yml.j2 b/infrastructure/docker-cluster/ansible/roles/portainer/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..6c69e60 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/portainer/templates/docker-compose.yml.j2 @@ -0,0 +1,39 @@ +version: '3.2' + +services: + agent: + image: portainer/agent:2.33.5 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /var/lib/docker/volumes:/var/lib/docker/volumes + networks: + - {{ traefik_public_net }} + deploy: + mode: global + placement: + constraints: [node.platform.os == linux] + + portainer: + image: portainer/portainer-ce:2.33.5 + command: -H tcp://portainer_agent:9001 --tlsskipverify + volumes: + - {{ data_dir }}/data:/data + networks: + - {{ traefik_public_net }} + deploy: + mode: replicated + replicas: 1 + labels: + - "traefik.enable=true" + - "traefik.swarm.network={{ traefik_public_net }}" + - "traefik.http.routers.portainer.rule=Host(`{{ subdomain }}.{{ main_domain }}`)" + - "traefik.http.routers.portainer.entrypoints=https" + - "traefik.http.routers.portainer.tls=true" + - "traefik.http.routers.portainer.tls.certresolver=main" + - "traefik.http.services.portainer.loadbalancer.server.port=9000" + placement: + constraints: [node.hostname == manager-node-3] + +networks: + {{ traefik_public_net }}: + external: true \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/portainer/vars/main.yml b/infrastructure/docker-cluster/ansible/roles/portainer/vars/main.yml new file mode 100644 index 0000000..4f605e4 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/portainer/vars/main.yml @@ -0,0 +1,2 @@ +subdomain: port +data_dir: "{{ ceph_volume }}/portainer" \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/ssh_hardening/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/ssh_hardening/tasks/main.yml new file mode 100644 index 0000000..8c456d8 --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/ssh_hardening/tasks/main.yml @@ -0,0 +1,30 @@ +--- +- name: SSH | Ensure privilege separation directory exists + ansible.builtin.file: + path: /run/sshd + state: directory + mode: '0755' + +- name: SSH | Root-Login nur mit Schlüssel erlauben + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PermitRootLogin' + line: 'PermitRootLogin prohibit-password' + validate: 'sshd -t -f %s' + notify: restart sshd + +- name: SSH | Passwort-Authentifizierung deaktivieren + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PasswordAuthentication' + line: 'PasswordAuthentication no' + validate: 'sshd -t -f %s' + notify: restart sshd + +- name: SSH | Leere Passwörter verbieten + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?PermitEmptyPasswords' + line: 'PermitEmptyPasswords no' + validate: 'sshd -t -f %s' + notify: restart sshd \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/dynamic/http.middlewares.authentik.toml b/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/dynamic/http.middlewares.authentik.toml new file mode 100644 index 0000000..8db552f --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/dynamic/http.middlewares.authentik.toml @@ -0,0 +1,18 @@ +[http] + [http.middlewares] + [http.middlewares.authentik.forwardAuth] + address = "http://authentik_server:9000/outpost.goauthentik.io/auth/traefik" + trustForwardHeader = true + authResponseHeaders = [ + "X-authentik-username", + "X-authentik-groups", + "X-authentik-email", + "X-authentik-name", + "X-authentik-uid", + "X-authentik-jwt", + "X-authentik-meta-jwks", + "X-authentik-meta-outpost", + "X-authentik-meta-provider", + "X-authentik-meta-app", + "X-authentik-meta-version" + ] \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/traefik.toml b/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/traefik.toml new file mode 100644 index 0000000..8014a6d --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/config/traefik.toml @@ -0,0 +1,80 @@ +[global] + checkNewVersion = true + sendAnonymousUsage = false + +[experimental] + otlpLogs = true + +[core] + defaultRuleSyntax = "v2" + +[accessLog] + filePath = "/logs/access.log" + format = "json" + +# Enable the Dashboard +[api] + dashboard = true + +# Write out Traefik logs +[log] + level = "INFO" + format = "json" + filePath = "/logs/traefik.log" + # [log.otlp.http] + # endpoint = "http://signoz_otel-collector:4318/v1/logs" + +[entryPoints] + [entryPoints.http] + address = ":80" + [entryPoints.http.http.redirections.entryPoint] + to = "https" + scheme = "https" + + [entryPoints.https] + address = ":443" + # [entryPoints.https.http.tls] + # certResolver = "main" + +# OTel +# [tracing] +# serviceName = "traefik" +# [tracing.otlp.http] +# endpoint = "http://signoz_otel-collector:4318/v1/traces" +# [tracing.otlp.http.tls] +# insecureSkipVerify = true + +# # Metrics +# [metrics] +# addInternals = false +# [metrics.otlp] +# serviceName = "traefik" +# addEntryPointsLabels = true +# addRoutersLabels = true +# addServicesLabels = true +# [metrics.otlp.http] +# endpoint = "http://signoz_otel-collector:4318/v1/metrics" + # [metrics.otlp.grpc] + # endpoint = "monitoring_alloy:4317" + # insecure = true + +# Let's Encrypt +[certificatesResolvers.main.acme] + email = "ma@coachhamburg.com" + storage = "acme.json" + # uncomment to use staging CA for testing + # caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" + # [certificatesResolvers.main.acme.tlsChallenge] + [certificatesResolvers.main.acme.dnsChallenge] + provider = "hetzner" + # Uncomment to use HTTP validation, like a caveman! + # [certificatesResolvers.main.acme.httpChallenge] + # entryPoint = "http" + +[providers] + [providers.swarm] + endpoint = "unix:///var/run/docker.sock" + exposedByDefault = false + [providers.file] + directory = "/etc/traefik/dynamic" + watch = true diff --git a/infrastructure/ansible/resources/traefik/data/acme.json b/infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/data/acme.json similarity index 100% rename from infrastructure/ansible/resources/traefik/data/acme.json rename to infrastructure/docker-cluster/ansible/roles/traefik/files/traefik/data/acme.json diff --git a/infrastructure/docker-cluster/ansible/roles/traefik/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/traefik/tasks/main.yml new file mode 100644 index 0000000..6ab1a2c --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/traefik/tasks/main.yml @@ -0,0 +1,44 @@ +--- +- name: TRAEFIK | Copy Stack Files + copy: + directory_mode: true + src: traefik + dest: "{{ ceph_volume }}" + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: TRAEFIK | Generate Compose file + ansible.builtin.template: + src: docker-compose.yml.j2 + dest: "{{ data_dir }}/traefik.yml" + mode: 0644 + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: TRAEFIK | Ensure permissions on acme.json + ansible.builtin.file: + path: "{{ data_dir }}/data/acme.json" + mode: '0600' + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: TRAEFIK | traefik_public Netzwerk erstellen + community.docker.docker_network: + name: traefik_public + driver: overlay + state: present + attachable: yes + ipam_config: + - subnet: '172.16.200.0/24' + gateway: '172.16.200.1' + run_once: true + delegate_to: "{{ groups['managers'][0] }}" + +- name: TRAEFIK | Deploy app stack + community.docker.docker_stack: + state: present + name: traefik + compose: + - "{{ data_dir }}/traefik.yml" + delegate_to: "{{ groups['managers'][0] }}" + run_once: true \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/traefik/templates/docker-compose.yml.j2 b/infrastructure/docker-cluster/ansible/roles/traefik/templates/docker-compose.yml.j2 new file mode 100644 index 0000000..b3d0f0b --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/traefik/templates/docker-compose.yml.j2 @@ -0,0 +1,52 @@ +services: + app: + image: traefik:v3.6.2 + ports: + - target: 80 + published: 80 + protocol: tcp + mode: host + - target: 443 + published: 443 + protocol: tcp + mode: host + - target: 8080 + published: 8080 + protocol: tcp + environment: + - HETZNER_API_TOKEN={{ hetzner_api_key }} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - {{ data_dir }}/config:/etc/traefik + - {{ data_dir }}/data/logs:/logs + - {{ data_dir }}/data/acme.json:/acme.json + # healthcheck: + # test: ["CMD", "traefik", "healthcheck", "--ping"] + # timeout: 1s + # interval: 10s + # retries: 3 + # start_period: 10s + networks: + - {{ traefik_public_net }} + # Global mode makes an instance of traefik listen on _every_ node, so that regardless of which + # node the request arrives on, it'll be forwarded to the correct backend service. + deploy: + mode: global + labels: + - "traefik.enable=true" + - "traefik.swarm.network={{ traefik_public_net }}" + - "traefik.http.routers.api.rule=Host(`{{ subdomain }}.{{ main_domain }}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" + - "traefik.http.routers.api.entrypoints=https" +{% if use_authentik %} - "traefik.http.routers.api.middlewares=authentik@file"{% endif %} +# - "traefik.http.routers.api.tls.domains[0].main={{ main_domain }}" +# - "traefik.http.routers.api.tls.domains[0].sans=*.{{ main_domain }}" + - "traefik.http.routers.api.tls=true" + - "traefik.http.routers.api.tls.certresolver=main" + - "traefik.http.routers.api.service=api@internal" + - "traefik.http.services.dummy.loadbalancer.server.port=9999" + placement: + constraints: [node.role == manager] + +networks: + {{ traefik_public_net }}: + external: true \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/traefik/vars/main.yml b/infrastructure/docker-cluster/ansible/roles/traefik/vars/main.yml new file mode 100644 index 0000000..b3b43ff --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/traefik/vars/main.yml @@ -0,0 +1,3 @@ +subdomain: router +use_authentik: false +data_dir: "{{ ceph_volume }}/traefik" \ No newline at end of file diff --git a/infrastructure/docker-cluster/ansible/roles/ufw_firewall/tasks/main.yml b/infrastructure/docker-cluster/ansible/roles/ufw_firewall/tasks/main.yml new file mode 100644 index 0000000..e992ebb --- /dev/null +++ b/infrastructure/docker-cluster/ansible/roles/ufw_firewall/tasks/main.yml @@ -0,0 +1,84 @@ +--- +- name: FIREWALL | UFW auf Standardeinstellungen zurücksetzen + community.general.ufw: + state: reset + +- name: FIREWALL | Standardmäßig allen ausgehenden Traffic erlauben + community.general.ufw: + direction: outgoing + policy: allow + +- name: FIREWALL | Standardmäßig allen eingehenden Traffic blockieren + community.general.ufw: + direction: incoming + policy: deny + +- name: FIREWALL | Eingehenden SSH-Traffic auf öffentlichem Interface erlauben + community.general.ufw: + rule: allow + port: "{{ ssh_port }}" + proto: tcp + interface: "{{ public_interface }}" + direction: in + +- name: FIREWALL | Eingehenden SSH-Traffic auf privatem Interface erlauben + community.general.ufw: + rule: allow + port: "{{ ssh_port }}" + proto: tcp + interface: "{{ private_interface }}" + direction: in + +- name: FIREWALL | Eingehenden HTTP/HTTPS-Traffic auf öffentlichem Interface erlauben + community.general.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto }}" + interface: "{{ public_interface }}" + direction: in + with_items: + - { port: '80', proto: 'tcp' } + - { port: '443', proto: 'tcp' } + +- name: FIREWALL | Ceph Monitor Ports auf privatem Interface erlauben + community.general.ufw: + rule: allow + port: "{{ item }}" + proto: tcp + interface: "{{ private_interface }}" + direction: in + with_items: + - '3300' + - '6789' + +- name: FIREWALL | Ceph OSD/MGR Port-Range auf öffentlichem Interface erlauben + community.general.ufw: + rule: allow + port: "6800:7568" + proto: tcp + interface: "{{ private_interface }}" + direction: in + +- name: FIREWALL | Docker Swarm Management Ports auf privatem Interface erlauben + community.general.ufw: + rule: allow + port: "2377" + proto: tcp + interface: "{{ private_interface }}" + direction: in + +- name: FIREWALL | Docker Swarm Discovery/Network Ports auf privatem Interface erlauben + community.general.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto }}" + interface: "{{ private_interface }}" + direction: in + with_items: + - { port: '7946', proto: 'tcp' } + - { port: '7946', proto: 'udp' } + - { port: '4789', proto: 'udp' } + +- name: FIREWALL | UFW aktivieren + community.general.ufw: + state: enabled \ No newline at end of file diff --git a/infrastructure/docker-cluster/pulumi/Pulumi.prod.yaml b/infrastructure/docker-cluster/pulumi/Pulumi.prod.yaml new file mode 100644 index 0000000..47921a5 --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/Pulumi.prod.yaml @@ -0,0 +1,3 @@ +config: + hcloud:token: + secure: AAABAGkphVtyNKTaI7oPUA7mZ4PehhSPRdIxfnEVdOc2Q+D+Q3bLpRofkiula4FSk+0bh8ModR46zmoDznrlrhqvhsm/mIcWmDOqKq+pyzRGrYKXpqi7sw1LhUanNV6Z diff --git a/infrastructure/docker-cluster/pulumi/Pulumi.yaml b/infrastructure/docker-cluster/pulumi/Pulumi.yaml new file mode 100644 index 0000000..6ee725a --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/Pulumi.yaml @@ -0,0 +1,7 @@ +name: avicenna-infra +description: Infrastructure to host and run the Applications and Services developed for Avicenna +runtime: go +config: + pulumi:tags: + value: + pulumi:template: go diff --git a/infrastructure/docker-cluster/pulumi/config/config.go b/infrastructure/docker-cluster/pulumi/config/config.go new file mode 100644 index 0000000..48086b6 --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/config/config.go @@ -0,0 +1,8 @@ +package config + +type InfraConfig struct { + StackReference string + SwarmNetworkName string + SwarmIpRange string + SwarmSubnetIpRange string +} diff --git a/infrastructure/docker-cluster/pulumi/go.mod b/infrastructure/docker-cluster/pulumi/go.mod new file mode 100644 index 0000000..97726ac --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/go.mod @@ -0,0 +1,90 @@ +module avicenna-infra + +go 1.24.0 + +require github.com/pulumi/pulumi/sdk/v3 v3.209.0 + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/charmbracelet/bubbles v0.16.1 // indirect + github.com/charmbracelet/bubbletea v0.25.0 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect + github.com/cyphar/filepath-securejoin v0.3.6 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.6.1 // indirect + github.com/go-git/go-git/v5 v5.13.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.2.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/hcl/v2 v2.22.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pgavlin/fx v0.1.6 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 // indirect + github.com/pulumi/esc v0.17.0 // indirect + github.com/pulumi/pulumi-command/sdk v1.1.3 // indirect + github.com/pulumi/pulumi-hcloud/sdk v1.29.0 // indirect + github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.3.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + github.com/zclconf/go-cty v1.13.2 // indirect + go.uber.org/atomic v1.9.0 // indirect + golang.org/x/crypto v0.45.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/grpc v1.72.1 // indirect + google.golang.org/protobuf v1.36.6 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/frand v1.4.2 // indirect +) diff --git a/infrastructure/docker-cluster/pulumi/go.sum b/infrastructure/docker-cluster/pulumi/go.sum new file mode 100644 index 0000000..c84d8ce --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/go.sum @@ -0,0 +1,309 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY= +github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc= +github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= +github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= +github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= +github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= +github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= +github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA= +github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= +github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M= +github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= +github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= +github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pgavlin/fx v0.1.6 h1:r9jEg69DhNoCd3Xh0+5mIbdbS3PqWrVWujkY76MFRTU= +github.com/pgavlin/fx v0.1.6/go.mod h1:KWZJ6fqBBSh8GxHYqwYCf3rYE7Gp2p0N8tJp8xv9u9M= +github.com/pgavlin/fx/v2 v2.0.3 h1:ZBVklTFjxcWvBVPE+ti5qwnmTIQ0Gq6nuj3J5RKDtKk= +github.com/pgavlin/fx/v2 v2.0.3/go.mod h1:Cvnwqq0BopdHUJ7CU50h1XPeKrF4ZwdFj1nJLXbAjCE= +github.com/pgavlin/fx/v2 v2.0.10 h1:ggyQ6pB+lEQEbEae48Wh/X221eLOamMD7i01ISe88u4= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231 h1:vkHw5I/plNdTr435cARxCW6q9gc0S/Yxz7Mkd38pOb0= +github.com/pulumi/appdash v0.0.0-20231130102222-75f619a67231/go.mod h1:murToZ2N9hNJzewjHBgfFdXhZKjY3z5cYC1VXk+lbFE= +github.com/pulumi/esc v0.17.0 h1:oaVOIyFTENlYDuqc3pW75lQT9jb2cd6ie/4/Twxn66w= +github.com/pulumi/esc v0.17.0/go.mod h1:XnSxlt5NkmuAj304l/gK4pRErFbtqq6XpfX1tYT9Jbc= +github.com/pulumi/pulumi-command/sdk v1.1.3 h1:2FdcqVenuHcGJfcVnUg6G22IeoQ/lY5UX6VexFJ4kT8= +github.com/pulumi/pulumi-command/sdk v1.1.3/go.mod h1:3ochnip+NSR3+lQh8//Cni6hR9ckswuc1c6URsmX4RM= +github.com/pulumi/pulumi-hcloud/sdk v1.29.0 h1:TQasXTpZlib8zCQXnsRSUpX0cB1/pRoPBIpPip0jwQc= +github.com/pulumi/pulumi-hcloud/sdk v1.29.0/go.mod h1:JID9el4HeuNGU9BJiPHQsGddjvkzoVbUUZtmD1dbuTg= +github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3 h1:HEHeifaSKme+mWXYkps5qYD45JJc86Y/0awBj7UC6vU= +github.com/pulumi/pulumi-tls/sdk/v5 v5.2.3/go.mod h1:UxIBAyqaLIo5bn63paPM7FU7DahbfeSIKbgTf9KldLY= +github.com/pulumi/pulumi/sdk/v3 v3.209.0 h1:Ti0FohAset2HEgogTxTOWoyvQd2N2+pkTSIn5DW3W7s= +github.com/pulumi/pulumi/sdk/v3 v3.209.0/go.mod h1:qBMg01woyYVNqNDJXpFL1e5gdN7oQQvCNNNtfq1gsEo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= +github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= +github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= +lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +pgregory.net/rapid v0.5.5 h1:jkgx1TjbQPD/feRoK+S/mXw9e1uj6WilpHrXJowi6oA= +pgregory.net/rapid v0.5.5/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/infrastructure/docker-cluster/pulumi/main.go b/infrastructure/docker-cluster/pulumi/main.go new file mode 100644 index 0000000..c4680ec --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "avicenna-infra/config" + "avicenna-infra/utils" + "fmt" + "net" + "slices" + "strings" + + "github.com/pulumi/pulumi-command/sdk/go/command/remote" + "github.com/pulumi/pulumi-hcloud/sdk/go/hcloud" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type Infrastructure struct { + placementGroup *hcloud.PlacementGroup + networkID *pulumi.IDOutput + managerNodes []*hcloud.Server + workerNodes []*hcloud.Server +} + +func main() { + infra := &Infrastructure{} + + pulumi.Run(func(ctx *pulumi.Context) error { + var err error + + cfg := config.InfraConfig{ + StackReference: strings.Join( + []string{ctx.Organization(), ctx.Project(), ctx.Stack()}, + "/"), + SwarmNetworkName: "gc-swarmnet", + SwarmIpRange: "10.0.0.0/16", + SwarmSubnetIpRange: "10.0.1.0/24", + } + pk, err := utils.CreateSshKey(ctx) + if err != nil { + panic(err.Error()) + } + ctx.Export("privKey", pk.PrivateKeyOpenssh) + + hkey, err := hcloud.NewSshKey(ctx, strings.Join([]string{cfg.StackReference, "key"}, "-"), &hcloud.SshKeyArgs{PublicKey: pk.PublicKeyOpenssh}) + if err != nil { + panic(err.Error()) + } + + infra.placementGroup, err = utils.CreatePlacementGroup(ctx, cfg.StackReference) + if err != nil { + panic(err.Error()) + } + + infra.networkID, err = utils.CreateClusterNet(ctx, cfg) + if err != nil { + panic(err.Error()) + } + + infra.managerNodes, err = utils.CreateServer(ctx, utils.CreateServerArgs{ + PlacementGroupId: infra.placementGroup.ID(), + NetworkId: infra.networkID, + NetworkFirstIP: string(utils.IncrementIP(net.ParseIP("10.0.1.0"))), + Basename: "manager-node", + Count: 3, + SshKey: hkey, + ServerType: "ccx23", + }) + if err != nil { + panic(err.Error()) + } + // infra.workerNodes, err = utils.CreateServer(ctx, utils.CreateServerArgs{ + // PlacementGroupId: infra.placementGroup.ID(), + // NetworkId: infra.networkID, + // NetworkFirstIP: string(utils.IncrementIP(net.ParseIP("10.0.1.20"))), + // Basename: "worker-node", + // Count: 2, + // SshKey: hkey, + // }) + // if err != nil { + // panic(err.Error()) + // } + + for idx, s := range slices.Concat(infra.managerNodes, infra.workerNodes) { + err := utils.InstallAnsibleDependencies(ctx, remote.ConnectionArgs{ + Host: s.Ipv4Address, + User: pulumi.String("root"), + PrivateKey: pk.PrivateKeyOpenssh, + }, fmt.Sprintf("%d", idx)) + if err != nil { + panic(err.Error()) + } + } + + // var advAddr = infra.managerNodes[0].Networks.ApplyT(func(net []hcloud.ServerNetworkType) string { + // return *net[0].Ip + // }).(pulumi.StringOutput) + + // tokens, err := utils.InitDockerSwarm(ctx, remote.ConnectionArgs{ + // Host: infra.managerNodes[0].Ipv4Address, + // User: pulumi.String("root"), + // PrivateKey: pk.PrivateKeyOpenssh}, advAddr) + // if err != nil { + // panic(err.Error()) + // } + + // ctx.Export("SwarmTokens", tokens) + + // inventory, err := utils.CreateAnsibleInventory(infra.managerNodes, infra.workerNodes) + // if err != nil { + // panic(err.Error()) + // } + // ctx.Export("inventory", inventory) + + sm := map[string]pulumi.Input{} + for idx, s := range slices.Concat(infra.managerNodes, infra.workerNodes) { + sm[fmt.Sprintf("node-%d-ip", idx)] = s.Ipv4Address + } + ctx.Export("server-ips", pulumi.Map(sm)) + + return nil + }) +} diff --git a/infrastructure/docker-cluster/pulumi/utils/ansible-configuration.go b/infrastructure/docker-cluster/pulumi/utils/ansible-configuration.go new file mode 100644 index 0000000..ceb1dd8 --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/utils/ansible-configuration.go @@ -0,0 +1,154 @@ +package utils + +import ( + "bytes" + "fmt" + "regexp" + "strings" + "text/template" + + "github.com/pulumi/pulumi-command/sdk/go/command/remote" + "github.com/pulumi/pulumi-hcloud/sdk/go/hcloud" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +type SwarmJoinTokens struct { + ManagerToken string + WorkerToken string +} + +type ServerInfo struct { + Name pulumi.StringOutput + IP pulumi.StringOutput +} + +func InstallAnsibleDependencies(ctx *pulumi.Context, connArgs remote.ConnectionArgs, uniqueness string) error { + _, err := remote.NewCommand(ctx, strings.Join([]string{uniqueness, "Install Ansible Dependencies"}, ": "), + &remote.CommandArgs{ + Connection: connArgs, + Create: pulumi.String("apt-get update && apt-get install -y python3-pip python3-jsondiff"), + }) + if err != nil { + return err + } + return nil +} + +func InitDockerSwarm(ctx *pulumi.Context, connArgs remote.ConnectionArgs, advertiseAddr pulumi.StringOutput) (pulumi.Output, error) { + var tokens SwarmJoinTokens + + fullCommand := advertiseAddr.ApplyT(func(addr string) *string { + initCommand := fmt.Sprintf("docker swarm init --advertise-addr %s", addr) + fullCommand := strings.Join([]string{initCommand, "echo \"Manager Token: $(docker swarm join-token -q manager)\"", "echo \"Worker Token: $(docker swarm join-token -q worker)\""}, " && ") + return &fullCommand + }).(pulumi.StringPtrOutput) + + out, err := remote.NewCommand(ctx, "Init docker swarm", + &remote.CommandArgs{ + Connection: connArgs, + Create: fullCommand, + }) + if err != nil { + return pulumi.StringOutput{}, err + } + + return out.Stdout.ApplyT(func(output string) SwarmJoinTokens { + searchWorker := "Worker Token: " + patternWorker := regexp.MustCompile(searchWorker + `(\S+)`) + searchManager := "Manager Token: " + patternManager := regexp.MustCompile(searchManager + `(\S+)`) + + matches := patternWorker.FindStringSubmatch(output) + if len(matches) > 1 { + extracted := matches[1] + tokens.WorkerToken = extracted + } + matches = patternManager.FindStringSubmatch(output) + if len(matches) > 1 { + extracted := matches[1] + tokens.ManagerToken = extracted + } + return tokens + }), nil +} + +func CreateAnsibleInventory(managerNodes, workerNodes []*hcloud.Server) (pulumi.Output, error) { + serverInfos := toServerInfo(managerNodes) + return pulumi.All(pulumi.ToOutput(serverInfos)).ApplyT(func(results []interface{}) (string, error) { + var serverInfos = results[0].([]ServerInfo) + // var workerSlice = results[1].([]*hcloud.Server) + + serverData := make(map[string][]ServerInfo) + + for _, s := range serverInfos { + serverData["Manager"] = append(serverData["Manager"], ServerInfo{ + Name: s.Name, + IP: s.IP, + }) + } + // for _, result := range workerSlice { + // server := result.(map[string]interface{}) + // serverData["Worker"] = append(serverData["Worker"], ServerInfo{ + // Name: server["name"].(string), + // IP: server["ipv4_address"].(string), + // }) + // } + fmt.Println(serverData["Manager"]) + fmt.Println(results[0]) + return generateInventoryFile(serverData) + }).(pulumi.Output), nil +} + +func toServerInfo(server []*hcloud.Server) pulumi.ArrayOutput { + serverInfo := []ServerInfo{} + for _, s := range server { + serverInfo = append(serverInfo, ServerInfo{ + Name: s.Name, + IP: s.Ipv4Address, + }) + } + return pulumi.All(serverInfo).ApplyT(func(args []interface{}) []interface{} { + var serverInfo []interface{} + + for _, s := range args { + val := s.(map[string]interface{}) + serverInfo = append(serverInfo, map[string]interface{}{ + "Name": val["Name"].(string), + "IP": val["IP"].(string), + }) + } + return serverInfo + }).(pulumi.ArrayOutput) +} + +func generateInventoryFile(inventory map[string][]ServerInfo) (string, error) { + const inventoryTmpl = ` +[all] +{{ range .Manager }} +{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key +{{ end }} +{{ range .Worker }} +{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key +{{ end }} + +[manager] +{{ range .Manager }} +{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key +{{ end }} + +[worker] +{{ range .Worker }} +{{ .Name }} ansible_host={{ .IP }} ansible_connection=ssh ansible_user=root ansible_ssh_private_key_file=../infra-base/private_key +{{ end }} + ` + tmpl, err := template.New("inventory").Parse(inventoryTmpl) + if err != nil { + return "", err + } + var buf bytes.Buffer + err = tmpl.Execute(&buf, inventory) + if err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/infrastructure/docker-cluster/pulumi/utils/resource-creation.go b/infrastructure/docker-cluster/pulumi/utils/resource-creation.go new file mode 100644 index 0000000..b1f26cd --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/utils/resource-creation.go @@ -0,0 +1,110 @@ +package utils + +import ( + "avicenna-infra/config" + "fmt" + "net" + + "github.com/pulumi/pulumi-hcloud/sdk/go/hcloud" + "github.com/pulumi/pulumi-tls/sdk/v5/go/tls" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func CreateSshKey(ctx *pulumi.Context) (*tls.PrivateKey, error) { + return tls.NewPrivateKey(ctx, "sshKey", &tls.PrivateKeyArgs{ + Algorithm: pulumi.String("ED25519"), + }, pulumi.AdditionalSecretOutputs([]string{"privKey"})) +} + +func CreatePlacementGroup(ctx *pulumi.Context, name string) (*hcloud.PlacementGroup, error) { + pg, err := hcloud.NewPlacementGroup(ctx, name, &hcloud.PlacementGroupArgs{ + Name: pulumi.String(name), + Type: pulumi.String("spread"), + }) + return pg, err +} + +func CreateClusterNet(ctx *pulumi.Context, cfg config.InfraConfig) (*pulumi.IDOutput, error) { + var id pulumi.IDOutput + network, err := hcloud.NewNetwork(ctx, cfg.SwarmNetworkName, &hcloud.NetworkArgs{ + Name: pulumi.String(cfg.SwarmNetworkName), + IpRange: pulumi.String(cfg.SwarmIpRange), + }) + if err != nil { + return nil, err + } + id = network.ID() + _, err = hcloud.NewNetworkSubnet(ctx, "network-subnet", &hcloud.NetworkSubnetArgs{ + Type: pulumi.String("cloud"), + NetworkId: IDtoIntOutput(id), + NetworkZone: pulumi.String("eu-central"), + IpRange: pulumi.String(cfg.SwarmSubnetIpRange), + }) + if err != nil { + return nil, err + } + return &id, nil +} + +type CreateServerArgs struct { + NetworkId pulumi.IDInput + PlacementGroupId pulumi.IDInput + // The first assignable IP of the network + NetworkFirstIP string + Basename string + Count int + SshKey *hcloud.SshKey + ServerType string +} + +func CreateServer(ctx *pulumi.Context, cfg CreateServerArgs) ([]*hcloud.Server, error) { + var nodes []*hcloud.Server + nextIp := cfg.NetworkFirstIP + for i := range cfg.Count { + sn := fmt.Sprintf("%s-%d", cfg.Basename, i+1) + s, err := hcloud.NewServer(ctx, sn, &hcloud.ServerArgs{ + Name: pulumi.String(sn), + Image: pulumi.String("docker-ce"), + ServerType: pulumi.String(cfg.ServerType), + Location: pulumi.StringPtr("fsn1"), + Networks: hcloud.ServerNetworkTypeArray{ + &hcloud.ServerNetworkTypeArgs{ + NetworkId: IDtoIntOutput(cfg.NetworkId), + Ip: pulumi.String(nextIp), + }, + }, + PlacementGroupId: IDtoIntPtrOutput(cfg.PlacementGroupId), + PublicNets: hcloud.ServerPublicNetArray{ + &hcloud.ServerPublicNetArgs{ + // Ipv4Enabled: pulumi.Bool(true), + Ipv6Enabled: pulumi.Bool(true), + }, + }, + SshKeys: pulumi.StringArray{cfg.SshKey.ID()}, + }) + if err != nil { + return nodes, err + } + + cephVolume, err := hcloud.NewVolume(ctx, fmt.Sprintf("ceph-%s", sn), &hcloud.VolumeArgs{ + Name: pulumi.Sprintf("%s-ceph-vol-0%d", s.Name, i+1), + Size: pulumi.Int(100), + Location: s.Location, + }) + if err != nil { + return nodes, fmt.Errorf("couldn't create volume: %w", err) + } + + _, err = hcloud.NewVolumeAttachment(ctx, fmt.Sprintf("ceph-vol-attach-%s", sn), &hcloud.VolumeAttachmentArgs{ + VolumeId: IDtoIntOutput(cephVolume.ID()), + ServerId: IDtoIntOutput(s.ID()), + }) + if err != nil { + return nodes, fmt.Errorf("couldn't attach volume to node %d", i) + } + + nodes = append(nodes, s) + nextIp = IncrementIP(net.ParseIP(nextIp)).String() + } + return nodes, nil +} diff --git a/infrastructure/docker-cluster/pulumi/utils/utils.go b/infrastructure/docker-cluster/pulumi/utils/utils.go new file mode 100644 index 0000000..1b2f2d1 --- /dev/null +++ b/infrastructure/docker-cluster/pulumi/utils/utils.go @@ -0,0 +1,41 @@ +package utils + +import ( + "net" + "strconv" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func IncrementIP(ip net.IP) net.IP { + if ip == nil { + return ip + } + ip = ip.To4() + ip[3]++ + return ip +} + +func IDtoInt(val string) (int, error) { + i, err := strconv.Atoi(val) + return i, err +} + +func IDtoIntPtr(val string) (*int, error) { + i, err := strconv.Atoi(val) + return &i, err +} + +// Convert pulumi.IDOutput to pulumi.InOutput +// +// Some hcloud ID's are int based, but pulumi.IDOutput doesn't have a ToIntOutput method +func IDtoIntOutput(val pulumi.IDInput) pulumi.IntOutput { + return val.ToIDOutput().ToStringOutput().ApplyT(IDtoInt).(pulumi.IntOutput) +} + +// Convert pulumi.IDOutput to pulumi.InOutput +// +// Some hcloud ID's are int based, but pulumi.IDOutput doesn't have a ToIntOutput method +func IDtoIntPtrOutput(val pulumi.IDInput) pulumi.IntPtrOutput { + return val.ToIDOutput().ToStringOutput().ApplyT(IDtoIntPtr).(pulumi.IntPtrOutput) +} diff --git a/infrastructure/single-docker-server/.gitignore b/infrastructure/single-docker-server/.gitignore new file mode 100644 index 0000000..5c99f2f --- /dev/null +++ b/infrastructure/single-docker-server/.gitignore @@ -0,0 +1,3 @@ +/bin/ +node_modules/ +.local/ \ No newline at end of file diff --git a/infrastructure/.gitkeep b/infrastructure/single-docker-server/.gitkeep similarity index 100% rename from infrastructure/.gitkeep rename to infrastructure/single-docker-server/.gitkeep diff --git a/infrastructure/Pulumi.dev.yaml b/infrastructure/single-docker-server/Pulumi.dev.yaml similarity index 100% rename from infrastructure/Pulumi.dev.yaml rename to infrastructure/single-docker-server/Pulumi.dev.yaml diff --git a/infrastructure/Pulumi.prod.yaml b/infrastructure/single-docker-server/Pulumi.prod.yaml similarity index 100% rename from infrastructure/Pulumi.prod.yaml rename to infrastructure/single-docker-server/Pulumi.prod.yaml diff --git a/infrastructure/Pulumi.yaml b/infrastructure/single-docker-server/Pulumi.yaml similarity index 100% rename from infrastructure/Pulumi.yaml rename to infrastructure/single-docker-server/Pulumi.yaml diff --git a/infrastructure/single-docker-server/README.md b/infrastructure/single-docker-server/README.md new file mode 100644 index 0000000..c2e22b0 --- /dev/null +++ b/infrastructure/single-docker-server/README.md @@ -0,0 +1,8 @@ +# Infrastructure Project + +Dieses Projekt erstellt einen Hetzner Server mit Docker vorinstalliert. + +## Verwendung + +Hier liegen die SSH Schlüssel für die ursprüngliche Avicenna Produktionsumgebung. +Desweiteren liegt hier ein Schlüssel von Factorypuls, vermutlich der Schlüssel für den Hetzner Server, welcher das Visual hostet. \ No newline at end of file diff --git a/infrastructure/ansible/playbook.yml b/infrastructure/single-docker-server/ansible/playbook.yml similarity index 100% rename from infrastructure/ansible/playbook.yml rename to infrastructure/single-docker-server/ansible/playbook.yml diff --git a/infrastructure/ansible/resources/kestra/kestra.yml b/infrastructure/single-docker-server/ansible/resources/kestra/kestra.yml similarity index 100% rename from infrastructure/ansible/resources/kestra/kestra.yml rename to infrastructure/single-docker-server/ansible/resources/kestra/kestra.yml diff --git a/infrastructure/ansible/resources/portainer/portainer.yml b/infrastructure/single-docker-server/ansible/resources/portainer/portainer.yml similarity index 100% rename from infrastructure/ansible/resources/portainer/portainer.yml rename to infrastructure/single-docker-server/ansible/resources/portainer/portainer.yml diff --git a/infrastructure/ansible/resources/traefik/config/traefik.toml b/infrastructure/single-docker-server/ansible/resources/traefik/config/traefik.toml similarity index 100% rename from infrastructure/ansible/resources/traefik/config/traefik.toml rename to infrastructure/single-docker-server/ansible/resources/traefik/config/traefik.toml diff --git a/infrastructure/ansible/resources/traefik/data/access.log b/infrastructure/single-docker-server/ansible/resources/traefik/data/access.log similarity index 100% rename from infrastructure/ansible/resources/traefik/data/access.log rename to infrastructure/single-docker-server/ansible/resources/traefik/data/access.log diff --git a/infrastructure/ansible/resources/traefik/data/traefik.log b/infrastructure/single-docker-server/ansible/resources/traefik/data/acme.json similarity index 100% rename from infrastructure/ansible/resources/traefik/data/traefik.log rename to infrastructure/single-docker-server/ansible/resources/traefik/data/acme.json diff --git a/infrastructure/single-docker-server/ansible/resources/traefik/data/traefik.log b/infrastructure/single-docker-server/ansible/resources/traefik/data/traefik.log new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/ansible/resources/traefik/network.yml b/infrastructure/single-docker-server/ansible/resources/traefik/network.yml similarity index 100% rename from infrastructure/ansible/resources/traefik/network.yml rename to infrastructure/single-docker-server/ansible/resources/traefik/network.yml diff --git a/infrastructure/ansible/resources/traefik/traefik.env b/infrastructure/single-docker-server/ansible/resources/traefik/traefik.env similarity index 100% rename from infrastructure/ansible/resources/traefik/traefik.env rename to infrastructure/single-docker-server/ansible/resources/traefik/traefik.env diff --git a/infrastructure/ansible/resources/traefik/traefik.yml b/infrastructure/single-docker-server/ansible/resources/traefik/traefik.yml similarity index 100% rename from infrastructure/ansible/resources/traefik/traefik.yml rename to infrastructure/single-docker-server/ansible/resources/traefik/traefik.yml diff --git a/infrastructure/ansible/roles/kestra/tasks/main.yml b/infrastructure/single-docker-server/ansible/roles/kestra/tasks/main.yml similarity index 100% rename from infrastructure/ansible/roles/kestra/tasks/main.yml rename to infrastructure/single-docker-server/ansible/roles/kestra/tasks/main.yml diff --git a/infrastructure/ansible/roles/portainer/tasks/main.yml b/infrastructure/single-docker-server/ansible/roles/portainer/tasks/main.yml similarity index 100% rename from infrastructure/ansible/roles/portainer/tasks/main.yml rename to infrastructure/single-docker-server/ansible/roles/portainer/tasks/main.yml diff --git a/infrastructure/ansible/roles/traefik/tasks/main.yml b/infrastructure/single-docker-server/ansible/roles/traefik/tasks/main.yml similarity index 100% rename from infrastructure/ansible/roles/traefik/tasks/main.yml rename to infrastructure/single-docker-server/ansible/roles/traefik/tasks/main.yml diff --git a/infrastructure/single-docker-server/dashboard.tar.gz b/infrastructure/single-docker-server/dashboard.tar.gz new file mode 100644 index 0000000..7fd1640 Binary files /dev/null and b/infrastructure/single-docker-server/dashboard.tar.gz differ diff --git a/infrastructure/index.ts b/infrastructure/single-docker-server/index.ts similarity index 100% rename from infrastructure/index.ts rename to infrastructure/single-docker-server/index.ts diff --git a/infrastructure/package-lock.json b/infrastructure/single-docker-server/package-lock.json similarity index 100% rename from infrastructure/package-lock.json rename to infrastructure/single-docker-server/package-lock.json diff --git a/infrastructure/package.json b/infrastructure/single-docker-server/package.json similarity index 100% rename from infrastructure/package.json rename to infrastructure/single-docker-server/package.json diff --git a/infrastructure/tsconfig.json b/infrastructure/single-docker-server/tsconfig.json similarity index 100% rename from infrastructure/tsconfig.json rename to infrastructure/single-docker-server/tsconfig.json