aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--defaults/main.yml19
-rw-r--r--molecule/default/converge.yml62
-rw-r--r--molecule/default/molecule.yml8
-rw-r--r--molecule/default/tests/test_client_setup.py103
-rw-r--r--molecule/default/tests/test_server_setup.py2
-rw-r--r--molecule/default/tests/test_systemd.py57
-rw-r--r--tasks/client_create_scripts_each.yml4
-rw-r--r--tasks/client_setup.yml57
-rw-r--r--templates/borg_backup.service.j24
10 files changed, 270 insertions, 64 deletions
diff --git a/README.md b/README.md
index 5281a01..4235294 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,23 @@ ansible-galaxy role install git+https://github.com/kliwniloc/ansible-role-borgba
## Role Variables
-For more details, see: [`defaults/main.yml`](defaults/main.yml)
+Some configuration options were left out of this high level overview. For the
+full set of configuration options, see: [`defaults/main.yml`](defaults/main.yml)
+
+### Backup User Configuration
+
+By default, the role runs all client-side operations as `root`. To use a
+non-root user, set `borg_client_user`:
+
+```yaml
+borg_client_user: backup
+```
+
+The user must exist before running the role; it will not be created
+automatically. Ensure the user can read all directories specified in
+`borg_included_dirs`, or backups will fail at runtime.
+
+### Server Configuration
You need to specify your Borg hostname (as defined in your Ansible inventory)
as well as the public SSH key from that Borg SSH server.
diff --git a/defaults/main.yml b/defaults/main.yml
index 337ca76..b3d901c 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -1,5 +1,24 @@
---
################################################################################
+# Borg Client User Configuration
+################################################################################
+
+# User that runs all client-side operations (SSH key, borg init, backups).
+# Defaults to root. Set to a non-root user for unprivileged backups.
+# The user must exist before running the role.
+borg_client_user: root
+
+# Home directory of the borg_client_user.
+# If not set, the role auto-detects it from the passwd database.
+# Override this only if you need a non-standard home directory location.
+# ---
+# borg_client_user_home: /home/backup
+
+# SSH key type for borg client authentication.
+# Options: ed25519 (recommended), rsa, ecdsa
+borg_ssh_key_type: rsa
+
+################################################################################
# Borg Server Host Configuration
################################################################################
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index c77202c..d90f33a 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -4,6 +4,7 @@
- borg-client
- borg-client-2
- borg-client-multi
+ - borg-client-nonroot
vars:
borg_server_host: borg-server
@@ -12,29 +13,35 @@
# This would usually be set by the user globally on their ansible
# repository and can be a security risk to do automatically. We will
# however set the variable here in the pre_tasks since it is for testing.
- - name: Set borg server openssh key variable
+ - name: Start ssh
+ ansible.builtin.systemd:
+ name: sshd
+ state: started
become: true
- block:
- - name: Start ssh
- ansible.builtin.systemd:
- name: sshd
- state: started
- become: true
- delegate_to: "{{ borg_server_host }}"
+ delegate_to: "{{ borg_server_host }}"
- - name: Fetch ssh_key
- ansible.builtin.command: >-
- ssh-keyscan -t rsa {{ borg_server_host }} | sed "s/^[^ ]* //"
- register: borg_server_ssh_keyscan
- changed_when: false
+ - name: Fetch ssh_key
+ ansible.builtin.command: >-
+ ssh-keyscan -t rsa {{ borg_server_host }} | sed "s/^[^ ]* //"
+ register: borg_server_ssh_keyscan
+ changed_when: false
- - name: Set ssh_key
- ansible.builtin.set_fact:
- borg_server_host_ssh_key: >-
- {{ borg_server_ssh_keyscan.stdout
- | split(" ")
- | reject("search", borg_server_host)
- | join(" ") }}
+ - name: Set ssh_key
+ ansible.builtin.set_fact:
+ borg_server_host_ssh_key: >-
+ {{ borg_server_ssh_keyscan.stdout
+ | split(" ")
+ | reject("search", borg_server_host)
+ | join(" ") }}
+
+ - name: Create backup user for non-root test
+ ansible.builtin.user:
+ name: backupuser
+ home: /home/backupuser
+ shell: /bin/bash
+ state: present
+ become: true
+ when: inventory_hostname == 'borg-client-nonroot'
- name: Converge - Default borg-client
hosts: borg-client
@@ -62,6 +69,7 @@
borg_server_host: borg-server
borg_server_user_home: /opt/borg
borg_decryption_keys_yaml_path: "{{ playbook_dir }}/decryption_keys.yml"
+ borg_ssh_key_type: ed25519
borg_backup_service_successful_exit_status:
- 1
- TEMPFAIL
@@ -105,3 +113,17 @@
borg_excluded_dirs:
- /home/*/.cache
borg_systemd_oncalendar: "*-*-* 04:00:00"
+
+- name: Converge - Non-root backup user
+ hosts: borg-client-nonroot
+
+ roles:
+ - role: kliwniloc.borgbackup
+ vars:
+ borg_server_host: borg-server
+ borg_server_user_home: /opt/borg
+ borg_client_user: backupuser
+ borg_decryption_keys_yaml_path: "{{ playbook_dir }}/decryption_keys.yml"
+ borg_included_dirs:
+ - /etc
+ borg_excluded_dirs: []
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 0d25587..b4f68cb 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -35,6 +35,14 @@ platforms:
networks:
- name: molecule-container-net
+ - name: borg-client-nonroot
+ image: ${MOLECULE_DISTRO_CLIENT:-debian:12}
+ dockerfile: Dockerfile.j2
+ pre_build_image: false
+ privileged: true
+ networks:
+ - name: molecule-container-net
+
- name: borg-server
image: ${MOLECULE_DISTRO_SERVER:-debian:12}
dockerfile: Dockerfile.j2
diff --git a/molecule/default/tests/test_client_setup.py b/molecule/default/tests/test_client_setup.py
index 25bf8e2..290e3dc 100644
--- a/molecule/default/tests/test_client_setup.py
+++ b/molecule/default/tests/test_client_setup.py
@@ -1,33 +1,72 @@
"""Tests for client setup configuration"""
import pytest
-testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi']
+testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot']
+
+
+CLIENT_USER_MAP = {
+ 'borg-client': 'root',
+ 'borg-client-2': 'root',
+ 'borg-client-multi': 'root',
+ 'borg-client-nonroot': 'backupuser',
+}
+
+CLIENT_SSH_KEY_TYPE_MAP = {
+ 'borg-client': 'rsa',
+ 'borg-client-2': 'ed25519',
+ 'borg-client-multi': 'rsa',
+ 'borg-client-nonroot': 'rsa',
+}
+
+
+def get_client_user(host):
+ hostname = host.backend.get_hostname()
+ return CLIENT_USER_MAP.get(hostname, 'root')
+
+
+def get_client_ssh_key_type(host):
+ hostname = host.backend.get_hostname()
+ return CLIENT_SSH_KEY_TYPE_MAP.get(hostname, 'rsa')
+
+
+def get_client_home(host):
+ user = get_client_user(host)
+ return f'/home/{user}' if user != 'root' else '/root'
class TestSSHSetup:
def test_ssh_directory_exists(self, host):
- ssh_dir = host.file('/root/.ssh')
+ client_home = get_client_home(host)
+ client_user = get_client_user(host)
+ ssh_dir = host.file(f'{client_home}/.ssh')
assert ssh_dir.exists
assert ssh_dir.is_directory
- assert ssh_dir.user == 'root'
- assert ssh_dir.group == 'root'
- assert ssh_dir.mode == 0o640
+ assert ssh_dir.user == client_user
+ assert ssh_dir.group == client_user
+ assert ssh_dir.mode == 0o700
def test_ssh_private_key_exists(self, host):
- key = host.file('/root/.ssh/id_rsa')
+ client_home = get_client_home(host)
+ client_user = get_client_user(host)
+ key_type = get_client_ssh_key_type(host)
+ key = host.file(f'{client_home}/.ssh/id_{key_type}')
assert key.exists
- assert key.user == 'root'
- assert key.group == 'root'
+ assert key.user == client_user
+ assert key.group == client_user
assert key.mode == 0o600
def test_ssh_public_key_exists(self, host):
- key = host.file('/root/.ssh/id_rsa.pub')
+ client_home = get_client_home(host)
+ client_user = get_client_user(host)
+ key_type = get_client_ssh_key_type(host)
+ key = host.file(f'{client_home}/.ssh/id_{key_type}.pub')
assert key.exists
- assert key.user == 'root'
- assert key.group == 'root'
+ assert key.user == client_user
+ assert key.group == client_user
def test_known_hosts_contains_borg_server(self, host):
- known_hosts = host.file('/root/.ssh/known_hosts')
+ client_home = get_client_home(host)
+ known_hosts = host.file(f'{client_home}/.ssh/known_hosts')
assert known_hosts.exists
assert known_hosts.contains('borg-server')
@@ -35,23 +74,30 @@ class TestSSHSetup:
class TestBackupScript:
def test_backup_script_exists(self, host):
hostname = host.backend.get_hostname()
+ client_user = get_client_user(host)
if hostname == 'borg-client-multi':
script1 = host.file('/usr/local/bin/run_borg_backup@configs')
script2 = host.file('/usr/local/bin/run_borg_backup@home-data')
assert script1.exists
- assert script1.user == 'root'
- assert script1.group == 'root'
+ assert script1.user == client_user
+ assert script1.group == client_user
assert script1.mode == 0o711
assert script2.exists
- assert script2.user == 'root'
- assert script2.group == 'root'
+ assert script2.user == client_user
+ assert script2.group == client_user
assert script2.mode == 0o711
+ elif hostname == 'borg-client-nonroot':
+ script = host.file('/usr/local/bin/run_borg_backup@borg-server')
+ assert script.exists
+ assert script.user == client_user
+ assert script.group == client_user
+ assert script.mode == 0o711
elif hostname in ('borg-client', 'borg-client-2'):
script = host.file('/usr/local/bin/run_borg_backup')
assert script.exists
- assert script.user == 'root'
- assert script.group == 'root'
+ assert script.user == client_user
+ assert script.group == client_user
assert script.mode == 0o711
else:
pytest.fail(f"Unexpected hostname: {hostname}")
@@ -64,6 +110,9 @@ class TestBackupScript:
script2 = host.file('/usr/local/bin/run_borg_backup@home-data')
assert script1.contains('borg create')
assert script2.contains('borg create')
+ elif hostname == 'borg-client-nonroot':
+ script = host.file('/usr/local/bin/run_borg_backup@borg-server')
+ assert script.contains('borg create')
elif hostname in ('borg-client', 'borg-client-2'):
script = host.file('/usr/local/bin/run_borg_backup')
assert script.contains('borg create')
@@ -82,8 +131,8 @@ class TestBackupScript:
script = host.file('/usr/local/bin/run_borg_backup')
assert script.contains('-C')
assert script.contains('lz4')
- elif hostname == 'borg-client':
- script = host.file('/usr/local/bin/run_borg_backup')
+ elif hostname in ('borg-client', 'borg-client-nonroot'):
+ script = host.file('/usr/local/bin/run_borg_backup') if hostname == 'borg-client' else host.file('/usr/local/bin/run_borg_backup@borg-server')
assert script.contains('-C')
assert script.contains('zstd')
else:
@@ -99,6 +148,10 @@ class TestBackupScript:
assert script1.contains('/opt/borg/configs')
assert script2.contains('borg@borg-server')
assert script2.contains('/opt/borg/home-data')
+ elif hostname == 'borg-client-nonroot':
+ script = host.file('/usr/local/bin/run_borg_backup@borg-server')
+ assert script.contains('borg@borg-server')
+ assert script.contains('/opt/borg')
elif hostname in ('borg-client', 'borg-client-2'):
script = host.file('/usr/local/bin/run_borg_backup')
assert script.contains('borg@borg-server')
@@ -115,6 +168,10 @@ class TestBackupScript:
assert '/etc' in script1.content_string
assert '/home' in script2.content_string
assert '--exclude' in script2.content_string
+ elif hostname == 'borg-client-nonroot':
+ script = host.file('/usr/local/bin/run_borg_backup@borg-server')
+ content = script.content_string
+ assert '/etc' in content
elif hostname in ('borg-client', 'borg-client-2'):
script = host.file('/usr/local/bin/run_borg_backup')
content = script.content_string
@@ -130,6 +187,9 @@ class TestBackupScript:
script2 = host.file('/usr/local/bin/run_borg_backup@home-data')
assert script1.mode == 0o711
assert script2.mode == 0o711
+ elif hostname == 'borg-client-nonroot':
+ script = host.file('/usr/local/bin/run_borg_backup@borg-server')
+ assert script.mode == 0o711
elif hostname in ('borg-client', 'borg-client-2'):
script = host.file('/usr/local/bin/run_borg_backup')
assert script.mode == 0o711
@@ -143,9 +203,10 @@ class TestMultiInstanceBaseScript:
if hostname != 'borg-client-multi':
return
+ client_user = get_client_user(host)
base_script = host.file('/usr/local/bin/run_borg_backup')
assert base_script.exists
- assert base_script.user == 'root'
+ assert base_script.user == client_user
assert base_script.mode == 0o711
def test_base_script_contains_both_blocks(self, host):
diff --git a/molecule/default/tests/test_server_setup.py b/molecule/default/tests/test_server_setup.py
index cb644cb..3c494f7 100644
--- a/molecule/default/tests/test_server_setup.py
+++ b/molecule/default/tests/test_server_setup.py
@@ -75,7 +75,7 @@ class TestBorgSSHSetup:
if not line.strip():
continue
assert line.startswith('restrict,command="borg serve'), f"Line should start with restrict,command: {line[:50]}"
- assert 'root@' in line, f"Line should contain root@ hostname marker: {line[-30:]}"
+ assert '@' in line and line.rstrip().endswith(('borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot')), f"Line should contain user@hostname marker: {line[-40:]}"
assert '--restrict-to-repository' in line, f"Line should have repo restriction: {line[:80]}"
def test_authorized_keys_multi_instance_no_cross_host_repos(self, host):
diff --git a/molecule/default/tests/test_systemd.py b/molecule/default/tests/test_systemd.py
index 9421959..7b7b2ab 100644
--- a/molecule/default/tests/test_systemd.py
+++ b/molecule/default/tests/test_systemd.py
@@ -2,7 +2,20 @@
import pytest
-testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi']
+testinfra_hosts = ['borg-client', 'borg-client-2', 'borg-client-multi', 'borg-client-nonroot']
+
+
+CLIENT_USER_MAP = {
+ 'borg-client': 'root',
+ 'borg-client-2': 'root',
+ 'borg-client-multi': 'root',
+ 'borg-client-nonroot': 'backupuser',
+}
+
+
+def get_client_user(host):
+ hostname = host.backend.get_hostname()
+ return CLIENT_USER_MAP.get(hostname, 'root')
class TestSystemdServiceFile:
@@ -18,6 +31,12 @@ class TestSystemdServiceFile:
assert service2.exists
assert service2.user == 'root'
assert service2.group == 'root'
+ elif hostname == 'borg-client-nonroot':
+ service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
+ assert service.exists
+ assert service.user == 'root'
+ assert service.group == 'root'
+ assert service.mode == 0o644
elif hostname in ('borg-client', 'borg-client-2'):
service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
assert service.exists
@@ -38,6 +57,13 @@ class TestSystemdServiceFile:
assert service.contains('[Service]')
assert service.contains('[Install]')
assert service.contains('Type=oneshot')
+ elif hostname == 'borg-client-nonroot':
+ service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
+ assert service.contains('[Unit]')
+ assert service.contains('[Service]')
+ assert service.contains('[Install]')
+ assert service.contains('Type=oneshot')
+ assert service.contains('ExecStart=/usr/local/bin/run_borg_backup')
elif hostname in ('borg-client', 'borg-client-2'):
service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
assert service.contains('[Unit]')
@@ -48,9 +74,26 @@ class TestSystemdServiceFile:
else:
pytest.fail(f"Unexpected hostname: {hostname}")
- def test_success_exit_status(self, host):
+ def test_service_user(self, host):
hostname = host.backend.get_hostname()
+ client_user = get_client_user(host)
+
if hostname == 'borg-client-multi':
+ service1 = host.file('/etc/systemd/system/borg_backup@configs.service')
+ service2 = host.file('/etc/systemd/system/borg_backup@home-data.service')
+ for service in [service1, service2]:
+ assert service.contains(f'User={client_user}')
+ assert service.contains(f'Group={client_user}')
+ elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'):
+ service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
+ assert service.contains(f'User={client_user}')
+ assert service.contains(f'Group={client_user}')
+ else:
+ pytest.fail(f"Unexpected hostname: {hostname}")
+
+ def test_success_exit_status(self, host):
+ hostname = host.backend.get_hostname()
+ if hostname in ('borg-client-multi', 'borg-client-nonroot'):
return
service = host.file('/etc/systemd/system/borg_backup@borg-server.service')
@@ -80,7 +123,7 @@ class TestSystemdTimerFile:
timer2 = host.file('/etc/systemd/system/borg_backup@home-data.timer')
assert timer1.exists
assert timer2.exists
- elif hostname in ('borg-client', 'borg-client-2'):
+ elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'):
timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer')
assert timer.exists
assert timer.user == 'root'
@@ -101,7 +144,7 @@ class TestSystemdTimerFile:
assert timer.contains('[Install]')
assert timer.contains('OnCalendar=')
assert timer.contains('AccuracySec=')
- elif hostname in ('borg-client', 'borg-client-2'):
+ elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'):
timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer')
assert timer.contains('[Unit]')
assert timer.contains('[Timer]')
@@ -122,7 +165,7 @@ class TestSystemdTimerFile:
elif hostname == 'borg-client-2':
timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer')
assert 'OnCalendar=*-*-* 03:00:00' in timer.content_string
- elif hostname == 'borg-client':
+ elif hostname in ('borg-client', 'borg-client-nonroot'):
timer = host.file('/etc/systemd/system/borg_backup@borg-server.timer')
assert 'OnCalendar=*-*-* 02:00:00' in timer.content_string
else:
@@ -140,7 +183,7 @@ class TestSystemdState:
assert c1.stdout.strip() == 'enabled'
assert c2.rc == 0
assert c2.stdout.strip() == 'enabled'
- elif hostname in ('borg-client', 'borg-client-2'):
+ elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'):
timer_name = 'borg_backup@borg-server.timer'
c = host.run(f"systemctl is-enabled {timer_name}")
assert c.rc == 0
@@ -156,7 +199,7 @@ class TestSystemdState:
c2 = host.run('systemctl is-active borg_backup@home-data.timer')
assert c1.rc == 0
assert c2.rc == 0
- elif hostname in ('borg-client', 'borg-client-2'):
+ elif hostname in ('borg-client', 'borg-client-2', 'borg-client-nonroot'):
timer_name = 'borg_backup@borg-server.timer'
c = host.run(f"systemctl is-active {timer_name}")
assert c.rc == 0
diff --git a/tasks/client_create_scripts_each.yml b/tasks/client_create_scripts_each.yml
index b91c385..6c0615f 100644
--- a/tasks/client_create_scripts_each.yml
+++ b/tasks/client_create_scripts_each.yml
@@ -3,8 +3,8 @@
ansible.builtin.file:
dest: "{{ script_location }}"
state: touch
- owner: root
- group: root
+ owner: "{{ borg_client_user }}"
+ group: "{{ borg_client_user }}"
modification_time: preserve
access_time: preserve
mode: "0711"
diff --git a/tasks/client_setup.yml b/tasks/client_setup.yml
index 8769852..a1a2267 100644
--- a/tasks/client_setup.yml
+++ b/tasks/client_setup.yml
@@ -1,10 +1,43 @@
---
+- name: Ensure borg_client_user exists
+ ansible.builtin.getent:
+ database: passwd
+ key: "{{ borg_client_user }}"
+ become: true
+
+- name: Compute borg_client_user_home if not set
+ ansible.builtin.set_fact:
+ borg_client_user_home: "{{ getent_passwd[borg_client_user][4] }}"
+ when: borg_client_user_home is not defined
+
+- name: Validate borg_client_user home exists
+ ansible.builtin.stat:
+ path: "{{ borg_client_user_home }}"
+ register: user_home_stat
+ become: true
+
+- name: Fail if borg_client_user home missing
+ ansible.builtin.fail:
+ msg: |
+ Home directory {{ borg_client_user_home }} for user {{ borg_client_user }} does not exist.
+ Please ensure the user has a valid home directory before running this role.
+ when: not user_home_stat.stat.exists
+
+- name: Check readability of included paths
+ ansible.builtin.stat:
+ path: "{{ item }}"
+ loop: "{{ borg_included_dirs }}"
+ register: included_paths_stat
+ become: true
+ become_user: "{{ borg_client_user }}"
+ when: borg_included_dirs | length > 0
+
- name: Create SSH Directory
ansible.builtin.file:
- path: /root/.ssh
- owner: root
- group: root
- mode: "0640"
+ path: "{{ borg_client_user_home }}/.ssh"
+ owner: "{{ borg_client_user }}"
+ group: "{{ borg_client_user }}"
+ mode: "0700"
state: directory
become: true
@@ -12,17 +45,19 @@
ansible.builtin.known_hosts:
name: "{{ borg_server_host_url }}"
key: "{{ borg_server_host_url }} {{ borg_server_host_ssh_key }}"
- path: /root/.ssh/known_hosts
+ path: "{{ borg_client_user_home }}/.ssh/known_hosts"
state: present
become: true
+ become_user: "{{ borg_client_user }}"
- name: Generate SSH keys
community.crypto.openssh_keypair:
- path: /root/.ssh/id_rsa
- owner: root
- group: root
+ path: "{{ borg_client_user_home }}/.ssh/id_{{ borg_ssh_key_type }}"
+ type: "{{ borg_ssh_key_type }}"
+ owner: "{{ borg_client_user }}"
+ group: "{{ borg_client_user }}"
mode: "0600"
- comment: root@{{ inventory_hostname }}
+ comment: "{{ borg_client_user }}@{{ inventory_hostname }}"
become: true
register: ssh_key
@@ -86,7 +121,7 @@
line: >-
restrict,command="borg serve{{ " --append-only" if borg_mode_append_only }}
{{ all_repos | map('regex_replace', '^', '--restrict-to-repository ') | join(' ') }}"
- {{ ssh_key.public_key | trim }} root@{{ inventory_hostname }}
+ {{ ssh_key.public_key | trim }} {{ borg_client_user }}@{{ inventory_hostname }}
state: present
become: true
delegate_to: "{{ borg_server_host }}"
@@ -98,6 +133,7 @@
environment:
BORG_PASSPHRASE: "{{ borg_passphrase }}"
become: true
+ become_user: "{{ borg_client_user }}"
register: init_borg_output
changed_when: init_borg_output.rc != 2
failed_when:
@@ -128,6 +164,7 @@
borg key export --paper
borg@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }}
become: true
+ become_user: "{{ borg_client_user }}"
register: borg_keys
changed_when: borg_keys.rc != 0
diff --git a/templates/borg_backup.service.j2 b/templates/borg_backup.service.j2
index 5bbd90f..04f2a49 100644
--- a/templates/borg_backup.service.j2
+++ b/templates/borg_backup.service.j2
@@ -5,8 +5,8 @@ Wants={{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg
[Service]
Type=oneshot
ExecStart={{ borg_backup_script_location }}
-User=root
-Group=root
+User={{ borg_client_user }}
+Group={{ borg_client_user }}
{% if borg_backup_service_successful_exit_status | length > 0 %}
SuccessExitStatus={{ borg_backup_service_successful_exit_status | join(' ') }}
{% endif %}