mirror of
				https://github.com/kevinveenbirkenbach/computer-playbook.git
				synced 2025-11-03 19:58:14 +00:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			fedaa02067
			...
			380aa4a37b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 380aa4a37b | |||
| 4fbf8f505c | 
@@ -22,4 +22,4 @@
 | 
			
		||||
    dest: "{{docker_compose.files.env}}"
 | 
			
		||||
    mode: '770'
 | 
			
		||||
    force: yes
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 
 | 
			
		||||
@@ -45,6 +45,6 @@
 | 
			
		||||
    repo: "https://github.com/bluesky-social/social-app.git"
 | 
			
		||||
    dest: "{{social_app_path}}"
 | 
			
		||||
    version: "main"
 | 
			
		||||
  notify: docker compose project build and setup 
 | 
			
		||||
  notify: docker compose up 
 | 
			
		||||
 | 
			
		||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
  template: 
 | 
			
		||||
    src: "env/{{database_type}}.env.j2"
 | 
			
		||||
    dest: "{{database_env}}"
 | 
			
		||||
  notify: docker compose project build and setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  when: not applications | is_feature_enabled('central_database',application_id)
 | 
			
		||||
 | 
			
		||||
- name: "Create central database"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,35 +5,25 @@
 | 
			
		||||
#- name: shut down docker compose project 
 | 
			
		||||
#  command:
 | 
			
		||||
#    cmd: docker-compose -p "{{ application_id }}" down
 | 
			
		||||
#  listen: docker compose project setup
 | 
			
		||||
#  listen: docker compose up
 | 
			
		||||
#  when: mode_reset | bool
 | 
			
		||||
 | 
			
		||||
# default setup for docker compose files
 | 
			
		||||
- name: docker compose project setup
 | 
			
		||||
- name: docker compose up
 | 
			
		||||
  shell: > 
 | 
			
		||||
    docker-compose -p {{ application_id }} up -d --force-recreate --remove-orphans
 | 
			
		||||
    2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }})
 | 
			
		||||
  args:
 | 
			
		||||
    chdir: "{{ docker_compose.directories.instance }}"
 | 
			
		||||
    executable: /bin/bash
 | 
			
		||||
  environment:
 | 
			
		||||
    COMPOSE_HTTP_TIMEOUT: 600
 | 
			
		||||
    DOCKER_CLIENT_TIMEOUT: 600
 | 
			
		||||
  listen: docker compose project setup
 | 
			
		||||
 | 
			
		||||
# it's necessary to rebuild when a build in the docker compose files is defined
 | 
			
		||||
# for performance reasons it's not recommended to use this if there is no build tag specified
 | 
			
		||||
- name: docker compose project build and setup 
 | 
			
		||||
  shell: >
 | 
			
		||||
    docker-compose -p {{ application_id }} up -d --force-recreate --build --remove-orphans 
 | 
			
		||||
    2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }})
 | 
			
		||||
      docker-compose -p {{ application_id }} up -d
 | 
			
		||||
      --force-recreate --remove-orphans --build
 | 
			
		||||
      2>&1 | tee >(systemd-cat -t docker-compose-{{ application_id }})
 | 
			
		||||
  args:
 | 
			
		||||
    chdir: "{{ docker_compose.directories.instance }}"
 | 
			
		||||
    executable: /bin/bash
 | 
			
		||||
  environment:
 | 
			
		||||
    COMPOSE_HTTP_TIMEOUT: 600
 | 
			
		||||
    DOCKER_CLIENT_TIMEOUT: 600
 | 
			
		||||
  listen: docker compose project build and setup
 | 
			
		||||
  listen: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: docker compose restart
 | 
			
		||||
  command:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
- name:           "Create (optional) '{{ docker_compose.files.dockerfile }}'"
 | 
			
		||||
  template:
 | 
			
		||||
    src:          "{{ playbook_dir }}/roles/{{ role_name }}/templates/Dockerfile"
 | 
			
		||||
    src:          "{{ playbook_dir }}/roles/{{ role_name }}/templates/Dockerfile.j2"
 | 
			
		||||
    dest:         "{{ docker_compose.files.dockerfile }}"
 | 
			
		||||
  notify:         docker compose project build and setup
 | 
			
		||||
  notify:         docker compose up
 | 
			
		||||
  ignore_errors:  false
 | 
			
		||||
  register:       create_dockerfile_result
 | 
			
		||||
  failed_when:
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
    dest:         "{{ docker_compose.files.env }}"
 | 
			
		||||
    mode:         '770'
 | 
			
		||||
    force:        yes
 | 
			
		||||
  notify:         docker compose project setup
 | 
			
		||||
  notify:         docker compose up
 | 
			
		||||
  register:       env_template
 | 
			
		||||
  ignore_errors:  false
 | 
			
		||||
  failed_when:
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
  template:
 | 
			
		||||
    src:          "docker-compose.yml.j2"
 | 
			
		||||
    dest:         "{{ docker_compose.files.docker_compose }}"
 | 
			
		||||
  notify:         docker compose project setup
 | 
			
		||||
  notify:         docker compose up
 | 
			
		||||
  register:       docker_compose_template
 | 
			
		||||
 | 
			
		||||
- name:           "Check if any container is running in {{ docker_compose.directories.instance }}"
 | 
			
		||||
@@ -35,8 +35,8 @@
 | 
			
		||||
    chdir:        "{{ docker_compose.directories.instance }}"
 | 
			
		||||
  register:       docker_ps
 | 
			
		||||
  changed_when:   (docker_ps.stdout | trim) == ""
 | 
			
		||||
  notify:         docker compose project setup
 | 
			
		||||
  notify:         docker compose up
 | 
			
		||||
  when:           not (docker_compose_template.changed or env_template.changed)
 | 
			
		||||
 | 
			
		||||
- name:           flush docker compose project setup
 | 
			
		||||
- name:           flush docker compose up
 | 
			
		||||
  meta:           flush_handlers
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
# Base for docker services
 | 
			
		||||
{# Base for docker services #}
 | 
			
		||||
 | 
			
		||||
    restart: {{docker_restart_policy}}
 | 
			
		||||
    env_file:
 | 
			
		||||
      - "{{docker_compose.files.env}}"
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
    src:  docker-compose.yml.j2 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}docker-compose.yml"
 | 
			
		||||
  notify:
 | 
			
		||||
    - docker compose project setup
 | 
			
		||||
    - docker compose up
 | 
			
		||||
  when: run_once_docker_discourse is not defined
 | 
			
		||||
 | 
			
		||||
- name: flush, to recreate discourse docker compose
 | 
			
		||||
 
 | 
			
		||||
@@ -24,4 +24,4 @@
 | 
			
		||||
    dest: "{{ import_directory_host }}/{{ item | basename | regex_replace('\\.j2$', '') }}"
 | 
			
		||||
    mode: '770'
 | 
			
		||||
  loop: "{{ lookup('fileglob', '{{ role_path }}/templates/import/*.j2', wantlist=True) }}"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
@@ -23,7 +23,7 @@
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "config.toml.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.config}}config.toml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								roles/docker-mariadb/defaults/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								roles/docker-mariadb/defaults/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
# defaults/
 | 
			
		||||
 | 
			
		||||
This directory contains default variable definition files for the `docker-mariadb` Ansible role. It centralizes all configurable values related to MariaDB deployment and can be adjusted without modifying task logic.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## files and their purpose
 | 
			
		||||
 | 
			
		||||
### `main.yml`
 | 
			
		||||
 | 
			
		||||
Defines default values for how the MariaDB database should be created.
 | 
			
		||||
 | 
			
		||||
* **`database_encoding`** (string):
 | 
			
		||||
 | 
			
		||||
  * **Default:** `"utf8mb4"`
 | 
			
		||||
  * **Reasoning:**
 | 
			
		||||
 | 
			
		||||
    * **Full Unicode support**: `utf8mb4` is the only MySQL/MariaDB character set that fully implements 4‑byte UTF‑8, allowing storage of emojis, supplementary symbols, and all global scripts without data loss.
 | 
			
		||||
    * **Future‑proof:** Modern applications and standards have converged on UTF‑8; using `utf8mb4` avoids migration challenges later.
 | 
			
		||||
    * **Performance trade‑off:** While slightly more storage might be used compared to `latin1`, the universality of `utf8mb4` outweighs the cost for most deployments.
 | 
			
		||||
 | 
			
		||||
* **`database_collation`** (string):
 | 
			
		||||
 | 
			
		||||
  * **Default:** `"utf8mb4_unicode_ci"`
 | 
			
		||||
  * **Reasoning:**
 | 
			
		||||
 | 
			
		||||
    * **Accurate sorting & comparison:** This collation uses full Unicode algorithm rules, ensuring linguistically correct comparisons across many languages.
 | 
			
		||||
    * **Case‑insensitive (`ci`):** Most web apps expect case‑insensitive matching for usernames, emails, and search queries, improving usability.
 | 
			
		||||
    * **Neutral choice:** Unlike language‑specific collations, `unicode_ci` works robustly in multilingual contexts without bias.
 | 
			
		||||
 | 
			
		||||
> **Tip:** If you have a legacy application requiring a different charset or collation (e.g., for backward compatibility with existing data), simply override `database_encoding` and `database_collation` in your playbook-level variables.
 | 
			
		||||
 | 
			
		||||
## Overriding default variables
 | 
			
		||||
 | 
			
		||||
To customize any of these values without editing role defaults:
 | 
			
		||||
 | 
			
		||||
1. Create or update a playbook-level vars file (e.g. `group_vars/all/docker-mariadb.yml`).
 | 
			
		||||
2. Set the desired values, for example:
 | 
			
		||||
 | 
			
		||||
   ```yaml
 | 
			
		||||
   database_encoding: "latin1"
 | 
			
		||||
   database_collation: "latin1_swedish_ci"
 | 
			
		||||
   ```
 | 
			
		||||
3. Run your playbook—Ansible’s variable precedence ensures your overrides take effect.
 | 
			
		||||
							
								
								
									
										3
									
								
								roles/docker-mariadb/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								roles/docker-mariadb/defaults/main.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# Check out the README.md file for more information, why this encodings and collations are used
 | 
			
		||||
database_encoding:  "utf8mb4"
 | 
			
		||||
database_collation: "utf8mb4_unicode_ci"
 | 
			
		||||
@@ -53,12 +53,14 @@
 | 
			
		||||
 | 
			
		||||
- name: "Create database: {{ database_name }}"
 | 
			
		||||
  mysql_db:
 | 
			
		||||
    name: "{{ database_name }}"
 | 
			
		||||
    state: present
 | 
			
		||||
    login_user: root
 | 
			
		||||
    name:           "{{ database_name }}"
 | 
			
		||||
    state:          present
 | 
			
		||||
    login_user:     root
 | 
			
		||||
    login_password: "{{ applications.mariadb.credentials.root_password }}"
 | 
			
		||||
    login_host: 127.0.0.1
 | 
			
		||||
    login_port: "{{database_port}}"
 | 
			
		||||
    login_host:     127.0.0.1
 | 
			
		||||
    login_port:     "{{ database_port }}"
 | 
			
		||||
    encoding:       "{{ database_encoding }}"
 | 
			
		||||
    collation:      "{{ database_collation }}"
 | 
			
		||||
 | 
			
		||||
- name: "Create database user: {{ database_username }}"
 | 
			
		||||
  mysql_user:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								roles/docker-mariadb/vars/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								roles/docker-mariadb/vars/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
# vars/
 | 
			
		||||
 | 
			
		||||
This directory contains variable definition files for the `docker-mariadb` Ansible role. It centralizes all configurable values related to MariaDB deployment and can be adjusted without modifying task logic.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## files and their purpose
 | 
			
		||||
 | 
			
		||||
### 1. `configuration.yml`
 | 
			
		||||
 | 
			
		||||
Contains configuration values that determine which Docker image version to use and what hostname the container will be registered under.
 | 
			
		||||
 | 
			
		||||
* **`version`** (string):
 | 
			
		||||
 | 
			
		||||
  * Default: `"latest"`
 | 
			
		||||
  * The MariaDB image tag to pull (e.g. `10.6`, `10.11`, or `latest`).
 | 
			
		||||
 | 
			
		||||
* **`hostname`** (string):
 | 
			
		||||
 | 
			
		||||
  * Default: `"central-mariadb"`
 | 
			
		||||
  * The container name and DNS alias within the `central_mariadb` network. Used by other services (like Moodle) to connect.
 | 
			
		||||
 | 
			
		||||
> **Tip:** Pin to a specific minor version (e.g., `10.6.12`) in production to avoid breaking changes on rebuilds.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
### 2. `main.yml`
 | 
			
		||||
 | 
			
		||||
Minimal file defining the application identifier for the role.
 | 
			
		||||
 | 
			
		||||
* **`application_id`** (string):
 | 
			
		||||
 | 
			
		||||
  * Default: `"mariadb"`
 | 
			
		||||
  * Logical name used in templates, notifications, or paths when multiple roles/services may coexist.
 | 
			
		||||
@@ -77,25 +77,25 @@
 | 
			
		||||
    src:  "mautrix/{{item.bridge_name}}.config.yml.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}mautrix/{{item.bridge_name}}/config.yaml"
 | 
			
		||||
  loop: "{{ bridges }}"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: add element configuration
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "element.config.json.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}element-config.json"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: add synapse homeserver configuration
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "synapse/homeserver.yaml.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}homeserver.yaml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: add synapse log configuration
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "synapse/log.config.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}{{domains.matrix.synapse}}.log.config"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
# https://github.com/matrix-org/synapse/issues/6303
 | 
			
		||||
- name: set correct folder permissions
 | 
			
		||||
@@ -106,7 +106,7 @@
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "docker-compose.yml.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}docker-compose.yml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
# Pull image when update is wished. 
 | 
			
		||||
# @todo This should be moved to update-docker
 | 
			
		||||
@@ -116,7 +116,7 @@
 | 
			
		||||
    chdir: "{{docker_compose.directories.instance}}"
 | 
			
		||||
  when: mode_update | bool
 | 
			
		||||
 | 
			
		||||
- name: docker compose project setup
 | 
			
		||||
- name: docker compose up
 | 
			
		||||
  command:
 | 
			
		||||
    cmd: "docker-compose -p {{application_id}} up -d --remove-orphans"
 | 
			
		||||
    chdir: "{{docker_compose.directories.instance}}"
 | 
			
		||||
 
 | 
			
		||||
@@ -12,4 +12,4 @@
 | 
			
		||||
 | 
			
		||||
- name: add docker-compose.yml
 | 
			
		||||
  template: src=docker-compose.yml.j2 dest={{docker_compose.directories.instance}}docker-compose.yml
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								roles/docker-moodle/Administration.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								roles/docker-moodle/Administration.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
# Administration
 | 
			
		||||
 | 
			
		||||
# Radical Erase of Setup
 | 
			
		||||
To manually erase the full moodle setup inkluding all data execute:
 | 
			
		||||
 | 
			
		||||
**CLI:**
 | 
			
		||||
 | 
			
		||||
```bash 
 | 
			
		||||
cd /opt/docker/moodle && \
 | 
			
		||||
docker compose down -v || {
 | 
			
		||||
  echo "docker compose down failed, cleaning up manually"
 | 
			
		||||
  rm -rv /mnt/hdd/data/docker/volumes/moodle_*
 | 
			
		||||
  docker compose down -v
 | 
			
		||||
} && \
 | 
			
		||||
rm -rv /opt/docker/moodle
 | 
			
		||||
``` 
 | 
			
		||||
 | 
			
		||||
Afterwards login to the database and execute
 | 
			
		||||
 | 
			
		||||
**MariaDB:**
 | 
			
		||||
```sql
 | 
			
		||||
DROP DATABASE IF EXISTS moodle;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
to delete all data in the database related to this role. 
 | 
			
		||||
 | 
			
		||||
# Virgin Setup
 | 
			
		||||
After the installation you can rerun this role to create a fresh setup of Moodle. 
 | 
			
		||||
@@ -1,3 +1,2 @@
 | 
			
		||||
# Todo
 | 
			
		||||
- Check if sendmail needs to be installed. See [Issue](https://github.com/bitnami/containers/issues/63311).
 | 
			
		||||
- Solve [issue](https://github.com/bitnami/containers/issues/72747)
 | 
			
		||||
- Check if sendmail needs to be installed. See [Issue](https://github.com/bitnami/containers/issues/63311).
 | 
			
		||||
@@ -20,10 +20,10 @@
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "default.conf"
 | 
			
		||||
    dest: "{{docker_compose_instance_confd_defaultconf_file}}"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: add docker-compose.yml
 | 
			
		||||
  template: 
 | 
			
		||||
    src:  "docker-compose.yml.j2" 
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}docker-compose.yml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@
 | 
			
		||||
    group:  "{{nextcloud_docker_user_id}}"
 | 
			
		||||
  loop:     "{{ lookup('fileglob', role_path ~ '/templates/config/*.j2', wantlist=True) }}"
 | 
			
		||||
  # Not all type of changes take instantly place. Due to this reason a rebuild is required.
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: "include role for {{application_id}} to receive certs & do modification routines"
 | 
			
		||||
  include_role:
 | 
			
		||||
 
 | 
			
		||||
@@ -3,4 +3,4 @@
 | 
			
		||||
    src: oauth2-proxy-keycloak.cfg.j2
 | 
			
		||||
    dest: "{{(path_docker_compose_instances | get_docker_compose(oauth2_proxy_application_id)).directories.volumes}}{{applications[application_id].configuration_file}}"
 | 
			
		||||
  notify:
 | 
			
		||||
    - docker compose project setup
 | 
			
		||||
    - docker compose up
 | 
			
		||||
@@ -21,7 +21,7 @@
 | 
			
		||||
    src: Gemfile.plugins
 | 
			
		||||
    dest: "{{openproject_plugins_folder}}Gemfile.plugins"
 | 
			
		||||
  notify:
 | 
			
		||||
    - docker compose project build and setup
 | 
			
		||||
    - docker compose up
 | 
			
		||||
 | 
			
		||||
- name: "include role docker-repository-setup for {{application_id}}"
 | 
			
		||||
  include_role: 
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
    src: servers.json.j2
 | 
			
		||||
    dest: "{{ pgadmin_host_server_file }}"
 | 
			
		||||
    mode: "0644"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: "Render .pgpass file"
 | 
			
		||||
  template:
 | 
			
		||||
@@ -19,4 +19,4 @@
 | 
			
		||||
    owner: "{{ pgadmin_user }}"
 | 
			
		||||
    group: "{{ pgadmin_group }}"
 | 
			
		||||
    mode: "0600"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
  template:
 | 
			
		||||
    src: "{{ config_inventory_path }}"
 | 
			
		||||
    dest: "{{docker_repository_path}}/app/config.yaml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  when:
 | 
			
		||||
    - run_once_docker_portfolio is not defined
 | 
			
		||||
    - config_file.stat.exists
 | 
			
		||||
@@ -63,7 +63,7 @@
 | 
			
		||||
  template:
 | 
			
		||||
    src: "config.yaml.j2"
 | 
			
		||||
    dest: "{{docker_repository_path}}/app/config.yaml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  when:
 | 
			
		||||
    - run_once_docker_portfolio is not defined
 | 
			
		||||
    - not config_file.stat.exists
 | 
			
		||||
@@ -72,7 +72,7 @@
 | 
			
		||||
  template:
 | 
			
		||||
    src:  docker-compose.yml.j2 
 | 
			
		||||
    dest: "{docker_compose.directories.instance}}docker-compose.yml"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  when: run_once_docker_portfolio is not defined
 | 
			
		||||
 | 
			
		||||
- name: run the portfolio tasks once
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    name: pkgmgr-install
 | 
			
		||||
  vars:
 | 
			
		||||
    package_name: cymais-presentation
 | 
			
		||||
    package_notify: docker compose project build and setup
 | 
			
		||||
    package_notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: Get path of cymais-presentation using pkgmgr
 | 
			
		||||
  command: pkgmgr path cymais-presentation
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,6 @@
 | 
			
		||||
    dest: "{{ docker_repository_path }}"
 | 
			
		||||
    update: yes
 | 
			
		||||
  notify:
 | 
			
		||||
    - docker compose project setup
 | 
			
		||||
    - docker compose up
 | 
			
		||||
    - rebuild docker repository
 | 
			
		||||
  become: true
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
    repo: "https://github.com/kevinveenbirkenbach/roulette-wheel.git"
 | 
			
		||||
    dest: "{{app_path}}"
 | 
			
		||||
    update: yes
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  become: true
 | 
			
		||||
  ignore_errors: true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
    name: pkgmgr-install
 | 
			
		||||
  vars:
 | 
			
		||||
    package_name: cymais-sphinx
 | 
			
		||||
    package_notify: docker compose project build and setup
 | 
			
		||||
    package_notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: Get path of cymais-sphinx using pkgmgr
 | 
			
		||||
  command: pkgmgr path cymais-sphinx
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
application_id:                   "sphinx"
 | 
			
		||||
application_id: "sphinx"
 | 
			
		||||
@@ -25,6 +25,6 @@
 | 
			
		||||
    dest: "{{docker_compose.files.env}}"
 | 
			
		||||
    mode: '770'
 | 
			
		||||
    force: yes
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
 | 
			
		||||
 
 | 
			
		||||
@@ -19,13 +19,13 @@
 | 
			
		||||
    src:  "taiga/{{item}}.py.j2"
 | 
			
		||||
    dest: "{{ docker_compose.directories.config }}taiga-{{item}}.py"
 | 
			
		||||
  when:   applications[application_id].features.oidc and applications[application_id].oidc.flavor == 'taigaio'
 | 
			
		||||
  notify: docker compose project build and setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  loop:   "{{ settings_files }}"
 | 
			
		||||
 | 
			
		||||
- name: "create {{docker_compose_init}}"
 | 
			
		||||
  template:
 | 
			
		||||
    src:  "docker-compose-inits.yml.j2"
 | 
			
		||||
    dest:  "{{docker_compose_init}}"
 | 
			
		||||
  notify: docker compose project build and setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,13 @@
 | 
			
		||||
  template:
 | 
			
		||||
    src: upload.ini.j2
 | 
			
		||||
    dest: "{{ docker_compose.directories.instance }}upload.ini"
 | 
			
		||||
  notify: docker compose project build and setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- name: "Transfering msmtprc to {{ host_msmtp_conf }}"
 | 
			
		||||
  template:
 | 
			
		||||
    src: "{{ playbook_dir }}/roles/msmtp/templates/msmtprc.conf.j2"
 | 
			
		||||
    dest: "{{ host_msmtp_conf }}"
 | 
			
		||||
  notify: docker compose project build and setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 | 
			
		||||
- include_tasks: "{{ playbook_dir }}/roles/docker-compose/tasks/create-files.yml"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								roles/system-storage-optimizer/files/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								roles/system-storage-optimizer/files/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -5,6 +5,7 @@ import sys
 | 
			
		||||
import shutil
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_command(command):
 | 
			
		||||
    """ Run a shell command and return its output """
 | 
			
		||||
    print(command)
 | 
			
		||||
@@ -12,37 +13,59 @@ def run_command(command):
 | 
			
		||||
    print(output)
 | 
			
		||||
    return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def stop_containers(containers):
 | 
			
		||||
    """Stop a list of containers."""
 | 
			
		||||
    container_list = ' '.join(containers)
 | 
			
		||||
    print(f"Stopping containers {container_list}...")
 | 
			
		||||
    run_command(f"docker stop {container_list}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def start_containers(containers):
 | 
			
		||||
    """Start a list of containers."""
 | 
			
		||||
    container_list = ' '.join(containers)
 | 
			
		||||
    print(f"Starting containers {container_list}...")
 | 
			
		||||
    run_command(f"docker start {container_list}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_database(image):
 | 
			
		||||
    databases = {"postgres", "mariadb", "redis", "memcached", "mongo"}
 | 
			
		||||
    # Split the string at the colon and take the first part
 | 
			
		||||
    prefix = image.split(':')[0]
 | 
			
		||||
    # Check if the prefix is in the database names
 | 
			
		||||
    return prefix in databases
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def is_symbolic_link(file_path):
 | 
			
		||||
    return os.path.islink(file_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_volume_path(volume):
 | 
			
		||||
    return run_command(f"docker volume inspect --format '{{{{ .Mountpoint }}}}' {volume}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_image(container):
 | 
			
		||||
    return run_command(f"docker inspect --format='{{{{.Config.Image}}}}' {container}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def has_healthcheck(container):
 | 
			
		||||
    """Check if a container has a HEALTHCHECK defined."""
 | 
			
		||||
    result = run_command(
 | 
			
		||||
        f"docker inspect --format='{{{{json .State.Health}}}}' {container}"
 | 
			
		||||
    )
 | 
			
		||||
    return result not in ("null", "")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_health_status(container):
 | 
			
		||||
    """Return the health status."""
 | 
			
		||||
    status = run_command(
 | 
			
		||||
        f"docker inspect --format='{{{{.State.Health.Status}}}}' {container}"
 | 
			
		||||
    )
 | 
			
		||||
    return status
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_rsync(src, dest):
 | 
			
		||||
    run_command(f"rsync -aP --remove-source-files {src} {dest}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def delete_directory(path):
 | 
			
		||||
    """Deletes a directory and all its contents."""
 | 
			
		||||
    try:
 | 
			
		||||
@@ -51,56 +74,68 @@ def delete_directory(path):
 | 
			
		||||
    except OSError as e:
 | 
			
		||||
        print(f"Error deleting directory {path}: {e}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pause_and_move(storage_path, volume, volume_path, containers):
 | 
			
		||||
    stop_containers(containers)
 | 
			
		||||
    # Create a new directory on the Storage
 | 
			
		||||
    storage_volume_path = os.path.join(storage_path, 'data', 'docker', 'volumes', volume)
 | 
			
		||||
    os.makedirs(storage_volume_path,exist_ok=False)
 | 
			
		||||
 | 
			
		||||
    # Move the data
 | 
			
		||||
    run_rsync(f"{volume_path}/",f"{storage_volume_path}/")
 | 
			
		||||
    
 | 
			
		||||
    # Delete the source directory
 | 
			
		||||
    os.makedirs(storage_volume_path, exist_ok=False)
 | 
			
		||||
    run_rsync(f"{volume_path}/", f"{storage_volume_path}/")
 | 
			
		||||
    delete_directory(volume_path)
 | 
			
		||||
 | 
			
		||||
    # Create a symbolic link
 | 
			
		||||
    os.symlink(storage_volume_path, volume_path)
 | 
			
		||||
    
 | 
			
		||||
    start_containers(containers)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def has_container_with_database(containers):
 | 
			
		||||
    for container in containers:
 | 
			
		||||
        # Get the image of the container
 | 
			
		||||
        image = get_image(container)
 | 
			
		||||
        if is_database(image):
 | 
			
		||||
            return True
 | 
			
		||||
    return False
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    # Argument parser setup
 | 
			
		||||
    parser = argparse.ArgumentParser(description='Migrate Docker volumes to SSD or HDD based on container image.')
 | 
			
		||||
    parser.add_argument('--rapid-storage-path', type=str, required=True, help='Path to the SSD storage')
 | 
			
		||||
    parser.add_argument('--mass-storage-path', type=str, required=True, help='Path to the HDD storage')
 | 
			
		||||
    
 | 
			
		||||
    # Parse arguments
 | 
			
		||||
    parser = argparse.ArgumentParser(
 | 
			
		||||
        description='Migrate Docker volumes to SSD or HDD based on container image.'
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--rapid-storage-path', type=str, required=True,
 | 
			
		||||
        help='Path to the SSD storage'
 | 
			
		||||
    )
 | 
			
		||||
    parser.add_argument(
 | 
			
		||||
        '--mass-storage-path', type=str, required=True,
 | 
			
		||||
        help='Path to the HDD storage'
 | 
			
		||||
    )
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    # Set paths from arguments
 | 
			
		||||
    rapid_storage_path = args.rapid_storage_path
 | 
			
		||||
    mass_storage_path = args.mass_storage_path
 | 
			
		||||
 | 
			
		||||
    # List all Docker volumes
 | 
			
		||||
    volumes = run_command("docker volume ls -q").splitlines()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    for volume in volumes:
 | 
			
		||||
        volume_path = get_volume_path(volume)
 | 
			
		||||
        containers = run_command(f"docker ps -q --filter volume={volume}").splitlines()
 | 
			
		||||
        containers = run_command(
 | 
			
		||||
            f"docker ps -q --filter volume={volume}"
 | 
			
		||||
        ).splitlines()
 | 
			
		||||
 | 
			
		||||
        if not containers:
 | 
			
		||||
            print(f"Skipped Volume {volume}. It does not belong to a running container.")
 | 
			
		||||
        elif is_symbolic_link(volume_path):
 | 
			
		||||
            continue
 | 
			
		||||
        if is_symbolic_link(volume_path):
 | 
			
		||||
            print(f"Skipped Volume {volume}. The storage path {volume_path} is a symbolic link.")
 | 
			
		||||
        elif has_container_with_database(containers):
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # Wait until containers with a healthcheck are healthy (not starting or unhealthy)
 | 
			
		||||
        for container in containers:
 | 
			
		||||
            if has_healthcheck(container):
 | 
			
		||||
                status = get_health_status(container)
 | 
			
		||||
                while status != 'healthy':
 | 
			
		||||
                    print(f"Warte auf Container {container}, Status '{status}'...")
 | 
			
		||||
                    time.sleep(1)
 | 
			
		||||
                    status = get_health_status(container)
 | 
			
		||||
 | 
			
		||||
        # Proceed with migration
 | 
			
		||||
        if has_container_with_database(containers):
 | 
			
		||||
            print(f"Safing volume {volume} on SSD.")
 | 
			
		||||
            pause_and_move(rapid_storage_path, volume, volume_path, containers)
 | 
			
		||||
        else:
 | 
			
		||||
@@ -108,4 +143,3 @@ if __name__ == "__main__":
 | 
			
		||||
            pause_and_move(mass_storage_path, volume, volume_path, containers)
 | 
			
		||||
 | 
			
		||||
    print("Operation completed.")
 | 
			
		||||
    
 | 
			
		||||
@@ -29,7 +29,7 @@
 | 
			
		||||
    repo: "{{ docker_repository_address }}"
 | 
			
		||||
    dest: "{{ docker_repository_directory  | default(docker_compose.directories.instance) }}"
 | 
			
		||||
    update: yes
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
  become: true
 | 
			
		||||
 | 
			
		||||
- name: "restore detached files"
 | 
			
		||||
@@ -45,4 +45,4 @@
 | 
			
		||||
    src: "{{ item }}.j2"
 | 
			
		||||
    dest: "{{docker_compose.directories.instance}}{{ item }}"
 | 
			
		||||
  loop: "{{ detached_files }}"
 | 
			
		||||
  notify: docker compose project setup
 | 
			
		||||
  notify: docker compose up
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										56
									
								
								tests/unit/test_storage_optimizer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								tests/unit/test_storage_optimizer.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import unittest
 | 
			
		||||
from unittest.mock import patch
 | 
			
		||||
import os
 | 
			
		||||
import importlib.util
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
# Dynamically load the module under test from the Ansible role's files directory
 | 
			
		||||
def load_optimizer_module():
 | 
			
		||||
    module_path = os.path.abspath(os.path.join(
 | 
			
		||||
        os.path.dirname(__file__),
 | 
			
		||||
        '..', "..", 'roles', 'system-storage-optimizer', 'files', 'system-storage-optimizer.py'
 | 
			
		||||
    ))
 | 
			
		||||
    spec = importlib.util.spec_from_file_location('storage_optimizer', module_path)
 | 
			
		||||
    optimizer = importlib.util.module_from_spec(spec)
 | 
			
		||||
    # Register module so patch('storage_optimizer...') works
 | 
			
		||||
    sys.modules[spec.name] = optimizer
 | 
			
		||||
    spec.loader.exec_module(optimizer)
 | 
			
		||||
    return optimizer
 | 
			
		||||
 | 
			
		||||
storage_optimizer = load_optimizer_module()
 | 
			
		||||
 | 
			
		||||
class TestHealthCheckLogic(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        # Prevent sleeping delays in inline loops
 | 
			
		||||
        patcher = patch('storage_optimizer.time.sleep', return_value=None)
 | 
			
		||||
        self.sleep_patcher = patcher.start()
 | 
			
		||||
        self.addCleanup(self.sleep_patcher.stop)
 | 
			
		||||
 | 
			
		||||
    @patch('storage_optimizer.run_command')
 | 
			
		||||
    def test_has_healthcheck_true(self, mock_run):
 | 
			
		||||
        mock_run.return_value = '{"Status":"starting","FailingStreak":0}'
 | 
			
		||||
        self.assertTrue(storage_optimizer.has_healthcheck('container-id'))
 | 
			
		||||
 | 
			
		||||
    @patch('storage_optimizer.run_command')
 | 
			
		||||
    def test_has_healthcheck_false(self, mock_run):
 | 
			
		||||
        mock_run.return_value = 'null'
 | 
			
		||||
        self.assertFalse(storage_optimizer.has_healthcheck('container-id'))
 | 
			
		||||
 | 
			
		||||
    @patch('storage_optimizer.get_image')
 | 
			
		||||
    def test_has_container_with_database(self, mock_get_image):
 | 
			
		||||
        test_cases = [
 | 
			
		||||
            ('postgres:13', True),
 | 
			
		||||
            ('mariadb:latest', True),
 | 
			
		||||
            ('redis:6', True),
 | 
			
		||||
            ('mongo:4', True),
 | 
			
		||||
            ('nginx:alpine', False),
 | 
			
		||||
        ]
 | 
			
		||||
        for image_name, expected in test_cases:
 | 
			
		||||
            mock_get_image.return_value = image_name
 | 
			
		||||
            result = storage_optimizer.has_container_with_database(['any'])
 | 
			
		||||
            self.assertEqual(result, expected,
 | 
			
		||||
                             f"Image {image_name} expected {expected}")
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user