initial commit, so far untested

This commit is contained in:
s3lph 2023-02-07 09:04:38 +01:00
commit 7859fe4fbc
Signed by: s3lph
GPG key ID: 0AA29A52FB33CFB5
17 changed files with 1282 additions and 0 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Ansible Collection - s3lph.nextcloud
Documentation for the collection.

69
galaxy.yml Normal file
View file

@ -0,0 +1,69 @@
### REQUIRED
# The namespace of the collection. This can be a company/brand/organization or product namespace under which all
# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with
# underscores or numbers and cannot contain consecutive underscores
namespace: s3lph
# The name of the collection. Has the same character restrictions as 'namespace'
name: nextcloud
# The version of the collection. Must be compatible with semantic versioning
version: 0.0.1
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
readme: README.md
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url)
# @nicks:irc/im.site#channel'
authors:
- s3lph <s3lph@kabelsalat.ch>
### OPTIONAL but strongly recommended
# A short summary description of the collection
description: Install and configure Nextcloud and PHP-FPM
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
license:
- MIT
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
# requirements as 'namespace' and 'name'
tags:
- nextcloud
- php
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
# collection label 'namespace.name'. The value is a version range
# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version
# range specifiers can be set and are separated by ','
dependencies:
community.mysql: '1.2.0'
s3lph.webserver: '>=0.0.1'
# The URL of the originating SCM repository
repository: https://git.kabelsalat.ch/s3lph/ansible-collection-nextcloud
# The URL to any online docs
documentation: https://git.kabelsalat.ch/s3lph/ansible-collection-nextcloud
# The URL to the homepage of the collection/project
homepage: https://git.kabelsalat.ch/s3lph/ansible-collection-nextcloud
# The URL to the collection issue tracker
issues: https://git.kabelsalat.ch/s3lph/ansible-collection-nextcloud/issues
# A list of file glob-like patterns used to filter any files or directories that should not be included in the build
# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry',
# and '.git' are always filtered. Mutually exclusive with 'manifest'
build_ignore: []
# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a
# list of MANIFEST.in style
# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key
# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive
# with 'build_ignore'
# manifest: null

52
meta/runtime.yml Normal file
View file

@ -0,0 +1,52 @@
---
# Collections must specify a minimum required ansible version to upload
# to galaxy
# requires_ansible: '>=2.9.10'
# Content that Ansible needs to load from another location or that has
# been deprecated/removed
# plugin_routing:
# action:
# redirected_plugin_name:
# redirect: ns.col.new_location
# deprecated_plugin_name:
# deprecation:
# removal_version: "4.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# removed_plugin_name:
# tombstone:
# removal_version: "2.0.0"
# warning_text: |
# See the porting guide on how to update your playbook to
# use ns.col.another_plugin instead.
# become:
# cache:
# callback:
# cliconf:
# connection:
# doc_fragments:
# filter:
# httpapi:
# inventory:
# lookup:
# module_utils:
# modules:
# netconf:
# shell:
# strategy:
# terminal:
# test:
# vars:
# Python import statements that Ansible needs to load from another location
# import_redirection:
# ansible_collections.ns.col.plugins.module_utils.old_location:
# redirect: ansible_collections.ns.col.plugins.module_utils.new_location
# Groups of actions/modules that take a common set of options
# action_groups:
# group_name:
# - module1
# - module2

31
plugins/README.md Normal file
View file

@ -0,0 +1,31 @@
# Collections Plugins Directory
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
would contain module utils and modules respectively.
Here is an example directory of the majority of plugins currently supported by Ansible:
```
└── plugins
├── action
├── become
├── cache
├── callback
├── cliconf
├── connection
├── filter
├── httpapi
├── inventory
├── lookup
├── module_utils
├── modules
├── netconf
├── shell
├── strategy
├── terminal
├── test
└── vars
```
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.14/plugins/plugins.html).

209
plugins/modules/app.py Normal file
View file

@ -0,0 +1,209 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: config
short_description: Set Nextcloud configuration options.
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "0.0.1"
description: Set Nextcloud configuration options via occ config.
options:
name:
description: Name of the app or apps to install/remove/enable/disable.
required: true
type: list
elements: str
state:
description: State the app or apps should be in.
required: false
default: enabled
type: str
choices:
- enabled
- disabled
- absent
force:
description: Install and enable apps that are not compatible with the current Nextcloud version.
required: false
default: false
type: bool
webroot:
description: Path to the Nextcloud webroot.
required: false
default: /var/www/html
type: str
php:
description: Path to the php-cli binary.
required: false
default: /usr/bin/php
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
#extends_documentation_fragment:
# - s3lph.nextcloud.app
author:
- s3lph
'''
EXAMPLES = r'''
- name: Install and enable photos app
s3lph.nextcloud.app:
name: photos
- name: Install and enable PIM apps
s3lph.nextcloud.app:
name:
- contacts
- calendar
- mail
- name: Disable resource hungry dashboard
s3lph.nextcloud.app:
name: dashboard
state: disabled
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
import json
import subprocess
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
webroot=dict(required=False, default='/var/www/html', type='str'),
php=dict(required=False, default='/usr/bin/php', type='str'),
name=dict(required=True, type='list'),
state=dict(required=False, default='enabled', type='str'),
force=dict(required=False, default=False, type='bool'),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
state = module.params['state']
if state not in ['enabled', 'disabled', 'absent']:
module.fail_json(msg='state must be one of enabled, disabled or absent', **result)
apps = module.params['name']
if isinstance(apps, str):
apps = [apps]
# Gather Nextcloud installation status
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
if not status['installed']:
module.fail_json(msg='Nextcloud installation has not been completed, so occ app is not available.', **result)
# Gather Nextcloud app list
ac = subprocess.run([module.params['php'], 'occ', 'app:list', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if ac.returncode != 0:
result['stdout'] = ac.stdout
result['stderr'] = ac.stderr
module.fail_json(msg='occ app:list returned non-zero exit code. Run with -vvv to view the output', **result)
app_status = json.loads(ac.stdout)
# Apply app configuration changes
for app in apps:
if state == 'absent' and (app in app_status['enabled'] or app in app_status['disabled']):
result['changed'] = True
if not module.check_mode:
c = subprocess.run([module.params['php'], 'occ', 'app:remove', '--keep-data', app],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if c.returncode != 0:
result['stdout'] = c.stdout
result['stderr'] = c.stderr
module.fail_json(msg='occ app:remove returned non-zero exit code. Run with -vvv to view the output', **result)
elif state in ['enabled', 'disabled'] and app not in app_status['enabled'] and app not in app_status['disabled']:
result['changed'] = True
if not module.check_mode:
cmdline = [module.params['php'], 'occ', 'app:install']
if state == 'disabled':
cmdline.append('--keep-disabled')
if module.params['force']:
cmdline.append('--force')
cmdline.append(app)
c = subprocess.run(cmdline,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if c.returncode != 0:
result['stdout'] = c.stdout
result['stderr'] = c.stderr
module.fail_json(msg='occ app:install returned non-zero exit code. Run with -vvv to view the output', **result)
elif state == 'enabled' and app in app_status['disabled']:
result['changed'] = True
if not module.check_mode:
cmdline = [module.params['php'], 'occ', 'app:enable']
if module.params['force']:
cmdline.append('--force')
cmdline.append(app)
c = subprocess.run(cmdline,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if c.returncode != 0:
result['stdout'] = c.stdout
result['stderr'] = c.stderr
module.fail_json(msg='occ app:enable returned non-zero exit code. Run with -vvv to view the output', **result)
elif state == 'disabled' and app in app_status['enabled']:
result['changed'] = True
if not module.check_mode:
c = subprocess.run([module.params['php'], 'occ', 'app:disable', app],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if c.returncode != 0:
result['stdout'] = c.stdout
result['stderr'] = c.stderr
module.fail_json(msg='occ app:disable returned non-zero exit code. Run with -vvv to view the output', **result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

255
plugins/modules/config.py Normal file
View file

@ -0,0 +1,255 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: config
short_description: Set Nextcloud configuration options.
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "0.0.1"
description: Set Nextcloud configuration options via occ config.
options:
system:
description: System configuration options to set.
required: false
default: {}
type: dict
apps:
description: App configuration options to set.
required: false
default: {}
type: dict
force:
description: >-
Override change protection of options that should never be changed.
Use with EXTREME CARE, as IRRECOVERABLE DATA LOSS may be the result of changing these options.
Currently the following options are covered by this protection: instanceid, secret, passwordsalt.
required: false
default: false
type: bool
webroot:
description: Path to the Nextcloud webroot.
required: false
default: /var/www/html
type: str
php:
description: Path to the php-cli binary.
required: false
default: /usr/bin/php
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
#extends_documentation_fragment:
# - s3lph.nextcloud.status
author:
- s3lph
'''
EXAMPLES = r'''
- name: Set up Redis cache config all redis configuration all at once
s3lph.nextcloud.config:
system:
redis:
host: localhost
port: 6379
dbindex: 0
memcache.local: "\OC\Memcache\Redis"
memcache.remote: "\OC\Memcache\Redis"
memcache.locking: "\OC\Memcache\Redis"
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
import json
import math
import subprocess
# Changing these keys may lead to irrecoverable data loss
REQUIRE_FORCE = ['instanceid', 'secret', 'passwordsalt']
def iter_system(module, tree=None, value=None):
if tree is None:
tree = []
if value is None:
value = module.params['system']
changed = False
# Recursively iterate the options tree
for k, v in value.items():
sublist = tree + [k]
if isinstance(v, dict):
changed = changed or iter_system(module, subtree, v)
continue
if isinstance(v, list):
v = {str(i): v for i, v in enumerate(v)}
changed = changed or iter_system(module, subtree, v)
continue
elif isinstance(v, int):
typ = 'integer'
elif isinstance(v, float):
typ = 'double'
elif isinstance(v, bool):
typ = 'boolean'
else:
typ = 'string'
# Get current value of the system option
rc = subprocess.run([module.params['php'], 'occ', 'config:system:get', '--output=json'] + subtree,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode not in [0, 1]:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
module.fail_json(msg='occ config:system:get returned non-zero exit code. Run with -vvv to view the output', **result)
if rc.returncode == 1:
old_value = None
else:
old_value = json.loads(rc.stdout)
if isinstance(v, float) and isinstance(old_value, float) and math.isclose(v, old_value):
continue
elif v == old_value:
continue
changed = True
if not module.check_mode:
# Remove key if the new value is none
if v is None:
wc = subprocess.run([module.params['php'], 'occ', 'config:system:delete'] + subtree,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if wc.returncode != 0:
result['stdout'] = wc.stdout
result['stderr'] = wc.stderr
msg = 'occ config:system:delete returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
# Set option to new value
else:
cmdline = [module.params['php'], 'occ', 'config:system:set', '--type', typ, '--value', str(v)] + subtree
wc = subprocess.run(cmdline,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if wc.returncode != 0:
result['stdout'] = wc.stdout
result['stderr'] = wc.stderr
msg = 'occ config:system:set returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
return changed
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
webroot=dict(required=False, default='/var/www/html', type='str'),
php=dict(required=False, default='/usr/bin/php', type='str'),
name=dict(required=True, type='list'),
state=dict(required=False, default='enabled', type='str'),
force=dict(required=False, default=False, type='bool'),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
# Gather Nextcloud installation status
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
if not status['installed']:
module.fail_json(msg='Nextcloud installation has not been completed, so occ config is not available.', **result)
# Check for protected options
for k in REQUIRE_FORCE:
if k in module.params['system'] and not module.params['force']:
msg = 'Refusing to change option "' + k + '" as IRRECOVERABLE DATA LOSS may be the result of such a change.'
module.fail_json(msg=msg, **result)
# Apply Nextcloud system configuration recursively
result['changed'] = iter_system(module)
# Apply Nextcloud app configuration
for app, ac in module.params['apps'].items():
for k, v in ac.items():
# Get current value of the app option
rc = subprocess.run([module.params['php'], 'occ', 'config:app:get', app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode not in [0, 1]:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
module.fail_json(msg='occ config:app:get returned non-zero exit code. Run with -vvv to view the output', **result)
if rc.returncode == 1:
old_value = None
else:
old_value = rc.stdout
if old_value == v:
continue
changed = True
if not module.check_mode:
# Delete key if value is None
if v is None:
rc = subprocess.run([module.params['php'], 'occ', 'config:app:delete', app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode != 0:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
msg = 'occ config:app:delete returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
else:
rc = subprocess.run([module.params['php'], 'occ', 'config:app:set', '--value', v, app, k],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if rc.returncode != 0:
result['stdout'] = rc.stdout
result['stderr'] = rc.stderr
msg = 'occ config:app:set returned non-zero exit code. Run with -vvv to view the output'
module.fail_json(msg=msg, **result)
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

223
plugins/modules/install.py Normal file
View file

@ -0,0 +1,223 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: install
short_description: Run occ maintenance:install
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "0.0.1"
description: Bootstrap Nextcloud with an invocation of occ maintenance:install.
options:
database:
description: Supported database type.
required: false
default: sqlite
type: str
database_name:
description: Name of the database.
required: false
type: str
database_host:
description: Hostname of the database.
required: false
default: localhost
type: str
database_port:
description: Port the database is listening on.
required: false
type: int
database_user:
description: User name to connect to the database.
required: false
type: str
database_pass:
description: Password of the database user.
required: false
type: str
database_table_space:
description: Table space of the database (oci only).
required: false
type: str
admin_user:
description: User name of the admin account.
required: false
default: admin
type: str
admin_pass:
description: Password of the admin account.
required: true
type: str
admin_email:
description: E-Mail of the admin account.
required: false
type: str
data_dir:
description: Path to data directory.
required: false
default: <webroot>/data
type: str
webroot:
description: Path to the Nextcloud webroot.
required: false
default: /var/www/html
type: str
php:
description: Path to the php-cli binary.
required: false
default: /usr/bin/php
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
#extends_documentation_fragment:
# - s3lph.nextcloud.install
author:
- s3lph
'''
EXAMPLES = r'''
'''
RETURN = r'''
status:
type: dict
description: Parsed output from occ status.
returned: success
sample:
installed: true
version: "25.0.3.2"
versionstring: "25.0.3
edition: ""
maintenance: false
needsDbUpgrade: false
productname: Nextcloud
extendedSupport: false
'''
from ansible.module_utils.basic import AnsibleModule
import json
import os
import subprocess
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
database=dict(required=False, default='sqlite', type='str'),
database_name=dict(required=False, type='str'),
database_host=dict(required=False default='localhost', type='str'),
database_port=dict(required=False, type='int'),
database_user=dict(required=False, type='str'),
database_pass=dict(required=False, type='str'),
database_table_space=dict(required=False, type='str'),
admin_user=dict(required=False, default='admin', type='str'),
admin_pass=dict(required=True, type='str'),
admin_email=dict(required=False, type='str'),
data_dir=dict(required=False, default='./data', type='str'),
webroot=dict(required=False, default='/var/www/html', type='str'),
php=dict(required=False, default='/usr/bin/php', type='str'),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
result['status'] = status
if status['installed']:
# Nextcloud installation has already been completed
module.exit_json(**result)
# Build installation cmdline
cmdline = [module.params['php'], 'occ', 'maintenance:install', '--no-interaction', '--no-ansi']
datadir = os.path.normpath(os.path.join(module.params['webroot'], module.params['data_dir']))
cmdline.append('--data-dir=' + datadir)
cmdline.append('--database=' + module.params['database'])
if module.params['database_name'] is not None:
cmdline.append('--database-name=' + module.params['database_name'])
if module.params['database_host'] is not None:
cmdline.append('--database-host=' + module.params['database_host'])
if module.params['database_port'] is not None:
cmdline.append('--database-port=' + module.params['database_port'])
if module.params['database_user'] is not None:
cmdline.append('--database-user=' + module.params['database_user'])
if module.params['database_pass'] is not None:
cmdline.append('--database-pass=' + module.params['database_pass'])
if module.params['database_table_space'] is not None:
cmdline.append('--database-table-space=' + module.params['database_table_space'])
cmdline.append('--admin-user=' + module.params['admin_user'])
cmdline.append('--admin-pass=' + module.params['admin_pass'])
if module.params['admin_email'] is not None:
cmdline.append('--admin-email=' + module.params['admin_email'])
if not module.check_mode:
# Perform Nextcloud installation
ic = subprocess.run(cmdline,
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
result['stdout'] = ic.stdout
result['stderr'] = ic.stderr
if ic.returncode != 0:
module.fail_json(msg='occ maintenance:install returned non-zero exit code. Run with -vvv to view the output', **result)
result['changed'] = True
# Get occ status once more
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
result['status'] = status
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,142 @@
#!/usr/bin/python
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = r'''
---
module: nextcloud_facts
short_description: Gather ansible_facts from Nextcloud.
# If this is part of a collection, you need to use semantic versioning,
# i.e. the version is of the form "2.5.0" and not "2.4".
version_added: "0.0.1"
description: Gather ansible_facts from Nextcloud via occ status and occ config:list.
options:
webroot:
description: Path to the Nextcloud webroot.
required: false
default: /var/www/html
type: str
php:
description: Path to the php-cli binary.
required: false
default: /usr/bin/php
type: str
# Specify this value according to your collection
# in format of namespace.collection.doc_fragment_name
#extends_documentation_fragment:
# - s3lph.nextcloud.nextcloud_facts
author:
- s3lph
'''
EXAMPLES = r'''
'''
RETURN = r'''
ansible_facts.nextcloud_status:
type: dict
description: Parsed output from occ status.
returned: success
sample:
installed: true
version: "25.0.3.2"
versionstring: "25.0.3
edition: ""
maintenance: false
needsDbUpgrade: false
productname: Nextcloud
extendedSupport: false
ansible_facts.nextcloud_config:
type: dict
description: Parsed output from occ status:list. Only present if installation was completed.
returned: success
sample:
system:
dbtype: sqlite3
version: "25.0.3.2"
apps:
activity:
installed_version: "2.17.0"
types: "filesystem"
enabled: "yes"
'''
from ansible.module_utils.basic import AnsibleModule
import json
import subprocess
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
webroot=dict(required=False, default='/var/www/html', type='str'),
php=dict(required=False, default='/usr/bin/php', type='str'),
)
# seed the result dict in the object
# we primarily care about changed and state
# changed is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
ansible_facts={}
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
# Gather Nextcloud installation status
sc = subprocess.run([module.params['php'], 'occ', 'status', '--output=json'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if sc.returncode != 0:
result['stdout'] = sc.stdout
result['stderr'] = sc.stderr
module.fail_json(msg='occ status returned non-zero exit code. Run with -vvv to view the output', **result)
status = json.loads(sc.stdout)
result['ansible_facts']['nextcloud_status'] = status
if not status['installed']:
module.exit_json(**result)
# Gather Nextcloud configuration
cc = subprocess.run([module.params['php'], 'occ', 'config:list', '--output=json', '--private'],
cwd=module.params['webroot'],
capture_output=True, encoding='utf-8')
if cc.returncode != 0:
result['stdout'] = cc.stdout
result['stderr'] = cc.stderr
module.fail_json(msg='occ config:list returned non-zero exit code. Run with -vvv to view the output', **result)
config = json.loads(cc.stdout)
result['ansible_facts']['nextcloud_config'] = config
module.exit_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,29 @@
---
nextcloud_major_version: "25"
nextcloud_trusted_domains:
- "cloud.example.org"
nextcloud_cli_baseurl: "https://{{ nextcloud_trusted_domains[0] }}"
nextcloud_redis_host: localhost
nextcloud_redis_port: 6379
nextcloud_redis_dbindex: 0
nextcloud_db_engine: mysql
nextcloud_db_host: localhost
nextcloud_db_port: 3306
nextcloud_db_user: nextcloud
nextcloud_db_name: nextcloud
nextcloud_enabled_apps: []
nextcloud_disabled_apps: []
nextcloud_app_packages: >-
{%- set _apps = [] -%}
{%- for app in nextcloud_enabled_apps -%}
{%- set _ = _apps.apend('nextcloud-' + nextcloud_major_version + '-app-' + app.replace('_', '-')) -%}
{%- endfor -%}
{{- _apps -}}
nextcloud_system_configuration: {}
nextcloud_apps_configuration: {}

View file

@ -0,0 +1,24 @@
---
- name: Disable Nextcloud apps
become: true
become_user: www-data
s3lph.nextcloud.app:
webroot: /var/lib/nextcloud/webroot
name: "{{ nextcloud_disabled_apps }}"
state: disabled
- name: Enable Nextcloud apps
become: true
become_user: www-data
s3lph.nextcloud.app:
webroot: /var/lib/nextcloud/webroot
name: "{{ nextcloud_enabled_apps }}"
- name: Apply Nextcloud configuration
become: true
become_user: www-data
s3lph.nextcloud.config:
webroot: /var/lib/nextcloud/webroot
system: "{{ nextcloud_system_configuration }}"
apps: "{{ nextcloud_apps_configuration }}"

View file

@ -0,0 +1,112 @@
---
- name: Add repo.s3lph.me key
ansible.builtin.apt_key:
url: https://repo.s3lph.me/debian/repo.gpg
keyring: /etc/apt/trusted.gpg.d/repo.s3lph.me.gpg
- name: Add repo.s3lph.me
ansible.builtin.apt_repository:
filename: repo.s3lph.me.list
repo: deb https://repo.s3lph.me/debian stable main
- name: Install apache2
ansible.builtin.apt:
name: apache2
- name: Install redis-server if using localhost
ansible.builtin.apt:
name: redis-server
when: "nextcloud_redis_host == 'localhost'"
- name: Install Nextcloud package
ansible.builtin.apt:
name: "nextcloud-{{ nextcloud_major_version }}"
- name: Remove die line from config.php
ansible.builtin.lineinfile:
path: /var/lib/nextcloud/webroot/config/config.php
regexp: "^die\s+'[^']*';"
state: absent
owner: www-data
group: www-data
mode: 0644
- name: Gather Nextcloud facts
become: true
become_user: www-data
s3lph.nextcloud.nextcloud_facts:
webroot: /var/lib/nextcloud/webroot
- name: Initialize MariaDB database if Nextcloud is not installed yet
when:
- not ansible_facts.nextcloud_status.installed
- nextcloud_db_engine == 'mysql'
- nextcloud_db_host == 'localhost'
block:
- name: Install MariaDB server
ansible.builtin.apt:
name: mariadb-server
- name: Create MariaDB Database
community.mysql.mysql_db:
name: '{{ nextcloud_db_name }}'
login_unix_socket: /run/mysqld/mysqld.sock
check_implicit_admin: yes
- name: Create nextcloud database user
community.mysql.mysql_user:
name: "{{ nextcloud_db_user }}"
host: "localhost"
password: "{{ nextcloud_db_pass }}"
priv: "{{ nextcloud_db_name }}.*:ALL" # grant all privileges (no grant)
login_unix_socket: /run/mysqld/mysqld.sock
check_implicit_admin: yes
- name: Perform Nextcloud first-time setup
become: true
become_user: www-data
s3lph.nextcloud.install:
webroot: /var/lib/nextcloud/webroot
data_dir: /var/lib/nextcloud/data
database: "{{ nextcloud_db_engine }}"
database_host: "{{ nextcloud_db_host | default(omit) }}"
database_port: "{{ nextcloud_db_port | default(omit) }}"
database_user: "{{ nextcloud_db_user | default(omit) }}"
database_pass: "{{ nextcloud_db_pass | default(omit) }}"
database_name: "{{ nextcloud_db_name | default(omit) }}"
database_table_space: "{{ nextcloud_db_table_space | default(omit) }}"
admin_user: "{{ nextcloud_admin_user | default(omit) }}"
admin_pass: "{{ nextcloud_admin_pass }}"
admin_email: "{{ nextcloud_admin_email | default(omit) }}"
register: nextcloud_register_installation
- name: Gather Nextcloud facts after completing the installation
become: true
become_user: www-data
s3lph.nextcloud.nextcloud_facts:
webroot: /var/lib/nextcloud/webroot
when: nextcloud_register_installation.changed
- name: Set common Nextcloud options
become: true
become_user: www-data
s3lph.nextcloud.config:
webroot: /var/lib/nextcloud/webroot
system:
trusted_domains: "{{ nextcloud_trusted_domains }}"
cli_baseurl: "{{ nextcloud_cli_baseurl }}"
redis:
host: "{{ nextcloud_redis_host }}"
port: "{{ nextcloud_redis_port }}"
dbindex: "{{ nextcloud_redis_dbindex }}"
memcache.local: '\OC\Memcache\Redis'
memcache.distributed: '\OC\Memcache\Redis'
memcache.locking: '\OC\Memcache\Redis'
- name: Install Nextcloud app packages
become: true
become_user: www-data
ansible.builtin.apt:
name: "{{ nextcloud_app_packages }}"

View file

@ -0,0 +1,11 @@
---
- ansible.builtin.import_tasks: install.yml
tags:
- "role::nextcloud"
- "role::nextcloud:install"
- ansible.builtin.import_tasks: config.yml
tags:
- "role::nextcloud"
- "role::nextcloud:config"

View file

@ -0,0 +1,15 @@
---
php_version: "8.1"
php_ini:
PHP:
memory_limit: "512M"
upload_max_filesize: "1G"
opcache:
opcache.enable: "1"
opcache.memory_consumption: "256"
opcache.interned_strings_buffer: "32"
opcache.max_accelerated_files: "10000"
opcache.revalidate_freq: "60"
opcache.save_comments: "1"

View file

@ -0,0 +1,55 @@
---
- name: Configure php-fpm
ansible.builtin.template:
src: "etc/php/conf.d/99-nextcloud.ini.j2"
dest: "/etc/php/{{ php_version }}/{{ item }}/conf.d/99-nextcloud.ini"
owner: root
group: root
mode: 0644
loop:
- fpm
- cli
notify:
- restart php-fpm
- name: Find enabled php-fpm apache2 config
ansible.builtin.find:
paths: ["/etc/apache2/conf-enabled/"]
patterns: ["php*-fpm.conf"]
file_type: file
register: php_register_conf_enabled
- name: Enable wanted and disable unwanted php-fpm apache2 config
ansible.builtin.file:
path: "{{ item.path }}"
state: >-
{%- if item.path.endswith(wanted) -%}
link
{%- else -%}
absent
{%- endif -%}
src: "/etc/apache2/conf-available{{ wanted }}"
owner: root
group: root
mode: 0644
vars:
wanted: "/php{{ php_version }}-fpm.conf"
loop: "{{ php_register_conf_enabled.files }}"
notify:
- restart apache2
- name: Enable apache2 modules
community.general.apache2_module:
name: "{{ item }}"
loop:
- proxy_fcgi
- setenvif
notify:
- restart apache2
- name: Start and enable php-fpm
ansible.builtin.service:
name: "php{{ php_version }}-fpm.service"
state: started
enabled: yes

View file

@ -0,0 +1,32 @@
---
- name: Add packages.sury.org key
ansible.builtin.apt_key:
url: https://packages.sury.org/php/apt.gpg
keyring: /etc/apt/trusted.gpg.d/packages.sury.org-php.gpg
- name: Add packages.sury.org
ansible.builtin.apt_repository:
filename: packages.sury.org-php.list
repo: "deb https://packages.sury.org/php/ {{ ansible_facts.distribution_release }} main"
- name: Install dependencies
ansible.builtin.apt:
name:
- "{{ php }}"
- "{{ php }}-fpm"
- "{{ php }}-cli"
- "{{ php }}-bcmath"
- "{{ php }}-bz2"
- "{{ php }}-curl"
- "{{ php }}-gd"
- "{{ php }}-gmp"
- "{{ php }}-imagick"
- "{{ php }}-intl"
- "{{ php }}-mbstring"
- "{{ php }}-mysql"
- "{{ php }}-redis"
- "{{ php }}-xml"
- "{{ php }}-zip"
vars:
php: "php{{ php_version }}"

11
roles/php/tasks/main.yml Normal file
View file

@ -0,0 +1,11 @@
---
- ansible.builtin.import_tasks: install.yml
tags:
- "role::php"
- "role::php:install"
- ansible.builtin.import_tasks: config.yml
tags:
- "role::php"
- "role::php:config"

View file

@ -0,0 +1,9 @@
{{ ansible_managed | comment(decoration=';;') }}
{% for name, section in php_ini.items() %}
[name]
{% endfor %}{% for key, value in section.items() %}
{{ key }}={{ value }}
{% endfor %}
{% endfor %}