unifi-backup 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Stanislav Meduna
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.1
2
+ Name: unifi_backup
3
+ Version: 0.1.0
4
+ Summary: A simple tool to fetch backups from the UniFi Network Application
5
+ Author-email: Stanislav Meduna <stano@meduna.org>
6
+ Project-URL: Homepage, https://github.com/numo68/unifi-backup
7
+ Project-URL: Bug Tracker, https://github.com/numo68/unifi-backup/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/x-rst
13
+ License-File: LICENSE
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: schema
16
+ Requires-Dist: pyunifi
17
+
18
+ Usage
19
+ =====
20
+
21
+ **unifi-backup** is a simple tool to fetch configuration backups from
22
+ the UniFi Network Application.
23
+
24
+ Arguments
25
+ ----------------
26
+
27
+ ``-h``, ``--help``
28
+ show the help message and exit.
29
+
30
+ ``-c FILE``, ``--config FILE``
31
+ the configuration file (default: ``~/.config/unifi-backup/config.yml``)
32
+
33
+ ``-o FILE``, ``--output FILE``
34
+ the output file. The file name can contain ``strftime`` directives. If the argument
35
+ is specified, ``directory``, ``name`` and ``keep`` fields of the configuration
36
+ are ignored.
37
+
38
+ Configuration file
39
+ ==================
40
+
41
+ The ``unifi-backup`` needs a configuration file
42
+ (default ``~/.config/unifi-backup/config.yml``). As the file contains secrets,
43
+ take care to set reasonable permissions. The file is in
44
+ the `YAML <https://yaml.org/>`_ format.
45
+
46
+ Configuration file
47
+ ------------------
48
+
49
+ .. code-block:: yaml
50
+
51
+ controller:
52
+ host: unifi
53
+ port: 8443
54
+ user: admin
55
+ password: ...
56
+ site: default
57
+ ssl_verify: true|false|/path/to/custom_cert.pem
58
+ output:
59
+ directory: .
60
+ name: "unifi-%Y%m%d-%H%M.unf"
61
+ keep: 12
62
+
63
+ All fields except ``password`` are optional.
64
+
65
+ ``host`` is a host name or an IP address. The host has to be the same
66
+ as the network application is configured for; using ``unifi`` here
67
+ and ``unifi.domain.lan`` won't work.
68
+
69
+ ``name`` specifies the name of the output file. ``strftime`` directives
70
+ are allowed.
71
+
72
+ ``keep`` removes all but the most recent ``*.unf`` files from the ``directory``,
73
+ that in this case has to be specified and has to be an absolute path.
74
+
75
+ ``directory`` has to already exist. As the backup is not encrypted
76
+ and contains secrets the permissions should be set accordingly.
@@ -0,0 +1,59 @@
1
+ Usage
2
+ =====
3
+
4
+ **unifi-backup** is a simple tool to fetch configuration backups from
5
+ the UniFi Network Application.
6
+
7
+ Arguments
8
+ ----------------
9
+
10
+ ``-h``, ``--help``
11
+ show the help message and exit.
12
+
13
+ ``-c FILE``, ``--config FILE``
14
+ the configuration file (default: ``~/.config/unifi-backup/config.yml``)
15
+
16
+ ``-o FILE``, ``--output FILE``
17
+ the output file. The file name can contain ``strftime`` directives. If the argument
18
+ is specified, ``directory``, ``name`` and ``keep`` fields of the configuration
19
+ are ignored.
20
+
21
+ Configuration file
22
+ ==================
23
+
24
+ The ``unifi-backup`` needs a configuration file
25
+ (default ``~/.config/unifi-backup/config.yml``). As the file contains secrets,
26
+ take care to set reasonable permissions. The file is in
27
+ the `YAML <https://yaml.org/>`_ format.
28
+
29
+ Configuration file
30
+ ------------------
31
+
32
+ .. code-block:: yaml
33
+
34
+ controller:
35
+ host: unifi
36
+ port: 8443
37
+ user: admin
38
+ password: ...
39
+ site: default
40
+ ssl_verify: true|false|/path/to/custom_cert.pem
41
+ output:
42
+ directory: .
43
+ name: "unifi-%Y%m%d-%H%M.unf"
44
+ keep: 12
45
+
46
+ All fields except ``password`` are optional.
47
+
48
+ ``host`` is a host name or an IP address. The host has to be the same
49
+ as the network application is configured for; using ``unifi`` here
50
+ and ``unifi.domain.lan`` won't work.
51
+
52
+ ``name`` specifies the name of the output file. ``strftime`` directives
53
+ are allowed.
54
+
55
+ ``keep`` removes all but the most recent ``*.unf`` files from the ``directory``,
56
+ that in this case has to be specified and has to be an absolute path.
57
+
58
+ ``directory`` has to already exist. As the backup is not encrypted
59
+ and contains secrets the permissions should be set accordingly.
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=62.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "unifi_backup"
7
+ version = "0.1.0"
8
+ authors = [
9
+ { name="Stanislav Meduna", email="stano@meduna.org" },
10
+ ]
11
+ description = "A simple tool to fetch backups from the UniFi Network Application"
12
+ readme = "README.rst"
13
+ requires-python = ">=3.7"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+ dependencies = [
20
+ "PyYAML",
21
+ "schema",
22
+ "pyunifi"
23
+ ]
24
+
25
+ [project.urls]
26
+ "Homepage" = "https://github.com/numo68/unifi-backup"
27
+ "Bug Tracker" = "https://github.com/numo68/unifi-backup/issues"
28
+
29
+ [project.scripts]
30
+ unifi-backup = "unifi_backup.cli:main"
31
+
32
+ [tool.setuptools]
33
+ packages = ["unifi_backup"]
34
+ package-dir = {"" = "src"}
35
+
36
+ [tool.pytest.ini_options]
37
+ pythonpath = [
38
+ "src",
39
+ ]
40
+ testpaths = [
41
+ "tests",
42
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,7 @@
1
+ """Allows to run the tool as ``python -m unifi_backup ...``
2
+ """
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -0,0 +1,197 @@
1
+ """Parse the arguments, read the configuration file and fetch the backup
2
+ from the UniFi network application
3
+ """
4
+
5
+ import shutil
6
+ import argparse
7
+ import logging
8
+ import re
9
+ from os import environ, path, scandir, remove
10
+ from datetime import datetime as dt
11
+ from yaml import safe_load
12
+ from schema import Schema, SchemaError, And, Or, Optional, Use
13
+
14
+ from pyunifi.controller import APIError
15
+ from pyunifi.controller import Controller
16
+
17
+ DEFAULT_CONFIGURATION_FILE = path.join(
18
+ environ["HOME"], ".config", "unifi-backup", "config.yml"
19
+ )
20
+
21
+ SCHEMA = Schema(
22
+ {
23
+ "controller": Schema(
24
+ {
25
+ Optional("host", default="unifi"): And(str, lambda s: len(s) > 0),
26
+ Optional("port", default=8443): Use(int),
27
+ Optional("user", default="admin"): And(str, lambda s: len(s) > 0),
28
+ "password": And(str, lambda s: len(s) > 0),
29
+ Optional("site", default="default"): And(str, lambda s: len(s) > 0),
30
+ Optional("ssl_verify", default=True): Or(
31
+ bool, And(str, lambda s: len(s) > 0)
32
+ ),
33
+ },
34
+ ),
35
+ Optional(
36
+ "output", default={"directory": ".", "name": "unifi-%Y%m%d%H%M.unf"}
37
+ ): Schema(
38
+ {
39
+ Optional("directory"): And(str, lambda s: len(s) > 0),
40
+ Optional("name", default="unifi-%Y%m%d%H%M.unf"): And(
41
+ str, lambda s: len(s) > 0
42
+ ),
43
+ Optional("keep"): And(Use(int), lambda x: x > 0),
44
+ },
45
+ ),
46
+ }
47
+ )
48
+
49
+
50
+ def parse_arguments():
51
+ """Parse the arguments
52
+
53
+ Returns
54
+ -------
55
+ dict
56
+ parsed arguments
57
+ """
58
+
59
+ parser = argparse.ArgumentParser(
60
+ prog="unifi-backup",
61
+ description="A tool to fetch backups from the UniFi network application",
62
+ )
63
+
64
+ parser.add_argument(
65
+ "-c",
66
+ "--config",
67
+ default=DEFAULT_CONFIGURATION_FILE,
68
+ metavar="FILE",
69
+ type=argparse.FileType("r"),
70
+ help="the configuration file (default: %(default)s)",
71
+ )
72
+
73
+ parser.add_argument(
74
+ "-o",
75
+ "--output",
76
+ metavar="FILE",
77
+ help="the output file (default is specified in the configuration file)",
78
+ )
79
+
80
+ return vars(parser.parse_args())
81
+
82
+
83
+ def parse_configuration(stream):
84
+ """Parse the configuration file
85
+
86
+ Parameters
87
+ ----------
88
+ stream : io.IOBase | str
89
+ Stream to read the configuration from.
90
+
91
+ Returns
92
+ -------
93
+ dict
94
+ Parsed and validated configuration
95
+ """
96
+ config = SCHEMA.validate(safe_load(stream))
97
+
98
+ return config
99
+
100
+
101
+ def fetch_backup(controller_config: dict, out_file: str):
102
+ """Fetch the backup from the controller
103
+
104
+ Parameters
105
+ ----------
106
+ controller_config : dict
107
+ Controller configuration
108
+ out_file : str
109
+ Path to the output file
110
+ """
111
+ controller = Controller(
112
+ host=controller_config["host"],
113
+ port=controller_config["port"],
114
+ username=controller_config["user"],
115
+ password=controller_config["password"],
116
+ site_id=controller_config["site"],
117
+ ssl_verify=controller_config["ssl_verify"],
118
+ )
119
+
120
+ # PR https://github.com/finish06/pyunifi/pull/76 fixing get_backup is not merged yet
121
+ # controller.get_backup(target_file=out_file)
122
+ dl_url = controller._run_command("backup", mgr="backup", params={"days": 0})[0][
123
+ "url"
124
+ ]
125
+ # This is what the web UI does: controller._run_command("async-backup", mgr="backup", params={"days": 0})
126
+ response = controller.session.get(controller.url + dl_url, stream=True)
127
+
128
+ if response.status_code != 200:
129
+ raise APIError(f"API backup failed: {response.status_code}")
130
+
131
+ with open(out_file, "wb") as _backfh:
132
+ return shutil.copyfileobj(response.raw, _backfh)
133
+
134
+
135
+ def rotate_files(output_config: dict):
136
+ """Rotate the files
137
+
138
+ Parameters
139
+ ----------
140
+ output_config : dict
141
+ Configuration
142
+ """
143
+ files = sorted(
144
+ [
145
+ f.name
146
+ for f in scandir(output_config["directory"])
147
+ if f.is_file and re.search(r"\.unf$", f.name)
148
+ ]
149
+ )
150
+
151
+ while len(files) > output_config["keep"]:
152
+ remove(path.join(output_config["directory"], files.pop(0)))
153
+
154
+
155
+ def main():
156
+ """_summary_
157
+
158
+ Raises
159
+ ------
160
+ ValueError
161
+ Configuration is invalid
162
+ ValueError
163
+ Output directory does not exist
164
+ """
165
+ arguments = parse_arguments()
166
+
167
+ try:
168
+ config = parse_configuration(arguments["config"])
169
+ except SchemaError as ex:
170
+ raise ValueError(
171
+ "configuration invalid\n" + str(ex.with_traceback(None))
172
+ ) from None
173
+
174
+ arguments["config"].close()
175
+
176
+ # If the file was given through an argument, use that and skip the rotation
177
+ # If not, use the configuration to construct the file name and do the rotation
178
+ # if requested and the directory was provided as an absolute path
179
+ if arguments["output"] is not None:
180
+ out_file = arguments["output"]
181
+ rotate = False
182
+ else:
183
+ if not path.isdir(config["output"]["directory"]):
184
+ raise ValueError(
185
+ f"Target directory {config['output']['directory']} does not exist or is not a directory"
186
+ )
187
+ rotate = (
188
+ path.isabs(config["output"]["directory"]) and "keep" in config["output"]
189
+ )
190
+ out_file = path.join(
191
+ config["output"]["directory"], dt.now().strftime(config["output"]["name"])
192
+ )
193
+
194
+ fetch_backup(config["controller"], out_file)
195
+
196
+ if rotate:
197
+ rotate_files(config["output"])
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.1
2
+ Name: unifi_backup
3
+ Version: 0.1.0
4
+ Summary: A simple tool to fetch backups from the UniFi Network Application
5
+ Author-email: Stanislav Meduna <stano@meduna.org>
6
+ Project-URL: Homepage, https://github.com/numo68/unifi-backup
7
+ Project-URL: Bug Tracker, https://github.com/numo68/unifi-backup/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/x-rst
13
+ License-File: LICENSE
14
+ Requires-Dist: PyYAML
15
+ Requires-Dist: schema
16
+ Requires-Dist: pyunifi
17
+
18
+ Usage
19
+ =====
20
+
21
+ **unifi-backup** is a simple tool to fetch configuration backups from
22
+ the UniFi Network Application.
23
+
24
+ Arguments
25
+ ----------------
26
+
27
+ ``-h``, ``--help``
28
+ show the help message and exit.
29
+
30
+ ``-c FILE``, ``--config FILE``
31
+ the configuration file (default: ``~/.config/unifi-backup/config.yml``)
32
+
33
+ ``-o FILE``, ``--output FILE``
34
+ the output file. The file name can contain ``strftime`` directives. If the argument
35
+ is specified, ``directory``, ``name`` and ``keep`` fields of the configuration
36
+ are ignored.
37
+
38
+ Configuration file
39
+ ==================
40
+
41
+ The ``unifi-backup`` needs a configuration file
42
+ (default ``~/.config/unifi-backup/config.yml``). As the file contains secrets,
43
+ take care to set reasonable permissions. The file is in
44
+ the `YAML <https://yaml.org/>`_ format.
45
+
46
+ Configuration file
47
+ ------------------
48
+
49
+ .. code-block:: yaml
50
+
51
+ controller:
52
+ host: unifi
53
+ port: 8443
54
+ user: admin
55
+ password: ...
56
+ site: default
57
+ ssl_verify: true|false|/path/to/custom_cert.pem
58
+ output:
59
+ directory: .
60
+ name: "unifi-%Y%m%d-%H%M.unf"
61
+ keep: 12
62
+
63
+ All fields except ``password`` are optional.
64
+
65
+ ``host`` is a host name or an IP address. The host has to be the same
66
+ as the network application is configured for; using ``unifi`` here
67
+ and ``unifi.domain.lan`` won't work.
68
+
69
+ ``name`` specifies the name of the output file. ``strftime`` directives
70
+ are allowed.
71
+
72
+ ``keep`` removes all but the most recent ``*.unf`` files from the ``directory``,
73
+ that in this case has to be specified and has to be an absolute path.
74
+
75
+ ``directory`` has to already exist. As the backup is not encrypted
76
+ and contains secrets the permissions should be set accordingly.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.rst
3
+ pyproject.toml
4
+ src/unifi_backup/__init__.py
5
+ src/unifi_backup/__main__.py
6
+ src/unifi_backup/cli.py
7
+ src/unifi_backup.egg-info/PKG-INFO
8
+ src/unifi_backup.egg-info/SOURCES.txt
9
+ src/unifi_backup.egg-info/dependency_links.txt
10
+ src/unifi_backup.egg-info/entry_points.txt
11
+ src/unifi_backup.egg-info/requires.txt
12
+ src/unifi_backup.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ unifi-backup = unifi_backup.cli:main
@@ -0,0 +1,3 @@
1
+ PyYAML
2
+ schema
3
+ pyunifi
@@ -0,0 +1 @@
1
+ unifi_backup