mirror of
https://github.com/ZettaIO/restic-compose-backup.git
synced 2025-10-10 12:20:58 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
74c0954e6f | ||
|
f6995eb506 | ||
|
ef28baed5e | ||
|
336cace237 | ||
|
cab4676b91 | ||
|
d002ad9390 | ||
|
98a10bf994 | ||
|
2535ce3421 | ||
|
8858f88ba4 | ||
|
c5b7f11db7 | ||
|
a099060b2e | ||
|
dd40152fe1 |
BIN
.github/logo.png
vendored
Normal file
BIN
.github/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
@@ -168,3 +168,8 @@ Contributions are welcome regardless of experience level. Don't hesitate submitt
|
|||||||
|
|
||||||
[restic]: https://restic.net/
|
[restic]: https://restic.net/
|
||||||
[documentation]: https://restic-compose-backup.readthedocs.io
|
[documentation]: https://restic-compose-backup.readthedocs.io
|
||||||
|
|
||||||
|
---
|
||||||
|
This project is sponsored by [zetta.io](https://www.zetta.io)
|
||||||
|
|
||||||
|
[](https://www.zetta.io)
|
||||||
|
@@ -5,6 +5,9 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- restic_compose_backup.env
|
- restic_compose_backup.env
|
||||||
- alerts.env
|
- alerts.env
|
||||||
|
labels:
|
||||||
|
restic-compose-backup.volumes: true
|
||||||
|
restic-compose-backup.volumes.include: 'src'
|
||||||
volumes:
|
volumes:
|
||||||
# Map in docker socket
|
# Map in docker socket
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
@@ -20,7 +23,7 @@ services:
|
|||||||
restic-compose-backup.volumes: true
|
restic-compose-backup.volumes: true
|
||||||
restic-compose-backup.volumes.include: "/tests"
|
restic-compose-backup.volumes.include: "/tests"
|
||||||
volumes:
|
volumes:
|
||||||
- ./tests:/srv/tests
|
- ./src/tests:/srv/tests
|
||||||
- ./.vscode:/srv/code
|
- ./.vscode:/srv/code
|
||||||
environment:
|
environment:
|
||||||
- SOME_VALUE=test
|
- SOME_VALUE=test
|
||||||
|
@@ -22,7 +22,7 @@ copyright = '2019, Zetta.IO Technology AS'
|
|||||||
author = 'Zetta.IO Technology AS'
|
author = 'Zetta.IO Technology AS'
|
||||||
|
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = '0.4.0'
|
release = '0.4.2'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
@@ -166,12 +166,27 @@ a webhook that will post embedded messages to a specific channel.
|
|||||||
|
|
||||||
The url usually looks like this: ``https://discordapp.com/api/webhooks/...```
|
The url usually looks like this: ``https://discordapp.com/api/webhooks/...```
|
||||||
|
|
||||||
DOCKER_BASE_URL
|
DOCKER_HOST
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
**Default value**: ``unix://tmp/docker.sock``
|
**Default value**: ``unix://tmp/docker.sock``
|
||||||
|
|
||||||
The location of the docker socket.
|
The socket or host of the docker service.
|
||||||
|
|
||||||
|
DOCKER_TLS_VERIFY
|
||||||
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If defined verify the host against a CA certificate.
|
||||||
|
Path to certs is defined in ``DOCKER_CERT_PATH``
|
||||||
|
and can be copied or mapped into this backup container.
|
||||||
|
|
||||||
|
DOCKER_CERT_PATH
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A path to a directory containing TLS certificates to use when
|
||||||
|
connecting to the Docker host. Combined with ``DOCKER_TLS_VERIFY``
|
||||||
|
this can be used to talk to docker through TLS in cases
|
||||||
|
were we cannot map in the docker socket.
|
||||||
|
|
||||||
Compose Labels
|
Compose Labels
|
||||||
--------------
|
--------------
|
||||||
|
@@ -13,9 +13,9 @@ When releasing a bugfix version we need to update the
|
|||||||
main image as well.
|
main image as well.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build src --tag zettaio/restic-compose-backup:0.3
|
docker build src --tag zettaio/restic-compose-backup:0.4
|
||||||
docker build src --tag zettaio/restic-compose-backup:0.3.3
|
docker build src --tag zettaio/restic-compose-backup:0.4.1
|
||||||
|
|
||||||
docker push zettaio/restic-compose-backup:0.3
|
docker push zettaio/restic-compose-backup:0.4
|
||||||
docker push zettaio/restic-compose-backup:0.3.3
|
docker push zettaio/restic-compose-backup:0.4.1
|
||||||
```
|
```
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
# DON'T COMMIT THIS FILE IF YOU MODIFY IN DEV
|
# DON'T COMMIT THIS FILE IF YOU MODIFY IN DEV
|
||||||
|
|
||||||
DOCKER_BASE_URL=unix://tmp/docker.sock
|
# DOCKER_HOST=unix://tmp/docker.sock
|
||||||
|
# DOCKER_TLS_VERIFY=1
|
||||||
|
# DOCKER_CERT_PATH=''
|
||||||
|
|
||||||
RESTIC_REPOSITORY=/restic_data
|
RESTIC_REPOSITORY=/restic_data
|
||||||
RESTIC_PASSWORD=password
|
RESTIC_PASSWORD=password
|
||||||
|
|
||||||
|
@@ -1 +1 @@
|
|||||||
__version__ = '0.4.0'
|
__version__ = '0.4.2'
|
||||||
|
@@ -17,7 +17,7 @@ def run(image: str = None, command: str = None, volumes: dict = None,
|
|||||||
labels=labels,
|
labels=labels,
|
||||||
# auto_remove=True, # We remove the container further down
|
# auto_remove=True, # We remove the container further down
|
||||||
detach=True,
|
detach=True,
|
||||||
environment=environment,
|
environment=environment + ['BACKUP_PROCESS_CONTAINER=true'],
|
||||||
volumes=volumes,
|
volumes=volumes,
|
||||||
network_mode=f'container:{source_container_id}', # Reuse original container's network stack.
|
network_mode=f'container:{source_container_id}', # Reuse original container's network stack.
|
||||||
working_dir=os.getcwd(),
|
working_dir=os.getcwd(),
|
||||||
|
@@ -57,6 +57,9 @@ def status(config, containers):
|
|||||||
logger.info("Status for compose project '%s'", containers.project_name)
|
logger.info("Status for compose project '%s'", containers.project_name)
|
||||||
logger.info("Repository: '%s'", config.repository)
|
logger.info("Repository: '%s'", config.repository)
|
||||||
logger.info("Backup currently running?: %s", containers.backup_process_running)
|
logger.info("Backup currently running?: %s", containers.backup_process_running)
|
||||||
|
logger.info("Checking docker availability")
|
||||||
|
|
||||||
|
utils.list_containers()
|
||||||
|
|
||||||
if containers.stale_backup_process_containers:
|
if containers.stale_backup_process_containers:
|
||||||
utils.remove_containers(containers.stale_backup_process_containers)
|
utils.remove_containers(containers.stale_backup_process_containers)
|
||||||
@@ -117,6 +120,7 @@ def backup(config, containers):
|
|||||||
mounts = containers.generate_backup_mounts('/volumes')
|
mounts = containers.generate_backup_mounts('/volumes')
|
||||||
volumes.update(mounts)
|
volumes.update(mounts)
|
||||||
|
|
||||||
|
logger.debug('Starting backup container with image %s', containers.this_container.image)
|
||||||
try:
|
try:
|
||||||
result = backup_runner.run(
|
result = backup_runner.run(
|
||||||
image=containers.this_container.image,
|
image=containers.this_container.image,
|
||||||
@@ -151,12 +155,18 @@ def backup(config, containers):
|
|||||||
|
|
||||||
def start_backup_process(config, containers):
|
def start_backup_process(config, containers):
|
||||||
"""The actual backup process running inside the spawned container"""
|
"""The actual backup process running inside the spawned container"""
|
||||||
if (not containers.backup_process_container
|
if not utils.is_true(os.environ.get('BACKUP_PROCESS_CONTAINER')):
|
||||||
or containers.this_container == containers.backup_process_container is False):
|
|
||||||
logger.error(
|
logger.error(
|
||||||
"Cannot run backup process in this container. Use backup command instead. "
|
"Cannot run backup process in this container. Use backup command instead. "
|
||||||
"This will spawn a new container with the necessary mounts."
|
"This will spawn a new container with the necessary mounts."
|
||||||
)
|
)
|
||||||
|
alerts.send(
|
||||||
|
subject="Cannot run backup process in this container",
|
||||||
|
body=(
|
||||||
|
"Cannot run backup process in this container. Use backup command instead. "
|
||||||
|
"This will spawn a new container with the necessary mounts."
|
||||||
|
)
|
||||||
|
)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
status(config, containers)
|
status(config, containers)
|
||||||
|
@@ -10,7 +10,6 @@ class Config:
|
|||||||
# Mandatory values
|
# Mandatory values
|
||||||
self.repository = os.environ.get('RESTIC_REPOSITORY')
|
self.repository = os.environ.get('RESTIC_REPOSITORY')
|
||||||
self.password = os.environ.get('RESTIC_REPOSITORY')
|
self.password = os.environ.get('RESTIC_REPOSITORY')
|
||||||
self.docker_base_url = os.environ.get('DOCKER_BASE_URL') or "unix://tmp/docker.sock"
|
|
||||||
self.cron_schedule = os.environ.get('CRON_SCHEDULE') or self.default_crontab_schedule
|
self.cron_schedule = os.environ.get('CRON_SCHEDULE') or self.default_crontab_schedule
|
||||||
self.cron_command = os.environ.get('CRON_COMMAND') or self.default_backup_command
|
self.cron_command = os.environ.get('CRON_COMMAND') or self.default_backup_command
|
||||||
|
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from restic_compose_backup import enums, utils
|
from restic_compose_backup import enums, utils
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
VOLUME_TYPE_BIND = "bind"
|
VOLUME_TYPE_BIND = "bind"
|
||||||
VOLUME_TYPE_VOLUME = "volume"
|
VOLUME_TYPE_VOLUME = "volume"
|
||||||
@@ -355,7 +357,7 @@ class RunningContainers:
|
|||||||
# Detect containers belonging to the current compose setup
|
# Detect containers belonging to the current compose setup
|
||||||
if (container.project_name == self.this_container.project_name
|
if (container.project_name == self.this_container.project_name
|
||||||
and not container.is_oneoff):
|
and not container.is_oneoff):
|
||||||
if container.id != self.this_container.id:
|
if container != self.backup_process_container:
|
||||||
self.containers.append(container)
|
self.containers.append(container)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@@ -3,7 +3,6 @@ import logging
|
|||||||
from typing import List
|
from typing import List
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
import docker
|
import docker
|
||||||
from restic_compose_backup.config import Config
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -11,8 +10,18 @@ TRUE_VALUES = ['1', 'true', 'True', True, 1]
|
|||||||
|
|
||||||
|
|
||||||
def docker_client():
|
def docker_client():
|
||||||
config = Config()
|
"""
|
||||||
return docker.DockerClient(base_url=config.docker_base_url)
|
Create a docker client from the following environment variables::
|
||||||
|
|
||||||
|
DOCKER_HOST=unix://tmp/docker.sock
|
||||||
|
DOCKER_TLS_VERIFY=1
|
||||||
|
DOCKER_CERT_PATH=''
|
||||||
|
"""
|
||||||
|
# NOTE: Remove this fallback in 1.0
|
||||||
|
if not os.environ.get('DOCKER_HOST'):
|
||||||
|
os.environ['DOCKER_HOST'] = 'unix://tmp/docker.sock'
|
||||||
|
|
||||||
|
return docker.from_env()
|
||||||
|
|
||||||
|
|
||||||
def list_containers() -> List[dict]:
|
def list_containers() -> List[dict]:
|
||||||
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_namespace_packages
|
|||||||
setup(
|
setup(
|
||||||
name="restic-compose-backup",
|
name="restic-compose-backup",
|
||||||
url="https://github.com/ZettaIO/restic-compose-backup",
|
url="https://github.com/ZettaIO/restic-compose-backup",
|
||||||
version="0.4.0",
|
version="0.4.2",
|
||||||
author="Einar Forselv",
|
author="Einar Forselv",
|
||||||
author_email="eforselv@gmail.com",
|
author_email="eforselv@gmail.com",
|
||||||
packages=find_namespace_packages(include=['restic_compose_backup']),
|
packages=find_namespace_packages(include=['restic_compose_backup']),
|
||||||
|
@@ -160,7 +160,7 @@
|
|||||||
"OpenStdin": true,
|
"OpenStdin": true,
|
||||||
"StdinOnce": true,
|
"StdinOnce": true,
|
||||||
"Env": [
|
"Env": [
|
||||||
"DOCKER_BASE_URL=unix://tmp/docker.sock",
|
"DOCKER_HOST=unix://tmp/docker.sock",
|
||||||
"RESTIC_REPOSITORY=/tmp/backup",
|
"RESTIC_REPOSITORY=/tmp/backup",
|
||||||
"RESTIC_PASSWORD=password",
|
"RESTIC_PASSWORD=password",
|
||||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
@@ -77,7 +77,7 @@ class ResticBackupTests(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
with mock.patch(list_containers_func, fixtures.containers(containers=containers)):
|
with mock.patch(list_containers_func, fixtures.containers(containers=containers)):
|
||||||
result = RunningContainers()
|
result = RunningContainers()
|
||||||
self.assertEqual(len(result.containers), 3, msg="Three containers expected")
|
self.assertEqual(len(result.containers), 4, msg="Three containers expected")
|
||||||
self.assertNotEqual(result.this_container, None, msg="No backup container found")
|
self.assertNotEqual(result.this_container, None, msg="No backup container found")
|
||||||
web_service = result.get_service('web')
|
web_service = result.get_service('web')
|
||||||
self.assertNotEqual(web_service, None)
|
self.assertNotEqual(web_service, None)
|
||||||
|
Reference in New Issue
Block a user