diff options
-rwxr-xr-x | create.py | 151 | ||||
-rw-r--r-- | deploy.yml | 4 | ||||
-rw-r--r-- | group_vars/labdns.yml | 17 | ||||
-rw-r--r-- | group_vars/pihole.yml | 7 | ||||
-rw-r--r-- | inventory | 3 | ||||
-rw-r--r-- | playbooks/labdns.yml | 7 | ||||
-rw-r--r-- | playbooks/pihole.yml | 7 | ||||
-rw-r--r-- | playbooks/roles/labdns/tasks/main.yml (renamed from playbooks/roles/pihole/tasks/main.yml) | 7 | ||||
-rw-r--r-- | playbooks/roles/labdns/templates/stacks/docker-compose.yml | 30 | ||||
-rw-r--r-- | playbooks/roles/labdns/templates/volumes/unbound/a-records.conf | 4 | ||||
-rw-r--r-- | playbooks/roles/labdns/templates/volumes/unbound/forward-records.conf | 5 | ||||
-rw-r--r-- | playbooks/roles/pihole/templates/stacks/docker-compose.yml | 43 | ||||
-rw-r--r-- | playbooks/roles/pihole/templates/volumes/dnsmasq/.gitkeep | 0 | ||||
-rw-r--r-- | playbooks/roles/pihole/templates/volumes/pihole/.gitkeep | 0 |
14 files changed, 156 insertions, 129 deletions
@@ -15,10 +15,10 @@ from functools import cache logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) + class Config: SECRETS = Path("secrets.enc") ROOT_DOMAIN = "liz.coffee" - PIHOLE = "https://dns.liz.coffee/api" CLOUDFLARE = "https://api.cloudflare.com/client/v4" INVENTORY = Path("inventory") @@ -28,21 +28,24 @@ class Config: GROUP_VARS = Path("group_vars/") NGINX_SITES_ENABLED = ANSIBLE_ROLES / Path("outbound/templates/proxy/nginx/conf.d") - INTERNAL_LOADBALANCER_HOST = "floating.home.arpa" + LABDNS_GROUP_VARS = GROUP_VARS / Path("labdns.yml") + LABDNS_DELIMITER = "internal_services:" + "\n" EXTERNAL_LOADBALANCER_HOST = "outbound.liz.coffee" - LOADBALANCER_INVENTORY_LINE = textwrap.dedent("""\ + LOADBALANCER_INVENTORY_LINE = textwrap.dedent( + """\ swarm-one ansible_host=10.128.0.201 ansible_user=serve \ ansible_connection=ssh ansible_become_password='{{ swarm_become_password }}' - """).strip() + """ + ).strip() @staticmethod @cache def read_secrets() -> Dict[str, str]: logger.info("Reading secrets...") result = subprocess.run( - ['ansible-vault', 'view', str(Config.SECRETS)], + ["ansible-vault", "view", str(Config.SECRETS)], stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, ) secrets = result.stdout.decode().splitlines() logger.info("Finished reading secrets.") @@ -53,14 +56,8 @@ class Config: return Config.read_secrets().get(name, "") -class IDns(ABC): - @abstractmethod - def put_cname(self, of: str, to: str) -> bool: - pass - - @dataclass -class CloudflareDns(IDns): +class CloudflareDns: api_token: str zone_id: str api_url: str @@ -69,14 +66,9 @@ class CloudflareDns(IDns): url = f"{self.api_url}/zones/{self.zone_id}/dns_records" headers = { "Authorization": f"Bearer {self.api_token}", - "Content-Type": "application/json" - } - data = { - "type": "CNAME", - "name": of, - "content": to, - "ttl": 3600 + "Content-Type": "application/json", } + data = {"type": "CNAME", "name": of, "content": to, "ttl": 3600} logger.info(f"Putting Cloudflare CNAME {of} -> {to}") response = requests.post(url, headers=headers, json=data) if response.ok: @@ -87,26 +79,29 @@ class CloudflareDns(IDns): @dataclass -class PiholeDns(IDns): - web_password: str - api_url: str - - def __authenticate(self) -> Optional[str]: - response = requests.post(f"{self.api_url}/auth", json={"password": self.web_password}) - return response.json().get("session", {}).get("sid") - - def put_cname(self, of: str, to: str) -> bool: - sid = self.__authenticate() - if not sid: - logger.error("Pi-hole authentication failed.") +class HomelabDns: + group_vars: Path + delimiter: str + + def put_internal(self, dns_prefix: str) -> bool: + new_lines = [] + with self.group_vars.open("r") as f: + lines = f.readlines() + try: + internal_services = lines.index(self.delimiter) + except ValueError: + logger.error(f"Cannot find delimiter {self.delimiter} in {self.group_vars}") + return False + new_lines = ( + lines[0:internal_services + 1] + + [f" - {dns_prefix}\n"] + + lines[internal_services + 1:] + ) + if not new_lines: return False - url = f"{self.api_url}/config/dns/cnameRecords/{of},{to}?sid={sid}" - response = requests.put(url) - if response.ok: - logger.info("Pi-hole DNS update successful.") - return True - logger.error(f"Pi-hole DNS update failed: {response.text}") - return False + with self.group_vars.open("w") as f: + f.write("".join(new_lines)) + return True class RoleGenerator: @@ -126,7 +121,9 @@ class RoleGenerator: def create_tasks(self): self.tasks_path.mkdir(parents=True, exist_ok=True) task_file = self.tasks_path / "main.yml" - task_file.write_text(textwrap.dedent(f"""\ + task_file.write_text( + textwrap.dedent( + f"""\ --- - name: Deploy {self.service} @@ -135,12 +132,16 @@ class RoleGenerator: service_name: {self.service} template_render_dir: "../templates" service_destination_dir: "{{{{ {self.service}_base }}}}" - """)) + """ + ) + ) def create_compose_template(self): (self.templates_path / "stacks").mkdir(parents=True, exist_ok=True) compose_file = self.templates_path / "stacks" / "docker-compose.yml" - compose_file.write_text(textwrap.dedent(f"""\ + compose_file.write_text( + textwrap.dedent( + f"""\ services: {self.service}: image: {self.image} @@ -156,15 +157,15 @@ class RoleGenerator: timeout: 15s interval: 30s retries: 3 - start_period: 10s + start_period: 5s deploy: mode: replicated update_config: parallelism: 1 failure_action: rollback order: start-first - delay: 10s - monitor: 45s + delay: 5s + monitor: 30s replicas: 1 labels: - traefik.enable=true @@ -178,16 +179,22 @@ class RoleGenerator: networks: proxy: external: true - """)) + """ + ) + ) def create_group_vars(self): path = Config.GROUP_VARS / f"{self.service}.yml" - path.write_text(textwrap.dedent(f"""\ + path.write_text( + textwrap.dedent( + f"""\ --- {self.service}_domain: {self.service}.{Config.ROOT_DOMAIN} {self.service}_base: "{{{{ swarm_base }}}}/{self.service}" - """)) + """ + ) + ) def create_volumes(self): (self.templates_path / "volumes" / "data").mkdir(parents=True, exist_ok=True) @@ -195,7 +202,9 @@ class RoleGenerator: def create_deploy_hook(self): path = Config.ANSIBLE_PLAYBOOKS / f"{self.service}.yml" - path.write_text(textwrap.dedent(f"""\ + path.write_text( + textwrap.dedent( + f"""\ --- - name: {self.service} setup @@ -203,11 +212,15 @@ class RoleGenerator: become: true roles: - {self.service} - """)) + """ + ) + ) with open(Config.ANSIBLE_DEPLOY, "a") as f: f.write("\n") f.write(f"- name: {self.service}\n") - f.write(f" ansible.builtin.import_playbook: playbooks/{self.service}.yml\n") + f.write( + f" ansible.builtin.import_playbook: playbooks/{self.service}.yml\n" + ) def create_all(self): self.create_inventory() @@ -220,7 +233,9 @@ class RoleGenerator: def create_nginx_conf(service_name: str): path = Config.NGINX_SITES_ENABLED / f"{service_name}.conf" - path.write_text(textwrap.dedent(f"""\ + path.write_text( + textwrap.dedent( + f"""\ server {{ listen 80; server_name {service_name}.liz.coffee; @@ -240,16 +255,18 @@ def create_nginx_conf(service_name: str): proxy_set_header Connection "upgrade"; }} }} - """)) + """ + ) + ) def main(): parser = argparse.ArgumentParser(description="Service initializer for liz.coffee.") - parser.add_argument('--service-name', type=str, required=True) - parser.add_argument('--container-image', type=str, required=True) - parser.add_argument('--service-port', type=str, default='80') - parser.add_argument('--external', action='store_true') - parser.add_argument('--internal', action='store_true') + parser.add_argument("--service-name", type=str, required=True) + parser.add_argument("--container-image", type=str, required=True) + parser.add_argument("--service-port", type=str, default="80") + parser.add_argument("--external", action="store_true") + parser.add_argument("--internal", action="store_true") args = parser.parse_args() logger.info(f"Initializing service setup for '{args.service_name}'") @@ -272,16 +289,20 @@ def main(): logger.info("External DNS (Cloudflare) CNAME created successfully.") if args.internal: - logger.info("Configuring internal DNS via Pi-hole...") - pi = PiholeDns(web_password=Config.from_secrets("pihole_webpwd"), api_url=Config.PIHOLE) - success = pi.put_cname(service_fqdn, Config.INTERNAL_LOADBALANCER_HOST) + logger.info("Configuring internal DNS via LabDNS...") + dns = HomelabDns( + group_vars=Config.LABDNS_GROUP_VARS, delimiter=Config.LABDNS_DELIMITER + ) + success = dns.put_internal(args.service_name) if not success: - logger.warning("Internal DNS (Pi-hole) CNAME creation failed.") + logger.warning("Internal DNS (LabDNS) Record creation failed.") else: - logger.info("Internal DNS (Pi-hole) CNAME created successfully.") + logger.info("Internal DNS (LabDNS) Record created successfully.") logger.info("Generating Ansible role and configuration...") - generator = RoleGenerator(args.service_name, args.container_image, args.service_port) + generator = RoleGenerator( + args.service_name, args.container_image, args.service_port + ) generator.create_all() logger.info("Role generation complete.") @@ -292,6 +313,6 @@ def main(): logger.info(f"Service '{args.service_name}' setup complete.") + if __name__ == "__main__": main() - @@ -24,8 +24,8 @@ - name: Keepalived ansible.builtin.import_playbook: playbooks/keepalived.yml -- name: Pihole - ansible.builtin.import_playbook: playbooks/pihole.yml +- name: LabDNS + ansible.builtin.import_playbook: playbooks/labdns.yml - name: Traextor ansible.builtin.import_playbook: playbooks/traextor.yml diff --git a/group_vars/labdns.yml b/group_vars/labdns.yml new file mode 100644 index 0000000..1209e98 --- /dev/null +++ b/group_vars/labdns.yml @@ -0,0 +1,17 @@ +--- + +labdns_base: "{{ swarm_base }}/labdns" + +internal_services: + - bin + - ci + - idm + - kanban + - loadbalancer + - notes + - passwd + - pihole + - proxy + - src + - swarm + - traefik diff --git a/group_vars/pihole.yml b/group_vars/pihole.yml deleted file mode 100644 index 7e6c0e2..0000000 --- a/group_vars/pihole.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -pihole_domain: "dns.{{ domain }}" -pihole_base: "{{ swarm_base }}/pihole" -upstream_dns_servers: - - 1.1.1.1 - - 1.0.0.1 @@ -35,7 +35,7 @@ swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh a [traefik] swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh ansible_become_password='{{ swarm_become_password }}' -[pihole] +[labdns] swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh ansible_become_password='{{ swarm_become_password }}' [traextor] @@ -49,6 +49,7 @@ swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh a [silverbullet] swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh ansible_become_password='{{ swarm_become_password }}' + [bin] swarm-one ansible_host=10.128.0.201 ansible_user=serve ansible_connection=ssh ansible_become_password='{{ swarm_become_password }}' diff --git a/playbooks/labdns.yml b/playbooks/labdns.yml new file mode 100644 index 0000000..59e09ec --- /dev/null +++ b/playbooks/labdns.yml @@ -0,0 +1,7 @@ +--- + +- name: labdns setup + hosts: labdns + become: true + roles: + - labdns diff --git a/playbooks/pihole.yml b/playbooks/pihole.yml deleted file mode 100644 index 6a8b523..0000000 --- a/playbooks/pihole.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -- name: pihole setup - hosts: pihole - become: true - roles: - - pihole diff --git a/playbooks/roles/pihole/tasks/main.yml b/playbooks/roles/labdns/tasks/main.yml index fc526dc..20c95f7 100644 --- a/playbooks/roles/pihole/tasks/main.yml +++ b/playbooks/roles/labdns/tasks/main.yml @@ -1,9 +1,8 @@ --- -- name: Deploy pihole +- name: Deploy labdns ansible.builtin.import_tasks: manage-docker-swarm-service.yml vars: - service_name: pihole + service_name: labdns template_render_dir: "../templates" - service_destination_dir: "{{ pihole_base }}" - + service_destination_dir: "{{ labdns_base }}" diff --git a/playbooks/roles/labdns/templates/stacks/docker-compose.yml b/playbooks/roles/labdns/templates/stacks/docker-compose.yml new file mode 100644 index 0000000..3327c18 --- /dev/null +++ b/playbooks/roles/labdns/templates/stacks/docker-compose.yml @@ -0,0 +1,30 @@ +--- + +services: + labdns: + image: mvance/unbound:latest + ports: + - "53:53/udp" + - "53:53/tcp" + volumes: + - {{ labdns_base }}/volumes/unbound/forward-records.conf:/opt/unbound/etc/unbound/forward-records.conf:ro + - {{ labdns_base }}/volumes/unbound/a-records.conf:/opt/unbound/etc/unbound/a-records.conf:ro + environment: + - TZ={{ timezone }} + - DEPLOYMENT_TIME={{ deployment_time }} +{% if not homelab_build %} + healthcheck: + test: ["CMD-SHELL", "drill loadbalancer.{{ domain }} @127.0.0.1 | grep -q {{ loadbalancer_ip }}"] + retries: 3 + timeout: 5s + start_period: 8s +{% endif %} + deploy: + mode: replicated + update_config: + parallelism: 1 + failure_action: rollback + order: start-first + monitor: 25s + replicas: 2 + diff --git a/playbooks/roles/labdns/templates/volumes/unbound/a-records.conf b/playbooks/roles/labdns/templates/volumes/unbound/a-records.conf new file mode 100644 index 0000000..9462aab --- /dev/null +++ b/playbooks/roles/labdns/templates/volumes/unbound/a-records.conf @@ -0,0 +1,4 @@ +# {{ domain }} +{% for service in internal_services %} +local-data: "{{ service }}.{{ domain }}. A {{ loadbalancer_ip }}" +{% endfor %} diff --git a/playbooks/roles/labdns/templates/volumes/unbound/forward-records.conf b/playbooks/roles/labdns/templates/volumes/unbound/forward-records.conf new file mode 100644 index 0000000..19af327 --- /dev/null +++ b/playbooks/roles/labdns/templates/volumes/unbound/forward-records.conf @@ -0,0 +1,5 @@ +forward-zone: + name: "." + forward-addr: 1.1.1.1@853#cloudflare-dns.com + forward-addr: 1.0.0.1@853#cloudflare-dns.com + forward-tls-upstream: yes diff --git a/playbooks/roles/pihole/templates/stacks/docker-compose.yml b/playbooks/roles/pihole/templates/stacks/docker-compose.yml deleted file mode 100644 index 573121f..0000000 --- a/playbooks/roles/pihole/templates/stacks/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ -services: - pihole: - image: pihole/pihole:latest - ports: - - "53:53/udp" - - "53:53/tcp" - volumes: - - {{ pihole_base }}/volumes/pihole:/etc/pihole - - {{ pihole_base }}/volumes/dnsmasq:/etc/dnsmasq.d - environment: - - DEPLOYMENT_TIME={{ deployment_time }} - - TZ={{ timezone }} - - FTLCONF_webserver_api_password={{ pihole_webpwd }} - - FTLCONF_dns_upstreams={{ upstream_dns_servers | join(';') }} - networks: - - proxy -{% if not homelab_build %} - healthcheck: - test: ["CMD-SHELL", "dig loadbalancer.{{ domain }} @127.0.0.1 | grep -q {{ loadbalancer_ip }}"] - retries: 3 - timeout: 5s - start_period: 8s -{% endif %} - deploy: - mode: replicated - update_config: - parallelism: 1 - order: start-first - failure_action: rollback - monitor: 10s - replicas: 1 - labels: - - traefik.enable=true - - traefik.swarm.network=proxy - - traefik.http.routers.piholeweb.tls=true - - traefik.http.routers.piholeweb.tls.certResolver=letsencrypt - - traefik.http.routers.piholeweb.rule=Host(`{{ pihole_domain }}`) - - traefik.http.routers.piholeweb.entrypoints=websecure - - traefik.http.services.piholeweb.loadbalancer.server.port=80 - -networks: - proxy: - external: true diff --git a/playbooks/roles/pihole/templates/volumes/dnsmasq/.gitkeep b/playbooks/roles/pihole/templates/volumes/dnsmasq/.gitkeep deleted file mode 100644 index e69de29..0000000 --- a/playbooks/roles/pihole/templates/volumes/dnsmasq/.gitkeep +++ /dev/null diff --git a/playbooks/roles/pihole/templates/volumes/pihole/.gitkeep b/playbooks/roles/pihole/templates/volumes/pihole/.gitkeep deleted file mode 100644 index e69de29..0000000 --- a/playbooks/roles/pihole/templates/volumes/pihole/.gitkeep +++ /dev/null |