diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..9b28eb4e --- /dev/null +++ b/.drone.yml @@ -0,0 +1,19 @@ +--- +kind: pipeline +name: default + +steps: + - name: check ansible syntax + image: plugins/ansible:3 + settings: + playbook: nas.yml + galaxy: requirements.yml + inventory: tests/inventories/integration_testing/inventory + syntax_check: true + + - name: lint + image: python:3 + commands: + - pip3 install ansible yamllint==1.27.1 ansible-lint==6.5 + - ansible-lint nas.yml + - yamllint . diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index d1e407e3..5bbebaef 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Molecule Test - uses: gofrolist/molecule-action@v2 + uses: gofrolist/molecule-action@dfbfd1af6a77523c8a937a1532f92808065a00a4 with: molecule_options: --base-config ../../tests/molecule/base.yml molecule_command: test diff --git a/README.md b/README.md index eae1ddaf..2f25f68d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ You can configure Ansible-NAS to set up any (or all!) of the applications listed If you have a spare domain name you can configure applications to be accessible externally to your home LAN too; they'll be configured with a sensible hostname and DNS gets updated accordingly if your home IP address changes. -### Available Applications +## Available Applications * [Airsonic](https://airsonic.github.io/) - catalog and stream music * [Bazarr](https://github.com/morpheus65535/bazarr) - companion to Radarr and Sonarr for downloading subtitles @@ -29,6 +29,7 @@ If you have a spare domain name you can configure applications to be accessible * [Dashy](https://dashy.to/) - A self-hosted startpage for your server. Easy to use visual editor, status checking, widgets, themes and tons more! * [Deluge](https://dev.deluge-torrent.org/) - A lightweight, Free Software, cross-platform BitTorrent client. * [DokuWiki](https://www.dokuwiki.org/) - A simple to use and highly versatile Open Source wiki software that doesn't require a database. +* [Drone CI](https://drone.io) - A self-service Continuous Integration platform for busy development teams. * [Duplicacy](https://duplicacy.com/) - A web UI for the Duplicacy cloud backup program, which provides lock-free deduplication backups to multiple providers * [Duplicati](https://www.duplicati.com/) - for backing up your stuff * [Emby](https://emby.media/) - Media streaming and management @@ -64,6 +65,7 @@ If you have a spare domain name you can configure applications to be accessible * [netboot.xyz](https://netboot.xyz/) - a PXE boot server * [Netdata](https://my-netdata.io/) - An extremely comprehensive system monitoring solution * [Nextcloud](https://nextcloud.com/) - A self-hosted Dropbox alternative +* [Nomad](https://www.nomadproject.io/) - A simple and flexible scheduler and software orchestrator * [NZBget](https://nzbget.net/) - The most efficient usenet downloader * [Octoprint](https://octoprint.org/) - Control and monitor your 3D printer * [Ombi](https://ombi.io/) - web application that automatically gives your users the ability to request content @@ -115,7 +117,7 @@ Ansible NAS doesn't set up your disk partitions, primarily because getting it wr ## Installation -See [Installation](https://davestephens.github.io/ansible-nas/installation/). +See [Installation](https://ansible-nas.io/docs/getting-started/installation/). ## Documentation diff --git a/nas.yml b/nas.yml index 621ae0fc..6ca54195 100644 --- a/nas.yml +++ b/nas.yml @@ -93,6 +93,10 @@ tags: - dokuwiki + - role: drone-ci + tags: + - drone_ci + - role: duplicacy tags: - duplicacy @@ -261,6 +265,10 @@ - nextcloud when: (nextcloud_enabled | default(False)) + - role: nomad + tags: + - nomad + - role: nzbget tags: - nzbget diff --git a/roles/drone-ci/defaults/main.yml b/roles/drone-ci/defaults/main.yml new file mode 100644 index 00000000..42be23b9 --- /dev/null +++ b/roles/drone-ci/defaults/main.yml @@ -0,0 +1,43 @@ +--- +drone_ci_enabled: false +drone_ci_available_externally: false + +# directories +drone_ci_data_directory: "{{ docker_home }}/drone-ci" + +# network +drone_ci_port_http: 8001 +drone_ci_runner_port_http: 8002 +drone_ci_address: "http://{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}:{{ drone_ci_port_http }}" +drone_ci_hostname: drone-ci + +# memory +drone_ci_memory: 1g +drone_ci_agent_memory: 1g + +# docker +drone_ci_container_name: drone-ci +drone_ci_runner_container_name: drone-ci-runner + +# Drone-CI Application Config +# the users that'll be granted admin, comma separated. Should match a gitea user. +drone_ci_admin_user: david + +# shared secret - use openssl rand -hex 16 to generate your own +drone_ci_agent_secret: d052ab29a86a02c6b6ff1e5851ee15e1 + +# debug logging +drone_ci_debug_logging: false + +# the url to your gitea server +drone_ci_gitea_url: "http://{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}:{{ gitea_port_http }}" + +# gitea auth credentials. see https://drone-ci.org/docs/administration/forges/gitea for more info +drone_ci_gitea_client_id: notset +drone_ci_gitea_client_secret: notset + +# how many drone runners to run +drone_ci_runner_capacity: 2 + +# name of the Drone runner +drone_ci_runner_name: "{{ ansible_nas_hostname }}" diff --git a/roles/drone-ci/molecule/default/molecule.yml b/roles/drone-ci/molecule/default/molecule.yml new file mode 100644 index 00000000..0671311d --- /dev/null +++ b/roles/drone-ci/molecule/default/molecule.yml @@ -0,0 +1,10 @@ +--- +provisioner: + inventory: + group_vars: + all: + drone_ci_enabled: true + drone_ci_gitea_client_id: asdfasdf12341234 + drone_ci_gitea_client_secret: asdfasd12341234 + gitea_port_http: 3001 + ansible_nas_hostname: ansible-nas-ci diff --git a/roles/drone-ci/molecule/default/side_effect.yml b/roles/drone-ci/molecule/default/side_effect.yml new file mode 100644 index 00000000..1adfbc19 --- /dev/null +++ b/roles/drone-ci/molecule/default/side_effect.yml @@ -0,0 +1,10 @@ +--- +- name: Stop + hosts: all + become: true + tasks: + - name: "Include {{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }} role" + include_role: + name: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') | basename }}" + vars: + drone_ci_enabled: false diff --git a/roles/drone-ci/molecule/default/verify.yml b/roles/drone-ci/molecule/default/verify.yml new file mode 100644 index 00000000..c993caf9 --- /dev/null +++ b/roles/drone-ci/molecule/default/verify.yml @@ -0,0 +1,26 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - include_vars: + file: ../../defaults/main.yml + + - name: Get container state + docker_container_info: + name: "{{ drone_ci_container_name }}" + register: result + + - name: Get container state + docker_container_info: + name: "{{ drone_ci_runner_container_name }}" + register: result_runner + + + - name: Check Drone CI is running + assert: + that: + - result.container['State']['Status'] == "running" + - result.container['State']['Restarting'] == false + - result_runner.container['State']['Status'] == "running" + - result_runner.container['State']['Restarting'] == false diff --git a/roles/drone-ci/molecule/default/verify_stopped.yml b/roles/drone-ci/molecule/default/verify_stopped.yml new file mode 100644 index 00000000..3f8ce56f --- /dev/null +++ b/roles/drone-ci/molecule/default/verify_stopped.yml @@ -0,0 +1,25 @@ +--- +- name: Verify + hosts: all + gather_facts: false + tasks: + - include_vars: + file: ../../defaults/main.yml + + - name: Try and stop and remove Drone CI + docker_container: + name: "{{ drone_ci_container_name }}" + state: absent + register: result + + - name: Try and stop and remove Drone CI runner + docker_container: + name: "{{ drone_ci_runner_container_name }}" + state: absent + register: result_runner + + - name: Check Drone CI is stopped + assert: + that: + - not result.changed + - not result_runner.changed diff --git a/roles/drone-ci/tasks/main.yml b/roles/drone-ci/tasks/main.yml new file mode 100644 index 00000000..db36a55b --- /dev/null +++ b/roles/drone-ci/tasks/main.yml @@ -0,0 +1,88 @@ +--- +- name: Start Drone-CI + block: + - name: Check for Gitea installation + fail: + msg: "Drone-CI requires Gitea enabled and running for authentication, please set that up first." + when: gitea_enabled is false + + - name: Check for Gitea config + fail: + msg: "Missing Gitea Oauth2 config! Read https://docs.drone.io/server/provider/gitea/ and set drone_ci_gitea_client_id and drone_ci_gitea_client_secret." + when: drone_ci_gitea_client_id == "notset" + + - name: Create Drone-CI Directories + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ drone_ci_data_directory }}" + + - name: Create Drone-CI container + docker_container: + name: "{{ drone_ci_container_name }}" + image: drone/drone:2 + pull: true + volumes: + - "{{ drone_ci_data_directory }}:/var/lib/drone:rw" + ports: + - "{{ drone_ci_port_http }}:80" + env: + DRONE_USER_CREATE: "username:{{ drone_ci_admin_user }},admin:true" + DRONE_SERVER_HOST: "{{ drone_ci_address }}" + DRONE_RPC_SECRET: "{{ drone_ci_agent_secret }}" + DRONE_GITEA_SERVER: "{{ drone_ci_gitea_url }}" + DRONE_GITEA_CLIENT_ID: "{{ drone_ci_gitea_client_id }}" + DRONE_GITEA_CLIENT_SECRET: "{{ drone_ci_gitea_client_secret }}" + DRONE_LOGS_DEBUG: "{{ drone_ci_debug_logging | string }}" + DRONE_SERVER_PROTO: "http" + restart_policy: unless-stopped + memory: "{{ drone_ci_memory }}" + labels: + traefik.enable: "{{ drone_ci_available_externally | string }}" + traefik.http.routers.drone_ci.rule: "Host(`{{ drone_ci_hostname }}.{{ ansible_nas_domain }}`)" + traefik.http.routers.drone_ci.tls.certresolver: "letsencrypt" + traefik.http.routers.drone_ci.tls.domains[0].main: "{{ ansible_nas_domain }}" + traefik.http.routers.drone_ci.tls.domains[0].sans: "*.{{ ansible_nas_domain }}" + traefik.http.services.drone_ci.loadbalancer.server.port: "80" + + - name: Create Drone-CI Runner container + docker_container: + name: "{{ drone_ci_runner_container_name }}" + image: drone/drone-runner-docker:1 + pull: true + volumes: + - "/var/run/docker.sock:/var/run/docker.sock:rw" + ports: + - "{{ drone_ci_runner_port_http }}:3000" + env: + DRONE_RPC_HOST: "{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}:{{ drone_ci_port_http }}" + DRONE_RPC_SECRET: "{{ drone_ci_agent_secret }}" + DRONE_RPC_PROTO: "http" + DRONE_RUNNER_CAPACITY: "{{ drone_ci_runner_capacity | string }}" + DRONE_RUNNER_NAME: "{{ drone_ci_runner_name }}" + restart_policy: unless-stopped + memory: "{{ drone_ci_agent_memory }}" + + - name: Add webhook allowed hosts to Gitea + blockinfile: + path: "{{ gitea_data_directory }}/gitea/gitea/conf/app.ini" + block: | + [webhook] + ALLOWED_HOST_LIST=private + SKIP_TLS_VERIFY=true + notify: restart gitea + when: drone_ci_enabled is true + +- name: Stop Drone-CI + block: + - name: Stop Drone-CI + docker_container: + name: "{{ drone_ci_container_name }}" + state: absent + + - name: Stop Drone-CI Runner + docker_container: + name: "{{ drone_ci_runner_container_name }}" + state: absent + when: drone_ci_enabled is false diff --git a/roles/nomad/defaults/main.yml b/roles/nomad/defaults/main.yml new file mode 100644 index 00000000..c9442609 --- /dev/null +++ b/roles/nomad/defaults/main.yml @@ -0,0 +1,9 @@ +--- +nomad_enabled: false +nomad_data_home: "{{ docker_home }}/nomad" +nomad_service_name: nomad + +# ports +nomad_port_http: 4646 +nomad_port_rpc: 4647 +nomad_port_serf: 4648 diff --git a/roles/nomad/handlers/main.yml b/roles/nomad/handlers/main.yml new file mode 100644 index 00000000..88b8c549 --- /dev/null +++ b/roles/nomad/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart Nomad + systemd: + state: restarted + name: "{{ nomad_service_name }}" + listen: "restart nomad" diff --git a/roles/nomad/tasks/main.yml b/roles/nomad/tasks/main.yml new file mode 100644 index 00000000..6d1bdb03 --- /dev/null +++ b/roles/nomad/tasks/main.yml @@ -0,0 +1,40 @@ +--- +- name: Install and start Nomad + block: + - name: Add Hashicorp GPG key + ansible.builtin.apt_key: + url: https://apt.releases.hashicorp.com/gpg + state: present + + - name: Add Hashicorp apt repository + ansible.builtin.apt_repository: + repo: "deb https://apt.releases.hashicorp.com {{ ansible_facts['lsb']['codename'] }} main" + state: present + filename: hashicorp + + - name: Install Nomad + ansible.builtin.apt: + name: nomad + state: present + + - name: Template Nomad config + ansible.builtin.template: + src: nomad.hcl + dest: /etc/nomad.d/nomad.hcl + notify: restart nomad + + - name: Start Nomad + ansible.builtin.systemd: + name: "{{ nomad_service_name }}" + state: started + enabled: yes + when: nomad_enabled is true + +- name: Stop Nomad + block: + - name: Stop Nomad + ansible.builtin.systemd: + name: "{{ nomad_service_name }}" + state: stopped + enabled: no + when: nomad_enabled is false diff --git a/roles/nomad/templates/nomad.hcl b/roles/nomad/templates/nomad.hcl new file mode 100644 index 00000000..ad6e41bc --- /dev/null +++ b/roles/nomad/templates/nomad.hcl @@ -0,0 +1,27 @@ +# Full configuration options can be found at https://www.nomadproject.io/docs/configuration + +data_dir = "{{ nomad_data_home }}/data" +bind_addr = "0.0.0.0" + +ports { + http = {{ nomad_port_http }} + rpc = {{ nomad_port_rpc }} + serf = {{ nomad_port_serf }} +} + +server { + # license_path is required as of Nomad v1.1.1+ + #license_path = "/opt/nomad/license.hclic" + enabled = true + bootstrap_expect = 1 +} + +client { + enabled = true + servers = ["0.0.0.0"] + + host_volume "docker_data" { + path = "{{ docker_home }}" + read_only = false + } +} diff --git a/website/docs/applications/development-tools/drone_ci.md b/website/docs/applications/development-tools/drone_ci.md new file mode 100644 index 00000000..6567250e --- /dev/null +++ b/website/docs/applications/development-tools/drone_ci.md @@ -0,0 +1,19 @@ +--- +title: "Drone CI" +--- + +Drone is a self-service Continuous Integration platform for busy development teams. + +Check it out at . + +## Usage + +Set `drone_ci_enabled: true` in your `inventories//nas.yml` file. + +Gitea (`gitea_enabled: true`) must be set up and running before attempting to set up Drone CI. + +## Setup Tasks + +An Oauth2 application must be set up in Gitea. Visit for more info, then set `drone_ci_gitea_client_id` and `drone_ci_gitea_client_secret` accordingly. The Gitea Redirect URL will be `http://:{{ drone_ci_port_http }}/login` + +Set `drone_ci_admin_user` to the same username as your user in Gitea. diff --git a/website/docs/applications/development-tools/nomad.md b/website/docs/applications/development-tools/nomad.md new file mode 100644 index 00000000..08389d11 --- /dev/null +++ b/website/docs/applications/development-tools/nomad.md @@ -0,0 +1,13 @@ +--- +title: "Nomad" +--- + +Homepage: + +A simple and flexible scheduler and orchestrator to deploy and manage containers and non-containerised applications across on-prem and clouds at scale. + +## Usage + +Set `nomad_enabled: true` in your `inventories//nas.yml` file. + +Nomad's web interface can be found at diff --git a/website/docs/applications/monitoring/speedtest.md b/website/docs/applications/monitoring/speedtest.md index 0d5eaa02..d9f82ad9 100644 --- a/website/docs/applications/monitoring/speedtest.md +++ b/website/docs/applications/monitoring/speedtest.md @@ -10,8 +10,8 @@ Continuously track your internet speed ## Usage -Set `speedtest_enabled: true` in your `inventories//nas.yml` file. +Set `speedtest_tracker_enabled: true` in your `inventories//nas.yml` file. -If you want to access Speedtest-Tracker externally, don't forget to set `speedtest_available_externally: true` in your `inventories//nas.yml` file. +If you want to access Speedtest-Tracker externally, don't forget to set `speedtest_tracker_available_externally: true` in your `inventories//nas.yml` file. The Speedtest-Tracker interface can be found at .