├── molecule ├── tls │ ├── certificates │ │ └── readme │ ├── tasks │ │ └── generate_selfsigned_cert.yml │ ├── molecule.yml │ └── converge.yml ├── cluster │ ├── certificates │ │ └── readme │ ├── tasks │ │ └── generate_selfsigned_cert.yml │ ├── molecule.yml │ └── converge.yml ├── default │ ├── converge.yml │ └── molecule.yml └── site-replication │ ├── converge.yml │ └── molecule.yml ├── .yamllint ├── .ansible-lint ├── renovate.json ├── handlers └── main.yml ├── vars └── main.yml ├── meta └── main.yml ├── tasks ├── main.yml ├── create_prometheus_bearer_token.yml ├── install_client.yml ├── create_minio_buckets.yml ├── configure_server.yml ├── create_minio_user.yml ├── configure_site_replication.yml └── install_server.yml ├── templates ├── minio.env.j2 ├── minio.service.j2 └── minio_policy.json.j2 ├── .github └── workflows │ ├── release.yml │ └── ci.yml ├── LICENSE ├── defaults └── main.yml ├── library └── minio_bucket.py └── README.md /molecule/tls/certificates/readme: -------------------------------------------------------------------------------- 1 | temporary directory for certificates 2 | -------------------------------------------------------------------------------- /molecule/cluster/certificates/readme: -------------------------------------------------------------------------------- 1 | temporary directory for certificates 2 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | line-length: 6 | max: 180 7 | level: warning 8 | -------------------------------------------------------------------------------- /.ansible-lint: -------------------------------------------------------------------------------- 1 | --- 2 | skip_list: 3 | - 'no-handler' 4 | - 'fqcn-builtins' 5 | - 'template-instead-of-copy' 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Reload minio systemd 4 | systemd: 5 | name: minio 6 | daemon_reload: true 7 | 8 | - name: Restart minio 9 | service: 10 | name: minio 11 | state: restarted 12 | -------------------------------------------------------------------------------- /vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | go_arch_map: 3 | i386: '386' 4 | x86_64: 'amd64' 5 | aarch64: 'arm64' 6 | armv7l: 'arm' 7 | armv6l: 'arm6vl' 8 | 9 | go_arch: "{{ go_arch_map[ansible_architecture] | default(ansible_architecture) }}" 10 | -------------------------------------------------------------------------------- /molecule/default/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | become: true 5 | gather_facts: true 6 | roles: 7 | - role: ricsanfre.minio 8 | minio_root_user: "miniadmin" 9 | minio_root_password: "supers1cret0" 10 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | galaxy_info: 3 | role_name: minio 4 | author: ricsanfre 5 | description: minio installation and configuration role 6 | 7 | license: license (MIT) 8 | 9 | min_ansible_version: "2.11" 10 | 11 | platforms: 12 | - name: Ubuntu 13 | versions: 14 | - focal 15 | - jammy 16 | - noble 17 | 18 | galaxy_tags: 19 | - system 20 | - s3 21 | - minio 22 | 23 | dependencies: [] 24 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install Minio Server 3 | - name: Install server 4 | include_tasks: install_server.yml 5 | when: minio_install_server 6 | 7 | # Install Minio Client 8 | - name: Install client 9 | include_tasks: install_client.yml 10 | when: minio_install_client 11 | 12 | # Configure server/cluster 13 | - name: Configure server 14 | include_tasks: configure_server.yml 15 | when: minio_url|length > 0 and minio_install_client 16 | # run once per cluster 17 | run_once: true 18 | -------------------------------------------------------------------------------- /tasks/create_prometheus_bearer_token.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Check if prometheus bearer token exist 4 | stat: 5 | path: "{{ prometheus_bearer_token_output }}" 6 | register: prometheus_toke_file 7 | 8 | - name: Create prometheus bearer token 9 | command: "{{ mc_command }} admin prometheus generate {{ minio_alias }} -json" 10 | changed_when: false 11 | register: prometheus_token 12 | when: not prometheus_toke_file.stat.exists 13 | 14 | - name: Copy bearer token. 15 | copy: 16 | content: "{{ prometheus_token.stdout | from_json }}" 17 | dest: "{{ prometheus_bearer_token_output }}" 18 | owner: "{{ minio_user }}" 19 | group: "{{ minio_group }}" 20 | mode: "0600" 21 | when: not prometheus_toke_file.stat.exists 22 | -------------------------------------------------------------------------------- /tasks/install_client.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Compose the Minio client download base url 4 | set_fact: 5 | _minio_client_download_url: "https://dl.minio.io/client/mc/release/linux-{{ go_arch }}/archive/mc.{{ minio_mc_version }}" 6 | 7 | - name: Get the Minio client checksum for architecture {{ go_arch }} 8 | set_fact: 9 | _minio_client_checksum: "{{ lookup('url', _minio_client_download_url + '.sha256sum').split(' ')[0] }}" 10 | 11 | - name: Download the Minio client 12 | get_url: 13 | url: "{{ _minio_client_download_url }}" 14 | dest: "{{ minio_client_bin }}" 15 | owner: "root" 16 | group: "root" 17 | mode: 0755 18 | checksum: "sha256:{{ _minio_client_checksum }}" 19 | register: _download_client 20 | until: _download_client is succeeded 21 | retries: 5 22 | delay: 2 23 | -------------------------------------------------------------------------------- /molecule/cluster/tasks/generate_selfsigned_cert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create private certificate 3 | openssl_privatekey: 4 | path: "certificates/{{ server_hostname }}_private.key" 5 | size: "{{ ssl_key_size | int }}" 6 | mode: 0644 7 | 8 | - name: Create CSR 9 | openssl_csr: 10 | path: "certificates/{{ server_hostname }}_cert.csr" 11 | privatekey_path: "certificates/{{ server_hostname }}_private.key" 12 | common_name: "{{ server_hostname }}" 13 | subject_alt_name: "{{ subject_alt_names }}" 14 | 15 | - name: Create certificates for keystore 16 | openssl_certificate: 17 | csr_path: "certificates/{{ server_hostname }}_cert.csr" 18 | path: "certificates/{{ server_hostname }}_public.crt" 19 | privatekey_path: "certificates/{{ server_hostname }}_private.key" 20 | provider: "{{ ssl_certificate_provider }}" 21 | -------------------------------------------------------------------------------- /molecule/default/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | lint: | 5 | set -e 6 | yamllint . 7 | ansible-lint . 8 | driver: 9 | name: docker 10 | platforms: 11 | - name: instance 12 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 13 | command: ${MOLECULE_DOCKER_COMMAND:-""} 14 | volumes: 15 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 16 | cgroupns_mode: host 17 | tmpfs: 18 | - /tmp 19 | - /run 20 | - /run/lock 21 | privileged: true 22 | pre_build_image: true 23 | exposed_ports: 24 | - 9091/tcp 25 | - 9092/tcp 26 | published_ports: 27 | - 0.0.0.0:9091:9091/tcp 28 | - 0.0.0.0:9092:9092/tcp 29 | provisioner: 30 | name: ansible 31 | playbooks: 32 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 33 | verifier: 34 | name: ansible 35 | -------------------------------------------------------------------------------- /molecule/tls/tasks/generate_selfsigned_cert.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create private certificate 3 | openssl_privatekey: 4 | path: "certificates/{{ inventory_hostname }}_private.key" 5 | size: "{{ ssl_key_size | int }}" 6 | mode: 0644 7 | 8 | - name: Create CSR 9 | openssl_csr: 10 | path: "certificates/{{ inventory_hostname }}_cert.csr" 11 | privatekey_path: "certificates/{{ inventory_hostname }}_private.key" 12 | common_name: "{{ server_hostname }}" 13 | # subject_alt_name: "IP:{{ ansible_default_ipv4.address }}" 14 | 15 | - name: Create certificates for keystore 16 | openssl_certificate: 17 | csr_path: "certificates/{{ inventory_hostname }}_cert.csr" 18 | path: "certificates/{{ inventory_hostname }}_public.crt" 19 | privatekey_path: "certificates/{{ inventory_hostname }}_private.key" 20 | provider: "{{ ssl_certificate_provider }}" 21 | -------------------------------------------------------------------------------- /molecule/tls/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | lint: | 5 | set -e 6 | yamllint . 7 | ansible-lint 8 | driver: 9 | name: docker 10 | platforms: 11 | - name: minio.ricsanfre.com 12 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 13 | command: ${MOLECULE_DOCKER_COMMAND:-""} 14 | volumes: 15 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 16 | cgroupns_mode: host 17 | tmpfs: 18 | - /tmp 19 | - /run 20 | - /run/lock 21 | privileged: true 22 | pre_build_image: true 23 | exposed_ports: 24 | - 9091/tcp 25 | - 9092/tcp 26 | published_ports: 27 | - 0.0.0.0:9091:9091/tcp 28 | - 0.0.0.0:9092:9092/tcp 29 | provisioner: 30 | name: ansible 31 | playbooks: 32 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 33 | inventory: 34 | host_vars: 35 | localhost: 36 | ansible_user: ${USER} 37 | 38 | verifier: 39 | name: ansible 40 | -------------------------------------------------------------------------------- /templates/minio.env.j2: -------------------------------------------------------------------------------- 1 | {{ ansible_managed | comment }} 2 | 3 | # Minio local/remote volumes. 4 | {% if minio_server_cluster_nodes | length > 0 %} 5 | MINIO_VOLUMES="{{ minio_server_cluster_nodes | join(' ') }}" 6 | {% else %} 7 | MINIO_VOLUMES="{{ minio_server_datadirs | join(' ') }}" 8 | {% endif %} 9 | 10 | # Minio cli options. 11 | MINIO_OPTS="--address {{ minio_server_addr }}:{{ minio_server_port }} --console-address {{ minio_server_addr }}:{{ minio_console_port }} {% if minio_enable_tls %} --certs-dir {{ minio_cert_dir }} {% endif %} {{ minio_server_opts }}" 12 | 13 | {% if minio_root_user %} 14 | # Access Key of the server. 15 | MINIO_ROOT_USER="{{ minio_root_user }}" 16 | {% endif %} 17 | {% if minio_root_password %} 18 | # Secret key of the server. 19 | MINIO_ROOT_PASSWORD="{{ minio_root_password }}" 20 | {% endif %} 21 | 22 | {% if minio_site_region %} 23 | # Minio server region 24 | MINIO_SITE_REGION="{{ minio_site_region }}" 25 | {% endif %} 26 | 27 | {{ minio_server_env_extra }} 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This workflow requires a GALAXY_API_KEY secret present in the GitHub 3 | # repository or organization. 4 | # 5 | # See: https://github.com/marketplace/actions/publish-ansible-role-to-galaxy 6 | # See: https://github.com/ansible/galaxy/issues/46 7 | 8 | name: Release 9 | 'on': 10 | push: 11 | tags: 12 | - '*' 13 | 14 | defaults: 15 | run: 16 | working-directory: 'ricsanfre.minio' 17 | 18 | jobs: 19 | 20 | release: 21 | name: Release 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Check out the codebase. 25 | uses: actions/checkout@v5 26 | with: 27 | path: 'ricsanfre.minio' 28 | 29 | - name: Set up Python 3. 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: '3.x' 33 | 34 | - name: Install Ansible. 35 | run: pip3 install ansible-core 36 | 37 | - name: Trigger a new import on Galaxy. 38 | run: ansible-galaxy role import --api-key ${{ secrets.GALAXY_API_KEY }} $(echo ${{ github.repository }} | cut -d/ -f1) $(echo ${{ github.repository }} | cut -d/ -f2) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ricsanfre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /templates/minio.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MinIO 3 | Documentation=https://docs.min.io 4 | Wants=network-online.target 5 | After=network-online.target 6 | AssertFileIsExecutable={{ minio_server_bin }} 7 | 8 | [Service] 9 | WorkingDirectory=/usr/local/ 10 | 11 | User={{ minio_user }} 12 | Group={{ minio_group }} 13 | ProtectProc=invisible 14 | 15 | EnvironmentFile={{ minio_server_envfile }} 16 | ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in {{ minio_server_envfile }}\"; exit 1; fi" 17 | 18 | ExecStart={{ minio_server_bin }} server $MINIO_OPTS $MINIO_VOLUMES 19 | 20 | # Let systemd restart this service always 21 | Restart=always 22 | 23 | # Specifies the maximum file descriptor number that can be opened by this process 24 | LimitNOFILE=65536 25 | 26 | # Specifies the maximum number of threads this process can create 27 | TasksMax=infinity 28 | 29 | # Disable timeout logic and wait until process is stopped 30 | TimeoutStopSec=infinity 31 | SendSIGKILL=no 32 | 33 | {% if (minio_server_port | int) < 1024 %} 34 | AmbientCapabilities=CAP_NET_BIND_SERVICE 35 | {% endif %} 36 | 37 | [Install] 38 | WantedBy=multi-user.target 39 | -------------------------------------------------------------------------------- /tasks/create_minio_buckets.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Ensure PIP is installed. 4 | package: 5 | name: 6 | - python3-pip 7 | - python3-virtualenv 8 | - python3-setuptools 9 | state: present 10 | 11 | - name: Install pip packages 12 | pip: 13 | name: 14 | - minio 15 | state: present 16 | extra_args: --upgrade 17 | virtualenv: "{{ minio_venv_path }}" 18 | environment: "{{ minio_pip_environment_vars }}" 19 | register: package_install 20 | until: package_install is succeeded 21 | 22 | - name: "Create Buckets" 23 | minio_bucket: 24 | s3_url: "{{ minio_url }}" 25 | region: "{{ minio_site_region }}" 26 | name: "{{ bucket.name }}" 27 | access_key: "{{ minio_root_user }}" 28 | secret_key: "{{ minio_root_password }}" 29 | state: present 30 | policy: "{{ omit if bucket.policy == 'private' else bucket.policy }}" 31 | versioning: "{{ bucket.versioning | default(omit) }}" 32 | validate_certs: false 33 | object_lock: "{{ bucket.object_lock | default(false) }}" 34 | vars: 35 | ansible_python_interpreter: "{{ minio_venv_path }}/bin/python" 36 | with_items: 37 | - "{{ minio_buckets }}" 38 | loop_control: 39 | loop_var: "bucket" 40 | -------------------------------------------------------------------------------- /tasks/configure_server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set insecure mc command 3 | set_fact: 4 | mc_command: "{{ minio_client_bin }} --insecure" 5 | when: not minio_validate_certificate 6 | 7 | - name: Set secure mc command 8 | set_fact: 9 | mc_command: "{{ minio_client_bin }}" 10 | when: minio_validate_certificate 11 | 12 | - name: Configure minio connection alias 13 | command: "{{ mc_command }} alias set {{ minio_alias }} {{ minio_url }} {{ minio_root_user }} {{ minio_root_password }}" 14 | register: alias_command 15 | changed_when: false 16 | failed_when: '"Added `" + minio_alias + "` successfully" not in alias_command.stdout' 17 | 18 | - name: Create Minio Buckets 19 | include_tasks: create_minio_buckets.yml 20 | when: minio_buckets | length > 0 21 | 22 | - name: Create Minio Users 23 | include_tasks: create_minio_user.yml 24 | with_items: 25 | - "{{ minio_users }}" 26 | loop_control: 27 | loop_var: "user" 28 | 29 | - name: Configure Minio Site Replication 30 | include_tasks: configure_site_replication.yml 31 | when: replication_sites | length > 0 32 | 33 | # Create Prometheus bearer token 34 | - name: Create Prometheus bearer token 35 | include_tasks: create_prometheus_bearer_token.yml 36 | when: minio_prometheus_bearer_token 37 | -------------------------------------------------------------------------------- /tasks/create_minio_user.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Check whether user is already configured 4 | command: "{{ mc_command }} admin user info {{ minio_alias }} {{ user.name }}" 5 | register: get_user 6 | changed_when: false 7 | ignore_errors: true 8 | 9 | - name: Create users 10 | command: "{{ mc_command }} admin user add {{ minio_alias }} {{ user.name }} {{ user.password }}" 11 | register: add_user 12 | changed_when: '"Added user `" + user.name + "` successfully" in add_user.stdout' 13 | when: 14 | - get_user.rc==1 15 | - '"Unable to get user info" in get_user.stderr' 16 | 17 | - name: Create users policy file 18 | template: 19 | src: minio_policy.json.j2 20 | dest: "{{ minio_policy_dir }}/{{ user.name }}.json" 21 | owner: "{{ minio_user }}" 22 | group: "{{ minio_group }}" 23 | mode: 0640 24 | register: create_policy 25 | 26 | - name: Add user policy 27 | command: "{{ mc_command }} admin policy create {{ minio_alias }} {{ user.name }} {{ minio_policy_dir }}/{{ user.name }}.json" 28 | register: add_policy 29 | when: create_policy.changed 30 | changed_when: '"Added policy `" + user.name + "` successfully" in add_policy.stdout' 31 | 32 | - name: Apply user policy 33 | command: "{{ mc_command }} admin policy attach {{ minio_alias }} {{ user.name }} -u {{ user.name }}" 34 | register: apply_policy 35 | when: create_policy.changed 36 | changed_when: '"Policy `" + user.name + "` is set on user `" + user.name + "`" in apply_policy.stdout' 37 | -------------------------------------------------------------------------------- /templates/minio_policy.json.j2: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | {% for acl in user.buckets_acl %} 5 | {% if acl['name'] and acl['policy'] %} 6 | {% if acl['policy'] == 'read-write' %} 7 | { 8 | "Effect": "Allow", 9 | "Action": [ 10 | "s3:DeleteObject", 11 | "s3:GetObject", 12 | "s3:ListBucket", 13 | "s3:PutObject" 14 | ], 15 | "Resource": [ 16 | "arn:aws:s3:::{{ acl['name'] }}", 17 | "arn:aws:s3:::{{ acl['name'] }}/*" 18 | ] 19 | {{ "}," if not loop.last else "}" }} 20 | {% endif %} 21 | {% if acl['policy'] == 'read-only' %} 22 | { 23 | "Effect": "Allow", 24 | "Action": [ 25 | "s3:GetObject", 26 | "s3:ListBucket" 27 | ], 28 | "Resource": [ 29 | "arn:aws:s3:::{{ acl['name'] }}", 30 | "arn:aws:s3:::{{ acl['name'] }}/*" 31 | ] 32 | {{ "}," if not loop.last else "}" }} 33 | {% endif %} 34 | {% if acl['policy'] == 'write-only' %} 35 | { 36 | "Effect": "Allow", 37 | "Action": [ 38 | "s3:PutObject" 39 | ], 40 | "Resource": [ 41 | "arn:aws:s3:::{{ acl['name'] }}/*" 42 | ] 43 | {{ "}," if not loop.last else "}" }} 44 | {% endif %} 45 | {% if acl['policy'] == 'custom' and acl['custom'] %} 46 | {% for custom in acl.custom %} 47 | { 48 | {{ custom['rule'] }} 49 | {{ "}," if not loop.last else "}" }} 50 | {% endfor %} 51 | {% endif %} 52 | {% endif %} 53 | {% endfor %} 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /tasks/configure_site_replication.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create replication site alias 3 | command: "{{ mc_command }} alias set {{ item.name }} {{ item.url }} {{ item.admin_user }} {{ item.admin_password }}" 4 | register: alias_command 5 | changed_when: false 6 | failed_when: '"Added `" + item.name + "` successfully" not in alias_command.stdout' 7 | with_items: "{{ replication_sites }}" 8 | 9 | - name: Get replication site state for Minio instance 10 | command: "{{ mc_command }} admin replicate info {{ minio_alias }} --json" 11 | register: replication_info 12 | 13 | - name: Setting replication state fact 14 | set_fact: 15 | replication_info_json: "{{ replication_info.stdout | from_json }}" 16 | 17 | - name: List of existing replication site names 18 | set_fact: 19 | existing_replication_sites: "{{ replication_info_json.sites | default([]) | map(attribute='name') | list }}" 20 | 21 | - name: Remove replication sites no longer configured 22 | command: "{{ mc_command }} admin replicate remove {{ minio_alias }} {{ item }} --force" 23 | when: replication_sites | default([]) | selectattr('name','equalto',item) | list | count == 0 and item != minio_alias 24 | with_items: "{{ existing_replication_sites }}" 25 | register: remove_replication_site 26 | changed_when: '"successfully" in remove_replication_site.stdout' 27 | 28 | - name: Adding replication sites 29 | command: "{{ mc_command }} admin replicate add {{ minio_alias }} {{ replication_sites | map(attribute='name') | join(' ') }}" 30 | when: replication_sites | default([]) | map(attribute='name') | list | difference(existing_replication_sites) | length > 0 31 | register: add_replication_site 32 | changed_when: '"successfully" in add_replication_site.stdout' 33 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: "30 7 * * 3" 10 | 11 | defaults: 12 | run: 13 | working-directory: 'ricsanfre.minio' 14 | 15 | jobs: 16 | 17 | lint: 18 | name: Lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Check out the codebase. 22 | uses: actions/checkout@v5 23 | with: 24 | path: 'ricsanfre.minio' 25 | 26 | - name: Set up Python 3. 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | 31 | - name: Install test dependencies. 32 | run: pip3 install yamllint ansible ansible-lint 33 | 34 | - name: Lint code. 35 | run: | 36 | yamllint . 37 | 38 | molecule: 39 | name: Molecule 40 | runs-on: ubuntu-latest 41 | strategy: 42 | matrix: 43 | distro: 44 | - os: ubuntu 45 | release: 22.04 46 | - os: ubuntu 47 | release: 24.04 48 | steps: 49 | - name: Check out the codebase. 50 | uses: actions/checkout@v5 51 | with: 52 | path: 'ricsanfre.minio' 53 | 54 | - name: Set up Python 3. 55 | uses: actions/setup-python@v5 56 | with: 57 | python-version: '3.x' 58 | 59 | - name: Install test dependencies. 60 | run: pip3 install ansible molecule molecule-plugins[docker] docker yamllint ansible-lint 61 | 62 | - name: Run Molecule tests. 63 | run: molecule test 64 | env: 65 | PY_COLORS: '1' 66 | ANSIBLE_FORCE_COLOR: '1' 67 | MOLECULE_DISTRO: ${{ matrix.distro.os }} 68 | MOLECULE_RELEASE: ${{ matrix.distro.release }} 69 | -------------------------------------------------------------------------------- /molecule/site-replication/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Minio1 3 | hosts: minio1.ricsanfre.com 4 | become: true 5 | gather_facts: true 6 | roles: 7 | - role: ricsanfre.minio 8 | minio_root_user: "miniadmin1" 9 | minio_root_password: "supers1cret01" 10 | minio_alias: "minio1" 11 | - name: Minio2 12 | hosts: minio2.ricsanfre.com 13 | become: true 14 | gather_facts: true 15 | roles: 16 | - role: ricsanfre.minio 17 | minio_alias: "minio2" 18 | minio_root_user: "miniadmin2" 19 | minio_root_password: "supers1cret02" 20 | - name: Converge 21 | hosts: minio3.ricsanfre.com 22 | become: true 23 | gather_facts: true 24 | vars: 25 | roles: 26 | - role: ricsanfre.minio 27 | minio_root_user: "miniadmin3" 28 | minio_root_password: "supers1cret03" 29 | minio_alias: "minio3" 30 | minio_buckets: 31 | - name: bucket1 32 | policy: read-write 33 | object_lock: true 34 | - name: bucket2 35 | policy: read-only 36 | object_lock: true 37 | - name: bucket3 38 | policy: private 39 | object_lock: true 40 | - name: bucket5 41 | policy: read-write 42 | versioning: enabled 43 | - name: bucket6 44 | policy: write-only 45 | versioning: enabled 46 | - name: bucket7 47 | policy: read-write 48 | versioning: suspended 49 | replication_sites: 50 | - name: minio1 51 | url: "http://minio1.ricsanfre.com:9091" 52 | admin_user: "miniadmin1" 53 | admin_password: "supers1cret01" 54 | - name: minio2 55 | url: "http://minio2.ricsanfre.com:9091" 56 | admin_user: "miniadmin2" 57 | admin_password: "supers1cret02" 58 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install server, client or both 3 | minio_install_server: true 4 | minio_install_client: true 5 | 6 | # minio unix user 7 | minio_group: minio 8 | minio_user: minio 9 | 10 | # minio data directories 11 | minio_server_make_datadirs: true 12 | minio_server_datadirs: 13 | - /var/lib/minio 14 | # Minio server cluster node list. 15 | minio_server_cluster_nodes: [] 16 | 17 | # minio bin path 18 | minio_server_bin: /usr/local/bin/minio 19 | minio_client_bin: /usr/local/bin/mc 20 | 21 | # mino server config dirs 22 | minio_etc_dir: /etc/minio 23 | minio_cert_dir: "{{ minio_etc_dir }}/ssl" 24 | minio_policy_dir: "{{ minio_etc_dir }}/policy" 25 | # mino server env file 26 | minio_server_envfile: "{{ minio_etc_dir }}/minio.conf" 27 | 28 | # Minio server listen address 29 | minio_server_port: "9091" 30 | minio_server_addr: "" 31 | minio_console_port: "9092" 32 | 33 | # Minio site region 34 | minio_site_region: "eu-west-1" 35 | 36 | # Additional environment variables to be set in minio server environment 37 | minio_server_env_extra: "" 38 | 39 | # Additional Minio server CLI options 40 | minio_server_opts: "" 41 | 42 | # Minio server url 43 | minio_url: "" 44 | 45 | # Minio credentials user and password 46 | minio_root_user: "" 47 | minio_root_password: "" 48 | 49 | # TLS 50 | minio_enable_tls: false 51 | minio_key: "" 52 | minio_cert: "" 53 | 54 | # Buckets 55 | minio_buckets: [] 56 | # minio_buckets: 57 | # - name: bucket1 58 | # policy: private 59 | # object_lock: false 60 | 61 | # Replication Sites 62 | replication_sites: [] 63 | # replication_sites: 64 | # - name: myminio2 65 | # url: "" 66 | # admin_user: "" 67 | # admin_password: "" 68 | 69 | minio_users: [] 70 | # minio_users: 71 | # - name: user1 72 | # password: supers1cret0 73 | 74 | # Minio client 75 | minio_validate_certificate: true 76 | minio_alias: "myminio" 77 | 78 | # Prometheus bearer token 79 | minio_prometheus_bearer_token: false 80 | prometheus_bearer_token_output: "{{ minio_etc_dir }}/prometheus_bearer.json" 81 | 82 | # Environment variables to use when calling Pip 83 | minio_pip_environment_vars: {} 84 | 85 | # Path to minio virtual environment, created to avoid breakage with system managed python libraries 86 | minio_venv_path: "/opt/minio-venv" 87 | 88 | # Version to install 89 | minio_server_version: "RELEASE.2025-04-22T22-12-26Z" 90 | minio_mc_version: "RELEASE.2025-04-16T18-13-26Z" 91 | -------------------------------------------------------------------------------- /molecule/cluster/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | lint: | 5 | set -e 6 | yamllint . 7 | ansible-lint 8 | driver: 9 | name: docker 10 | platforms: 11 | - name: minio1.ricsanfre.com 12 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 13 | command: ${MOLECULE_DOCKER_COMMAND:-""} 14 | volumes: 15 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 16 | - /data/minio/disk1:/mnt/disk 17 | cgroupns_mode: host 18 | tmpfs: 19 | - /tmp 20 | - /run 21 | - /run/lock 22 | privileged: true 23 | pre_build_image: true 24 | exposed_ports: 25 | - 9091/tcp 26 | - 9092/tcp 27 | docker_networks: 28 | - name: internal 29 | ipam_config: 30 | - subnet: "10.11.0.0/16" 31 | gateway: "10.11.0.254" 32 | networks: 33 | - name: internal 34 | ipv4_address: "10.11.0.1" 35 | published_ports: 36 | - 0.0.0.0:9091:9091/tcp 37 | - 0.0.0.0:9092:9092/tcp 38 | - name: minio2.ricsanfre.com 39 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 40 | command: ${MOLECULE_DOCKER_COMMAND:-""} 41 | volumes: 42 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 43 | - /data/minio/disk2:/mnt/disk 44 | cgroupns_mode: host 45 | tmpfs: 46 | - /tmp 47 | - /run 48 | - /run/lock 49 | privileged: true 50 | pre_build_image: true 51 | exposed_ports: 52 | - 9091/tcp 53 | - 9092/tcp 54 | networks: 55 | - name: internal 56 | ipv4_address: "10.11.0.2" 57 | - name: minio3.ricsanfre.com 58 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 59 | command: ${MOLECULE_DOCKER_COMMAND:-""} 60 | volumes: 61 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 62 | - /data/minio/disk3:/mnt/disk 63 | cgroupns_mode: host 64 | tmpfs: 65 | - /tmp 66 | - /run 67 | - /run/lock 68 | privileged: true 69 | pre_build_image: true 70 | exposed_ports: 71 | - 9091/tcp 72 | - 9092/tcp 73 | networks: 74 | - name: internal 75 | ipv4_address: "10.11.0.3" 76 | provisioner: 77 | name: ansible 78 | playbooks: 79 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 80 | inventory: 81 | host_vars: 82 | localhost: 83 | ansible_user: ${USER} 84 | verifier: 85 | name: ansible 86 | -------------------------------------------------------------------------------- /molecule/site-replication/molecule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | dependency: 3 | name: galaxy 4 | lint: | 5 | set -e 6 | yamllint . 7 | ansible-lint 8 | driver: 9 | name: docker 10 | platforms: 11 | - name: minio1.ricsanfre.com 12 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 13 | command: ${MOLECULE_DOCKER_COMMAND:-""} 14 | volumes: 15 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 16 | - /data/minio/disk1:/mnt/disk 17 | cgroupns_mode: host 18 | tmpfs: 19 | - /tmp 20 | - /run 21 | - /run/lock 22 | privileged: true 23 | pre_build_image: true 24 | exposed_ports: 25 | - 9091/tcp 26 | - 9092/tcp 27 | docker_networks: 28 | - name: internal 29 | ipam_config: 30 | - subnet: "10.11.0.0/16" 31 | gateway: "10.11.0.254" 32 | networks: 33 | - name: internal 34 | ipv4_address: "10.11.0.1" 35 | published_ports: 36 | - 0.0.0.0:9091:9091/tcp 37 | - 0.0.0.0:9092:9092/tcp 38 | - name: minio2.ricsanfre.com 39 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 40 | command: ${MOLECULE_DOCKER_COMMAND:-""} 41 | volumes: 42 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 43 | - /data/minio/disk2:/mnt/disk 44 | cgroupns_mode: host 45 | tmpfs: 46 | - /tmp 47 | - /run 48 | - /run/lock 49 | privileged: true 50 | pre_build_image: true 51 | exposed_ports: 52 | - 9091/tcp 53 | - 9092/tcp 54 | networks: 55 | - name: internal 56 | ipv4_address: "10.11.0.2" 57 | - name: minio3.ricsanfre.com 58 | image: "ricsanfre/docker-${MOLECULE_DISTRO:-ubuntu}-ansible:${MOLECULE_RELEASE:-latest}" 59 | command: ${MOLECULE_DOCKER_COMMAND:-""} 60 | volumes: 61 | - /sys/fs/cgroup:/sys/fs/cgroup:rw 62 | - /data/minio/disk3:/mnt/disk 63 | cgroupns_mode: host 64 | tmpfs: 65 | - /tmp 66 | - /run 67 | - /run/lock 68 | privileged: true 69 | pre_build_image: true 70 | exposed_ports: 71 | - 9091/tcp 72 | - 9092/tcp 73 | networks: 74 | - name: internal 75 | ipv4_address: "10.11.0.3" 76 | provisioner: 77 | name: ansible 78 | playbooks: 79 | converge: ${MOLECULE_PLAYBOOK:-converge.yml} 80 | inventory: 81 | host_vars: 82 | localhost: 83 | ansible_user: ${USER} 84 | verifier: 85 | name: ansible 86 | -------------------------------------------------------------------------------- /molecule/tls/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | become: true 5 | gather_facts: true 6 | vars: 7 | server_hostname: minio.ricsanfre.com 8 | ssl_key_size: 4096 9 | ssl_certificate_provider: selfsigned 10 | 11 | pre_tasks: 12 | - name: Generate self-signed SSL certificates for minio 13 | include_tasks: tasks/generate_selfsigned_cert.yml 14 | args: 15 | apply: 16 | delegate_to: localhost 17 | become: false 18 | - name: Load tls key and cert 19 | set_fact: 20 | minio_key: "{{ lookup('file', 'certificates/' + inventory_hostname + '_private.key') }}" 21 | minio_cert: "{{ lookup('file', 'certificates/' + inventory_hostname + '_public.crt') }}" 22 | 23 | roles: 24 | - role: ricsanfre.minio 25 | minio_root_user: "miniadmin" 26 | minio_root_password: "supers1cret0" 27 | minio_enable_tls: true 28 | minio_validate_certificate: false 29 | minio_prometheus_bearer_token: true 30 | minio_buckets: 31 | - name: bucket1 32 | policy: read-write 33 | - name: bucket2 34 | policy: read-only 35 | - name: bucket3 36 | policy: private 37 | - name: bucket4 38 | policy: private 39 | - name: bucket5 40 | policy: read-write 41 | versioning: enabled 42 | - name: bucket6 43 | policy: read-write 44 | versioning: suspended 45 | minio_users: 46 | - name: user1 47 | password: supers1cret0 48 | buckets_acl: 49 | - name: bucket1 50 | policy: read-write 51 | - name: bucket2 52 | policy: read-only 53 | object_lock: true 54 | - name: bucket3 55 | policy: read-only 56 | object_lock: false 57 | - name: bucket4 58 | policy: write-only 59 | object_lock: false 60 | versioning: "enabled" 61 | - name: bucket5 62 | policy: custom 63 | custom: 64 | - rule: | 65 | "Effect": "Allow", 66 | "Action": [ 67 | "s3:GetObject", 68 | "s3:DeleteObject", 69 | "s3:PutObject", 70 | "s3:AbortMultipartUpload", 71 | "s3:ListMultipartUploadParts" 72 | ], 73 | "Resource": [ 74 | "arn:aws:s3:::bucket3/*" 75 | ] 76 | - rule: | 77 | "Effect": "Allow", 78 | "Action": [ 79 | "s3:ListBucket" 80 | ], 81 | "Resource": [ 82 | "arn:aws:s3:::bucket3" 83 | ] 84 | -------------------------------------------------------------------------------- /molecule/cluster/converge.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Converge 3 | hosts: all 4 | become: true 5 | gather_facts: true 6 | vars: 7 | server_hostname: minio.ricsanfre.com 8 | subject_alt_names: "DNS:minio.ricsanfre.com,DNS:minio1.ricsanfre.com,DNS:minio2.ricsanfre.com,DNS:minio3.ricsanfre.com" 9 | ssl_key_size: 4096 10 | ssl_certificate_provider: selfsigned 11 | 12 | pre_tasks: 13 | - name: Generate self-signed SSL certificates for minio 14 | include_tasks: tasks/generate_selfsigned_cert.yml 15 | args: 16 | apply: 17 | delegate_to: localhost 18 | become: false 19 | run_once: true 20 | 21 | - name: Load tls key and cert 22 | set_fact: 23 | minio_key: "{{ lookup('file', 'certificates/' + server_hostname + '_private.key') }}" 24 | minio_cert: "{{ lookup('file', 'certificates/' + server_hostname + '_public.crt') }}" 25 | run_once: true 26 | 27 | roles: 28 | - role: ricsanfre.minio 29 | minio_root_user: "miniadmin" 30 | minio_root_password: "supers1cret0" 31 | minio_enable_tls: true 32 | minio_validate_certificate: false 33 | minio_prometheus_bearer_token: true 34 | minio_server_datadirs: 35 | - '/mnt/disk/minio' 36 | minio_server_cluster_nodes: 37 | - 'https://minio{1...3}.ricsanfre.com:9091/mnt/disk/minio' 38 | minio_buckets: 39 | - name: bucket1 40 | policy: read-write 41 | - name: bucket2 42 | policy: read-only 43 | - name: bucket3 44 | policy: private 45 | - name: bucket4 46 | policy: private 47 | - name: bucket5 48 | policy: read-write 49 | versioning: enabled 50 | - name: bucket6 51 | policy: write-only 52 | versioning: "enabled" 53 | - name: bucket7 54 | policy: read-write 55 | versioning: suspended 56 | minio_users: 57 | - name: user1 58 | password: supers1cret0 59 | buckets_acl: 60 | - name: bucket1 61 | policy: read-write 62 | - name: bucket2 63 | policy: read-only 64 | object_lock: true 65 | - name: bucket3 66 | policy: read-only 67 | object_lock: false 68 | - name: bucket4 69 | policy: custom 70 | custom: 71 | - rule: | 72 | "Effect": "Allow", 73 | "Action": [ 74 | "s3:GetObject", 75 | "s3:DeleteObject", 76 | "s3:PutObject", 77 | "s3:AbortMultipartUpload", 78 | "s3:ListMultipartUploadParts" 79 | ], 80 | "Resource": [ 81 | "arn:aws:s3:::bucket3/*" 82 | ] 83 | - rule: | 84 | "Effect": "Allow", 85 | "Action": [ 86 | "s3:ListBucket" 87 | ], 88 | "Resource": [ 89 | "arn:aws:s3:::bucket3" 90 | ] 91 | -------------------------------------------------------------------------------- /tasks/install_server.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Compose the Minio server download url 4 | set_fact: 5 | _minio_server_download_url: "https://dl.minio.io/server/minio/release/linux-{{ go_arch }}/archive/minio.{{ minio_server_version }}" 6 | 7 | - name: Get the Minio server checksum for architecture {{ go_arch }} 8 | set_fact: 9 | _minio_server_checksum: "{{ lookup('url', _minio_server_download_url + '.sha256sum').split(' ')[0] }}" 10 | 11 | 12 | - name: Create Minio group 13 | group: 14 | name: "{{ minio_group }}" 15 | state: present 16 | 17 | - name: Create Minio user 18 | user: 19 | name: "{{ minio_user }}" 20 | group: "{{ minio_group }}" 21 | system: "yes" 22 | shell: "/usr/sbin/nologin" 23 | 24 | - name: Create the Minio config directories 25 | file: 26 | path: "{{ item }}" 27 | state: directory 28 | owner: "{{ minio_user }}" 29 | group: "{{ minio_group }}" 30 | mode: 0750 31 | with_items: 32 | - "{{ minio_etc_dir }}" 33 | - "{{ minio_cert_dir }}" 34 | - "{{ minio_policy_dir }}" 35 | 36 | - name: Create the Minio data storage directories 37 | file: 38 | path: "{{ item }}" 39 | state: directory 40 | owner: "{{ minio_user }}" 41 | group: "{{ minio_group }}" 42 | mode: 0750 43 | when: minio_server_make_datadirs 44 | with_items: "{{ minio_server_datadirs }}" 45 | 46 | - name: Download the Minio server 47 | get_url: 48 | url: "{{ _minio_server_download_url }}" 49 | dest: "{{ minio_server_bin }}" 50 | owner: "root" 51 | group: "root" 52 | mode: 0755 53 | checksum: "sha256:{{ _minio_server_checksum }}" 54 | register: _download_server 55 | until: _download_server is succeeded 56 | retries: 5 57 | delay: 2 58 | notify: Restart minio 59 | 60 | - name: Generate the Minio server envfile 61 | template: 62 | src: minio.env.j2 63 | dest: "{{ minio_server_envfile }}" 64 | owner: "{{ minio_user }}" 65 | group: "{{ minio_group }}" 66 | mode: 0640 67 | notify: Restart minio 68 | 69 | - name: Create the Minio server systemd config 70 | template: 71 | src: minio.service.j2 72 | dest: "/etc/systemd/system/minio.service" 73 | owner: "root" 74 | group: "root" 75 | mode: 0644 76 | notify: 77 | - Reload minio systemd 78 | - Restart minio 79 | 80 | - name: Copy SSL private key file 81 | copy: 82 | dest: "{{ minio_cert_dir }}/private.key" 83 | content: "{{ minio_key }}" 84 | owner: "{{ minio_user }}" 85 | group: "{{ minio_user }}" 86 | mode: 0640 87 | become: true 88 | when: minio_enable_tls and (minio_key | default("")) != "" 89 | notify: Restart minio 90 | 91 | - name: Copy cert file 92 | copy: 93 | dest: "{{ minio_cert_dir }}/public.crt" 94 | content: "{{ minio_cert }}" 95 | owner: "{{ minio_user }}" 96 | group: "{{ minio_user }}" 97 | mode: 0640 98 | become: true 99 | when: minio_enable_tls and (minio_cert | default("")) != "" 100 | notify: Restart minio 101 | 102 | - name: Flush handlers 103 | meta: flush_handlers 104 | 105 | - name: Ensure minio is started at boot 106 | service: 107 | name: minio 108 | enabled: true 109 | state: started 110 | 111 | - name: Set secure minio url if not defined 112 | set_fact: 113 | minio_url: "https://{{ minio_server_address | default('127.0.0.1') }}:{{ minio_server_port }}" 114 | when: minio_enable_tls and minio_url | length == 0 115 | 116 | - name: Set unsecure minio_url if not defined 117 | set_fact: 118 | minio_url: "http://{{ minio_server_address | default('127.0.0.1') }}:{{ minio_server_port }}" 119 | when: not minio_enable_tls and minio_url | length == 0 120 | -------------------------------------------------------------------------------- /library/minio_bucket.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding: utf-8 3 | 4 | DOCUMENTATION = """ 5 | --- 6 | module: minio_bucket 7 | author: 8 | - "Alexis Facques" (@alexisfacques) 9 | short_description: Creates minio s3 buckets 10 | description: 11 | - "Minio S3 buckets use policies and not ACLs, making modules 'aws_s3' and 12 | 's3 bucket' incompatible" 13 | options: 14 | s3_url: 15 | description: 16 | - The name or IP address of the name server to query. 17 | required: true 18 | region: 19 | description: 20 | - Region name of buckets in S3 service 21 | required: false 22 | name: 23 | description: 24 | - The name of the S3 bucket. 25 | required: true 26 | access_key: 27 | description: 28 | - S3 access key. 29 | required: true 30 | secret_key: 31 | description: 32 | - S3 secret_key. 33 | required: true 34 | state: 35 | description: 36 | - Create or remove the s3 bucket. 37 | required: false 38 | policy: 39 | description: 40 | - Policies to bind to the s3 bucket. 41 | required: false 42 | versioning: 43 | description: 44 | - Bucket Versioning. 45 | required: false 46 | validate_certs: 47 | description: 48 | - When set to "no", SSL certificates will not be validated for 49 | boto versions >= 2.6.0. 50 | required: false 51 | object_lock: 52 | description: 53 | - When set to "yes", buckets will be created with 54 | object locking enabled. 55 | required: false 56 | """ 57 | 58 | EXAMPLES = """ 59 | - minio_bucket: 60 | s3_url: s3.min.io 61 | name: my_bucket 62 | access_key: my_access_key 63 | secret_key: my_secret_key 64 | policy: 65 | - *:read-only 66 | versioning: enabled 67 | state: present 68 | """ 69 | 70 | from ansible.module_utils.basic import AnsibleModule 71 | from minio.commonconfig import ENABLED 72 | from minio.versioningconfig import VersioningConfig, SUSPENDED 73 | 74 | import re 75 | import urllib3 76 | import json 77 | 78 | from minio import Minio 79 | from minio.error import S3Error 80 | 81 | class UncheckedException(Exception): 82 | pass 83 | 84 | class REMatcher: 85 | def __init__(self, string): 86 | """ 87 | Adapted from: https://stackoverflow.com/a/2555047/7152435 88 | A handy little class that retains the matched groups of a re.match 89 | result. 90 | """ 91 | self.__string = string 92 | 93 | def match(self, regexp): 94 | """ 95 | Returns whether or not the tested "self.__string" matches "regexp". 96 | """ 97 | self.rematch = re.match(regexp, self.__string) 98 | return bool(self.rematch) 99 | 100 | def group(self, i): 101 | """ 102 | Returns the capture group "i" of the prior match, if any. 103 | """ 104 | return self.rematch.group(i) 105 | 106 | def get_ro_statements(bucket_name): 107 | return [ 108 | { 109 | "Effect": "Allow", 110 | "Principal": { 111 | "AWS": ["*"] 112 | }, 113 | "Action": ["s3:GetBucketLocation"], 114 | "Resource": ["arn:aws:s3:::%s" % bucket_name] 115 | }, { 116 | "Effect": "Allow", 117 | "Principal": { 118 | "AWS":["*"] 119 | }, 120 | "Action": ["s3:ListBucket"], 121 | "Resource": ["arn:aws:s3:::%s" % bucket_name] 122 | }, { 123 | "Effect": "Allow", 124 | "Principal": { 125 | "AWS":["*"] 126 | }, 127 | "Action": ["s3:GetObject"], 128 | "Resource": ["arn:aws:s3:::%s/*" % bucket_name] 129 | } 130 | ] 131 | 132 | def get_wo_statements(bucket_name): 133 | return [ 134 | { 135 | "Effect": "Allow", 136 | "Principal": { 137 | "AWS": ["*"] 138 | }, 139 | "Action": ["s3:ListBucketMultipartUploads","s3:GetBucketLocation"], 140 | "Resource": ["arn:aws:s3:::%s" % bucket_name] 141 | }, { 142 | "Effect": "Allow", 143 | "Principal": { 144 | "AWS": ["*"] 145 | }, 146 | "Action": [ 147 | "s3:PutObject", 148 | "s3:AbortMultipartUpload", 149 | "s3:DeleteObject", 150 | "s3:ListMultipartUploadParts" 151 | ], 152 | "Resource": ["arn:aws:s3:::%s/*" % bucket_name] 153 | } 154 | ] 155 | 156 | def get_rw_statements(bucket_name): 157 | return [ 158 | { 159 | "Effect": "Allow", 160 | "Principal": { 161 | "AWS": ["*"] 162 | }, 163 | "Action": [ 164 | "s3:GetBucketLocation", 165 | "s3:ListBucket", 166 | "s3:ListBucketMultipartUploads" 167 | ], 168 | "Resource": ["arn:aws:s3:::%s" % bucket_name] 169 | }, { 170 | "Effect":"Allow", 171 | "Principal":{ 172 | "AWS":["*"] 173 | }, 174 | "Action":[ 175 | "s3:GetObject", 176 | "s3:ListMultipartUploadParts", 177 | "s3:PutObject", 178 | "s3:AbortMultipartUpload", 179 | "s3:DeleteObject" 180 | ], 181 | "Resource":["arn:aws:s3:::%s/*" % bucket_name] 182 | } 183 | ] 184 | 185 | def set_bucket_versioning(client, bucket_name, bucket_versioning): 186 | if bucket_versioning == "enabled": 187 | client.set_bucket_versioning(bucket_name, VersioningConfig(ENABLED)) 188 | if bucket_versioning == "suspended": 189 | client.set_bucket_versioning(bucket_name, VersioningConfig(SUSPENDED)) 190 | 191 | def main(): 192 | exit_message = dict(failed=False) 193 | 194 | module = AnsibleModule( 195 | argument_spec=dict( 196 | s3_url=dict(required=True, type="str"), 197 | region=dict(required=False, type="str", default="us-west-1"), 198 | name=dict(required=True, type="str"), 199 | access_key=dict(required=True, type="str"), 200 | secret_key=dict(required=True, type="str"), 201 | state=dict(required=False, type="str", default="present", choices=["absent","present"]), 202 | policy=dict(required=False, type="str", choices=["read-only","write-only","read-write"]), 203 | versioning=dict(required=False, type="str", choices=["suspended","enabled"]), 204 | validate_certs=dict(required=False, type="bool", default=True), 205 | object_lock=dict(required=False, type="bool", default=False) 206 | ), 207 | # No changes will be made to this environment with this module 208 | supports_check_mode=True 209 | ) 210 | 211 | bucket_name = module.params["name"] 212 | policy = module.params["policy"] 213 | bucket_versioning = module.params["versioning"] 214 | m = REMatcher(module.params["s3_url"]) 215 | 216 | if m.match(r"^http://([\w./:-]+)"): 217 | is_https = False 218 | unschemed_s3_url = m.group(1) 219 | 220 | elif m.match(r"^https://([\w./:-]+)"): 221 | is_https = True 222 | unschemed_s3_url = m.group(1) 223 | 224 | elif m.match(r"^(?:[a-zA-Z0-9]+:\/\/)*([\w./:-]+)"): 225 | is_https = True 226 | unschemed_s3_url = m.group(1) 227 | 228 | else: 229 | raise UncheckedException("Unhandled structure for 's3_url'.") 230 | 231 | http_client = None if module.params["validate_certs"] \ 232 | else urllib3.PoolManager( 233 | timeout=urllib3.Timeout.DEFAULT_TIMEOUT, 234 | cert_reqs='CERT_NONE' 235 | ) 236 | 237 | try: 238 | client = Minio( 239 | unschemed_s3_url, 240 | access_key=module.params["access_key"], 241 | secret_key=module.params["secret_key"], 242 | secure=is_https, 243 | region=module.params["region"], 244 | http_client=http_client 245 | ) 246 | 247 | if module.params["state"] == "absent": 248 | if module.check_mode: 249 | module.exit_json( 250 | faised=False, 251 | changed=client.bucket_exists(bucket_name) 252 | ) 253 | 254 | elif client.bucket_exists(bucket_name): 255 | client.remove_bucket(bucket_name) 256 | exit_message = dict(failed=False, changed=True) 257 | 258 | else: 259 | exit_message = dict(failed=False, changed=False) 260 | 261 | else: 262 | if module.check_mode: 263 | module.exit_json(faised=False, changed=True) 264 | 265 | if not client.bucket_exists(bucket_name): 266 | client.make_bucket( 267 | bucket_name=bucket_name, 268 | object_lock=module.params["object_lock"] 269 | ) 270 | if policy: 271 | client.set_bucket_policy(bucket_name, json.dumps({ 272 | "Version": "2012-10-17", 273 | "Statement": get_ro_statements(bucket_name) \ 274 | if policy == "read-only" else \ 275 | get_wo_statements(bucket_name) \ 276 | if policy == "write-only" else \ 277 | get_rw_statements(bucket_name) 278 | })) 279 | exit_message = dict(failed=False, changed=True) 280 | else: 281 | exit_message = dict(failed=False, changed=False) 282 | set_bucket_versioning(client, bucket_name, bucket_versioning) 283 | 284 | except urllib3.exceptions.MaxRetryError: 285 | exit_message = dict( 286 | failed=True, 287 | msg="Could not connect to '%s'." % unschemed_s3_url 288 | ) 289 | 290 | except Exception as e: 291 | exit_message = dict( 292 | failed=True, 293 | msg="An error has occured with the \"minio_bucket\" module: %s" % e 294 | ) 295 | 296 | if exit_message["failed"]: 297 | module.fail_json(**exit_message) 298 | 299 | else: 300 | module.exit_json(**exit_message) 301 | 302 | if __name__ == "__main__": 303 | main() 304 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ansible Role: Minio Server Installation and Configuration 2 | ========= 3 | 4 | This role install and configure [Minio](http://min.io) in a linux server. 5 | 6 | 7 | Requirements 8 | ------------ 9 | 10 | None 11 | 12 | Role Variables 13 | -------------- 14 | 15 | Available variables are listed below along with default values (see `defaults\main.yaml`) 16 | 17 | - Wheter to install or not minio server and minio client 18 | 19 | ```yml 20 | minio_install_server: true 21 | minio_install_client: true 22 | ``` 23 | - Minio server installation details 24 | 25 | Minio release version to install 26 | 27 | ```yml 28 | minio_server_version: "RELEASE.2025-04-22T22-12-26Z" 29 | minio_mc_version: "RELEASE.2025-04-16T18-13-26Z" 30 | ``` 31 | 32 | Minio UNIX user/group 33 | ```yml 34 | minio_group: minio 35 | minio_user: minio 36 | ``` 37 | Minio installation directories to place server configuration (`minio_etc_dir`), TLS certificates (`minio_cert_dir`) and user access policies (`minio_policy_dir`) 38 | ```yml 39 | minio_etc_dir: /etc/minio 40 | minio_cert_dir: "{{ minio_etc_dir }}/ssl" 41 | minio_policy_dir: "{{ minio_etc_dir }}/policy" 42 | ``` 43 | Minio server IP address (`minio_server_address`), if empty server listen in all available IP addresses, and server/console listening ports (`minio_server_port` and `minio_console_port`) 44 | ```yml 45 | minio_server_port: "9091" 46 | minio_server_addr: "" 47 | minio_console_port: "9092" 48 | ``` 49 | 50 | Minio admin user and password 51 | ```yml 52 | minio_root_user: "" 53 | minio_root_password: "" 54 | ``` 55 | 56 | Minio site region 57 | ```yml 58 | minio_site_region: "eu-west-1" 59 | ``` 60 | 61 | Minio data directories (`minio_server_datadirs`) and whether force the creation in case they do not exist (`minio_server_make_datadirs`) 62 | 63 | ```yml 64 | minio_server_make_datadirs: true 65 | minio_server_datadirs: 66 | - /var/lib/minio 67 | ``` 68 | 69 | ```yaml 70 | minio_server_cluster_nodes: [] 71 | ``` 72 | 73 | Set a list of nodes to create a [distributed cluster (Multi-Node Multi-Drive deployment)](https://min.io/docs/minio/linux/operations/install-deploy-manage/deploy-minio-multi-node-multi-drive.html). 74 | 75 | In this mode, ansible will create your server datadirs (`minio_serverdata_dirs`), but use this list (`minio_server_cluster_nodes`) for the server startup. 76 | 77 | > Multi-drive configuration requires datadirs on separate disks to satisfy Minio's distributed storage requirements. 78 | 79 | See recommendations for using, same configuration in all nodes, sequential hostnames and local-atached storage with sequential mounts in the documentation (https://min.io/docs/minio/linux/operations/install-deploy-manage/deploy-minio-multi-node-multi-drive.html) 80 | 81 | Example: 82 | 83 | ```yaml 84 | minio_server_datadirs: 85 | - '/mnt/disk1/minio' 86 | - '/mnt/disk2/minio' 87 | - '/mnt/disk3/minio' 88 | - '/mnt/disk4/minio' 89 | minio_server_cluster_nodes: 90 | - 'https://minio{1...4}.example.net:9091/mnt/disk{1...4}/minio' 91 | ``` 92 | 93 | - Minio client configuration 94 | 95 | Connection alias name `minio_alias` and whether validate or not SSL certificates (`minio_validate_certificates`) 96 | 97 | ```yml 98 | minio_validate_certificate: true 99 | minio_alias: "myminio" 100 | ``` 101 | 102 | - Configuration of TLS 103 | 104 | To enable configuration of TLS set `minio_enable_tls` to true and provide the private key and public certificate as content loaded into `minio_key` and `minio_cert` variables. 105 | 106 | They can be loaded from files using an ansible task like: 107 | 108 | ```yml 109 | - name: Load tls key and cert from files 110 | set_fact: 111 | minio_key: "{{ lookup('file','certificates/{{ inventory_hostname }}_private.key') }}" 112 | minio_cert: "{{ lookup('file','certificates/{{ inventory_hostname }}_public.crt') }}" 113 | 114 | ``` 115 | 116 | - Buckets to be created 117 | 118 | Variable `minio_buckets` create the list of provided buckets, and applying a specifc policy. For creating the buckets, a modified version of Ansible Module from Alexis Facques is used (https://github.com/alexisfacques/ansible-module-s3-minio-bucket) 119 | 120 | ```yml 121 | minio_buckets: 122 | - name: bucket1 123 | policy: read-only 124 | - name: bucket2 125 | policy: read-write 126 | object_lock: false 127 | - name: bucket3 128 | policy: private 129 | object_lock: true 130 | ``` 131 | > NOTE The module use remote connection to Minio Server using Python API (`minio` python package). Role ensure that PIP is installed and install `minio` package. 132 | 133 | During bucket creation three types of policy can be specified: `private`, `read-only` or `read-write` buckets. 134 | > Setting policy bucket to `read-only` or `read-write` enables anonymous access to the bucket (public bucket). 135 | > To rekeep the bucket private and not enable annymous access `private` policy needs to be specified 136 | 137 | 138 | Minio object locking can also be enabled or disabled: `true` or `false`. 139 | 140 | - Users to be created and buckets ACLs 141 | 142 | Users can be automatically created using `minio_users` variable: a list of users can be provided, each user with three variables `name` (user name), `password` (user password) and `buckets_acl` list of buckets and type of access granted to each bucket (read-only or read-write). 143 | The role automatically create policy json files containing the user policy statements and load them into the server. 144 | 145 | Predefined `read-only`,`write-only` and `read-write` policies, containing pre-defined access statements, can be used. Custom policies can be also defined using `custom` policy. In this case list of access statements need to be provided. 146 | 147 | 148 | ```yml 149 | minio_users: 150 | - name: user1 151 | password: supers1cret0 152 | buckets_acl: 153 | - name: bucket1 154 | policy: read-write 155 | - name: bucket2 156 | policy: read-only 157 | - name: bucket3 158 | policy: custom 159 | custom: 160 | - rule: | 161 | "Effect": "Allow", 162 | "Action": [ 163 | "s3:GetObject", 164 | "s3:DeleteObject", 165 | "s3:PutObject", 166 | "s3:AbortMultipartUpload", 167 | "s3:ListMultipartUploadParts" 168 | ], 169 | "Resource": [ 170 | "arn:aws:s3:::bucket3/*" 171 | ] 172 | - rule: | 173 | "Effect": "Allow", 174 | "Action": [ 175 | "s3:ListBucket" 176 | ], 177 | "Resource": [ 178 | "arn:aws:s3:::bucket3" 179 | ] 180 | ``` 181 | The previous configuration will create the following policy.json file for the user 182 | 183 | ```json 184 | { 185 | "Version": "2012-10-17", 186 | "Statement": [ 187 | { 188 | "Effect": "Allow", 189 | "Action": [ 190 | "s3:DeleteObject", 191 | "s3:GetObject", 192 | "s3:ListBucket", 193 | "s3:PutObject" 194 | ], 195 | "Resource": [ 196 | "arn:aws:s3:::bucket1", 197 | "arn:aws:s3:::bucket1/*" 198 | ] 199 | }, 200 | { 201 | "Effect": "Allow", 202 | "Action": [ 203 | "s3:GetObject", 204 | "s3:ListBucket" 205 | ], 206 | "Resource": [ 207 | "arn:aws:s3:::bucket2", 208 | "arn:aws:s3:::bucket2/*" 209 | ] 210 | }, 211 | { 212 | "Effect": "Allow", 213 | "Action": [ 214 | "s3:AbortMultipartUpload", 215 | "s3:DeleteObject", 216 | "s3:GetObject", 217 | "s3:ListMultipartUploadParts", 218 | "s3:PutObject" 219 | ], 220 | "Resource": [ 221 | "arn:aws:s3:::bucket3/*" 222 | ] 223 | }, 224 | { 225 | "Effect": "Allow", 226 | "Action": [ 227 | "s3:ListBucket" 228 | ], 229 | "Resource": [ 230 | "arn:aws:s3:::bucket3" 231 | ] 232 | } 233 | ] 234 | } 235 | ``` 236 | 237 | - Generate Prometheus bearer token 238 | 239 | ```yml 240 | minio_prometheus_bearer_token: false 241 | prometheus_bearer_token_output: "{{ minio_etc_dir }}/prometheus_bearer.json" 242 | ``` 243 | 244 | Setting `minio_prometheus_bearer_token` to true, generates a file `/etc/minio/prometheus_bearer.json` which contains the result of executing the command: 245 | 246 | `mc admin prometheus generate myminio -json` 247 | 248 | 249 | - Install Minio pip library to python virtualenv 250 | 251 | Python virtual env is created to encapsulated installation of required python packages (minio). 252 | 253 | This is to avoid errors pip installation errors in newer python releases, included in Ubuntu 24.04, having a protection on python system packages. 254 | 255 | ```shell 256 | # pip3 install --upgrade minio 257 | error: externally-managed-environment 258 | 259 | × This environment is externally managed 260 | ╰─> To install Python packages system-wide, try apt install 261 | python3-xyz, where xyz is the package you are trying to 262 | install. 263 | 264 | If you wish to install a non-Debian-packaged Python package, 265 | create a virtual environment using python3 -m venv path/to/venv. 266 | Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make 267 | sure you have python3-full installed. 268 | 269 | If you wish to install a non-Debian packaged Python application, 270 | it may be easiest to use pipx install xyz, which will manage a 271 | virtual environment for you. Make sure you have pipx installed. 272 | 273 | See /usr/share/doc/python3.12/README.venv for more information. 274 | 275 | note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages. 276 | hint: See PEP 668 for the detailed specification. 277 | ``` 278 | 279 | Location of the virtual environemnt can be configured using following ansible variable: 280 | ```yaml 281 | # Path to minio virtual environment, created to avoid breakage with system managed python libraries 282 | minio_venv_path: "/opt/minio-venv" 283 | ``` 284 | 285 | - Site Replication 286 | 287 | Variable `replication_sites` creates the list of multiple independent MinIO deployments to configure as a cluster of replicas called peer sites. For further information on what is replicated across sites refer to the minio documentation (https://min.io/docs/minio/linux/operations/install-deploy-manage/multi-site-replication.html) 288 | 289 | ```yml 290 | replication_sites: 291 | - name: myminio2 292 | url: "http://replication.minio.com:9091" 293 | admin_user: "myminio2" 294 | admin_password: "supers1cret02" 295 | ``` 296 | The `url` is the url of the site that will be replicated to from the currently configured site in the playbook. The `admin_user` and `admin_password` variables are authentication credentials for the site to be replicated to with admin privileges. 297 | 298 | As noted in the `site-replication` documentation 299 | > - Initially, only **one** of the sites added for replication may have data. After site-replication is successfully configured, this data is replicated to the other (initially empty) sites. Subsequently, objects may be written to any of the sites, and they will be replicated to all other sites. 300 | > - **Removing a site** is not allowed from a set of replicated sites once configured. 301 | > - All sites must be using the **same** external IDP(s) if any. 302 | > - For [SSE-S3 or SSE-KMS encryption via KMS](https://min.io/docs/minio/linux/operations/server-side-encryption.html "MinIO KMS Guide"), all sites **must** have access to a central KMS deployment. This can be achieved via a central KES server or multiple KES servers (say one per site) connected via a central KMS (Vault) server. 303 | 304 | 305 | Dependencies 306 | ------------ 307 | 308 | None 309 | 310 | Example Playbook 311 | ---------------- 312 | 313 | The following playbook install and configure minio server and client, enabling TLS and generating self-signed SSL certificates. 314 | It also create some buckets and users with proper ACLs 315 | 316 | ```yml 317 | --- 318 | - name: Install and configure Minio Server 319 | hosts: minio 320 | become: true 321 | gather_facts: true 322 | vars: 323 | server_hostname: minio.example.com 324 | ssl_key_size: 4096 325 | ssl_certificate_provider: selfsigned 326 | 327 | pre_tasks: 328 | - name: Generate self-signed SSL certificates for minio 329 | include_tasks: generate_selfsigned_cert.yml 330 | args: 331 | apply: 332 | delegate_to: localhost 333 | become: false 334 | - name: Load tls key and cert 335 | set_fact: 336 | minio_key: "{{ lookup('file','certificates/' + inventory_hostname + '_private.key') }}" 337 | minio_cert: "{{ lookup('file','certificates/' + inventory_hostname + '_public.crt') }}" 338 | 339 | roles: 340 | - role: ricsanfre.minio 341 | minio_root_user: "miniadmin" 342 | minio_root_password: "supers1cret0" 343 | minio_enable_tls: true 344 | minio_buckets: 345 | - name: bucket1 346 | policy: read-write 347 | - name: bucket2 348 | policy: read-write 349 | minio_users: 350 | - name: user1 351 | password: supers1cret0 352 | buckets_acl: 353 | - name: bucket1 354 | policy: read-write 355 | - name: bucket2 356 | policy: read-only 357 | 358 | ``` 359 | 360 | `pre-tasks` section include tasks to generate a private key and a self-signed certificate and load them into `minio_key` and `minio_cert` variables. 361 | 362 | Where `generate_selfsigned_cert.yml` contain the tasks for generating a Private Key and SSL self-signed certificate: 363 | 364 | ```yml 365 | --- 366 | - name: Create private certificate 367 | openssl_privatekey: 368 | path: "certificates/{{ inventory_hostname }}_private.key" 369 | size: "{{ ssl_key_size | int }}" 370 | mode: 0644 371 | 372 | - name: Create CSR 373 | openssl_csr: 374 | path: "certificates/{{ inventory_hostname }}_cert.csr" 375 | privatekey_path: "certificates/{{ inventory_hostname }}_private.key" 376 | common_name: "{{ server_hostname }}" 377 | 378 | - name: Create certificates for keystore 379 | openssl_certificate: 380 | csr_path: "certificates/{{ inventory_hostname }}_cert.csr" 381 | path: "certificates/{{ inventory_hostname }}_public.crt" 382 | privatekey_path: "certificates/{{ inventory_hostname }}_private.key" 383 | provider: "{{ ssl_certificate_provider }}" 384 | 385 | ``` 386 | 387 | 388 | License 389 | ------- 390 | 391 | MIT 392 | 393 | Author Information 394 | ------------------ 395 | 396 | Created by Ricardo Sanchez (ricsanfre) 397 | Bucket creation ansible module based on module from Alexix Facques (https://github.com/alexisfacques/ansible-module-s3-minio-bucket) 398 | --------------------------------------------------------------------------------