{"id":21479298,"url":"https://github.com/rycus86/domain-automation","last_synced_at":"2025-07-15T11:31:39.019Z","repository":{"id":66144728,"uuid":"120798975","full_name":"rycus86/domain-automation","owner":"rycus86","description":"Automatic DNS and SSL management tool","archived":false,"fork":false,"pushed_at":"2025-05-07T23:09:48.000Z","size":104,"stargazers_count":5,"open_issues_count":15,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-08T00:20:54.532Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rycus86.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2018-02-08T18:13:52.000Z","updated_at":"2023-01-13T08:21:44.000Z","dependencies_parsed_at":"2024-05-05T21:30:41.724Z","dependency_job_id":"d64733a8-1bbe-4e99-8985-a9613c544635","html_url":"https://github.com/rycus86/domain-automation","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rycus86/domain-automation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdomain-automation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdomain-automation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdomain-automation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdomain-automation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rycus86","download_url":"https://codeload.github.com/rycus86/domain-automation/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rycus86%2Fdomain-automation/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265431613,"owners_count":23764031,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-23T11:24:33.051Z","updated_at":"2025-07-15T11:31:38.718Z","avatar_url":"https://github.com/rycus86.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Domain automation\n\n[![Build Status](https://travis-ci.org/rycus86/domain-automation.svg?branch=master)](https://travis-ci.org/rycus86/domain-automation)\n[![Docker Stars](https://img.shields.io/docker/stars/rycus86/domain-automation.svg)](https://hub.docker.com/r/rycus86/domain-automation/)\n[![Coverage Status](https://coveralls.io/repos/github/rycus86/domain-automation/badge.svg?branch=master)](https://coveralls.io/github/rycus86/domain-automation?branch=master)\n[![Code Climate](https://codeclimate.com/github/rycus86/domain-automation/badges/gpa.svg)](https://codeclimate.com/github/rycus86/domain-automation)\n\nPython app to help automate dynamic DNS and SSL certificate updates\nwith notifications and signals.\n\n## Design\n\nThe application is designed to execute two steps for each managed subdomains\nin a sequence, repeated on a schedule:\n\n1. Check and adjust DNS records\n2. Check and update SSL certificates\n\nNotifications are sent for each DNS record and SSL update.\nThe DNS maintenance is mainly for servers on dynamic IP addresses, \ne.g. the public IP address is not fixed, and it needs updating every time it changes.\nThe public IP address is fetched once per run, and it is used for every subdomain\nwithin the same run.\n\nThe application defines [factories](https://github.com/rycus86/domain-automation/blob/master/src/factories.py)\nto create manager instances for each component:\n\n- [scheduler](https://github.com/rycus86/domain-automation/tree/master/src/scheduler) for the scheduling logic (start, cancel, etc.)\n- [notification](https://github.com/rycus86/domain-automation/tree/master/src/notifications) for sending notifications on updates\n- [discovery](https://github.com/rycus86/domain-automation/tree/master/src/discovery) to collect the list of managed subdomains\n- [dns manager](https://github.com/rycus86/domain-automation/tree/master/src/dns_manager) to get the public IP address, the current IP address for DNS records of subdomains, and to update them if needed\n- [ssl manager](https://github.com/rycus86/domain-automation/tree/master/src/ssl_manager) to fetch or renew SSL certificates when needed\n\nNotification managers are composable to process updates sequentially,\neach of them for every notification.\nEvery other manager has one configured instance.\n\nEvery component uses a *noop* implementation by default.\nThis can be overridden with configuration, coming from a configuration file\nor environment variables.\n\nFor example, a configuration called `COMPONENT_CONFIG` would be look up in:\n\n1. A specific key-value file, if it contains a line with `COMPONENT_CONFIG=...`\n2. The default key-value file at `/var/secrets/app.config` for fallback\n3. The `COMPONENT_CONFIG` environment variable if not found in the files\n4. The default value specified in the component\n\n*Note:* `bool` values generally accept the `yes`, `true`, `1` values to make them true,\nignoring the character case.\n\n## Component implementations\n\nThe application currently supports the following implementations for its managers.\n\n### Schedulers\n\nThe scheduler instance to use can be configured using the `SCHEDULER_CLASS` key.\nThe value should be full module plus class name, for example\n`scheduler.oneshot.OneShotScheduler` for the default implementation.\nIn this example, the `OneShotScheduler` class is defined in the `oneshot` module,\nunder the `scheduler` module ([see it here](https://github.com/rycus86/domain-automation/blob/master/src/scheduler/oneshot.py)).\n\n#### One-shot scheduler\n\n`SCHEDULER_CLASS=scheduler.oneshot.OneShotScheduler`\n\nThis is the default scheduler used.\nIt executes the update once, and it does not repeat.\nThis means the application executes one update, then exits.\n\n#### Five minutes scheduler\n\n`SCHEDULER_CLASS=scheduler.repeat.FiveMinutesScheduler`\n\nAfter an update, the next run is scheduled to start 5 minutes after the previous one\nhas finished.\nBy default, the first run will start after 5 minutes the application has started.\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| Start the first execution as soon as the application starts | `IMMEDIATE_START` | `/var/secrets/app.config` | `no` | no |\n\n#### Docker aware scheduled\n\n`SCHEDULER_CLASS=scheduler.repeat_docker.DockerAwareScheduler`\n\nBased on the *five minutes scheduler* above, it also connects to the Docker API on the host,\nand listens for *Swarm service create events*, to kick off an out-of-schedule update.\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| Start the first execution as soon as the application starts | `IMMEDIATE_START` | `/var/secrets/app.config` | `no` | no |\n\n### Notifications\n\nNotification managers are configured with the `NOTIFICATION_MANAGER_CLASS` key.\nTo include more than one notification manager, use a comma separated value for each of their\nfull package plus class name (without whitespace), for example:\n\n```\nNOTIFICATION_MANAGER_CLASS=notifications.log_notification.LoggingNotificationManager,notifications.slack_message.SlackNotificationManager\n```\n\nThe default `notifications.noop.NoopNotificationManager` will not execute or log anything.\n\n#### Log notification manager\n\n`NOTIFICATION_MANAGER_CLASS=notifications.log_notification.LoggingNotificationManager`\n\nLogs messages to the standard output or error streams, depending on whether the update\nhas been successful or not.\n\n#### Slack notification manager\n\n`NOTIFICATION_MANAGER_CLASS=notifications.slack_message.SlackNotificationManager`\n\nSends updates and messages to a [Slack](https://slack.com/) channel, using a [chatbot](https://api.slack.com/bot-users).\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| Slack API token | `SLACK_TOKEN` | `/var/secrets/notifications` | none | yes |\n| Slack channel | `SLACK_CHANNEL` | `/var/secrets/notifications` | `general` | no |\n| Name of the Slack bot | `SLACK_BOT_NAME` | `/var/secrets/notifications` | `domain-automation-bot` | no |\n| The URL of the Slack bot's avatar image | `SLACK_BOT_ICON` | `/var/secrets/notifications` | none | no |\n\n#### Docker signal notification manager\n\n`NOTIFICATION_MANAGER_CLASS=notifications.docker_signal.DockerSignalNotification`\n\nSends Docker signals (using `docker kill` to containers with the specified label.\nThe value of the label is the signal to send, for example: `domain.automation.signal=HUP`\nwould send a `SIGHUP` signal to the main process (`pid 1`) in the container.\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| The Docker __container__ label name | `DOCKER_SIGNAL_LABEL` | `/var/secrets/notifications` | `domain.automation.signal` | no |\n\nThis manager uses __container__ labels (not Swarm service labels), but it does support\nSwarm services and stacks.\nThe actual signal in Swarm is sent through a temporary global service, see a bit more details\n[in my blog post](https://blog.viktoradam.net/2018/02/17/auto-dns-and-ssl-management/).\n\n### Discovery\n\nThe discovery manager instance can be configured using the `DISCOVERY_CLASS` key.\nIts purpose is to provide the list of subdomains the application manages.\n\n#### Docker labels discovery\n\n`DISCOVERY_CLASS=discovery.docker_labels.DockerLabelsDiscovery`\n\nThis implementation uses Docker labels (*either* service or container labels)\nto collect the subdomains.\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| Name of the Docker label | `DOCKER_DISCOVERY_LABEL` | `/var/secrets/discovery` | `discovery.domain.name` | no |\n| The default *root* domain | `DEFAULT_DOMAIN` | `/var/secrets/app.config` | `localhost.local` | no |\n\nMultiple subdomains may be given on a single label value, separated by the `,` comma character.\n\n### DNS managers\n\nDNS managers are responsible for keeping DNS records pointing to *dynamic IP addresses*\nup to date.\nThe implementation to use is configured using the `DNS_MANAGER_CLASS` configuration key.\n\n#### Cloudflare DNS manager\n\n`DNS_MANAGER_CLASS=dns_manager.cloudflare_dns.CloudflareDNSManager`\n\nThis manager manager DNS records through [Cloudflare](https://www.cloudflare.com/) `A` records with *IPv4* addresses.\nThe implentation needs configuration for using the [Cloudflare API](https://api.cloudflare.com/) with the\nregistered email address and the token that belongs to it.\nTo fetch the current public IP address, [api.ipify.org](https://www.ipify.org) is used.\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| The registered email address in Cloudflare | `CLOUDFLARE_EMAIL` | `/var/secrets/cloudflare` | none | yes |\n| The Cloudflare API access token | `CLOUDFLARE_TOKEN` | `/var/secrets/cloudflare` | none | yes |\n\n### SSL managers\n\nSSL managers fetch and renew SSL certificates.\nThe implementation is chosen by the `SSL_MANAGER_CLASS` configuration.\n\n#### Certbot using Cloudflare DNS authenticator\n\n`SSL_MANAGER_CLASS=ssl_manager.certbot_cf_ssl.CertbotCloudflareSSLManager`\n\nThis implementation uses [certbot](https://github.com/certbot/certbot) to \nget new and renewed SSL certificates from [Let's Encrypt](https://letsencrypt.org/).\nThe domain verification is done through *TXT* DNS records in Cloudflare using\nthe [Cloudflare DNS Authenticator plugin](https://github.com/certbot/certbot/tree/master/certbot-dns-cloudflare).\n\n| Configuration item | Configuration key | Configuration file | Default value | Required |\n| ------------------ | ----------------- | ------------------ | ------------- | -------- |\n| The registered email address in Cloudflare | `CLOUDFLARE_EMAIL` | `/var/secrets/cloudflare` | none | yes |\n| The Cloudflare API access token | `CLOUDFLARE_TOKEN` | `/var/secrets/cloudflare` | none | yes |\n| Allowed DNS propagation time (in seconds) to wait before the domain verification starts | `DNS_PROPAGATION_SECONDS` | `/var/secrets/certbot` | `30` | no |\n| Timeout for the `certbot` command execution (in seconds) | `CERTBOT_TIMEOUT` | `/var/secrets/certbot` | `120` | no |\n| Use staging *ACME* servers for testing | `CERTBOT_STAGING` | `/var/secrets/certbot` | `no` | no |\n\n## Usage\n\nThe application is written for Python 3 but *should* work with Python 2.7 as well.\n\nHaving the configuration in place, either through configuration files or\nenvironment variables or a mix of them, the app can be started with:\n\n```shell\n$ python app.py\n```\n\nTo install any missing dependencies, run:\n\n```shell\n$ pip install -r requirements.txt\n```\n\nThe application is also available as a [Docker image](https://hub.docker.com/r/rycus86/domain-automation/).\nTo run it:\n\n```shell\n$ docker run -d --name domain-automation            \\\n    -v /var/run/docker.sock:/var/run/docker.sock    \\\n    -e SETTING_TO_OVERRIDE=abcd                     \\\n    -v $PWD/config.file:/var/secrets/app.config     \\\n    rycus86/domain-automation\n```\n\nThe `/var/run/docker.sock` mount is only required for managers using the Docker API.\nThe Docker image supports the `amd64`, `armv7` and `arm64v8` platforms.\n\nThe same container above as a Compose service:\n\n```yaml\nversion: '2'\nservices:\n\n  automation:\n    image: rycus86/domain-automation\n    environment:\n      - SETTING_TO_OVERRIDE=abcd\n    volumes:\n      - $PWD/config.file:/var/secrets/app.config\n      - /var/run/docker.sock:/var/run/docker.sock\n```\n\nAgain, the same, using Swarm services and secrets:\n\n```yaml\nversion: '3.5'\nservices:\n\n  automation:\n    image: rycus86/domain-automation\n    deploy:\n      replicas: 1\n      placement:\n        constraints:\n          - node.role == manager\n    environment:\n      - SETTING_TO_OVERRIDE=abcd\n    secrets:\n      - source: app-config\n        target: /var/secrets/app.config\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock\n\nsecrets:\n  app-config:\n    file: ./config.file\n    name: app-config-v${VERSION}\n```\n\nMore information and explanation can be found on my blog\n[in the related post](https://blog.viktoradam.net/2018/02/17/auto-dns-and-ssl-management/).\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fdomain-automation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frycus86%2Fdomain-automation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frycus86%2Fdomain-automation/lists"}