bacchus
Advanced tools
| from .base import HomeServerApp | ||
| class Kodi(HomeServerApp): | ||
| """Kodi""" | ||
| def setup(self): | ||
| pass |
| from .base import HomeServerApp | ||
| class PiHole(HomeServerApp): | ||
| """Kodi""" | ||
| def setup(self): | ||
| pass |
+2
-1
| Metadata-Version: 2.1 | ||
| Name: bacchus | ||
| Version: 1.0.2 | ||
| Version: 1.1.0 | ||
| Summary: Home Server solution based on docker | ||
@@ -16,3 +16,4 @@ License: MIT | ||
| Requires-Dist: docker (>=4.1,<5.0) | ||
| Requires-Dist: jinja2 (>=2.11.2,<3.0.0) | ||
| Requires-Dist: netifaces (>=0.10.9,<0.11.0) | ||
| Requires-Dist: requests (>=2.22.0,<3.0.0) |
+2
-1
| [tool.poetry] | ||
| name = "bacchus" | ||
| version = "1.0.2" | ||
| version = "1.1.0" | ||
| description = "Home Server solution based on docker" | ||
@@ -20,2 +20,3 @@ authors = ["David Francos <opensource@davidfrancos.net>"] | ||
| netifaces = "^0.10.9" | ||
| jinja2 = "^2.11.2" | ||
@@ -22,0 +23,0 @@ [tool.poetry.dev-dependencies] |
+2
-1
@@ -17,2 +17,3 @@ # -*- coding: utf-8 -*- | ||
| 'docker>=4.1,<5.0', | ||
| 'jinja2>=2.11.2,<3.0.0', | ||
| 'netifaces>=0.10.9,<0.11.0', | ||
@@ -26,3 +27,3 @@ 'requests>=2.22.0,<3.0.0'] | ||
| 'name': 'bacchus', | ||
| 'version': '1.0.2', | ||
| 'version': '1.1.0', | ||
| 'description': 'Home Server solution based on docker', | ||
@@ -29,0 +30,0 @@ 'long_description': None, |
+40
-21
@@ -0,3 +1,3 @@ | ||
| import itertools | ||
| import docker | ||
| # from bacchus.homeassistant import HomeAssistant | ||
| from bacchus.jackett import Jackett | ||
@@ -16,9 +16,20 @@ from bacchus.transmission import Transmission | ||
| from bacchus.certificates import CertManager | ||
| from bacchus.kodi import Kodi | ||
| from bacchus.pihole import PiHole | ||
| __all__ = [ | ||
| CertManager, Nginx, OpenVPN, NextCloud, Transmission, Jackett, Lidarr, | ||
| LazyLibrarian, Radarr, Medusa, Jellyfin | ||
| DockerCompose, CertManager, Nginx, OpenVPN, NextCloud, Transmission, | ||
| Jackett, Lidarr, LazyLibrarian, Radarr, Medusa, Jellyfin, Kodi, PiHole | ||
| ] | ||
| CATEGORIES = { | ||
| 'base': [CertManager, Nginx, OpenVPN, PiHole], | ||
| 'media_download': | ||
| [Jackett, Lidarr, LazyLibrarian, Radarr, Medusa, Transmission], | ||
| 'media_management': [Jellyfin], | ||
| 'media_player': [Kodi], | ||
| 'cloud': [NextCloud] | ||
| } | ||
| class HomeServerSetup: | ||
@@ -30,31 +41,39 @@ """Base cmdline class. | ||
| """ | ||
| def __init__(self, domain, docker_prefix="bacchus", **kwargs): | ||
| def __init__(self, domain, **kwargs): | ||
| """Setup providers. | ||
| Kwargs will be inherited as metadata, for example, nextcloud will use the nextcloud_username | ||
| and nextcloud_password variables | ||
| Kwargs will be inherited as metadata | ||
| """ | ||
| client = docker.from_env() | ||
| self.client = docker.from_env() | ||
| # tree-like, both compose (wich itself is a provider) and providers | ||
| # need access to each other, so we'd do that trough the parent. | ||
| self.providers = {} | ||
| self.compose = DockerCompose(domain, client, docker_prefix, None, self, | ||
| **kwargs) | ||
| self.providers.update({ | ||
| cls.__name__: cls(domain, client, docker_prefix, self.compose, | ||
| self, **kwargs) | ||
| for cls in __all__ | ||
| }) | ||
| self.providers.update({cls.__name__: cls(domain, self, **kwargs) | ||
| for cls in __all__}) | ||
| def configure(self, provider_name=None): | ||
| def configure(self, provider_name=None, categories=None): | ||
| """Configure given providers.""" | ||
| compose = self.providers['DockerCompose'] | ||
| compose.copy_template() | ||
| compose.create_env_files() | ||
| compose.start() | ||
| if provider_name: | ||
| return self.providers[provider_name].setup() | ||
| providers = [self.providers[provider_name]] | ||
| elif categories: | ||
| providers = list( | ||
| itertools.chain.from_iterable( | ||
| [CATEGORIES[b] for b in categories])) | ||
| else: | ||
| providers = self.providers.values() | ||
| self.compose.copy_template() | ||
| self.compose.create_env_files() | ||
| self.compose.start() | ||
| self.selected_providers = [a.__class__.__name__ for a in providers] | ||
| self.selected_categories = categories | ||
| for provider in self.providers.values(): | ||
| for provider in providers: | ||
| provider.wait_for_status() | ||
| provider.wait_for_config() | ||
| provider.setup() | ||
| self.compose.restart() | ||
| compose.restart() |
+10
-6
@@ -17,17 +17,20 @@ from pathlib import Path | ||
| """ | ||
| def __init__(self, domain, client, docker_prefix, compose, parent, **kwargs): | ||
| def __init__(self, domain, parent, **kwargs): | ||
| self.service_name = self.__class__.__name__.lower() | ||
| self.providers = parent.providers | ||
| self.compose = compose | ||
| self.parent = parent | ||
| self.path = DOCKER_PATH / 'data' / self.service_name | ||
| self.domain = domain | ||
| self.meta = kwargs | ||
| self.meta['project_name'] = docker_prefix | ||
| self.meta['project_name'] = 'bacchus' | ||
| self.logger = logging.getLogger(self.__class__.__name__) | ||
| self.logger.setLevel(logging.DEBUG) | ||
| self.path.mkdir(parents=True, exist_ok=True) | ||
| self.client = client | ||
| self.prefix = docker_prefix | ||
| self.client = parent.client | ||
| @property | ||
| def compose(self): | ||
| return self.providers.get('DockerCompose') | ||
| @property | ||
| def running(self): | ||
@@ -37,3 +40,4 @@ return self.__class__.__name__.lower() in self.compose.services | ||
| def container_for(self, service_name): | ||
| return next((a for a in self.client.containers.list() if a.id == self.compose.get_service_id(service_name))) | ||
| return next((a for a in self.client.containers.list() | ||
| if a.id == self.compose.get_service_id(service_name))) | ||
@@ -40,0 +44,0 @@ @property |
@@ -8,3 +8,3 @@ from bacchus.base import HomeServerApp | ||
| '--dns', 'gandiv5', '-a', '-d', f'private.{self.domain}', | ||
| '--email', f'{self.meta["nextcloud_username"]}', 'run' | ||
| '--email', f'{self.meta["email"]}', 'run' | ||
| ] | ||
@@ -18,13 +18,15 @@ if (self.path / 'certificates' / | ||
| self.logger.debug( | ||
| self.client.containers.run( | ||
| 'goacme/lego', | ||
| command=cmd, | ||
| environment={'GANDIV5_API_KEY': self.meta['dns_api_key']}, | ||
| volumes={ | ||
| self.path.absolute(): { | ||
| 'bind': '/.lego', | ||
| 'mode': 'rw' | ||
| } | ||
| }, | ||
| detach=False)) | ||
| self.client.containers.run('goacme/lego', | ||
| command=cmd, | ||
| environment={ | ||
| 'GANDIV5_API_KEY': | ||
| self.meta['dns_api_key'] | ||
| }, | ||
| volumes={ | ||
| self.path.absolute(): { | ||
| 'bind': '/.lego', | ||
| 'mode': 'rw' | ||
| } | ||
| }, | ||
| detach=False)) | ||
| except Exception as err: | ||
@@ -31,0 +33,0 @@ print(err) |
+14
-16
@@ -7,23 +7,21 @@ from cleo import Command | ||
| class GreetCommand(Command): | ||
| """ | ||
| Installs bacchus | ||
| class InstallCommand(Command): | ||
| """Installs bacchus | ||
| install | ||
| {domain? : Domain (FQDN) for virtualhosts} | ||
| {username? : Nextcloud first user's username} | ||
| {password? : Nextcloud first user's password} | ||
| {dns? : DNS Provider (ghandi) API key} | ||
| {iface? : (Optional) Main interface name} | ||
| {provider? : (Optional) Set up only one service} | ||
| {--email=? : Your e-mail address} | ||
| {--domain=? : Domain (FQDN) on gandi.net} | ||
| {--dns=? : DNS Provider (gandi.net) API key} | ||
| {--iface=? : (Optional) Main interface name} | ||
| {--categories=? : (Optional) Set up specific categories} | ||
| {--provider=? : (Optional) Set up only one service} | ||
| """ | ||
| def handle(self): | ||
| """Handle command""" | ||
| setup = HomeServerSetup(domain=self.argument('domain'), | ||
| nextcloud_username=self.argument('username'), | ||
| nextcloud_password=self.argument('password'), | ||
| iface=self.argument('iface'), | ||
| dns_api_key=self.argument('dns')) | ||
| setup = HomeServerSetup(domain=self.option('domain'), | ||
| email=self.option('email'), | ||
| iface=self.option('iface'), | ||
| dns_api_key=self.option('dns')) | ||
| setup.configure(self.argument('provider')) | ||
| setup.configure(self.option('provider'), self.option('categories')) | ||
@@ -33,3 +31,3 @@ | ||
| application = Application() | ||
| application.add(GreetCommand()) | ||
| application.add(InstallCommand()) | ||
| application.run() |
+27
-10
@@ -16,3 +16,5 @@ from pathlib import Path | ||
| def env(self): | ||
| return {**os.environ, 'COMPOSE_PROJECT_NAME': self.meta['project_name']} | ||
| return { | ||
| **os.environ, 'COMPOSE_PROJECT_NAME': self.meta['project_name'] | ||
| } | ||
@@ -33,5 +35,9 @@ def create_env_files(self): | ||
| def copy_template(self): | ||
| shutil.copyfile((TEMPLATES / 'docker-compose.yml').as_posix(), | ||
| (DOCKER_PATH / 'docker-compose.yml').as_posix()) | ||
| compose = (TEMPLATES / 'docker-compose.yml').read_text() | ||
| if not Path('/dev/dri').exists(): | ||
| compose = compose.replace(""" devices: | ||
| - /dev/dri:/dev/dri""", '') | ||
| (DOCKER_PATH / 'docker-compose.yml').write_text(compose) | ||
| # Hack to allow nginx docker config mount | ||
@@ -46,5 +52,8 @@ nginx_path = DOCKER_PATH / 'data' / 'nginx' | ||
| subprocess.check_output( | ||
| ['docker-compose', '-p', self.meta['project_name'], 'up', '-d' ], | ||
| ['docker-compose', '-p', self.meta['project_name'], 'up', '-d'], | ||
| cwd=DOCKER_PATH, | ||
| env={**os.environ, **self.env}) | ||
| env={ | ||
| **os.environ, | ||
| **self.env | ||
| }) | ||
@@ -59,10 +68,18 @@ def stop(self): | ||
| return subprocess.check_output(['docker-compose', 'ps', '-q', name], | ||
| cwd=DOCKER_PATH, | ||
| env=self.env).strip().decode() | ||
| cwd=DOCKER_PATH, | ||
| env=self.env).strip().decode() | ||
| def wait_for_status(self): | ||
| pass | ||
| def setup(self): | ||
| pass | ||
| @property | ||
| def services(self): | ||
| services = [a.strip() for a in subprocess.check_output(['docker-compose', 'ps', '--services'], | ||
| cwd=DOCKER_PATH, | ||
| env=self.env).decode().splitlines()] | ||
| services = [ | ||
| a.strip() for a in subprocess.check_output( | ||
| ['docker-compose', 'ps', '--services'], | ||
| cwd=DOCKER_PATH, | ||
| env=self.env).decode().splitlines() | ||
| ] | ||
| return services | ||
@@ -69,0 +86,0 @@ |
@@ -53,5 +53,3 @@ import json | ||
| def setup(self): | ||
| self.setup_nginx() |
@@ -18,4 +18,2 @@ import json | ||
| self.install() | ||
| self.logger.debug('Creating new user') | ||
| self.create_users() | ||
| self.logger.debug('Setting up nginx paths') | ||
@@ -27,18 +25,4 @@ self.setup_paths() | ||
| self.setup_onlyoffice() | ||
| self.logger.debug('Setting up music') | ||
| self.setup_music() | ||
| self.logger.debug('Setting up SMS sync') | ||
| self.setup_ocsms() | ||
| self.logger.debug('Setting up tasks') | ||
| self.setup_tasks() | ||
| self.logger.debug('Setting up calendar') | ||
| self.setup_calendar() | ||
| self.logger.debug('Setting up carnet') | ||
| self.setup_carnet() | ||
| self.logger.debug('Setting up keepass web') | ||
| self.setup_keeweb() | ||
| self.logger.debug('Setting up deck') | ||
| self.setup_deck() | ||
| self.logger.debug('Setting up contacts') | ||
| self.setup_contacts() | ||
| self.logger.debug('Installing apps') | ||
| self.setup_apps() | ||
| self.logger.debug('Set up nextcloud') | ||
@@ -55,7 +39,2 @@ | ||
| def create_users(self): | ||
| self.occ('user:add', '--password-from-env', '--display-name', | ||
| self.meta['nextcloud_username'], | ||
| self.meta['nextcloud_username']) | ||
| def setup_paths(self): | ||
@@ -67,30 +46,8 @@ self.occ('config:system:set', 'overwritewebroot', '--value', '/') | ||
| def setup_music(self): | ||
| self.occ('app:install', 'music') | ||
| def setup_apps(self): | ||
| apps = ('ocsms', 'tasks', 'calendar', 'deck', 'contacts', 'side_menu', | ||
| 'maps', 'breezedark') | ||
| for app in apps: | ||
| self.occ('app:install', app) | ||
| def setup_ocsms(self): | ||
| self.occ('app:install', 'ocsms') | ||
| def setup_tasks(self): | ||
| self.occ('app:install', 'tasks') | ||
| def setup_calendar(self): | ||
| """Setup calendar""" | ||
| self.occ('app:install', 'calendar') | ||
| def setup_carnet(self): | ||
| """Setup carnet note-taking app""" | ||
| self.occ('app:install', 'carnet') | ||
| def setup_deck(self): | ||
| """Setup deck kanban""" | ||
| self.occ('app:install', 'deck') | ||
| def setup_contacts(self): | ||
| """Setup contacts""" | ||
| self.occ('app:install', 'contacts') | ||
| def setup_keeweb(self): | ||
| self.occ('app:install', 'keeweb') | ||
| def install_external_links(self): | ||
@@ -190,4 +147,3 @@ """Install top links to all the rest of apps, to centralice everything on nextcloud""" | ||
| demux=False, | ||
| stderr=False, | ||
| environment={'OC_PASS': self.meta['nextcloud_password']})) | ||
| stderr=False)) | ||
| result = self.container.exec_run(args, **kwargs) | ||
@@ -194,0 +150,0 @@ self.logger.debug(result) |
+24
-2
| from .base import HomeServerApp | ||
| from .base import TEMPLATES | ||
| from pathlib import Path | ||
| from jinja2 import Template | ||
| import subprocess | ||
@@ -10,3 +11,24 @@ | ||
| self.logger.debug('Setting nginx configuration template') | ||
| (self.path / 'nginx.conf').write_text( | ||
| (TEMPLATES / 'nginx.tpl').read_text().format(domain=self.domain)) | ||
| template = Template((TEMPLATES / 'nginx.tpl').read_text()) | ||
| services = { | ||
| 'Transmission': ["transmission", "9091"], | ||
| 'Lidarr': ["lidarr", "8686"], | ||
| 'Jellyfin': ["jellyfin", "8096"], | ||
| 'NextCloud': ["nextcloud", "80"], | ||
| 'Medusa': ["medusa", "8081"], | ||
| 'LazyLibrarian': ["lazylibrarian", "5299"], | ||
| 'Radarr': ["radarr", "7878"], | ||
| 'Jackett': ["jackett", "9117"], | ||
| 'Kodi': ['kodi', '8080'], | ||
| 'PiHole': ['pihole', '80'] | ||
| } | ||
| context = dict(domain=self.domain, | ||
| services=dict([ | ||
| v for k, v in services.items() | ||
| if k in self.parent.selected_providers | ||
| ]), | ||
| selected=self.parent.selected_providers) | ||
| (self.path / 'nginx.conf').write_text(template.render(**context)) |
| import json | ||
| from contextlib import suppress | ||
| from functools import lru_cache | ||
@@ -51,10 +52,10 @@ from .base import TEMPLATES | ||
| ]) | ||
| with suppress(Exception): | ||
| conn = sqlite3.connect(str((self.path / 'nzbdrone.db').absolute())) | ||
| cursor = conn.cursor() | ||
| cursor.executemany('insert into Indexers values (?, ?, ?, ?, ?, ?, ?)', | ||
| indexers) | ||
| conn.commit() | ||
| conn.close() | ||
| conn = sqlite3.connect(str((self.path / 'nzbdrone.db').absolute())) | ||
| cursor = conn.cursor() | ||
| cursor.executemany('insert into Indexers values (?, ?, ?, ?, ?, ?, ?)', | ||
| indexers) | ||
| conn.commit() | ||
| conn.close() | ||
| def setup(self): | ||
@@ -61,0 +62,0 @@ self.setup_nginx() |
@@ -120,3 +120,2 @@ version: '3' | ||
| - ./data/media/nextcloud/:/data/ | ||
| - ./nextcloud_config/:/var/www/html/config/ | ||
| env_file: | ||
@@ -153,2 +152,3 @@ - .env_nextcloud | ||
| - jackett | ||
| - kodi | ||
| image: nginx | ||
@@ -204,2 +204,16 @@ restart: always | ||
| restart: unless-stopped | ||
| kodi: | ||
| restart: always | ||
| image: xayon/docker-kodi-beta:gbm | ||
| privileged: True | ||
| volumes: | ||
| - ./data/kodi_matrix:/root | ||
| - /dev/bus/usb:/dev/bus/usb | ||
| - /etc/group:/etc/group:ro | ||
| - /etc/passwd:/etc/passwd:ro | ||
| - /etc/shadow:/etc/shadow:ro | ||
| ports: | ||
| - 8080:8080 | ||
| networks: | ||
| - common | ||
@@ -206,0 +220,0 @@ networks: |
| from .base import HomeServerApp | ||
| class HomeAssistant(HomeServerApp): | ||
| @property | ||
| def config_file(self): | ||
| return self.path / 'config' / 'configuration.yaml' | ||
| def setup(self): | ||
| """Setup home assistant base url.""" | ||
| self.container.stop() | ||
| self.setup_nginx() | ||
| self.container.start() | ||
| def setup_nginx(self): | ||
| """Setup base url to use it as a proxy server. It requires the FULL url.""" | ||
| config = self.config_file.read_text() | ||
| if not 'base_url' in config: | ||
| config += f"http:\n base_url: https://{self.domain}/homeassistant/" | ||
| self.config_file.write_text(config) |
Sorry, the diff of this file is not supported yet
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
58452
3.05%35
2.94%1127
1.17%