From d599ddecc2e89ef8480b4d556004d5cc81728f96 Mon Sep 17 00:00:00 2001
From: Chris Croome <chris@webarchitects.co.uk>
Date: Mon, 27 Jan 2025 16:33:19 +0000
Subject: [PATCH] WIP

---
 README.md               | 40 ++++++++++++++++++++++++
 defaults/main.yml       | 27 ++++++++++++++++
 handlers/main.yml       | 37 ++++++++++++++++++++++
 meta/argument_specs.yml | 42 +++++++++++++++++++++++++
 tasks/check.yml         | 30 ++++++++++++++++++
 tasks/conf.yml          | 40 ++++++++++++++++++++++++
 tasks/config.yml        | 68 +++++++++++++++++++++++++++++++++++++++++
 tasks/main.yml          | 11 +++++++
 tasks/pkg.yml           | 15 +++++++++
 vars/main.yml           | 17 +++++++++--
 10 files changed, 325 insertions(+), 2 deletions(-)
 create mode 100644 handlers/main.yml
 create mode 100644 tasks/check.yml
 create mode 100644 tasks/conf.yml
 create mode 100644 tasks/config.yml

diff --git a/README.md b/README.md
index fe10c1a..cd93efd 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,46 @@
 
 An Ansible role to install and configure [Valkey](https://valkey.io/) on Debian and Ubuntu.
 
+## Multiple Valkey instances
+
+The advice is to have [one instance per application](https://redis.io/blog/benchmark-shared-vs-dedicated-redis-instances/) rather than having one instance and adding seperate users and databases to it.
+
+If UNIX sockets are used, rather than TCP/IP ports, then socket groups can be used for access control.
+
+The Debian advice for multiple instances in `/usr/lib/systemd/system/valkey-server@.service` is:
+
+```
+# Templated service file for valkey-server(1)
+#
+# Each instance of valkey-server requires its own configuration file:
+#
+#   $ cp /etc/valkey/valkey.conf /etc/valkey/valkey-myname.conf
+#   $ chown valkey:valkey /etc/valkey/valkey-myname.conf
+#
+# Ensure each instance is using their own database:
+#
+#   $ sed -i -e 's@^dbfilename .*@dbfilename dump-myname.rdb@' /etc/valkey/valkey-myname.conf
+#
+# We then listen exlusively on UNIX sockets to avoid TCP port collisions:
+#
+#   $ sed -i -e 's@^port .*@port 0@' /etc/valkey/valkey-myname.conf
+#   $ sed -i -e 's@^\(# \)\{0,1\}unixsocket .*@unixsocket /run/valkey-myname/valkey-server.sock@' /etc/valkey/valkey-myname.conf
+#
+# ... and ensure we are logging, etc. in a unique location:
+#
+#   $ sed -i -e 's@^logfile .*@logfile /var/log/valkey/valkey-server-myname.log@' /etc/valkey/valkey-myname.conf
+#   $ sed -i -e 's@^pidfile .*@pidfile /run/valkey-myname/valkey-server.pid@' /etc/valkey/valkey-myname.conf
+#
+# We can then start the service as follows, validating we are using our own
+# configuration:
+#
+#   $ systemctl start valkey-server@myname.service
+#   $ valkey-cli -s /run/valkey-myname/valkey-server.sock info | grep config_file
+#
+#  -- Lucas Kanashiro <kanashiro@debian.org>  Wed, 26 Jun 2024 18:17:24 -0300
+
+```
+
 ## Copyright
 
 Copyright 2025 Chris Croome, &lt;[chris@webarchitects.co.uk](mailto:chris@webarchitects.co.uk)&gt;.
diff --git a/defaults/main.yml b/defaults/main.yml
index 92723cb..b1411da 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -9,6 +9,33 @@
 # You should have received a copy of the GNU General Public License along with the Webarchitects Valkey Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
 valkey: false
+valkey_instances:
+  - name: server
+    state: enabled
+    config:
+      - name: unixsocket
+        value: /run/valkey/valkey-server.sock
+      - name: unixsocketperm
+        value: "770"
+    config_file: /etc/valkey/valkey.conf
+  # - name: nextcloud
+  #   state: enabled
+  #   config:
+  #     - name: dbfilename
+  #       value: dump-nextcloud.rdb
+  #     - name: logfile
+  #       value: /var/log/valkey/valkey-netxcloud.log
+  #     - name: pidfile
+  #       value: /run/valkey/valkey-nextcloud.pid
+  #     - name: port
+  #       value: 0
+  #     - name: unixsocket
+  #       value: /run/valkey/valkey-nextcloud.sock
+  #     - name: unixsocketgroup
+  #       value: nextcloud
+  #     - name: unixsocketperm
+  #       value: "770"
+  #   config_file: /etc/valkey/valkey-nextcloud.conf
 valkey_enabled: true
 valkey_pkgs:
   - name: bookworm
diff --git a/handlers/main.yml b/handlers/main.yml
new file mode 100644
index 0000000..f7e92d5
--- /dev/null
+++ b/handlers/main.yml
@@ -0,0 +1,37 @@
+# This file is part of the Webarchitects Valkey Ansible role.
+#
+# The Webarchitects Valkey Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects Valkey Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects Valkey Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: Valkey handlers
+  block:
+
+    - name: Reload systemd
+      ansible.builtin.systemd:
+        daemon_reload: true
+      listen: Reload systemd
+
+    - name: Valkey restarted
+      ansible.builtin.systemd:
+        name: valkey-server
+        state: restarted
+      listen: Restart valkey
+
+    - name: Ensure Valkey is running
+      ansible.builtin.service_facts:
+      register: valkey_service_facts
+      until: (valkey_service_facts | community.general.json_query(valkey_jpq.service)).state == "running"
+      retries: 10
+      delay: 2
+      listen: Restart valkey
+
+  tags:
+    - valkey
+    - valkey_conf
+    - valkey_config
+    - valkey_install
+    - valkey_pkg
+...
diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml
index bb2eda5..7e263af 100644
--- a/meta/argument_specs.yml
+++ b/meta/argument_specs.yml
@@ -22,10 +22,48 @@ argument_specs:
         type: str
         required: true
         description: String that will be present in the apt cache policy when backports are enabled.
+      valkey_config:
+        type: list
+        required: false
+        description: A list of Valkey config names and values.
+        options:
+          name:
+            type: str
+            required: true
+            description: A Valkey config variable name.
+          value:
+            type: str
+            required: true
+            description: A Valkey config variable value.
+      valkey_config_backup:
+        type: str
+        required: true
+        description: Path for the Valkey config backup.
       valkey_enabled:
         type: bool
         required: true
         description: Enable and start Valkey.
+      valkey_jpq:
+        type: dict
+        required: true
+        description: A dictionary of JMESPath query strings.
+        options:
+          pkgs_absent:
+            type: str
+            required: true
+            description: JMESPath query string for the packages absent.
+          pkgs_present:
+            type: str
+            required: true
+            description: JMESPath query string for the packages present.
+          pkgs_present_backports:
+            type: str
+            required: true
+            description: JMESPath query string for the backports packages present.
+          service:
+            type: str
+            required: true
+            description: JMESPath query string for the Valkey service.
       valkey_pkgs:
         type: list
         elements: dict
@@ -52,6 +90,10 @@ argument_specs:
             type: list
             required: true
             description: A list of deb packages that should be present from backports.
+      valkey_protected_configs:
+        type: list
+        required: false
+        description: A list of Valkey config to be editing using lineinfile.
       valkey_verify:
         type: bool
         required: true
diff --git a/tasks/check.yml b/tasks/check.yml
new file mode 100644
index 0000000..ced7ce7
--- /dev/null
+++ b/tasks/check.yml
@@ -0,0 +1,30 @@
+# Copyright 2025 Chris Croome
+#
+# This file is part of the Webarchitects Valkey Ansible role.
+#
+# The Webarchitects Valkey Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects Valkey Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects Valkey Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: Valkey Checks
+  block:
+
+    - name: Get server information
+      community.general.redis_info:
+      register: valkey_info
+
+    - name: Debug valkey_info
+      ansible.builtin.debug:
+        var: valkey_info
+        verbosity: "{% if ansible_check_mode | bool or ansible_diff_mode | bool %}0{% else %}1{% endif %}"
+
+    - name: Debug valkey_info.info.valkey_version
+      ansible.builtin.debug:
+        var: valkey_info.info.valkey_version
+
+  tags:
+    - valkey
+    - valkey_check
+...
diff --git a/tasks/conf.yml b/tasks/conf.yml
new file mode 100644
index 0000000..0981803
--- /dev/null
+++ b/tasks/conf.yml
@@ -0,0 +1,40 @@
+# The Webarchitects Valkey Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects Valkey Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects Valkey Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: Valkey Conf
+  block:
+
+    - name: Configure Valkey protected config
+      ansible.builtin.lineinfile:
+        path: /etc/valkey/valkey.conf
+        line: "{{ valkey_cfg.name }} {{ valkey_cfg.value }}"
+        regex: "{{ valkey_cfg_name_regex }}"
+        mode: "0640"
+        owner: valkey
+        group: valkey
+      vars:
+        valkey_cfg_name_regex: "^[#]?[ ]?{{ valkey_cfg.name | ansible.builtin.regex_escape }}[ ]"
+      when: valkey_cfg.name in valkey_protected_configs
+      register: valkey_protected_cfg
+      notify: Restart valkey
+
+    - name: Flush handlers for Valkey protected config change
+      ansible.builtin.meta: flush_handlers
+      when: valkey_protected_cfg.changed | bool
+
+    - name: Configure Valkey non-protected config
+      community.general.redis:
+        command: config
+        name: "{{ valkey_cfg.name }}"
+        value: "{{ valkey_cfg.value }}"
+      when: valkey_cfg.name not in valkey_protected_configs
+      register: valkey_non_protected_config
+      notify: Restart valkey
+
+  tags:
+    - valkey
+    - valkey_config
+...
diff --git a/tasks/config.yml b/tasks/config.yml
new file mode 100644
index 0000000..2f08431
--- /dev/null
+++ b/tasks/config.yml
@@ -0,0 +1,68 @@
+# Copyright 2025 Chris Croome
+#
+# This file is part of the Webarchitects Valkey Ansible role.
+#
+# The Webarchitects Valkey Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects Valkey Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects Valkey Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: Valkey Config
+  block:
+
+    - name: Set a fact for the Valkey config backup path
+      ansible.builtin.set_fact:
+        valkey_config_backup: "/etc/valkey/.valkey.conf.{{ ansible_facts.date_time.iso8601_basic_short }}.bak"
+
+    - name: Backup Valkey config
+      ansible.builtin.copy:
+        src: /etc/valkey/valkey.conf
+        dest: "{{ valkey_config_backup }}"
+        remote_src: true
+        mode: "0400"
+        owner: valkey
+        group: valkey
+      changed_when: false
+
+    - name: Configure Valkey
+      ansible.builtin.include_tasks: conf.yml
+      loop: "{{ valkey_config }}"
+      loop_control:
+        loop_var: valkey_cfg
+        label: "{{ valkey_cfg.name }}"
+      tags:
+        - valkey_conf
+        - valkey_config
+
+    - name: Stat Valkey config
+      ansible.builtin.stat:
+        path: /etc/valkey/valkey.conf
+        checksum_algorithm: sha256
+        get_attributes: false
+        get_checksum: true
+        get_mime: false
+      register: valkey_cgf_path
+
+    - name: Stat Valkey config backup
+      ansible.builtin.stat:
+        path: "{{ valkey_config_backup }}"
+        checksum_algorithm: sha256
+        get_attributes: false
+        get_checksum: true
+        get_mime: false
+      register: valkey_cgf_bak_path
+
+    - name: Unchanged Valkey config backup absent
+      ansible.builtin.file:
+        path: "{{ valkey_config_backup }}"
+        state: absent
+      changed_when: false
+      when:
+        - valkey_cgf_bak_path.stat.exists | bool
+        - valkey_cgf_path.stat.checksum == valkey_cgf_bak_path.stat.checksum
+
+  tags:
+    - valkey
+    - valkey_config
+...
diff --git a/tasks/main.yml b/tasks/main.yml
index ad548cf..f0bc893 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -32,6 +32,17 @@
         - valkey_install
         - valkey_pkg
 
+    - name: Include Valkey checks
+      ansible.builtin.include_tasks: check.yml
+      tags:
+        - valkey_check
+
+    - name: Include Valkey config
+      ansible.builtin.include_tasks: config.yml
+      tags:
+        - valkey_conf
+        - valkey_config
+
   when: valkey | bool
   tags:
     - valkey
diff --git a/tasks/pkg.yml b/tasks/pkg.yml
index a4603cc..791919a 100644
--- a/tasks/pkg.yml
+++ b/tasks/pkg.yml
@@ -11,6 +11,16 @@
 - name: Valkey packages present and absent
   block:
 
+    - name: Set vm.overcommit_memory to 1 in /etc/sysctl.conf
+      ansible.posix.sysctl:
+        name: vm.overcommit_memory
+        value: '1'
+        state: present
+        sysctl_file: /etc/sysctl.conf
+        sysctl_set: true
+        reload: true
+      notify: Restart valkey
+
     - name: Update apt cache
       ansible.builtin.apt:
         update_cache: true
@@ -21,7 +31,9 @@
       ansible.builtin.apt:
         pkg:
           - gnupg
+          - procps
           - python3-debian
+          - python3-redis
         state: present
 
     - name: Update package facts
@@ -148,6 +160,7 @@
         ( ( valkey_apt_installed is defined ) and ( valkey_apt_installed.changed | bool ) ) or
         ( ( valkey_apt_removed is defined ) and ( valkey_apt_removed.changed | bool ) )
 
+    # TODO move to handlers
     - name: Valkey service enabled and started
       block:
 
@@ -166,6 +179,7 @@
         - ( "valkey-server" in valkey_pkgs_installed )
         - valkey_enabled | bool
 
+    # TODO move to handlers
     - name: Valkey service disabled and stopped
       block:
 
@@ -186,5 +200,6 @@
 
   tags:
     - valkey
+    - valkey_install
     - valkey_pkg
 ...
diff --git a/vars/main.yml b/vars/main.yml
index 16a99d8..fb2e1f1 100644
--- a/vars/main.yml
+++ b/vars/main.yml
@@ -12,15 +12,28 @@
 valkeyvarnames: "{{ q('varnames', '^valkey_') | sort }}"
 valkeyhostvars: "{{ dict(valkeyvarnames | list | zip(q('vars', *valkeyvarnames))) }}"
 
-# backports string
+# Distro backports string
 valkey_apt_backports: "{{ ansible_facts.distribution_release }}-backports"
 
+# Valkey config backup path
+valkey_config_backup: "/etc/valkey/.valkey.conf.{{ ansible_facts.date_time.iso8601_basic_short }}.bak"
+
 # JMESPath queries
 valkey_jpq:
   pkgs_absent: "[?name=='{{ ansible_facts.distribution_release }}'].pkgs_absent|[0]"
   pkgs_present: "[?name=='{{ ansible_facts.distribution_release }}'].pkgs_present|[0]"
   pkgs_present_backports: "[?name=='{{ ansible_facts.distribution_release }}'].pkgs_present_backports|[0]"
+  service: 'ansible_facts.services.["valkey-server.service"]|[0]'
 
-# Check veriables using the arg spec
+# Check variables using the arg spec
 valkey_verify: true
+
+# Valkey config that can't be changed using community.general.redis
+valkey_protected_configs:
+  - enable-debug-command
+  - enable-module-command
+  - enable-protected-configs
+  - unixsocket
+  - unixsocketperm
+...
 ...
-- 
GitLab