diff --git a/README.md b/README.md index d7cab43..266db84 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Roles for deploying matrix infrastructure using ansible. +## Roles + +- [`cinny`](roles/cinny/README.md): [Cinny](https://cinny.in/) Web Client + ## License [CNPLv7+](LICENSE.md): Cooperative Nonviolent Public License diff --git a/playbooks/cinny.yml b/playbooks/cinny.yml new file mode 100644 index 0000000..7a3445d --- /dev/null +++ b/playbooks/cinny.yml @@ -0,0 +1,6 @@ +--- +- name: Deploy and configure cinny + hosts: "{{ cinny_hosts | default('cinny') }}" + become: "{{ cinny_become | default(true) }}" + roles: + - role: finallycoffee.matrix.cinny diff --git a/roles/cinny/README.md b/roles/cinny/README.md new file mode 100644 index 0000000..0cc4305 --- /dev/null +++ b/roles/cinny/README.md @@ -0,0 +1,29 @@ +# `finallycoffee.matrix.cinny` ansible role + +> [!WARNING] +> This role is a WIP and not yet usable + +## Supported deployment methods + +Set your `deployment_method` to: + +- [`docker` (docs)](docs/docker.md) (current default) +- `podman` +- [`nginx` (docs)](docs/nginx.md) +- [`tarball` (docs)](docs/tarball.md) + +Not yet implemented but planned: + +- `apache2` +- `caddy` + +## Configuration + +All cinny `config.json` configuration keys are available as a snake-cased ansible variable: +- `cinny_config_homeserver_list` +- `cinny_config_allow_custom_homeservers` +- [...] + +If you want to provide structured configuration directly, you can either provide additional configuration in `cinny_config` or overwrite all existing defaults by setting `cinny_config_complete`. + +To ensure cinny is removed from the system, set `cinny_state` to `absent` (default is `present`). diff --git a/roles/cinny/defaults/main/config.yml b/roles/cinny/defaults/main/config.yml new file mode 100644 index 0000000..a5f73c8 --- /dev/null +++ b/roles/cinny/defaults/main/config.yml @@ -0,0 +1,25 @@ +--- +cinny_testvar: abc +cinny_config_complete: >- + {{ cinny_config | default({}) + | combine(cinny_default_config | default({})) }} +cinny_config: {} +cinny_default_config: + homeserverList: "{{ cinny_config_homeserver_list }}" + allowCustomHomeservers: "{{ cinny_config_allow_custom_homeservers }}" + featuredCommunities: + openAsDefault: "{{ cinny_config_featured_communities_open_as_default }}" + spaces: "{{ cinny_config_featured_communities_spaces }}" + rooms: "{{ cinny_config_featured_communities_rooms }}" + servers: "{{ cinny_config_featured_communities_servers }}" + hashRouter: + enabled: "{{ cinny_config_hash_router_enabled }}" + basename: "{{ cinny_config_hash_router_basename }}" +cinny_config_homeserver_list: [] +cinny_config_allow_custom_homeservers: true +cinny_config_featured_communities_open_as_default: false +cinny_config_featured_communities_spaces: [] +cinny_config_featured_communities_rooms: [] +cinny_config_featured_communities_servers: [] +cinny_config_hash_router_enabled: false +cinny_config_hash_router_basename: "/" diff --git a/roles/cinny/defaults/main/container.yml b/roles/cinny/defaults/main/container.yml new file mode 100644 index 0000000..c6be016 --- /dev/null +++ b/roles/cinny/defaults/main/container.yml @@ -0,0 +1,30 @@ +--- +cinny_container_image: >- + {{ + cinny_container_image_registry + '/' + + ((cinny_container_image_namespace + '/') + if cinny_container_image_namespace | default(false, true) else '') + + cinny_container_image_name + ':' + + (cinny_container_image_tag | default('v' + cinny_version, true)) + }} +cinny_container_image_registry: "ghcr.io" +cinny_container_image_namespace: "cinnyapp" +cinny_container_image_name: "cinny" +cinny_container_image_tag: ~ +cinny_container_name: "cinny" +cinny_container_restart_policy: >- + {{ (cinny_deployment_method == 'docker') + | ternary('unless-stopped', + (cinny_deployment_method == 'podman' | + ternary('on-failure', 'always'))) + }} +cinny_container_source: pull + +cinny_container_user: "{{ cinny_host_uid }}" +cinny_container_full_volumes: >- + {{ cinny_container_default_volumes + + cinny_container_volumes | default([]) }} +cinny_container_default_volumes: + - "{{ cinny_config_file }}:/usr/share/nginx/html/config.json:ro" + + diff --git a/roles/cinny/defaults/main/main.yml b/roles/cinny/defaults/main/main.yml new file mode 100644 index 0000000..53a51b6 --- /dev/null +++ b/roles/cinny/defaults/main/main.yml @@ -0,0 +1,18 @@ +--- +cinny_user: cinny +cinny_state: "present" +cinny_version: "4.2.1" +cinny_deployment_method: "docker" + +cinny_base_path: "/opt/cinny" +cinny_source_path: "{{ cinny_base_path }}/src" +cinny_dist_path: "{{ cinny_source_path }}/dist" +cinny_config_path: "{{ cinny_base_path }}/config" +cinny_config_file: "{{ cinny_config_path }}/config.json" + +cinny_host_uid: >- + {{ (cinny_user_info is defined and 'uid' in cinny_user_info) + | ternary(cinny_user_info.uid, cinny_user) }} +cinny_host_gid: >- + {{ (cinny_user_info is defined and 'group' in cinny_user_info) + | ternary(cinny_user_info.group, cinny_user) }} diff --git a/roles/cinny/defaults/main/nginx.yml b/roles/cinny/defaults/main/nginx.yml new file mode 100644 index 0000000..c97c7aa --- /dev/null +++ b/roles/cinny/defaults/main/nginx.yml @@ -0,0 +1,9 @@ +--- +cinny_nginx_listen_port: 8080 +cinny_nginx_server: ~ +cinny_nginx_location: / + +cinny_nginx_available_sites: "/etc/nginx/sites-available" +cinny_nginx_enabled_sites: "/etc/nginx/sites-enabled" +cinny_nginx_vhost_name: "cinny" +cinny_nginx_vhost_enable: true diff --git a/roles/cinny/defaults/main/tarball.yml b/roles/cinny/defaults/main/tarball.yml new file mode 100644 index 0000000..dc6f721 --- /dev/null +++ b/roles/cinny/defaults/main/tarball.yml @@ -0,0 +1,10 @@ +--- +cinny_tarball_server: "https://github.com" +cinny_tarball_url: >- + {{ cinny_tarball_server }}/cinnyapp/cinny/releases/download/v{{ cinny_version }}/cinny-v{{ cinny_version }}.tar.gz +cinny_tarball_url_username: ~ +cinny_tarball_url_password: ~ + +cinny_tarball_path: "/tmp/cinny-v{{ cinny_version }}.tar.gz" + +cinny_running_version_file: "{{ cinny_source_path }}/cinny_version.txt" diff --git a/roles/cinny/docs/docker.md b/roles/cinny/docs/docker.md new file mode 100644 index 0000000..aeca8fb --- /dev/null +++ b/roles/cinny/docs/docker.md @@ -0,0 +1,33 @@ +# `cinny` deployment using `docker` + +> [!NOTE] +> Needs the python library `docker` on the `ansible_host`. + +## Configuration + +The following options to the +[`docker_container` module](https://docs.ansible.com/ansible/latest/collections/community/docker/docker_container_module.html) +are available under the `cinny_container_` prefix: + +- `env` +- `ports` +- `labels` +- `networks` +- `etc_hosts` +- `purge_networks` + +The following variables are pre-populated by the role, so override them with care: + +- `name` +- `image` +- `user` +- `volumes` +- `restart_policy` + +## Pulling from a self-hosted container registry + +Set `cinny_container_image_registry` to use a self-hosted docker registry / mirror / cache. + +If you need to authenticate to your registry and are not yet logged in, set `cinny_container_image_registry_{username,password}` and the role will attempt to log in. + +Set `cinny_container_image_registry_reauthorize` to `true` if you want to force a reauthorization at the registry. diff --git a/roles/cinny/docs/nginx.md b/roles/cinny/docs/nginx.md new file mode 100644 index 0000000..36b18ed --- /dev/null +++ b/roles/cinny/docs/nginx.md @@ -0,0 +1,11 @@ +# `cinny` deployment using `nginx` virtual host + +The role will create a virtual host named after `cinny_nginx_vhost_name` (default: `cinny`) in `cinny_nginx_available_sites` (default: `/etc/nginx/sites-available`). + +If you choose `cinny_nginx_vhost_enable` (default: `true`), it will also create a symlink from `cinny_nginx_enabled_sites` to it's vhost. + +> [!TIP] +> If you are deploying multiple cinny instances on a single host, customize `cinny_nginx_vhost_name` to contain your `cinny_nginx_server` in order to avoid filename collisions. + +> [!IMPORTANT] +> If `cinny_nginx_vhost_enable` is `true`, the role will expect `nginx` to be in the `$PATH` (in order to test the configuration using `nginx -t`) diff --git a/roles/cinny/docs/tarball.md b/roles/cinny/docs/tarball.md new file mode 100644 index 0000000..dc4de90 --- /dev/null +++ b/roles/cinny/docs/tarball.md @@ -0,0 +1,13 @@ +# `cinny` deployment from a tarball + +The role supports just downloading and extracting a tarball, which can +then be served with a webserver of your own choice. + +When `cinny_deployment_method` is set to `tarball`, the directory +`cinny_dist_path` (defaults to `/opt/cinny/src/dist`) needs to be +served at the desired URL. + +Additionally, the following equivalent rules to +[the sample docker+nginx configuration](https://github.com/cinnyapp/cinny/blob/dev/docker-nginx.conf) +are needed for the webapp to work properly. For hosting in a path +like `/app`, set `cinny_config_hash_router_basename`. diff --git a/roles/cinny/tasks/configure.yml b/roles/cinny/tasks/configure.yml new file mode 100644 index 0000000..e4245c4 --- /dev/null +++ b/roles/cinny/tasks/configure.yml @@ -0,0 +1,37 @@ +--- +- name: Ensure cinny user '{{ cinny_user }}' is {{ cinny_state }} + ansible.builtin.user: + name: "{{ cinny_user }}" + system: "{{ cinny_user_system | default(true, true) }}" + create_home: "{{ cinny_user_create_home | default(false, true) }}" + state: "{{ cinny_state }}" + groups: "{{ cinny_user_groups | default(omit) }}" + append: "{{ cinny_user_groups_append | default(omit) }}" + register: cinny_user_info + +- name: Ensure host path are {{ cinny_state }} + ansible.builtin.file: + name: "{{ path.name }}" + state: "{{ (cinny_state == 'present') | ternary('directory', 'absent') }}" + owner: "{{ path.owner | default(cinny_host_uid) }}" + group: "{{ path.group | default(cinny_host_gid) }}" + mode: "{{ path.mode | default('0750') }}" + loop_control: + loop_var: path + label: "{{ path.name }}" + loop: + - name: "{{ cinny_base_path }}" + mode: '0755' + - name: "{{ cinny_config_path }}" + mode: '0755' + - name: "{{ cinny_source_path }}" + mode: '0755' + +- name: Ensure config file is {{ cinny_state }} + ansible.builtin.copy: + content: "{{ cinny_config_complete | to_nice_json }}" + dest: "{{ cinny_config_file }}" + owner: "{{ cinny_host_uid }}" + group: "{{ cinny_host_gid }}" + mode: "{{ cinny_config_file_mode | default('0664') }}" + when: cinny_state == 'present' diff --git a/roles/cinny/tasks/deploy-apache2.yml b/roles/cinny/tasks/deploy-apache2.yml new file mode 100644 index 0000000..192e36d --- /dev/null +++ b/roles/cinny/tasks/deploy-apache2.yml @@ -0,0 +1,3 @@ +--- +- fail: + msg: "Not yet implemented" diff --git a/roles/cinny/tasks/deploy-caddy.yml b/roles/cinny/tasks/deploy-caddy.yml new file mode 100644 index 0000000..192e36d --- /dev/null +++ b/roles/cinny/tasks/deploy-caddy.yml @@ -0,0 +1,3 @@ +--- +- fail: + msg: "Not yet implemented" diff --git a/roles/cinny/tasks/deploy-docker.yml b/roles/cinny/tasks/deploy-docker.yml new file mode 100644 index 0000000..baf28c8 --- /dev/null +++ b/roles/cinny/tasks/deploy-docker.yml @@ -0,0 +1,33 @@ +--- +- name: Ensure docker client is logged {{ (cinny_state == 'present') | ternary('in', 'out') }} + community.docker.docker_login: + registry_url: "{{ cinny_container_image_registry }}" + username: "{{ cinny_container_image_registry_username }}" + password: "{{ cinny_container_image_registry_password }}" + reauthorize: "{{ cinny_container_image_registry_reauthorize | default(omit, true) }}" + state: "{{ cinny_state }}" + when: + - cinny_container_image_registry_username | default(false, true) + - cinny_container_image_registry_password | default(false, true) + +- name: Ensure container image '{{ cinny_container_image }}' is {{ cinny_state }} locally + community.docker.docker_image: + name: "{{ cinny_container_image }}" + state: "{{ cinny_state }}" + source: "{{ cinny_container_source | default('pull') }}" + force_source: "{{ cinny_container_image_tag | default(false, true) }}" + +- name: Ensure container '{{ cinny_container_name }}' is {{ cinny_state }} + community.docker.docker_container: + name: "{{ cinny_container_name }}" + image: "{{ cinny_container_image }}" + state: "{{ (cinny_state == 'present') | ternary('started', 'absent') }}" + env: "{{ cinny_container_env | default(omit) }}" + user: "{{ cinny_container_user }}" + ports: "{{ cinny_container_ports | default(omit) }}" + labels: "{{ cinny_container_labels | default(omit) }}" + volumes: "{{ cinny_container_full_volumes }}" + networks: "{{ cinny_container_networks | default(omit) }}" + etc_hosts: "{{ cinny_container_etc_hosts | default(omit) }}" + restart_policy: "{{ cinny_container_restart_policy }}" + purge_networks: "{{ cinny_container_purge_networks | default(omit) }}" diff --git a/roles/cinny/tasks/deploy-nginx.yml b/roles/cinny/tasks/deploy-nginx.yml new file mode 100644 index 0000000..33a0bd6 --- /dev/null +++ b/roles/cinny/tasks/deploy-nginx.yml @@ -0,0 +1,44 @@ +--- +- name: Deploy nginx virtual host config file + ansible.builtin.template: + src: nginx.conf.j2 + dest: "{{ cinny_nginx_available_sites }}/{{ cinny_nginx_vhost_name }}" + mode: "0640" + when: cinny_state == 'present' + +- name: Enable nginx virtual host + ansible.builtin.file: + path: "{{ cinny_nginx_enabled_sites }}/{{ cinny_nginx_vhost_name }}" + src: "{{ cinny_nginx_available_sites }}/{{ cinny_nginx_vhost_name }}" + state: "{{ (cinny_state == 'present') | ternary('link', 'absent') }}" + when: cinny_nginx_vhost_enable + +- name: Clean up nginx virtural host config file + ansible.builtin.file: + path: "{{ cinny_nginx_available_sites }}/{{ cinny_nginx_vhost_name }}" + state: absent + when: cinny_state == 'absent' + +- name: Ensure nginx configuration is valid + ansible.builtin.command: + cmd: "nginx -t" + when: + - cinny_state == 'present' + - cinny_nginx_vhost_enable + +- name: Reload nginx using systemd + ansible.builtin.systemd_service: + name: "nginx.service" + state: reloaded + when: + - cinny_state == 'present' + - cinny_nginx_vhost_enable + - ansible_facts['service_mgr'] == 'systemd' + +- name: Inform user about required nginx reload + ansible.builtin.debug: + msg: "Restart nginx service (no systemd found)" + when: + - cinny_state == 'present' + - cinny_nginx_vhost_enable + - ansible_facts['service_mgr'] != 'systemd' diff --git a/roles/cinny/tasks/deploy-podman.yml b/roles/cinny/tasks/deploy-podman.yml new file mode 100644 index 0000000..a72efa8 --- /dev/null +++ b/roles/cinny/tasks/deploy-podman.yml @@ -0,0 +1,22 @@ +--- +- name: Ensure container image '{{ cinny_container_image }}' is {{ cinny_state }} locally + containers.podman.podman_image: + name: "{{ cinny_container_image }}" + state: "{{ cinny_state }}" + pull: "{{ cinny_container_source == 'pull' }}" + force: "{{ cinny_container_image_tag | default(false, true) }}" + +- name: Ensure container '{{ cinny_container_name }}' is {{ cinny_state }} + containers.podman.podman_container: + name: "{{ cinny_container_name }}" + image: "{{ cinny_container_image }}" + state: "{{ (cinny_state == 'present') | ternary('started', 'absent') }}" + env: "{{ cinny_container_env | default(omit) }}" + user: "{{ cinny_container_user }}" + ports: "{{ cinny_container_ports | default(omit) }}" + labels: "{{ cinny_container_labels | default(omit) }}" + volumes: "{{ cinny_container_full_volumes }}" + network: "{{ cinny_container_networks | default(omit) }}" + hostname: "{{ cinny_container_hostname | default(omit) }}" + etc_hosts: "{{ cinny_container_etc_hosts | default(omit) }}" + restart_policy: "{{ cinny_container_restart_policy }}" diff --git a/roles/cinny/tasks/deploy-tarball.yml b/roles/cinny/tasks/deploy-tarball.yml new file mode 100644 index 0000000..1867f3e --- /dev/null +++ b/roles/cinny/tasks/deploy-tarball.yml @@ -0,0 +1,46 @@ +--- +- name: Check if running cinny version is saved on host + ansible.builtin.stat: + path: "{{ cinny_running_version_file }}" + register: cinny_running_version_st + +- name: Retrieve running cinny version + ansible.builtin.slurp: + path: "{{ cinny_running_version_file }}" + register: cinny_running_version_info + when: cinny_running_version_st.stat.exists + +- name: Extract running cinny version + set_fact: + cinny_is_update: >- + {{ not cinny_running_version_st.stat.exists or + (cinny_version is version(cinny_running_version, 'gt', version_type='semver')) }} + vars: + cinny_running_version: >- + {{ (cinny_running_version_info is defined) + | ternary(cinny_running_version_info['content'] | b64decode, false) }} + +- name: Download tarball from GitHub release page + ansible.builtin.get_url: + url: "{{ cinny_tarball_url }}" + dest: "{{ cinny_tarball_path }}" + url_username: "{{ cinny_tarball_url_username | default(omit, true) }}" + url_password: "{{ cinny_tarball_url_password | default(omit, true) }}" + mode: "0664" + when: cinny_is_update + +- name: Ensure old application files are gone + ansible.builtin.file: + path: "{{ cinny_dist_path }}" + state: absent + when: cinny_is_update + +- name: Extract tarball to {{ cinny_source_path }} + ansible.builtin.unarchive: + src: "{{ cinny_tarball_path }}" + dest: "{{ cinny_source_path }}" + remote_src: true + owner: "{{ cinny_host_uid }}" + group: "{{ cinny_host_gid }}" + mode: "u+rwX,g+rwX,o+rX" + when: cinny_is_update diff --git a/roles/cinny/tasks/main.yml b/roles/cinny/tasks/main.yml new file mode 100644 index 0000000..3aa538f --- /dev/null +++ b/roles/cinny/tasks/main.yml @@ -0,0 +1,23 @@ +--- +- name: Check if state is valid + ansible.builtin.fail: + msg: "Unknown state '{{ cinny_state }}'. Valid states are {{ cinny_states | join(', ') }}" + when: cinny_state not in cinny_states + +- name: Check if deployment method is supported + ansible.builtin.fail: + msg: "Deployment method '{{ cinny_deployment_method }}' is not supported! (supported are: {{ cinny_deployment_methods | join(', ') }})" + when: cinny_deployment_method not in cinny_deployment_methods + +- name: Include base configuration + ansible.builtin.include_tasks: + file: configure.yml + +- name: Deploy tarball if required + ansible.builtin.include_tasks: + file: deploy-tarball.yml + when: cinny_deployment_method in cinny_needs_tarball + +- name: Deploy using {{ cinny_deployment_method }} + ansible.builtin.include_tasks: + file: "deploy-{{ cinny_deployment_method }}.yml" diff --git a/roles/cinny/templates/nginx.conf.j2 b/roles/cinny/templates/nginx.conf.j2 new file mode 100644 index 0000000..f2be868 --- /dev/null +++ b/roles/cinny/templates/nginx.conf.j2 @@ -0,0 +1,23 @@ +server { + listen {{ cinny_nginx_listen_port }}; + listen [::]:{{ cinny_nginx_listen_port }}; + + {%- if cinny_nginx_server_name | default(false, true) %} + server_name {{ cinny_nginx_server_name }}; + {%- endif %} + location {{ cinny_nginx_location }} { + root {{ cinny_dist_path }}; + + rewrite ^/config.json$ /config.json break; + rewrite ^/manifest.json$ /manifest.json break; + + rewrite ^.*/olm.wasm$ /olm.wasm break; + rewrite ^/sw.js$ /sw.js break; + rewrite ^/pdf.worker.min.js$ /pdf.worker.min.js break; + + rewrite ^/public/(.*)$ /public/$1 break; + rewrite ^/assets/(.*)$ /assets/$1 break; + + rewrite ^(.+)$ /index.html break; + } +} diff --git a/roles/cinny/vars/main.yml b/roles/cinny/vars/main.yml new file mode 100644 index 0000000..b8001db --- /dev/null +++ b/roles/cinny/vars/main.yml @@ -0,0 +1,17 @@ +--- +cinny_states: + - present + - absent + +cinny_deployment_methods: + - docker + - podman + - nginx + - caddy + - apache2 + - tarball + +cinny_needs_tarball: + - nginx + - caddy + - apache2