mirror of
https://github.com/fscotto/infra.git
synced 2026-10-03 21:39:50 +00:00
Deploy temporary Atlas Nextcloud and ONLYOFFICE stack
This commit is contained in:
@@ -49,6 +49,38 @@ atlas_zfs_backup_reservation: 500G
|
||||
atlas_zfs_dataset_photobook: media/photobook
|
||||
atlas_mount_root: /zpool
|
||||
atlas_manage_storage: true
|
||||
atlas_manage_nextcloud: true
|
||||
atlas_nextcloud_domain: cloud.fscotto.co
|
||||
atlas_onlyoffice_domain: office.fscotto.co
|
||||
# Resolved official amd64 images on 2026-10-03; updates are deliberate.
|
||||
atlas_nextcloud_image: docker.io/library/nextcloud:33.0.9-apache@sha256:a97666d6ae931bde78a80cfba8abdf46d436d7b540f31895803f6fb0a012d689
|
||||
atlas_nextcloud_postgres_image: docker.io/library/postgres:17-bookworm@sha256:639ab7ceb90e13123085b741fb31ef493fba25463002f6da665352e7b534b652
|
||||
atlas_nextcloud_redis_image: docker.io/library/redis:7.4-bookworm@sha256:c6eabf748fc7a61dbb5a705c78bcf3d6377b1127a97d0ce965c11c44ba46896f
|
||||
atlas_onlyoffice_image: docker.io/onlyoffice/documentserver:9.4.0.1@sha256:3ab6ebc7c605e5a32b7ae3ff19daed4925090245acc8100ce2230bd766c88212
|
||||
atlas_nextcloud_users:
|
||||
- username: fabio
|
||||
display_name: Fabio
|
||||
password: "{{ vault_nextcloud_fabio_password }}"
|
||||
- username: chiara
|
||||
display_name: Chiara
|
||||
password: "{{ vault_nextcloud_chiara_password }}"
|
||||
atlas_nextcloud_apps:
|
||||
- id: groupfolders
|
||||
version: 21.0.9
|
||||
url: https://github.com/nextcloud-releases/groupfolders/releases/download/v21.0.9/groupfolders-v21.0.9.tar.gz
|
||||
checksum: sha256:d8b95f0778425f646f2311ba5b42d8e2fcfdf37dc2fd35fcac8d3f01bde38a21
|
||||
- id: onlyoffice
|
||||
version: 10.2.1
|
||||
url: https://github.com/ONLYOFFICE/onlyoffice-nextcloud/releases/download/v10.2.1/onlyoffice.tar.gz
|
||||
checksum: sha256:144998af0610ccd17ee8d7025e2f8001472da03f6dab90ff38039247825e3a9b
|
||||
- id: contacts
|
||||
version: 8.9.1
|
||||
url: https://github.com/nextcloud-releases/contacts/releases/download/v8.9.1/contacts-v8.9.1.tar.gz
|
||||
checksum: sha256:a25cdf448b192631b8e8eb7addc31b40382b33871b10521f4308ac5a6e0457bf
|
||||
- id: calendar
|
||||
version: 6.6.2
|
||||
url: https://github.com/nextcloud-releases/calendar/releases/download/v6.6.2/calendar-v6.6.2.tar.gz
|
||||
checksum: sha256:7e83632d4436d3037a34d1c73cbc06d5ccb6e8fc10f096a86a43a0515588529c
|
||||
# Rootless Gitea was restored from the stopped-source export before production activation.
|
||||
atlas_manage_gitea: true
|
||||
atlas_gitea_production_enabled: true
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
---
|
||||
atlas_manage_storage: false
|
||||
atlas_manage_nextcloud: false
|
||||
atlas_nextcloud_root: "{{ atlas_app_data_mountpoint }}/nextcloud"
|
||||
atlas_nextcloud_dataset: "{{ atlas_zfs_pool }}/services/data/nextcloud"
|
||||
atlas_nextcloud_domain: ""
|
||||
atlas_onlyoffice_domain: ""
|
||||
atlas_nextcloud_http_port: 8080
|
||||
atlas_onlyoffice_http_port: 8081
|
||||
atlas_nextcloud_network_subnet: 10.90.10.0/24
|
||||
atlas_nextcloud_network_gateway: 10.90.10.1
|
||||
atlas_nextcloud_quadlet_dir: "{{ atlas_admin_home }}/.config/containers/systemd"
|
||||
atlas_nextcloud_private_dir: "{{ atlas_admin_home }}/.config/atlas-nextcloud"
|
||||
atlas_nextcloud_app_cache: "{{ atlas_admin_home }}/.cache/atlas-nextcloud-apps"
|
||||
atlas_nextcloud_image: ""
|
||||
atlas_nextcloud_postgres_image: ""
|
||||
atlas_nextcloud_redis_image: ""
|
||||
atlas_onlyoffice_image: ""
|
||||
atlas_nextcloud_admin: admin
|
||||
atlas_nextcloud_users: []
|
||||
atlas_nextcloud_apps: []
|
||||
atlas_nextcloud_services:
|
||||
- atlas-nextcloud-db.service
|
||||
- atlas-nextcloud-redis.service
|
||||
- atlas-nextcloud.service
|
||||
- atlas-onlyoffice.service
|
||||
atlas_manage_sharing: false
|
||||
# Destructive first-boot action; normally false once the pool exists.
|
||||
atlas_create_pool: false
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
- name: Import the declared Atlas Gitea public domain
|
||||
ansible.builtin.import_tasks: gitea_public_domain.yml
|
||||
|
||||
- name: Import Atlas Nextcloud steady-state stack
|
||||
ansible.builtin.import_tasks: nextcloud.yml
|
||||
|
||||
- name: Import Atlas iCloudPD storage and boot-started Quadlet tasks
|
||||
ansible.builtin.import_tasks: icloudpd.yml
|
||||
|
||||
|
||||
309
ansible/roles/profile_atlas/tasks/nextcloud.yml
Normal file
309
ansible/roles/profile_atlas/tasks/nextcloud.yml
Normal file
@@ -0,0 +1,309 @@
|
||||
---
|
||||
- name: Manage the empty Atlas Nextcloud and ONLYOFFICE stack
|
||||
tags: [atlas, nextcloud]
|
||||
when: atlas_manage_nextcloud | bool
|
||||
block:
|
||||
- name: Validate dedicated paths, domains and pinned images
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_manage_storage | bool
|
||||
- atlas_manage_firewall | bool
|
||||
- atlas_nextcloud_root == atlas_app_data_mountpoint ~ '/nextcloud'
|
||||
- atlas_nextcloud_dataset == atlas_zfs_pool ~ '/services/data/nextcloud'
|
||||
- atlas_nextcloud_domain is match('^[a-z0-9.-]+$')
|
||||
- atlas_onlyoffice_domain is match('^[a-z0-9.-]+$')
|
||||
- atlas_nextcloud_domain != atlas_onlyoffice_domain
|
||||
- atlas_nextcloud_http_port | int > 1024
|
||||
- atlas_onlyoffice_http_port | int > 1024
|
||||
- atlas_nextcloud_http_port != atlas_onlyoffice_http_port
|
||||
- "['calendar', 'contacts', 'onlyoffice', 'groupfolders'] | difference(atlas_nextcloud_apps | map(attribute='id') | list) | length == 0"
|
||||
- atlas_nextcloud_users | length > 0
|
||||
- atlas_nextcloud_admin not in (atlas_nextcloud_users | map(attribute='username') | list)
|
||||
- atlas_nextcloud_users | map(attribute='username') | unique | list | length == atlas_nextcloud_users | length
|
||||
- item is search('@sha256:[0-9a-f]{64}$')
|
||||
loop:
|
||||
- "{{ atlas_nextcloud_image }}"
|
||||
- "{{ atlas_nextcloud_postgres_image }}"
|
||||
- "{{ atlas_nextcloud_redis_image }}"
|
||||
- "{{ atlas_onlyoffice_image }}"
|
||||
|
||||
- name: Require dedicated Vault secrets without exposing them
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item | default('') is match('^[a-zA-Z0-9]{32,}$')
|
||||
loop: >-
|
||||
{{ [vault_nextcloud_database_password | default(''),
|
||||
vault_nextcloud_redis_password | default(''),
|
||||
vault_nextcloud_admin_password | default(''),
|
||||
vault_nextcloud_onlyoffice_jwt | default('')] +
|
||||
(atlas_nextcloud_users | map(attribute='password') | list) }}
|
||||
no_log: true
|
||||
|
||||
- name: Verify the existing application-data parent is mounted
|
||||
community.general.zfs_facts:
|
||||
name: "{{ atlas_zfs_pool }}/{{ atlas_zfs_dataset_app_data }}"
|
||||
properties: name,mounted,mountpoint
|
||||
register: atlas_nextcloud_parent
|
||||
|
||||
- name: Require the verified application-data parent
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- atlas_nextcloud_parent.ansible_facts.ansible_zfs_datasets | length == 1
|
||||
- atlas_nextcloud_parent.ansible_facts.ansible_zfs_datasets[0].mounted == 'yes'
|
||||
- atlas_nextcloud_parent.ansible_facts.ansible_zfs_datasets[0].mountpoint == atlas_app_data_mountpoint
|
||||
|
||||
- name: Create the dedicated Nextcloud namespace and component datasets
|
||||
community.general.zfs:
|
||||
name: "{{ atlas_nextcloud_dataset }}{{ item }}"
|
||||
state: present
|
||||
extra_zfs_properties:
|
||||
compression: zstd
|
||||
mountpoint: "{{ atlas_nextcloud_root }}{{ item }}"
|
||||
loop: ['', /app, /files, /database, /cache, /office]
|
||||
|
||||
- name: Inspect component directories before seeding ownership
|
||||
ansible.builtin.stat:
|
||||
path: "{{ atlas_nextcloud_root }}{{ item }}"
|
||||
follow: false
|
||||
get_checksum: false
|
||||
loop: [/app, /files, /database, /cache, /office]
|
||||
register: atlas_nextcloud_component_paths
|
||||
|
||||
- name: Seed only root-owned new dataset roots without recursive ownership changes
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.stat.path }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0700"
|
||||
loop: "{{ atlas_nextcloud_component_paths.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when:
|
||||
- item.stat.exists
|
||||
- item.stat.uid | default(-1) | int == 0
|
||||
|
||||
- name: Ensure private rootless stack configuration directories exist
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- {path: "{{ atlas_nextcloud_private_dir }}", mode: "0700"}
|
||||
- {path: "{{ atlas_nextcloud_app_cache }}", mode: "0755"}
|
||||
- {path: "{{ atlas_nextcloud_quadlet_dir }}", mode: "0700"}
|
||||
- {path: "{{ atlas_admin_home }}/.config/systemd/user", mode: "0700"}
|
||||
|
||||
- name: Inspect the dedicated ONLYOFFICE bind directories
|
||||
ansible.builtin.stat:
|
||||
path: "{{ atlas_nextcloud_root }}/office/{{ item }}"
|
||||
follow: false
|
||||
get_checksum: false
|
||||
loop: [data, lib, logs, database]
|
||||
register: atlas_onlyoffice_bind_paths
|
||||
|
||||
- name: Create ONLYOFFICE bind directories only when absent
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.invocation.module_args.path }}"
|
||||
state: directory
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0700"
|
||||
loop: "{{ atlas_onlyoffice_bind_paths.results }}"
|
||||
loop_control:
|
||||
label: "{{ item.item }}"
|
||||
when: not item.stat.exists
|
||||
|
||||
- name: Store private mounted password files inside a restricted host directory
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.value }}\n"
|
||||
dest: "{{ atlas_nextcloud_private_dir }}/{{ item.name }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- {name: postgres-password, value: "{{ vault_nextcloud_database_password }}"}
|
||||
- {name: redis-password, value: "{{ vault_nextcloud_redis_password }}"}
|
||||
- {name: admin-password, value: "{{ vault_nextcloud_admin_password }}"}
|
||||
- {name: onlyoffice-jwt, value: "{{ vault_nextcloud_onlyoffice_jwt }}"}
|
||||
no_log: true
|
||||
diff: false
|
||||
register: atlas_nextcloud_secret_files
|
||||
|
||||
- name: Render private Redis and ONLYOFFICE configuration
|
||||
ansible.builtin.template:
|
||||
src: "{{ item.src }}"
|
||||
dest: "{{ atlas_nextcloud_private_dir }}/{{ item.dest }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- {src: atlas-nextcloud-redis.conf.j2, dest: redis.conf, mode: "0644"}
|
||||
- {src: atlas-onlyoffice.env.j2, dest: onlyoffice.env, mode: "0600"}
|
||||
no_log: true
|
||||
diff: false
|
||||
register: atlas_nextcloud_private_configuration
|
||||
|
||||
- name: Download checksum-pinned compatible application releases
|
||||
ansible.builtin.get_url:
|
||||
url: "{{ item.url }}"
|
||||
dest: "{{ atlas_nextcloud_app_cache }}/{{ item.id }}-{{ item.version }}.tar.gz"
|
||||
checksum: "{{ item.checksum }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0644"
|
||||
loop: "{{ atlas_nextcloud_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.id }} {{ item.version }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Admit only the Aegis gateway to the Nextcloud and Office HTTP listeners
|
||||
ansible.posix.firewalld:
|
||||
rich_rule: >-
|
||||
rule family="ipv4" source address="{{ atlas_aegis_ip }}"
|
||||
port port="{{ item }}" protocol="tcp" accept
|
||||
zone: "{{ atlas_firewalld_zone }}"
|
||||
state: enabled
|
||||
permanent: true
|
||||
immediate: true
|
||||
loop: ["{{ atlas_nextcloud_http_port }}", "{{ atlas_onlyoffice_http_port }}"]
|
||||
|
||||
- name: Enable lingering for the declared rootless owner
|
||||
ansible.builtin.command:
|
||||
argv: [loginctl, enable-linger, "{{ atlas_admin_username }}"]
|
||||
creates: "/var/lib/systemd/linger/{{ atlas_admin_username }}"
|
||||
|
||||
- name: Render Nextcloud component and network Quadlets
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "{{ atlas_nextcloud_quadlet_dir }}/{{ item }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0644"
|
||||
loop:
|
||||
- atlas-nextcloud.network
|
||||
- atlas-nextcloud-db.container
|
||||
- atlas-nextcloud-redis.container
|
||||
- atlas-nextcloud.container
|
||||
- atlas-onlyoffice.container
|
||||
register: atlas_nextcloud_quadlets
|
||||
|
||||
- name: Render recurring Nextcloud cron user units
|
||||
ansible.builtin.template:
|
||||
src: "{{ item }}.j2"
|
||||
dest: "{{ atlas_admin_home }}/.config/systemd/user/{{ item }}"
|
||||
owner: "{{ atlas_admin_username }}"
|
||||
group: "{{ atlas_admin_group }}"
|
||||
mode: "0644"
|
||||
loop: [atlas-nextcloud-cron.service, atlas-nextcloud-cron.timer]
|
||||
register: atlas_nextcloud_cron_units
|
||||
|
||||
- name: Manage and verify rootless Nextcloud services
|
||||
become_user: "{{ atlas_admin_username }}"
|
||||
environment:
|
||||
XDG_RUNTIME_DIR: "/run/user/{{ atlas_admin_uid }}"
|
||||
DBUS_SESSION_BUS_ADDRESS: "unix:path=/run/user/{{ atlas_admin_uid }}/bus"
|
||||
when: not ansible_check_mode
|
||||
block:
|
||||
- name: Pull the pinned images before starting services
|
||||
containers.podman.podman_image:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
loop:
|
||||
- "{{ atlas_nextcloud_image }}"
|
||||
- "{{ atlas_nextcloud_postgres_image }}"
|
||||
- "{{ atlas_nextcloud_redis_image }}"
|
||||
- "{{ atlas_onlyoffice_image }}"
|
||||
|
||||
- name: Reload the user manager to generate component units
|
||||
ansible.builtin.systemd:
|
||||
scope: user
|
||||
daemon_reload: true
|
||||
|
||||
- name: Start the declared Nextcloud and ONLYOFFICE services
|
||||
ansible.builtin.systemd:
|
||||
scope: user
|
||||
name: "{{ item }}"
|
||||
state: >-
|
||||
{{ 'restarted' if (atlas_nextcloud_quadlets is changed or
|
||||
atlas_nextcloud_private_configuration is changed or
|
||||
atlas_nextcloud_secret_files is changed) else 'started' }}
|
||||
loop: "{{ atlas_nextcloud_services }}"
|
||||
|
||||
- name: Wait for the application configuration directory to be initialized
|
||||
become: true
|
||||
become_user: root
|
||||
ansible.builtin.wait_for:
|
||||
path: "{{ atlas_nextcloud_root }}/app/config/config.php"
|
||||
timeout: 600
|
||||
|
||||
- name: Derive container web-user host IDs from the actual rootless maps
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- unshare
|
||||
- python3
|
||||
- -c
|
||||
- >-
|
||||
import json;
|
||||
print(json.dumps({k: next(int(b)+33-int(a) for a,b,n in
|
||||
(l.split() for l in open('/proc/self/'+k+'_map'))
|
||||
if int(a)<=33<int(a)+int(n)) for k in ['uid','gid']}))
|
||||
register: atlas_nextcloud_web_mapping
|
||||
changed_when: false
|
||||
|
||||
- name: Read the current application SELinux label without changing it
|
||||
become: true
|
||||
become_user: root
|
||||
ansible.builtin.command:
|
||||
argv: [stat, -c, '%C', "{{ atlas_nextcloud_root }}/app/config"]
|
||||
register: atlas_nextcloud_config_label
|
||||
changed_when: false
|
||||
|
||||
- name: Maintain the managed Nextcloud configuration include
|
||||
become: true
|
||||
become_user: root
|
||||
ansible.builtin.template:
|
||||
src: atlas-nextcloud.config.php.j2
|
||||
dest: "{{ atlas_nextcloud_root }}/app/config/atlas.config.php"
|
||||
owner: "{{ (atlas_nextcloud_web_mapping.stdout | from_json).uid }}"
|
||||
group: "{{ (atlas_nextcloud_web_mapping.stdout | from_json).gid }}"
|
||||
mode: "0640"
|
||||
seuser: "{{ atlas_nextcloud_config_label.stdout.split(':')[0] }}"
|
||||
serole: "{{ atlas_nextcloud_config_label.stdout.split(':')[1] }}"
|
||||
setype: "{{ atlas_nextcloud_config_label.stdout.split(':')[2] }}"
|
||||
selevel: "{{ atlas_nextcloud_config_label.stdout.split(':')[3:] | join(':') }}"
|
||||
diff: false
|
||||
|
||||
- name: Wait for Nextcloud to complete its initial installation
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, status, --output=json]
|
||||
register: atlas_nextcloud_status
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 10
|
||||
until: >-
|
||||
atlas_nextcloud_status.rc == 0 and
|
||||
atlas_nextcloud_status.stdout.startswith('{') and
|
||||
(atlas_nextcloud_status.stdout | from_json).installed | default(false)
|
||||
|
||||
- name: Import declared ongoing application and account configuration
|
||||
ansible.builtin.include_tasks: nextcloud_application.yml
|
||||
|
||||
- name: Enable and start the recurring Nextcloud cron timer
|
||||
ansible.builtin.systemd:
|
||||
scope: user
|
||||
name: atlas-nextcloud-cron.timer
|
||||
state: "{{ 'restarted' if atlas_nextcloud_cron_units is changed else 'started' }}"
|
||||
enabled: true
|
||||
|
||||
- name: Verify ONLYOFFICE local health without publishing the domain
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ atlas_onlyoffice_http_port }}/healthcheck"
|
||||
return_content: true
|
||||
register: atlas_onlyoffice_health
|
||||
retries: 60
|
||||
delay: 10
|
||||
until: atlas_onlyoffice_health.status | default(0) == 200 and atlas_onlyoffice_health.content | default('') | trim == 'true'
|
||||
171
ansible/roles/profile_atlas/tasks/nextcloud_application.yml
Normal file
171
ansible/roles/profile_atlas/tasks/nextcloud_application.yml
Normal file
@@ -0,0 +1,171 @@
|
||||
---
|
||||
- name: Inspect installed application state
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, app:list, --output=json]
|
||||
register: atlas_nextcloud_current_apps
|
||||
changed_when: false
|
||||
|
||||
- name: Record enabled and disabled application versions
|
||||
ansible.builtin.set_fact:
|
||||
atlas_nextcloud_installed_apps: >-
|
||||
{{ (atlas_nextcloud_current_apps.stdout | from_json).enabled |
|
||||
combine((atlas_nextcloud_current_apps.stdout | from_json).disabled) }}
|
||||
|
||||
- name: Refuse implicit application upgrades or downgrades
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- item.id not in atlas_nextcloud_installed_apps or atlas_nextcloud_installed_apps[item.id] == item.version
|
||||
fail_msg: Application versions must be changed in a deliberate upgrade window.
|
||||
loop: "{{ atlas_nextcloud_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.id }}"
|
||||
|
||||
- name: Install only absent checksum-verified application archives
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- exec
|
||||
- --user
|
||||
- '33'
|
||||
- atlas-nextcloud
|
||||
- tar
|
||||
- -xzf
|
||||
- "/mnt/atlas-apps/{{ item.id }}-{{ item.version }}.tar.gz"
|
||||
- -C
|
||||
- /var/www/html/custom_apps
|
||||
loop: "{{ atlas_nextcloud_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.id }}"
|
||||
when: item.id not in atlas_nextcloud_installed_apps
|
||||
changed_when: true
|
||||
|
||||
- name: Enable the declared applications
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, app:enable, "{{ item.id }}"]
|
||||
loop: "{{ atlas_nextcloud_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.id }}"
|
||||
when: item.id not in (atlas_nextcloud_current_apps.stdout | from_json).enabled
|
||||
changed_when: true
|
||||
|
||||
- name: Inspect existing application users without exposing passwords
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, user:list, --output=json]
|
||||
register: atlas_nextcloud_current_users
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure the two standard users exist without resetting existing passwords
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- podman
|
||||
- exec
|
||||
- --user
|
||||
- '33'
|
||||
- --env
|
||||
- OC_PASS
|
||||
- atlas-nextcloud
|
||||
- php
|
||||
- occ
|
||||
- user:add
|
||||
- --password-from-env
|
||||
- --display-name
|
||||
- "{{ item.display_name }}"
|
||||
- "{{ item.username }}"
|
||||
environment:
|
||||
OC_PASS: "{{ item.password }}"
|
||||
loop: "{{ atlas_nextcloud_users }}"
|
||||
when: item.username not in (atlas_nextcloud_current_users.stdout | from_json)
|
||||
changed_when: true
|
||||
no_log: true
|
||||
diff: false
|
||||
|
||||
- name: Inspect standard-user group membership and quota
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, user:info, --output=json, "{{ item.username }}"]
|
||||
loop: "{{ atlas_nextcloud_users }}"
|
||||
loop_control:
|
||||
label: "{{ item.username }}"
|
||||
register: atlas_nextcloud_user_info
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: Require that family users are not administrators
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- "'admin' not in (item.stdout | from_json).groups"
|
||||
loop: "{{ atlas_nextcloud_user_info.results }}"
|
||||
no_log: true
|
||||
|
||||
- name: Maintain unlimited initial standard-user quotas
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, user:setting, "{{ item.item.username }}", files, quota, none]
|
||||
loop: "{{ atlas_nextcloud_user_info.results }}"
|
||||
when: (item.stdout | from_json).quota != 'none'
|
||||
changed_when: true
|
||||
no_log: true
|
||||
|
||||
- name: Inspect the family group
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, group:list, --output=json]
|
||||
register: atlas_nextcloud_groups
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure the family group exists
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, group:add, famiglia]
|
||||
when: "'famiglia' not in (atlas_nextcloud_groups.stdout | from_json)"
|
||||
changed_when: true
|
||||
|
||||
- name: Ensure both standard users belong to the family group
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, group:adduser, famiglia, "{{ item.username }}"]
|
||||
loop: "{{ atlas_nextcloud_users }}"
|
||||
when: item.username not in ((atlas_nextcloud_groups.stdout | from_json).get('famiglia', []))
|
||||
changed_when: true
|
||||
no_log: true
|
||||
|
||||
- name: Inspect configured family folders
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, groupfolders:list, --output=json]
|
||||
register: atlas_nextcloud_folders_before
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure a shared Famiglia folder exists
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, groupfolders:create, Famiglia]
|
||||
when: >-
|
||||
(atlas_nextcloud_folders_before.stdout | from_json |
|
||||
selectattr('mountPoint', 'equalto', 'Famiglia') | list | length) == 0
|
||||
changed_when: true
|
||||
|
||||
- name: Inspect the resulting family folder
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, groupfolders:list, --output=json]
|
||||
register: atlas_nextcloud_folders_after
|
||||
changed_when: false
|
||||
|
||||
- name: Select the existing family folder without changing unrelated folders
|
||||
ansible.builtin.set_fact:
|
||||
atlas_nextcloud_family_folder: >-
|
||||
{{ atlas_nextcloud_folders_after.stdout | from_json |
|
||||
selectattr('mountPoint', 'equalto', 'Famiglia') | first }}
|
||||
|
||||
- name: Maintain family read, create, write and delete permissions
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, groupfolders:group,
|
||||
"{{ atlas_nextcloud_family_folder.id }}", famiglia, write, delete]
|
||||
when: (atlas_nextcloud_family_folder.groups_list | default({}, true)).get('famiglia', 0) | int != 15
|
||||
changed_when: true
|
||||
|
||||
- name: Inspect the background job mode
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, config:app:get, core, backgroundjobs_mode]
|
||||
register: atlas_nextcloud_background_mode
|
||||
changed_when: false
|
||||
failed_when: atlas_nextcloud_background_mode.rc not in [0, 1]
|
||||
|
||||
- name: Maintain cron background processing
|
||||
ansible.builtin.command:
|
||||
argv: [podman, exec, --user, '33', atlas-nextcloud, php, occ, background:cron]
|
||||
when: atlas_nextcloud_background_mode.stdout | trim != 'cron'
|
||||
changed_when: true
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Atlas recurring Nextcloud background jobs
|
||||
Requires=atlas-nextcloud.service
|
||||
After=atlas-nextcloud.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/podman exec --user 33 atlas-nextcloud php -f /var/www/html/cron.php
|
||||
TimeoutStartSec=15min
|
||||
NoNewPrivileges=true
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Run Nextcloud background jobs every five minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=5min
|
||||
Unit=atlas-nextcloud-cron.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=Atlas Nextcloud PostgreSQL
|
||||
RequiresMountsFor={{ atlas_nextcloud_root }}/database
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-nextcloud-db
|
||||
Image={{ atlas_nextcloud_postgres_image }}
|
||||
Network=atlas-nextcloud.network
|
||||
NetworkAlias=atlas-nextcloud-db
|
||||
Environment=POSTGRES_DB=nextcloud
|
||||
Environment=POSTGRES_USER=nextcloud
|
||||
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password
|
||||
Volume={{ atlas_nextcloud_private_dir }}/postgres-password:/run/secrets/postgres-password:ro,z
|
||||
Volume={{ atlas_nextcloud_root }}/database:/var/lib/postgresql/data:Z
|
||||
PodmanArgs=--memory=1g
|
||||
HealthCmd=pg_isready -U nextcloud -d nextcloud
|
||||
HealthInterval=30s
|
||||
HealthStartPeriod=60s
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,8 @@
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
requirepass {{ vault_nextcloud_redis_password }}
|
||||
maxmemory 128mb
|
||||
maxmemory-policy noeviction
|
||||
save ""
|
||||
appendonly no
|
||||
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Atlas Nextcloud private Redis
|
||||
RequiresMountsFor={{ atlas_nextcloud_root }}/cache
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-nextcloud-redis
|
||||
Image={{ atlas_nextcloud_redis_image }}
|
||||
Network=atlas-nextcloud.network
|
||||
NetworkAlias=atlas-nextcloud-redis
|
||||
Volume={{ atlas_nextcloud_private_dir }}/redis.conf:/usr/local/etc/redis/atlas.conf:ro,z
|
||||
Volume={{ atlas_nextcloud_root }}/cache:/data:Z
|
||||
Exec=redis-server /usr/local/etc/redis/atlas.conf
|
||||
PodmanArgs=--memory=256m
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
// Managed ongoing application settings; never import or migrate user data.
|
||||
$CONFIG = [
|
||||
'trusted_domains' => ['{{ atlas_nextcloud_domain }}', 'atlas-nextcloud'],
|
||||
'trusted_proxies' => ['{{ atlas_aegis_ip }}', '{{ atlas_nextcloud_network_gateway }}'],
|
||||
'overwrite.cli.url' => 'https://{{ atlas_nextcloud_domain }}',
|
||||
'overwritehost' => '{{ atlas_nextcloud_domain }}',
|
||||
'overwriteprotocol' => 'https',
|
||||
'allow_local_remote_servers' => true,
|
||||
'default_quota' => 'none',
|
||||
'skeletondirectory' => '',
|
||||
'maintenance_window_start' => 1,
|
||||
'default_phone_region' => 'IT',
|
||||
'twofactor_enforced' => false,
|
||||
'onlyoffice' => [
|
||||
'DocumentServerUrl' => 'https://{{ atlas_onlyoffice_domain }}/',
|
||||
'DocumentServerInternalUrl' => 'http://atlas-onlyoffice/',
|
||||
'StorageUrl' => 'http://atlas-nextcloud/',
|
||||
'jwt_secret' => trim(file_get_contents('/run/secrets/onlyoffice-jwt')),
|
||||
'jwt_header' => 'AuthorizationJwt',
|
||||
'allow_local_address' => true,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
[Unit]
|
||||
Description=Atlas Nextcloud
|
||||
Requires=atlas-nextcloud-db.service atlas-nextcloud-redis.service
|
||||
After=atlas-nextcloud-db.service atlas-nextcloud-redis.service
|
||||
RequiresMountsFor={{ atlas_nextcloud_root }}/app {{ atlas_nextcloud_root }}/files
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-nextcloud
|
||||
Image={{ atlas_nextcloud_image }}
|
||||
Network=atlas-nextcloud.network
|
||||
NetworkAlias=atlas-nextcloud
|
||||
PublishPort={{ ansible_host }}:{{ atlas_nextcloud_http_port }}:80
|
||||
PublishPort=127.0.0.1:{{ atlas_nextcloud_http_port }}:80
|
||||
Environment=POSTGRES_HOST=atlas-nextcloud-db
|
||||
Environment=POSTGRES_DB=nextcloud
|
||||
Environment=POSTGRES_USER=nextcloud
|
||||
Environment=POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password
|
||||
Environment=NEXTCLOUD_ADMIN_USER={{ atlas_nextcloud_admin }}
|
||||
Environment=NEXTCLOUD_ADMIN_PASSWORD_FILE=/run/secrets/admin-password
|
||||
Environment="NEXTCLOUD_TRUSTED_DOMAINS={{ atlas_nextcloud_domain }} atlas-nextcloud"
|
||||
Environment=REDIS_HOST=atlas-nextcloud-redis
|
||||
Environment=REDIS_HOST_PASSWORD_FILE=/run/secrets/redis-password
|
||||
Environment=APACHE_DISABLE_REWRITE_IP=1
|
||||
Environment=PHP_MEMORY_LIMIT=512M
|
||||
Environment=PHP_UPLOAD_LIMIT=2G
|
||||
Volume={{ atlas_nextcloud_root }}/app:/var/www/html:Z
|
||||
Volume={{ atlas_nextcloud_root }}/files:/var/www/html/data:Z
|
||||
Volume={{ atlas_nextcloud_app_cache }}:/mnt/atlas-apps:ro,z
|
||||
Volume={{ atlas_nextcloud_private_dir }}/postgres-password:/run/secrets/postgres-password:ro,z
|
||||
Volume={{ atlas_nextcloud_private_dir }}/admin-password:/run/secrets/admin-password:ro,z
|
||||
Volume={{ atlas_nextcloud_private_dir }}/redis-password:/run/secrets/redis-password:ro,z
|
||||
Volume={{ atlas_nextcloud_private_dir }}/onlyoffice-jwt:/run/secrets/onlyoffice-jwt:ro,z
|
||||
PodmanArgs=--memory=2g
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStartSec=900
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,5 @@
|
||||
# Managed by Ansible: private rootless application network, no host services.
|
||||
[Network]
|
||||
NetworkName=atlas-nextcloud
|
||||
Subnet={{ atlas_nextcloud_network_subnet }}
|
||||
Gateway={{ atlas_nextcloud_network_gateway }}
|
||||
@@ -0,0 +1,26 @@
|
||||
[Unit]
|
||||
Description=Atlas ONLYOFFICE Docs Community
|
||||
RequiresMountsFor={{ atlas_nextcloud_root }}/office
|
||||
|
||||
[Container]
|
||||
ContainerName=atlas-onlyoffice
|
||||
Image={{ atlas_onlyoffice_image }}
|
||||
Network=atlas-nextcloud.network
|
||||
NetworkAlias=atlas-onlyoffice
|
||||
PublishPort={{ ansible_host }}:{{ atlas_onlyoffice_http_port }}:80
|
||||
PublishPort=127.0.0.1:{{ atlas_onlyoffice_http_port }}:80
|
||||
EnvironmentFile={{ atlas_nextcloud_private_dir }}/onlyoffice.env
|
||||
Volume={{ atlas_nextcloud_root }}/office/data:/var/www/onlyoffice/Data:Z
|
||||
Volume={{ atlas_nextcloud_root }}/office/lib:/var/lib/onlyoffice:Z
|
||||
Volume={{ atlas_nextcloud_root }}/office/logs:/var/log/onlyoffice:Z
|
||||
Volume={{ atlas_nextcloud_root }}/office/database:/var/lib/postgresql:Z
|
||||
PodmanArgs=--memory=4g --shm-size=256m
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
TimeoutStartSec=1200
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
@@ -0,0 +1,7 @@
|
||||
JWT_ENABLED=true
|
||||
JWT_SECRET={{ vault_nextcloud_onlyoffice_jwt }}
|
||||
JWT_HEADER=AuthorizationJwt
|
||||
ALLOW_PRIVATE_IP_ADDRESS=true
|
||||
ALLOW_META_IP_ADDRESS=false
|
||||
USE_UNAUTHORIZED_STORAGE=false
|
||||
WOPI_ENABLED=false
|
||||
Reference in New Issue
Block a user