├── debian ├── source │ └── format ├── triggers ├── rules ├── install ├── copyright ├── postrm ├── postinst └── control ├── srv └── salt │ └── omv │ └── deploy │ └── compose │ ├── files │ ├── compose_cnf.j2 │ ├── global_env_yml.j2 │ ├── dockerfile_script.j2 │ ├── compose_yml.j2 │ ├── dockerfile.j2 │ ├── override_yml.j2 │ ├── compose_env_yml.j2 │ ├── dockerfile_conf.j2 │ ├── prune.j2 │ ├── stop.j2 │ ├── start.j2 │ ├── update.j2 │ └── backup.j2 │ ├── init.sls │ ├── default.sls │ ├── 40job.sls │ ├── 20dockerfile.sls │ ├── 30docker.sls │ └── 10compose.sls ├── .gitattributes ├── README.md ├── usr ├── share │ └── openmediavault │ │ ├── workbench │ │ ├── component.d │ │ │ ├── omv-services-compose-navigation-page.yaml │ │ │ ├── omv-services-compose-files-global-form-page.yaml │ │ │ ├── omv-services-compose-files-url-form-page.yaml │ │ │ ├── omv-services-compose-configs-form-page.yaml │ │ │ ├── omv-services-compose-dockerfiles-form-page.yaml │ │ │ ├── omv-services-compose-files-form-page.yaml │ │ │ ├── omv-services-compose-networks-datatable-page.yaml │ │ │ ├── omv-services-compose-repos-datatable-page.yaml │ │ │ ├── omv-services-compose-files-example-form-page.yaml │ │ │ ├── omv-services-compose-networks-form-page.yaml │ │ │ ├── omv-services-compose-volumes-datatable-page.yaml │ │ │ ├── omv-services-compose-restore-datatable-page.yaml │ │ │ ├── omv-services-compose-schedule-datatable-page.yaml │ │ │ ├── omv-services-compose-dockerfiles-datatable-page.yaml │ │ │ ├── omv-services-compose-configs-datatable-page.yaml │ │ │ ├── omv-services-compose-containers-datatable-page.yaml │ │ │ ├── omv-services-compose-stats-datatable-page.yaml │ │ │ ├── omv-services-compose-images-datatable-page.yaml │ │ │ ├── omv-services-compose-services-datatable-page.yaml │ │ │ └── omv-services-compose-settings-form-page.yaml │ │ ├── route.d │ │ │ ├── services.compose.yaml │ │ │ ├── services.compose.files.yaml │ │ │ ├── services.compose.repos.yaml │ │ │ ├── services.compose.stats.yaml │ │ │ ├── services.compose.configs.yaml │ │ │ ├── services.compose.images.yaml │ │ │ ├── services.compose.restore.yaml │ │ │ ├── services.compose.volumes.yaml │ │ │ ├── services.compose.networks.yaml │ │ │ ├── services.compose.schedule.yaml │ │ │ ├── services.compose.services.yaml │ │ │ ├── services.compose.containers.yaml │ │ │ ├── services.compose.dockerfiles.yaml │ │ │ ├── services.compose.settings.yaml │ │ │ ├── services.compose.files.create.yaml │ │ │ ├── services.compose.configs.create.yaml │ │ │ ├── services.compose.networks.create.yaml │ │ │ ├── services.compose.files.edit.yaml │ │ │ ├── services.compose.schedule.create.yaml │ │ │ ├── services.compose.url.create.yaml │ │ │ ├── services.compose.configs.edit.yaml │ │ │ ├── services.compose.dockerfiles.create.yaml │ │ │ ├── services.compose.examples.create.yaml │ │ │ ├── services.compose.schedule.edit.yaml │ │ │ ├── services.compose.dockerfiles.edit.yaml │ │ │ └── services.compose.files.global.yaml │ │ ├── navigation.d │ │ │ ├── services.compose.yaml │ │ │ ├── services.compose.stats.yaml │ │ │ ├── services.compose.configs.yaml │ │ │ ├── services.compose.networks.yaml │ │ │ ├── services.compose.settings.yaml │ │ │ ├── services.compose.repos.yaml │ │ │ ├── services.compose.restore.yaml │ │ │ ├── services.compose.volumes.yaml │ │ │ ├── services.compose.files.yaml │ │ │ ├── services.compose.dockerfiles.yaml │ │ │ ├── services.compose.images.yaml │ │ │ ├── services.compose.services.yaml │ │ │ ├── services.compose.schedule.yaml │ │ │ └── services.compose.containers.yaml │ │ ├── log.d │ │ │ ├── omv-compose-stop.yaml │ │ │ ├── omv-compose-backup.yaml │ │ │ ├── omv-compose-start.yaml │ │ │ ├── omv-compose-update.yaml │ │ │ └── omv-compose-restore.yaml │ │ └── dashboard.d │ │ │ ├── containers_term.yaml │ │ │ ├── containers.yaml │ │ │ └── containers_grid.yaml │ │ ├── confdb │ │ ├── migrations.d │ │ │ ├── conf.service.compose_8.0.3.sh │ │ │ ├── conf.service.compose_7.1.4.sh │ │ │ ├── conf.service.compose_7.6.3.sh │ │ │ ├── conf.service.compose_6.4.sh │ │ │ ├── conf.service.compose_7.2.4.sh │ │ │ ├── conf.service.compose_6.9.2.sh │ │ │ ├── conf.service.compose_8.0.2.sh │ │ │ ├── conf.service.compose_7.5.3.sh │ │ │ ├── conf.service.compose_7.6.2.sh │ │ │ ├── conf.service.compose_6.9.6.sh │ │ │ ├── conf.service.compose_7.2.sh │ │ │ ├── conf.service.compose_6.7.sh │ │ │ ├── conf.service.compose_7.2.5.sh │ │ │ ├── conf.service.compose_6.9.sh │ │ │ ├── conf.service.compose_7.3.sh │ │ │ ├── conf.service.compose_7.0.2.sh │ │ │ ├── conf.service.compose_8.0.1.sh │ │ │ ├── conf.service.compose_7.0.8.sh │ │ │ ├── conf.service.compose_7.2.8.sh │ │ │ ├── conf.service.compose_6.9.1.sh │ │ │ ├── conf.service.compose_7.4.3.sh │ │ │ ├── conf.service.compose_7.6.0.sh │ │ │ ├── conf.service.compose_6.11.sh │ │ │ ├── conf.service.compose_6.0.4.sh │ │ │ └── conf.service.compose_7.5.0.sh │ │ └── create.d │ │ │ └── conf.service.compose.sh │ │ ├── datamodels │ │ ├── conf.service.compose.globalenv.json │ │ ├── conf.service.compose.config.json │ │ ├── conf.service.compose.file.json │ │ ├── conf.service.compose.dockerfile.json │ │ ├── conf.service.compose.job.json │ │ └── conf.service.compose.json │ │ └── engined │ │ ├── inc │ │ └── 90composebackup.inc │ │ └── module │ │ └── compose.inc ├── sbin │ ├── omv-compose-download-icons │ ├── omv-compose-prune │ ├── omv-compose-stop-multi │ ├── omv-compose-start-multi │ ├── omv-compose-update-multi │ ├── omv-compose-restore-multi │ ├── omv-compose-init-git │ ├── omv-compose-stop │ ├── omv-compose-start │ ├── omv-compose-update │ ├── omv-compose-backup-multi │ └── omv-compose-restore └── bin │ └── autocompose.py ├── etc └── logrotate.d │ └── omv-compose-command └── .gitignore /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/triggers: -------------------------------------------------------------------------------- 1 | activate restart-engined 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/compose_cnf.j2: -------------------------------------------------------------------------------- 1 | {{ file.body }} 2 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/global_env_yml.j2: -------------------------------------------------------------------------------- 1 | {{ body }} 2 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/dockerfile_script.j2: -------------------------------------------------------------------------------- 1 | {{ file.scriptfile }} 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.gif binary 4 | *.jpg binary 5 | *.png binary 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | openmediavault-compose 2 | ====================== 3 | 4 | docker-compose plugin for OpenMediaVault 5 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - .{{ salt['pillar.get']('deploy_compose', 'default') }} 3 | -------------------------------------------------------------------------------- /debian/install: -------------------------------------------------------------------------------- 1 | etc/* etc 2 | usr/bin/* usr/bin 3 | usr/sbin/* usr/sbin 4 | usr/share/openmediavault/* usr/share/openmediavault 5 | srv/* srv 6 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/compose_yml.j2: -------------------------------------------------------------------------------- 1 | {{ pillar['headers']['auto_generated'] }} 2 | {{ pillar['headers']['warning'] }} 3 | 4 | # {{ file.name }} 5 | # {{ file.description }} 6 | 7 | {{ body }} 8 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/dockerfile.j2: -------------------------------------------------------------------------------- 1 | {{ pillar['headers']['auto_generated'] }} 2 | {{ pillar['headers']['warning'] }} 3 | 4 | # {{ file.name }} 5 | # {{ file.description }} 6 | 7 | {{ file.body }} 8 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/override_yml.j2: -------------------------------------------------------------------------------- 1 | {{ pillar['headers']['auto_generated'] }} 2 | {{ pillar['headers']['warning'] }} 3 | 4 | # {{ file.name }} 5 | # {{ file.description }} 6 | 7 | {{ body }} 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-navigation-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-navigation-page 5 | type: navigationPage 6 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose" 5 | title: _("Compose") 6 | component: omv-services-compose-navigation-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_8.0.3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | omv_module_set_dirty compose 8 | 9 | exit 0 10 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/compose_env_yml.j2: -------------------------------------------------------------------------------- 1 | {{ pillar['headers']['auto_generated'] }} 2 | {{ pillar['headers']['warning'] }} 3 | 4 | # environment file for {{ file.name }} 5 | # {{ file.description }} 6 | 7 | {{ body }} 8 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/dockerfile_conf.j2: -------------------------------------------------------------------------------- 1 | {{ pillar['headers']['auto_generated'] }} 2 | {{ pillar['headers']['warning'] }} 3 | 4 | # config file for {{ file.name }} 5 | # {{ file.description }} 6 | 7 | {{ file.conffile }} 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.1.4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | omv_module_set_dirty compose 8 | 9 | exit 0 10 | 11 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.6.3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | grp="dockerterm" 4 | 5 | if getent group "${grp}" > /dev/null; then 6 | groupdel --force "${grp}" 7 | fi 8 | 9 | exit 0 10 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.files.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files" 5 | title: _("Files") 6 | component: omv-services-compose-files-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.repos.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/repos" 5 | title: _("Repos") 6 | component: omv-services-compose-repos-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.stats.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/stats" 5 | title: _("Stats") 6 | component: omv-services-compose-stats-datatable-page 7 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Contact: OpenMediaVault Plugin Developers 3 | Copyright: 2022-2025 openmediavault plugin developers 4 | License: GPL-3 5 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.configs.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/configs" 5 | title: _("Configs") 6 | component: omv-services-compose-configs-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.images.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/images" 5 | title: _("Images") 6 | component: omv-services-compose-images-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.restore.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/restore" 5 | title: _("Restore") 6 | component: omv-services-compose-restore-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.volumes.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/volumes" 5 | title: _("Volumes") 6 | component: omv-services-compose-volumes-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.networks.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/networks" 5 | title: _("Networks") 6 | component: omv-services-compose-networks-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.schedule.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/schedule" 5 | title: _("Schedule") 6 | component: omv-services-compose-schedule-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.services.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/services" 5 | title: _("Services") 6 | component: omv-services-compose-services-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose" 5 | text: _("Compose") 6 | icon: "mdi:playlist-music-outline" 7 | url: "/services/compose" 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.containers.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/containers" 5 | title: _("Containers") 6 | component: omv-services-compose-containers-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.dockerfiles.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/dockerfiles" 5 | title: _("Dockerfiles") 6 | component: omv-services-compose-dockerfiles-datatable-page 7 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.settings.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/settings" 5 | title: _("Settings") 6 | editing: true 7 | component: omv-services-compose-settings-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.stats.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.stats" 5 | text: _("Stats") 6 | position: 40 7 | icon: mdi:poll 8 | url: "/services/compose/stats" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.configs.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.configs" 5 | text: _("Configs") 6 | position: 22 7 | icon: mdi:text 8 | url: "/services/compose/configs" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.networks.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.networks" 5 | text: _("Networks") 6 | position: 60 7 | icon: mdi:lan 8 | url: "/services/compose/networks" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.settings.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.settings" 5 | text: _("Settings") 6 | position: 10 7 | icon: "tune" 8 | url: "/services/compose/settings" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.repos.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.repos" 5 | text: _("Repos") 6 | position: 120 7 | icon: mdi:file-key-outline 8 | url: "/services/compose/repos" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.restore.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.restore" 5 | text: _("Restore") 6 | position: 110 7 | icon: mdi:restore 8 | url: "/services/compose/restore" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.volumes.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.volumes" 5 | text: _("Volumes") 6 | position: 70 7 | icon: mdi:database 8 | url: "/services/compose/volumes" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.files.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.files" 5 | text: _("Files") 6 | position: 20 7 | icon: mdi:file-document-outline 8 | url: "/services/compose/files" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.files.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files/create" 5 | title: _("Create") 6 | notificationTitle: _("Created file.") 7 | component: omv-services-compose-file-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.dockerfiles.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.dockerfiles" 5 | text: _("Dockerfiles") 6 | position: 90 7 | icon: mdi:docker 8 | url: "/services/compose/dockerfiles" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.images.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.images" 5 | text: _("Images") 6 | position: 50 7 | icon: mdi:image-multiple-outline 8 | url: "/services/compose/images" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.services.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.services" 5 | text: _("Services") 6 | position: 30 7 | icon: mdi:format-list-group 8 | url: "/services/compose/services" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.schedule.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.schedule" 5 | text: _("Schedule") 6 | position: 100 7 | icon: mdi:calendar-clock-outline 8 | url: "/services/compose/schedule" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.configs.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/configs/create" 5 | title: _("Create") 6 | notificationTitle: _("Created config.") 7 | component: omv-services-compose-config-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/navigation.d/services.compose.containers.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: navigation-item 3 | data: 4 | path: "services.compose.containers" 5 | text: _("Containers") 6 | position: 80 7 | icon: mdi:list-box-outline 8 | url: "/services/compose/containers" 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.networks.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/networks/create" 5 | title: _("Create") 6 | notificationTitle: _("Created network.") 7 | component: omv-services-compose-network-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.files.edit.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files/edit/:uuid" 5 | title: _("Edit") 6 | editing: true 7 | notificationTitle: _("Edit file.") 8 | component: omv-services-compose-file-form-page 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.schedule.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/schedule/create" 5 | title: _("Create") 6 | notificationTitle: _("Created scheduled job.") 7 | component: omv-services-compose-schedule-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.url.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files/url" 5 | title: _("Create from URL") 6 | notificationTitle: _("Created file from url.") 7 | component: omv-services-compose-file-url-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.configs.edit.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/configs/edit/:uuid" 5 | title: _("Edit") 6 | editing: true 7 | notificationTitle: _("Edit config.") 8 | component: omv-services-compose-config-form-page 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.dockerfiles.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/dockerfiles/create" 5 | title: _("Create") 6 | notificationTitle: _("Created dockerfile.") 7 | component: omv-services-compose-dockerfile-form-page 8 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.examples.create.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files/example" 5 | title: _("Create from example") 6 | notificationTitle: _("Created file.") 7 | component: omv-services-compose-file-example-form-page 8 | -------------------------------------------------------------------------------- /etc/logrotate.d/omv-compose-command: -------------------------------------------------------------------------------- 1 | /var/log/omv-compose-backup.log 2 | /var/log/omv-compose-restore.log 3 | /var/log/omv-compose-update.log 4 | /var/log/omv-compose-start.log 5 | /var/log/omv-compose-stop.log 6 | { 7 | monthly 8 | missingok 9 | rotate 12 10 | compress 11 | notifempty 12 | } 13 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.schedule.edit.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/schedule/edit/:uuid" 5 | title: _("Edit") 6 | editing: true 7 | notificationTitle: _("Edit scheduled job.") 8 | component: omv-services-compose-schedule-form-page 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.dockerfiles.edit.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/dockerfiles/edit/:uuid" 5 | title: _("Edit") 6 | editing: true 7 | notificationTitle: _("Edit dockerfile.") 8 | component: omv-services-compose-dockerfile-form-page 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/dockerfiles"; then 8 | omv_config_add_node "/config/services/compose" "dockerfiles" "" 9 | fi 10 | 11 | exit 0 12 | 13 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.2.4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/urlHostname"; then 8 | omv_config_add_key "/config/services/compose" "urlHostname" "" 9 | fi 10 | 11 | exit 0 12 | 13 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/route.d/services.compose.files.global.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: route 3 | data: 4 | url: "/services/compose/files/global" 5 | title: _("Global environment variables") 6 | editing: true 7 | notificationTitle: _("Edit global environment variable file.") 8 | component: omv-services-compose-files-global-form-page 9 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.9.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/datasharedfolderref"; then 8 | omv_config_add_key "/config/services/compose" "datasharedfolderref" "" 9 | fi 10 | 11 | exit 0 12 | 13 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_8.0.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/dockersharedfolderref"; then 8 | omv_config_add_key "/config/services/compose" "dockersharedfolderref" "" 9 | fi 10 | 11 | exit 0 12 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.5.3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | echo "Updating database ..." 8 | 9 | if ! omv_config_exists "/config/services/compose/hostshell"; then 10 | omv_config_add_key "/config/services/compose" "hostshell" "0" 11 | omv_module_set_dirty compose 12 | fi 13 | 14 | exit 0 15 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.globalenv.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose.globalenv", 4 | "title": "global environment file", 5 | "queryinfo": { 6 | "xpath": "//services/compose/globalenv", 7 | "iterable": false 8 | }, 9 | "properties": { 10 | "enabled": { 11 | "type": "boolean" 12 | }, 13 | "globalenv": { 14 | "type": "string" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.6.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | echo "Updating database ..." 8 | 9 | change=0 10 | 11 | for i in execenable host port debug hostshell; do 12 | if omv_config_exists "/config/services/compose/${i}"; then 13 | omv_config_delete "/config/services/compose/${i}" 14 | change=1 15 | fi 16 | done 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.9.6.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/globalenv"; then 8 | omv_config_add_node "/config/services/compose" "globalenv" 9 | omv_config_add_key "/config/services/compose/globalenv" "enabled" "1" 10 | omv_config_add_key "/config/services/compose/globalenv" "globalenv" "" 11 | fi 12 | 13 | exit 0 14 | 15 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | xpath="/config/services/compose" 8 | keys=("files" "services" "stats" "images" "networks" "volumes" "containers") 9 | 10 | for key in "${keys[@]}"; do 11 | if ! omv_config_exists "${xpath}/cachetime${key}"; then 12 | omv_config_add_key "${xpath}" "cachetime${key}" "60" 13 | fi 14 | done 15 | 16 | exit 0 17 | 18 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.7.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/dockerStorage"; then 8 | dockerPath="/var/lib/docker" 9 | if [ -f "/usr/bin/docker" ]; then 10 | dockerRoot="$(docker info | grep "Docker Root Dir:" | awk '{ print $4 }')" 11 | if [ -d "${dockerRoot}" ]; then 12 | dockerPath="${dockerRoot}" 13 | fi 14 | fi 15 | omv_config_add_key "/config/services/compose" "dockerStorage" "${dockerPath}" 16 | fi 17 | 18 | exit 0 19 | 20 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.2.5.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | xpath="/config/services/compose/files/file" 11 | count=$(omv_config_get_count "${xpath}"); 12 | index=1; 13 | while [ ${index} -le ${count} ]; do 14 | pos="${xpath}[position()=${index}]" 15 | if ! omv_config_exists "${pos}/override"; then 16 | omv_config_add_key "${pos}" "override" "" 17 | fi 18 | index=$(( index + 1 )) 19 | done; 20 | 21 | omv_module_set_dirty compose 22 | 23 | exit 0 24 | 25 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/log.d/omv-compose-stop.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: log 3 | data: 4 | id: omv-compose-stop 5 | text: _("Compose Stop") 6 | columns: 7 | - name: _("Date & Time") 8 | sortable: true 9 | prop: date 10 | cellTemplateName: localeDateTime 11 | flexGrow: 1 12 | - name: _("Action") 13 | sortable: true 14 | prop: action 15 | flexGrow: 1 16 | - name: _("Message") 17 | sortable: true 18 | prop: message 19 | flexGrow: 4 20 | request: 21 | service: LogFile 22 | method: getList 23 | params: 24 | id: omv-compose-stop 25 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.9.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/backupsharedfolderref"; then 8 | omv_config_add_key "/config/services/compose" "backupsharedfolderref" "" 9 | fi 10 | if ! omv_config_exists "/config/services/compose/backupmaxsize"; then 11 | omv_config_add_key "/config/services/compose" "backupmaxsize" "1" 12 | fi 13 | if ! omv_config_exists "/config/services/compose/jobs"; then 14 | omv_config_add_node "/config/services/compose" "jobs" 15 | fi 16 | 17 | exit 0 18 | 19 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/log.d/omv-compose-backup.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: log 3 | data: 4 | id: omv-compose-backup 5 | text: _("Compose Backup") 6 | columns: 7 | - name: _("Date & Time") 8 | sortable: true 9 | prop: date 10 | cellTemplateName: localeDateTime 11 | flexGrow: 1 12 | - name: _("Action") 13 | sortable: true 14 | prop: action 15 | flexGrow: 1 16 | - name: _("Message") 17 | sortable: true 18 | prop: message 19 | flexGrow: 4 20 | request: 21 | service: LogFile 22 | method: getList 23 | params: 24 | id: omv-compose-backup 25 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/log.d/omv-compose-start.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: log 3 | data: 4 | id: omv-compose-start 5 | text: _("Compose Start") 6 | columns: 7 | - name: _("Date & Time") 8 | sortable: true 9 | prop: date 10 | cellTemplateName: localeDateTime 11 | flexGrow: 1 12 | - name: _("Action") 13 | sortable: true 14 | prop: action 15 | flexGrow: 1 16 | - name: _("Message") 17 | sortable: true 18 | prop: message 19 | flexGrow: 4 20 | request: 21 | service: LogFile 22 | method: getList 23 | params: 24 | id: omv-compose-start 25 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/log.d/omv-compose-update.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: log 3 | data: 4 | id: omv-compose-update 5 | text: _("Compose Update") 6 | columns: 7 | - name: _("Date & Time") 8 | sortable: true 9 | prop: date 10 | cellTemplateName: localeDateTime 11 | flexGrow: 1 12 | - name: _("Action") 13 | sortable: true 14 | prop: action 15 | flexGrow: 1 16 | - name: _("Message") 17 | sortable: true 18 | prop: message 19 | flexGrow: 4 20 | request: 21 | service: LogFile 22 | method: getList 23 | params: 24 | id: omv-compose-update 25 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/log.d/omv-compose-restore.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: log 3 | data: 4 | id: omv-compose-restore 5 | text: _("Compose Restore") 6 | columns: 7 | - name: _("Date & Time") 8 | sortable: true 9 | prop: date 10 | cellTemplateName: localeDateTime 11 | flexGrow: 1 12 | - name: _("Action") 13 | sortable: true 14 | prop: action 15 | flexGrow: 1 16 | - name: _("Message") 17 | sortable: true 18 | prop: message 19 | flexGrow: 4 20 | request: 21 | service: LogFile 22 | method: getList 23 | params: 24 | id: omv-compose-restore 25 | -------------------------------------------------------------------------------- /debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /etc/default/openmediavault 6 | . /usr/share/openmediavault/scripts/helper-functions 7 | 8 | remove_action() { 9 | dpkg-trigger update-workbench 10 | } 11 | 12 | case "$1" in 13 | purge) 14 | remove_action 15 | # Remove the configuration data 16 | omv_config_delete "/config/services/compose" 17 | ;; 18 | 19 | remove) 20 | remove_action 21 | ;; 22 | 23 | upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) 24 | ;; 25 | 26 | *) 27 | echo "postrm called with unknown argument '$1'" >&2 28 | exit 1 29 | ;; 30 | esac 31 | 32 | exit 0 33 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/configs"; then 8 | omv_config_add_node "/config/services/compose" "configs" 9 | fi 10 | 11 | xpath="/config/services/compose/jobs/job" 12 | count=$(omv_config_get_count "${xpath}"); 13 | index=1; 14 | while [ ${index} -le ${count} ]; do 15 | pos="${xpath}[position()=${index}]" 16 | if ! omv_config_exists "${pos}/excludes"; then 17 | omv_config_add_key "${pos}" "excludes" "" 18 | fi 19 | index=$(( index + 1 )) 20 | done; 21 | 22 | exit 0 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.tmp 3 | *.bak 4 | *.swp 5 | *~ 6 | 7 | # Eclipse 8 | .project 9 | .metadata 10 | .settings/ 11 | *.launch 12 | .buildpath 13 | 14 | # Sublime Text 15 | *.sublime-workspace 16 | *.sublime-project 17 | 18 | # Vim 19 | [._]*.s[a-w][a-z] 20 | [._]s[a-w][a-z] 21 | *.un~ 22 | Session.vim 23 | .netrwhist 24 | 25 | # SVN 26 | .svn/ 27 | 28 | # Mac 29 | .DS_Store 30 | .AppleDouble 31 | .LSOverride 32 | 33 | # Windows 34 | Thumbs.db 35 | ehthumbs.db 36 | Desktop.ini 37 | 38 | # OpenMediaVault / Debian 39 | debian/openmediavault-* 40 | debian/files 41 | debian/*.debhelper.log 42 | debian/*.debhelper 43 | debian/*substvars 44 | debian/debhelper-build-stamp 45 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.0.2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | xpath="/config/services/compose/jobs/job" 11 | count=$(omv_config_get_count "${xpath}"); 12 | index=1; 13 | while [ ${index} -le ${count} ]; do 14 | pos="${xpath}[position()=${index}]" 15 | if ! omv_config_exists "${pos}/prune"; then 16 | omv_config_add_key "${pos}" "prune" "0" 17 | status=1 18 | fi 19 | index=$(( index + 1 )) 20 | done; 21 | 22 | if [ ${status} -eq 1 ]; then 23 | omv_module_set_dirty compose 24 | fi 25 | 26 | exit 0 27 | 28 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_8.0.1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/runconfig"; then 8 | omv_config_add_key "/config/services/compose" "runconfig" "0" 9 | fi 10 | 11 | if ! omv_config_exists "/config/services/compose/logmaxsize"; then 12 | omv_config_add_key "/config/services/compose" "logmaxsize" "50" 13 | fi 14 | 15 | if ! omv_config_exists "/config/services/compose/liverestore"; then 16 | omv_config_add_key "/config/services/compose" "liverestore" "0" 17 | fi 18 | 19 | omv_module_set_dirty compose 20 | 21 | exit 0 22 | 23 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-download-icons: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | url="https://github.com/OpenMediaVault-Plugin-Developers/packages/raw/master/compose-files/icons.tar.gz" 4 | icon_file="/tmp/icons.tar.gz" 5 | icon_dir="/var/www/openmediavault/assets/composeImages/" 6 | 7 | # download tar ball 8 | wget --quiet ${url} -O ${icon_file} 9 | 10 | # remove existing dir and recreate 11 | rm -rf "${icon_dir}" 12 | mkdir -p "${icon_dir}" 13 | 14 | # extract tar ball 15 | tar -xzf "${icon_file}" --strip-components=2 -C "${icon_dir}" 16 | 17 | # fix permissions on icon files 18 | chown -R openmediavault-webgui:openmediavault-webgui "${icon_dir}" 19 | 20 | # remove temp file 21 | rm -f "${icon_file}" 22 | 23 | exit 0 24 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose.config", 4 | "title": "additional files for compose", 5 | "queryinfo": { 6 | "xpath": "//services/compose/configs/config", 7 | "iterable": true, 8 | "idproperty": "uuid" 9 | }, 10 | "properties": { 11 | "uuid": { 12 | "type": "string", 13 | "format": "uuidv4" 14 | }, 15 | "name": { 16 | "type": "string" 17 | }, 18 | "description": { 19 | "type": "string" 20 | }, 21 | "fileref": { 22 | "type": "string", 23 | "format": "uuidv4" 24 | }, 25 | "body": { 26 | "type": "string" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.0.8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | echo "Updating database ..." 8 | xpath="/config/services/compose/jobs/job" 9 | count=$(omv_config_get_count "${xpath}"); 10 | index=1; 11 | while [ ${index} -le ${count} ]; do 12 | pos="${xpath}[position()=${index}]" 13 | if ! omv_config_exists "${pos}/prebackup"; then 14 | omv_config_add_key "${pos}" "prebackup" "" 15 | fi 16 | if ! omv_config_exists "${pos}/postbackup"; then 17 | omv_config_add_key "${pos}" "postbackup" "" 18 | fi 19 | index=$(( index + 1 )) 20 | done; 21 | 22 | omv_module_set_dirty compose 23 | 24 | exit 0 25 | 26 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/dashboard.d/containers_term.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: dashboard-widget 3 | data: 4 | id: 10e100ae-41a9-11f0-bfdc-9378defeb859 5 | title: _("Container Terminals") 6 | description: _("Displays containers with a link to cterm exec terminal in a table.") 7 | type: datatable 8 | permissions: 9 | role: 10 | - admin 11 | - user 12 | datatable: 13 | columns: 14 | - name: _("Name") 15 | prop: name 16 | flexGrow: 1 17 | sortable: true 18 | - name: _("Terminal") 19 | prop: term 20 | sortable: true 21 | flexGrow: 1 22 | store: 23 | proxy: 24 | service: Compose 25 | get: 26 | method: getContainersTerm 27 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/dashboard.d/containers.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: dashboard-widget 3 | data: 4 | id: 33ebe908-8878-11ec-991b-f356096873ad 5 | title: _("Containers") 6 | description: _("Displays information about containers in a table.") 7 | type: datatable 8 | permissions: 9 | role: 10 | - admin 11 | - user 12 | datatable: 13 | columns: 14 | - name: _("Name") 15 | prop: name 16 | flexGrow: 1 17 | sortable: true 18 | - name: _("Image") 19 | prop: image 20 | sortable: true 21 | flexGrow: 2 22 | - name: _("Status") 23 | prop: status 24 | flexGrow: 1 25 | sortable: true 26 | store: 27 | proxy: 28 | service: Compose 29 | get: 30 | method: getContainers 31 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.2.8.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/showcmd"; then 8 | omv_config_add_key "/config/services/compose" "showcmd" "0" 9 | fi 10 | 11 | xpath="/config/services/compose/files/file" 12 | count=$(omv_config_get_count "${xpath}"); 13 | index=1; 14 | while [ ${index} -le ${count} ]; do 15 | pos="${xpath}[position()=${index}]" 16 | if ! omv_config_exists "${pos}/showenv"; then 17 | omv_config_add_key "${pos}" "showenv" "0" 18 | fi 19 | if ! omv_config_exists "${pos}/showoverride"; then 20 | omv_config_add_key "${pos}" "showoverride" "0" 21 | fi 22 | index=$(( index + 1 )) 23 | done; 24 | 25 | exit 0 26 | 27 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.9.1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | if ! omv_config_exists "/config/services/compose/composeowner"; then 8 | omv_config_add_key "/config/services/compose" "composeowner" "root" 9 | omv_module_set_dirty compose 10 | fi 11 | if ! omv_config_exists "/config/services/compose/composegroup"; then 12 | omv_config_add_key "/config/services/compose" "composegroup" "root" 13 | fi 14 | if ! omv_config_exists "/config/services/compose/mode"; then 15 | omv_config_add_key "/config/services/compose" "mode" "700" 16 | fi 17 | if ! omv_config_exists "/config/services/compose/fileperms"; then 18 | omv_config_add_key "/config/services/compose" "fileperms" "600" 19 | fi 20 | 21 | exit 0 22 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.4.3.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | xpath="/config/services/compose/jobs/job" 11 | count=$(omv_config_get_count "${xpath}"); 12 | index=1; 13 | while [ ${index} -le ${count} ]; do 14 | pos="${xpath}[position()=${index}]" 15 | if ! omv_config_exists "${pos}/filestart"; then 16 | omv_config_add_key "${pos}" "filestart" "0" 17 | status=1 18 | fi 19 | if ! omv_config_exists "${pos}/filestop"; then 20 | omv_config_add_key "${pos}" "filestop" "0" 21 | status=1 22 | fi 23 | index=$(( index + 1 )) 24 | done; 25 | 26 | if [ ${status} -eq 1 ]; then 27 | omv_module_set_dirty compose 28 | fi 29 | 30 | exit 0 31 | 32 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.file.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose.file", 4 | "title": "compose file", 5 | "queryinfo": { 6 | "xpath": "//services/compose/files/file", 7 | "iterable": true, 8 | "idproperty": "uuid" 9 | }, 10 | "properties": { 11 | "uuid": { 12 | "type": "string", 13 | "format": "uuidv4" 14 | }, 15 | "name": { 16 | "type": "string" 17 | }, 18 | "description": { 19 | "type": "string" 20 | }, 21 | "body": { 22 | "type": "string" 23 | }, 24 | "showenv": { 25 | "type": "boolean" 26 | }, 27 | "env": { 28 | "type": "string" 29 | }, 30 | "showoverride": { 31 | "type": "boolean" 32 | }, 33 | "override": { 34 | "type": "string" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.6.0.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | echo "Updating database ..." 8 | 9 | change=0 10 | 11 | for i in execenable host port debug hostshell; do 12 | if omv_config_exists "/config/services/compose/${i}"; then 13 | omv_config_delete "/config/services/compose/${i}" 14 | change=1 15 | fi 16 | done 17 | 18 | if [ ${change} -eq 1 ]; then 19 | # stop and remove service 20 | term="omv_compose_term.service" 21 | unit="/etc/systemd/system/${term}" 22 | if [ -f "${unit}" ]; then 23 | systemctl stop "${term}" || : 24 | rm -fv "${unit}" 25 | systemctl daemon-reload 26 | fi 27 | rm -rfv /opt/omv_compose_term 28 | rm -fv /etc/omv_compose_term.conf 29 | omv_module_set_dirty compose 30 | fi 31 | 32 | exit 0 33 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.dockerfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose.dockerfile", 4 | "title": "dockerfile", 5 | "queryinfo": { 6 | "xpath": "//services/compose/dockerfiles/dockerfile", 7 | "iterable": true, 8 | "idproperty": "uuid" 9 | }, 10 | "properties": { 11 | "uuid": { 12 | "type": "string", 13 | "format": "uuidv4" 14 | }, 15 | "name": { 16 | "type": "string" 17 | }, 18 | "description": { 19 | "type": "string" 20 | }, 21 | "body": { 22 | "type": "string" 23 | }, 24 | "script": { 25 | "type": "string" 26 | }, 27 | "scriptfile": { 28 | "type": "string" 29 | }, 30 | "conf": { 31 | "type": "string" 32 | }, 33 | "conffile": { 34 | "type": "string" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-files-global-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-files-global-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | get: 10 | method: getGlobalEnv 11 | post: 12 | method: setGlobalEnv 13 | fields: 14 | - type: checkbox 15 | name: enabled 16 | label: _('Enabled') 17 | value: true 18 | - type: codeEditor 19 | name: globalenv 20 | label: _("Global Environment") 21 | value: "" 22 | language: "shell" 23 | buttons: 24 | - template: submit 25 | execute: 26 | type: url 27 | url: "/services/compose/files" 28 | - template: cancel 29 | execute: 30 | type: url 31 | url: "/services/compose/files" 32 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.11.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | xpath="/config/services/compose/jobs/job" 11 | count=$(omv_config_get_count "${xpath}"); 12 | index=1; 13 | while [ ${index} -le ${count} ]; do 14 | pos="${xpath}[position()=${index}]" 15 | if ! omv_config_exists "${pos}/backup"; then 16 | omv_config_add_key "${pos}" "backup" "1" 17 | status=1 18 | fi 19 | if ! omv_config_exists "${pos}/update"; then 20 | omv_config_add_key "${pos}" "update" "0" 21 | status=1 22 | fi 23 | index=$(( index + 1 )) 24 | done; 25 | 26 | if [ ${status} -eq 1 ]; then 27 | # update jobs to add backup and update fields 28 | echo "Regenerating jobs to add backup and update fields ..." 29 | omv-salt deploy run compose 30 | fi 31 | 32 | exit 0 33 | 34 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-files-url-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-file-url-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | post: 10 | method: setUrl 11 | fields: 12 | - type: textInput 13 | name: url 14 | label: _("URL") 15 | value: "" 16 | validators: 17 | required: true 18 | - type: textInput 19 | name: name 20 | label: _("Name") 21 | value: "" 22 | validators: 23 | required: true 24 | - type: textInput 25 | name: description 26 | label: _("Description") 27 | value: "" 28 | buttons: 29 | - template: submit 30 | execute: 31 | type: url 32 | url: "/services/compose/files" 33 | - template: cancel 34 | execute: 35 | type: url 36 | url: "/services/compose/files" 37 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/dashboard.d/containers_grid.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: dashboard-widget 3 | data: 4 | id: 76185fc5-024b-4246-93e7-735ef1331b20 5 | title: _("Containers") 6 | description: _("Displays information about containers in a grid.") 7 | type: grid 8 | reloadPeriod: 10000 9 | permissions: 10 | role: 11 | - admin 12 | - user 13 | grid: 14 | item: 15 | content: '{{ name }}' 16 | tooltip: '{% if status|slice(0,3) == "Up " %}{{ "Running" | translate }}{% else %}{{ "Not running" | translate }}{% endif %}' 17 | class: 'omv-text-center omv-text-nowrap {% if status|slice(0,3) == "Up " %}omv-background-color-pair-success{% else %}omv-background-color-pair-error{% endif %}' 18 | contentClass: 'omv-text-truncate' 19 | store: 20 | proxy: 21 | service: Compose 22 | get: 23 | method: getContainers 24 | filters: 25 | - operator: ne 26 | arg0: 27 | prop: name 28 | arg1: 'Docker' 29 | sorters: 30 | - prop: name 31 | dir: asc -------------------------------------------------------------------------------- /usr/sbin/omv-compose-prune: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable= 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.1 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i timeout=180 16 | declare -i i=0 17 | 18 | # logging location 19 | logDir="/var/log/" 20 | logFile="${logDir}/omv-compose-prune.log" 21 | 22 | _log() 23 | { 24 | msg=${1} 25 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composeupdate] ${msg}" | tee -a ${logFile} >&2 26 | } 27 | 28 | _log "Starting docker image prune ..." 29 | 30 | while pgrep -f omv-compose-update -l > /dev/null; do 31 | _log "Update is running. Waiting ... ${i}" 32 | sleep 10 33 | (( i++ )) 34 | if [ ${i} -gt ${timeout} ]; then 35 | _log "Timed out waiting. Exiting." 36 | exit 10 37 | fi 38 | done 39 | 40 | docker image prune -f | tee -a ${logFile} >&2 41 | 42 | _log "Done." 43 | 44 | exit 0 45 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/prune.j2: -------------------------------------------------------------------------------- 1 | {%- set separator = ' ' -%} 2 | {{ pillar['headers']['multiline'] -}} 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | # m h dom mon dow user command 6 | {%- for job in jobs | selectattr('enable') | selectattr('prune') %} 7 | {% if job.execution == "exactly" -%} 8 | {% if job.everynminute | to_bool %}*/{{ job.minute }}{% else %}{{ job.minute }}{% endif -%} 9 | {{ separator }}{% if job.everynhour | to_bool %}*/{{ job.hour }}{% else %}{{ job.hour }}{% endif -%} 10 | {{ separator }}{% if job.everyndayofmonth | to_bool %}*/{{ job.dayofmonth }}{% else %}{{ job.dayofmonth }}{% endif -%} 11 | {{ separator }}{{ job.month }}{{ separator }}{{ job.dayofweek }} 12 | {%- else -%} 13 | @{{ job.execution }} 14 | {%- endif -%} 15 | {{ separator }}root omv-compose-prune 16 | {%- if not job.sendemail | to_bool -%} 17 | {{ separator }}>/dev/null 2>&1 18 | {%- else -%} 19 | {{ separator }}2>&1 | mail -E -s "Cron{{ ' - ' ~ job.comment | replace('\n', ' ') if job.comment | length > 0 else '' }}" -a "From: Cron Daemon " root >/dev/null 2>&1 20 | {%- endif -%} 21 | {%- endfor -%} 22 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-stop-multi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2053,SC2086,SC2162 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.1 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i count=0 16 | declare -i index=0 17 | 18 | . /usr/share/openmediavault/scripts/helper-functions 19 | 20 | filter="${1}" 21 | 22 | if [[ "${filter}" == "*" ]]; then 23 | filter="" 24 | elif [[ "${filter}" == *","* ]]; then 25 | filter="@(${filter//,/|})" 26 | fi 27 | 28 | xpath="/config/services/compose/files/file" 29 | count=$(omv_config_get_count "${xpath}") 30 | index=1 31 | while [ ${index} -le ${count} ]; do 32 | pos="${xpath}[position()=${index}]" 33 | name=$(omv_config_get "${pos}/name") 34 | if [ -n "${filter}" ] && [[ "${name}" != ${filter} ]]; then 35 | index=$(( index + 1 )) 36 | continue 37 | fi 38 | echo ${name} 39 | omv-compose-stop "${name}" 40 | index=$(( index + 1 )) 41 | done; 42 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/stop.j2: -------------------------------------------------------------------------------- 1 | {%- set separator = ' ' -%} 2 | {{ pillar['headers']['multiline'] -}} 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | # m h dom mon dow user command 6 | {%- for job in jobs | selectattr('enable') | selectattr('filestop') %} 7 | {% if job.execution == "exactly" -%} 8 | {% if job.everynminute | to_bool %}*/{{ job.minute }}{% else %}{{ job.minute }}{% endif -%} 9 | {{ separator }}{% if job.everynhour | to_bool %}*/{{ job.hour }}{% else %}{{ job.hour }}{% endif -%} 10 | {{ separator }}{% if job.everyndayofmonth | to_bool %}*/{{ job.dayofmonth }}{% else %}{{ job.dayofmonth }}{% endif -%} 11 | {{ separator }}{{ job.month }}{{ separator }}{{ job.dayofweek }} 12 | {%- else -%} 13 | @{{ job.execution }} 14 | {%- endif -%} 15 | {{ separator }}root omv-compose-stop-multi '{{ job.filter }}' 16 | {%- if not job.sendemail | to_bool -%} 17 | {{ separator }}>/dev/null 2>&1 18 | {%- else -%} 19 | {{ separator }}2>&1 | mail -E -s "Cron{{ ' - ' ~ job.comment | replace('\n', ' ') if job.comment | length > 0 else '' }}" -a "From: Cron Daemon " root >/dev/null 2>&1 20 | {%- endif -%} 21 | {%- endfor -%} 22 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-start-multi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2053,SC2086,SC2162 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.1 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i count=0 16 | declare -i index=0 17 | 18 | . /usr/share/openmediavault/scripts/helper-functions 19 | 20 | filter="${1}" 21 | 22 | if [[ "${filter}" == "*" ]]; then 23 | filter="" 24 | elif [[ "${filter}" == *","* ]]; then 25 | filter="@(${filter//,/|})" 26 | fi 27 | 28 | xpath="/config/services/compose/files/file" 29 | count=$(omv_config_get_count "${xpath}") 30 | index=1 31 | while [ ${index} -le ${count} ]; do 32 | pos="${xpath}[position()=${index}]" 33 | name=$(omv_config_get "${pos}/name") 34 | if [ -n "${filter}" ] && [[ "${name}" != ${filter} ]]; then 35 | index=$(( index + 1 )) 36 | continue 37 | fi 38 | echo ${name} 39 | omv-compose-start "${name}" 40 | index=$(( index + 1 )) 41 | done; 42 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-update-multi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2053,SC2086,SC2162 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.2 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i count=0 16 | declare -i index=0 17 | 18 | . /usr/share/openmediavault/scripts/helper-functions 19 | 20 | filter="${1}" 21 | 22 | if [[ "${filter}" == "*" ]]; then 23 | filter="" 24 | elif [[ "${filter}" == *","* ]]; then 25 | filter="@(${filter//,/|})" 26 | fi 27 | 28 | xpath="/config/services/compose/files/file" 29 | count=$(omv_config_get_count "${xpath}") 30 | index=1 31 | while [ ${index} -le ${count} ]; do 32 | pos="${xpath}[position()=${index}]" 33 | name=$(omv_config_get "${pos}/name") 34 | if [ -n "${filter}" ] && [[ "${name}" != ${filter} ]]; then 35 | index=$(( index + 1 )) 36 | continue 37 | fi 38 | echo ${name} 39 | omv-compose-update "${name}" 40 | index=$(( index + 1 )) 41 | done; 42 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/start.j2: -------------------------------------------------------------------------------- 1 | {%- set separator = ' ' -%} 2 | {{ pillar['headers']['multiline'] -}} 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | # m h dom mon dow user command 6 | {%- for job in jobs | selectattr('enable') | selectattr('filestart') %} 7 | {% if job.execution == "exactly" -%} 8 | {% if job.everynminute | to_bool %}*/{{ job.minute }}{% else %}{{ job.minute }}{% endif -%} 9 | {{ separator }}{% if job.everynhour | to_bool %}*/{{ job.hour }}{% else %}{{ job.hour }}{% endif -%} 10 | {{ separator }}{% if job.everyndayofmonth | to_bool %}*/{{ job.dayofmonth }}{% else %}{{ job.dayofmonth }}{% endif -%} 11 | {{ separator }}{{ job.month }}{{ separator }}{{ job.dayofweek }} 12 | {%- else -%} 13 | @{{ job.execution }} 14 | {%- endif -%} 15 | {{ separator }}root omv-compose-start-multi '{{ job.filter }}' 16 | {%- if not job.sendemail | to_bool -%} 17 | {{ separator }}>/dev/null 2>&1 18 | {%- else -%} 19 | {{ separator }}2>&1 | mail -E -s "Cron{{ ' - ' ~ job.comment | replace('\n', ' ') if job.comment | length > 0 else '' }}" -a "From: Cron Daemon " root >/dev/null 2>&1 20 | {%- endif -%} 21 | {%- endfor -%} 22 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/update.j2: -------------------------------------------------------------------------------- 1 | {%- set separator = ' ' -%} 2 | {{ pillar['headers']['multiline'] -}} 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | # m h dom mon dow user command 6 | {%- for job in jobs | selectattr('enable') | selectattr('update') %} 7 | {% if job.execution == "exactly" -%} 8 | {% if job.everynminute | to_bool %}*/{{ job.minute }}{% else %}{{ job.minute }}{% endif -%} 9 | {{ separator }}{% if job.everynhour | to_bool %}*/{{ job.hour }}{% else %}{{ job.hour }}{% endif -%} 10 | {{ separator }}{% if job.everyndayofmonth | to_bool %}*/{{ job.dayofmonth }}{% else %}{{ job.dayofmonth }}{% endif -%} 11 | {{ separator }}{{ job.month }}{{ separator }}{{ job.dayofweek }} 12 | {%- else -%} 13 | @{{ job.execution }} 14 | {%- endif -%} 15 | {{ separator }}root omv-compose-update-multi '{{ job.filter }}' 16 | {%- if not job.sendemail | to_bool -%} 17 | {{ separator }}>/dev/null 2>&1 18 | {%- else -%} 19 | {{ separator }}2>&1 | mail -E -s "Cron{{ ' - ' ~ job.comment | replace('\n', ' ') if job.comment | length > 0 else '' }}" -a "From: Cron Daemon " root >/dev/null 2>&1 20 | {%- endif -%} 21 | {%- endfor -%} 22 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_6.0.4.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | xpath="/config/services/compose/files/file" 11 | count=$(omv_config_get_count "${xpath}"); 12 | index=1; 13 | while [ ${index} -le ${count} ]; do 14 | pos="${xpath}[position()=${index}]" 15 | if ! omv_config_exists "${pos}/env"; then 16 | omv_config_add_key "${pos}" "env" "" 17 | status=1 18 | fi 19 | index=$(( index + 1 )) 20 | done; 21 | 22 | if [ ${status} -eq 1 ]; then 23 | # update compose files and put in sub-directories 24 | echo "Regenerating compose files and moving to sub-directories ..." 25 | omv-salt deploy run compose 26 | 27 | # remove old compose files in root directory 28 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 29 | sfpath=$(omv_get_sharedfolder_path "${sfref}") 30 | echo "Removing old compose files from ${sfpath} ..." 31 | if [ -d "${sfpath}" ] && [ ! "${sfpath}" = "/" ]; then 32 | find "${sfpath}" -maxdepth 1 -type f -name "*.yml" -print -delete 33 | fi 34 | fi 35 | 36 | exit 0 37 | 38 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/files/backup.j2: -------------------------------------------------------------------------------- 1 | {%- set separator = ' ' -%} 2 | {{ pillar['headers']['multiline'] -}} 3 | SHELL=/bin/sh 4 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 5 | # m h dom mon dow user command 6 | {%- for job in jobs | selectattr('enable') | selectattr('backup') %} 7 | {% if job.execution == "exactly" -%} 8 | {% if job.everynminute | to_bool %}*/{{ job.minute }}{% else %}{{ job.minute }}{% endif -%} 9 | {{ separator }}{% if job.everynhour | to_bool %}*/{{ job.hour }}{% else %}{{ job.hour }}{% endif -%} 10 | {{ separator }}{% if job.everyndayofmonth | to_bool %}*/{{ job.dayofmonth }}{% else %}{{ job.dayofmonth }}{% endif -%} 11 | {{ separator }}{{ job.month }}{{ separator }}{{ job.dayofweek }} 12 | {%- else -%} 13 | @{{ job.execution }} 14 | {%- endif -%} 15 | {{ separator }}root omv-compose-backup-multi -f '{{ job.filter }}' -u '{{ job.uuid }}' 16 | {%- if not job.sendemail | to_bool -%} 17 | {{ separator }}>/dev/null 2>&1 18 | {%- else -%} 19 | {{ separator }}2>&1 | mail -E -s "Cron{{ ' - ' ~ job.comment | replace('\n', ' ') if job.comment | length > 0 else '' }}" -a "From: Cron Daemon " root >/dev/null 2>&1 20 | {%- endif -%} 21 | {%- endfor -%} 22 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/default.sls: -------------------------------------------------------------------------------- 1 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 2 | # @author OpenMediaVault Plugin Developers 3 | # @copyright Copyright (c) 2019-2025 openmediavault plugin developers 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | {% set dirpath = '/srv/salt' | path_join(tpldir) %} 19 | 20 | include: 21 | {% for file in salt['file.readdir'](dirpath) | sort %} 22 | {% if file not in ('.', '..', 'init.sls', 'default.sls') %} 23 | {% if file.endswith('.sls') %} 24 | - .{{ file | replace('.sls', '') }} 25 | {% endif %} 26 | {% endif %} 27 | {% endfor %} 28 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/migrations.d/conf.service.compose_7.5.0.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . /usr/share/openmediavault/scripts/helper-functions 6 | 7 | status=0 8 | 9 | echo "Updating database ..." 10 | 11 | if ! omv_config_exists "/config/services/compose/execenable"; then 12 | omv_config_add_key "/config/services/compose" "execenable" 0 13 | status=1 14 | fi 15 | 16 | if ! omv_config_exists "/config/services/compose/host"; then 17 | omv_config_add_key "/config/services/compose" "host" "0.0.0.0" 18 | status=1 19 | fi 20 | 21 | if ! omv_config_exists "/config/services/compose/port"; then 22 | omv_config_add_key "/config/services/compose" "port" "5000" 23 | status=1 24 | fi 25 | 26 | if ! omv_config_exists "/config/services/compose/debug"; then 27 | omv_config_add_key "/config/services/compose" "debug" "0" 28 | status=1 29 | fi 30 | 31 | xpath="/config/services/compose/jobs/job" 32 | count=$(omv_config_get_count "${xpath}"); 33 | index=1; 34 | while [ ${index} -le ${count} ]; do 35 | pos="${xpath}[position()=${index}]" 36 | if ! omv_config_exists "${pos}/verbose"; then 37 | omv_config_add_key "${pos}" "verbose" "1" 38 | status=1 39 | fi 40 | index=$(( index + 1 )) 41 | done; 42 | 43 | if [ ${status} -eq 1 ]; then 44 | omv_module_set_dirty compose 45 | fi 46 | 47 | exit 0 48 | -------------------------------------------------------------------------------- /debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | . /etc/default/openmediavault 6 | . /usr/share/openmediavault/scripts/helper-functions 7 | 8 | case "$1" in 9 | configure) 10 | # Activate package triggers. 11 | dpkg-trigger update-workbench 12 | 13 | # remove cache files 14 | rm -f /var/cache/openmediavault/compose_cache_* 15 | 16 | # Initialize and migrate configuration database. 17 | echo "Updating configuration database ..." 18 | omv-confdbadm create "conf.service.compose" 19 | if [ -n "$2" ]; then 20 | omv-confdbadm migrate "conf.service.compose" "${2}" 21 | fi 22 | 23 | # clear cache 24 | find /var/cache/openmediavault/ -type f -name "compose_cache_*" 25 | 26 | # mark dirty if cterm isn't installed 27 | cterm=$(dpkg -l | awk '$2 == "openmediavault-cterm" { print $1 }') 28 | if [ ! "${cterm}" = "ii" ]; then 29 | echo "openmediavault-cterm is not installed." 30 | echo "Marking compose saltstack module dirty." 31 | omv_module_set_dirty compose 32 | fi 33 | ;; 34 | 35 | abort-upgrade|abort-remove|abort-deconfigure) 36 | ;; 37 | 38 | *) 39 | echo "postinst called with unknown argument '$1'" >&2 40 | exit 1 41 | ;; 42 | esac 43 | 44 | exit 0 45 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-restore-multi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2086,SC2207,SC2162 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.9.2 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | . /usr/share/openmediavault/scripts/helper-functions 16 | 17 | filter=${1} 18 | 19 | if [ -z "${filter}" ]; then 20 | filter="*" 21 | fi 22 | 23 | if [[ "${filter}" == *","* ]]; then 24 | filter="@(${filter//,/|})" 25 | fi 26 | 27 | # Get the backup shared folder reference and path 28 | sfref=$(omv_config_get "/config/services/compose/backupsharedfolderref") 29 | if ! omv_isuuid "${sfref}"; then 30 | echo "No backup sharedfolder set." 31 | exit 13 32 | fi 33 | backuppath="$(omv_get_sharedfolder_path "${sfref}")" 34 | if [ ! -d "${backuppath}" ]; then 35 | echo "Backup shared folder directory does not exist. Exiting..." 36 | exit 14 37 | fi 38 | backuppath="${backuppath/%\/}" 39 | echo "Backup path :: ${backuppath}" 40 | 41 | find "${backuppath}" -mindepth 1 -maxdepth 1 -type d -iname "${filter}" | while read path; do 42 | echo "path :: ${path}" 43 | compose="$(basename "${path}")" 44 | echo "compose :: ${compose}" 45 | omv-compose-restore "${compose}" 46 | done 47 | 48 | exit 0 49 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: openmediavault-compose 2 | Section: net 3 | XB-Plugin-Section: utilities 4 | Priority: optional 5 | Maintainer: OpenMediaVault Plugin Developers 6 | Build-Depends: debhelper-compat (= 14), dh-sequence-single-binary 7 | Standards-Version: 4.7.2 8 | Homepage: http://omv-extras.org/ 9 | 10 | Package: openmediavault-compose 11 | Architecture: all 12 | Depends: openmediavault (>= 8), 13 | openmediavault-omvextrasorg (>= 8), 14 | openmediavault-sharerootfs, 15 | python3-docker, 16 | python3-pretty-yaml, 17 | ${misc:Depends} 18 | Recommends: docker-ce (>= 28.4), 19 | docker-compose-plugin (>= 2.39), 20 | openmediavault-cterm (>= 8) 21 | Description: OpenMediaVault compose plugin 22 | This plugin enhances OpenMediaVault by providing a comprehensive solution for managing 23 | Docker containers and their resources. Key features include: 24 | . 25 | * Docker-Compose Management: Maintain and execute Docker Compose files. 26 | * Automated Backups: Schedule regular backups of containers to ensure data integrity. 27 | * Backup Restoration: Restore backups to minimize downtime. 28 | * Cleanup Automation: Automatically purge outdated images, containers, and resources. 29 | * Container Monitoring: Access statistics on running containers. 30 | * Image Status Tracking: Monitor image statuses and relevant metrics. 31 | * Network Management: Create and manage Docker networks. 32 | * Log Viewing: View logs for your containers. 33 | . 34 | The openmediavault-cterm plugin is a mandatory requirement of this plugin. 35 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-configs-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-config-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | get: 10 | method: getConfig 11 | params: 12 | uuid: "{{ _routeParams.uuid }}" 13 | post: 14 | method: setConfig 15 | fields: 16 | - type: confObjUuid 17 | - type: textInput 18 | name: name 19 | label: _("Filename") 20 | value: "" 21 | disabled: '{{ _routeConfig.data.editing | toboolean }}' 22 | validators: 23 | required: true 24 | hint: _("This is the filename. This should not be a path.") 25 | - type: textInput 26 | name: description 27 | label: _("Description") 28 | value: "" 29 | - type: select 30 | name: fileref 31 | label: _('Compose File') 32 | textField: name 33 | valueField: uuid 34 | store: 35 | proxy: 36 | service: Compose 37 | get: 38 | method: enumerateFiles 39 | hint: _("The config file will be created in this compose file's directory.") 40 | - type: codeEditor 41 | name: body 42 | label: _("Body") 43 | value: "" 44 | language: "none" 45 | buttons: 46 | - template: submit 47 | execute: 48 | type: url 49 | url: "/services/compose/configs" 50 | - template: cancel 51 | execute: 52 | type: url 53 | url: "/services/compose/configs" 54 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-dockerfiles-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-dockerfile-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | get: 10 | method: getDockerfile 11 | params: 12 | uuid: "{{ _routeParams.uuid }}" 13 | post: 14 | method: setDockerfile 15 | fields: 16 | - type: confObjUuid 17 | - type: textInput 18 | name: name 19 | label: _("Name") 20 | value: "" 21 | disabled: '{{ _routeConfig.data.editing | toboolean }}' 22 | validators: 23 | required: true 24 | - type: textInput 25 | name: description 26 | label: _("Description") 27 | value: "" 28 | - type: codeEditor 29 | name: body 30 | label: _("Dockerfile") 31 | value: "" 32 | language: "yaml" 33 | - type: textInput 34 | name: script 35 | label: _("Script filename") 36 | value: "" 37 | - type: codeEditor 38 | name: scriptfile 39 | label: _("Script") 40 | value: "" 41 | language: "shell" 42 | - type: textInput 43 | name: conf 44 | label: _("Conf filename") 45 | value: "" 46 | - type: codeEditor 47 | name: conffile 48 | label: _("Conf file") 49 | value: "" 50 | language: "shell" 51 | buttons: 52 | - template: submit 53 | execute: 54 | type: url 55 | url: "/services/compose/dockerfiles" 56 | - template: cancel 57 | execute: 58 | type: url 59 | url: "/services/compose/dockerfiles" 60 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-files-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-file-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | get: 10 | method: getFile 11 | params: 12 | uuid: "{{ _routeParams.uuid }}" 13 | post: 14 | method: setFile 15 | fields: 16 | - type: confObjUuid 17 | - type: textInput 18 | name: name 19 | label: _("Name") 20 | value: "" 21 | disabled: '{{ _routeConfig.data.editing | toboolean }}' 22 | validators: 23 | required: true 24 | - type: textInput 25 | name: description 26 | label: _("Description") 27 | value: "" 28 | - type: codeEditor 29 | name: body 30 | label: _("File") 31 | value: "" 32 | language: "yaml" 33 | - type: checkbox 34 | name: showenv 35 | label: _("Show environment file") 36 | value: false 37 | - type: codeEditor 38 | name: env 39 | label: _("Environment") 40 | value: "" 41 | language: "shell" 42 | modifiers: 43 | - type: visible 44 | constraint: 45 | operator: truthy 46 | arg0: 47 | prop: showenv 48 | - type: checkbox 49 | name: showoverride 50 | label: _("Show override") 51 | value: false 52 | - type: codeEditor 53 | name: override 54 | label: _("Override") 55 | value: "" 56 | language: "yaml" 57 | modifiers: 58 | - type: visible 59 | constraint: 60 | operator: truthy 61 | arg0: 62 | prop: showoverride 63 | buttons: 64 | - template: submit 65 | execute: 66 | type: url 67 | url: "/services/compose/files" 68 | - template: cancel 69 | execute: 70 | type: url 71 | url: "/services/compose/files" 72 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-init-git: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /usr/share/openmediavault/scripts/helper-functions 4 | 5 | sfpath="${1}" 6 | 7 | if [ ! -d "${sfpath}" ]; then 8 | echo "Invalid directory: ${sfpath}" 9 | exit 1 10 | fi 11 | 12 | echo "Removing old .git directory (if found) ..." 13 | rm -rf "${sfpath}/.git" 14 | 15 | echo "Initializing new git repo ..." 16 | GIT_OPTIONAL_LOCKS=0 git init --initial-branch=main "${sfpath}" --quiet 17 | 18 | echo "Configuring ..." 19 | git -C "${sfpath}" config user.name "openmediavault-compose" 20 | git -C "${sfpath}" config user.email "compose@localhost" 21 | 22 | echo "Adding existing files to repo ..." 23 | cd "${sfpath}" || exit 2 24 | 25 | # Add global.env if it exists 26 | [ -f "global.env" ] && git add global.env 27 | 28 | # Add docker-compose relevant files 29 | for dir in */; do 30 | [ -d "${dir}" ] || continue 31 | name="${dir%/}" 32 | [ -f "${dir}${name}.yml" ] && git add "${dir}${name}.yml" 33 | [ -f "${dir}${name}.env" ] && git add "${dir}${name}.env" 34 | [ -f "${dir}compose.override.yml" ] && git add "${dir}compose.override.yml" 35 | done 36 | 37 | # Add configs from OMV database 38 | xpath="/config/services/compose/configs/config" 39 | count=$(omv_config_get_count "${xpath}") 40 | 41 | for (( index=1; index<=count; index++ )); do 42 | pos="${xpath}[position()=${index}]" 43 | if omv_config_exists "${pos}/name"; then 44 | cfgname="$(omv_config_get "${pos}/name")" 45 | cfguuid="$(omv_config_get "${pos}/fileref")" 46 | 47 | if [ -n "${cfgname}" ] && [ -n "${cfguuid}" ]; then 48 | filename="$(omv_config_get "/config/services/compose/files/file[uuid='${cfguuid}']/name")" 49 | if [ -n "${filename}" ]; then 50 | cfgpath="${filename}/${cfgname}" 51 | [ -f "${cfgpath}" ] && git add "${cfgpath}" 52 | fi 53 | fi 54 | fi 55 | done 56 | 57 | # Commit if there are staged changes 58 | if git diff --cached --quiet; then 59 | echo "No files to commit." 60 | else 61 | git commit --quiet --message "initial commit" 62 | echo "Initial commit created." 63 | fi 64 | 65 | echo "Done." 66 | 67 | exit 0 68 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-networks-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-networks-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | rowId: name 11 | stateId: d32e223c-0591-11ee-9d61-9bfb68b33e85 12 | sorters: 13 | - dir: asc 14 | prop: name 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getNetworksBg 20 | task: true 21 | columns: 22 | - name: _("Name") 23 | prop: name 24 | flexGrow: 1 25 | sortable: true 26 | - name: _("Driver") 27 | prop: driver 28 | flexGrow: 1 29 | sortable: true 30 | actions: 31 | - template: create 32 | execute: 33 | type: url 34 | url: "/services/compose/networks/create" 35 | - type: iconButton 36 | icon: mdi:delete 37 | tooltip: _("Delete") 38 | enabledConstraints: 39 | minSelected: 1 40 | maxSelected: 1 41 | execute: 42 | type: taskDialog 43 | taskDialog: 44 | config: 45 | title: _("docker network rm ...") 46 | startOnInit: true 47 | request: 48 | service: Compose 49 | method: doDockerNetworkCmd 50 | params: 51 | name: "{{ _selected[0].name }}" 52 | command: "rm" 53 | - type: iconButton 54 | icon: mdi:magnify 55 | tooltip: _("Inspect") 56 | enabledConstraints: 57 | minSelected: 1 58 | maxSelected: 1 59 | execute: 60 | type: taskDialog 61 | taskDialog: 62 | config: 63 | title: _("docker network inspect ...") 64 | startOnInit: true 65 | request: 66 | service: Compose 67 | method: doDockerNetworkCmd 68 | params: 69 | name: "{{ _selected[0].name }}" 70 | command: "inspect" 71 | buttons: 72 | stop: 73 | hidden: true 74 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-repos-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-repos-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | rowId: repo 11 | stateId: 1abf3ad8-b67a-11ef-904d-5bc7eacaba79 12 | sorters: 13 | - dir: asc 14 | prop: repo 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getRepoList 20 | columns: 21 | - name: _("Repo") 22 | prop: repo 23 | flexGrow: 4 24 | sortable: true 25 | actions: 26 | - type: iconButton 27 | icon: mdi:login 28 | tooltip: _("Login") 29 | execute: 30 | type: formDialog 31 | formDialog: 32 | title: _("Login to docker repo ...") 33 | fields: 34 | - type: textInput 35 | name: url 36 | label: _('URL') 37 | value: '' 38 | - type: textInput 39 | name: username 40 | label: _('Username') 41 | value: '' 42 | - type: passwordInput 43 | name: passwd 44 | label: _('Password') 45 | value: '' 46 | buttons: 47 | submit: 48 | text: _("Login") 49 | execute: 50 | type: request 51 | request: 52 | service: Compose 53 | method: repoLogin 54 | - type: iconButton 55 | icon: mdi:logout 56 | tooltip: _("Logout") 57 | enabledConstraints: 58 | minSelected: 1 59 | maxSelected: 1 60 | confirmationDialogConfig: 61 | template: confirmation-danger 62 | message: _("Are you sure you want to delete?") 63 | execute: 64 | type: request 65 | request: 66 | service: Compose 67 | method: repoLogout 68 | params: 69 | repo: "{{ _selected[0].repo }}" 70 | progressMessage: _("Logging out ...") 71 | successNotification: _("Logged out.") 72 | task: false 73 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-files-example-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-file-example-form-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | rowId: name 10 | stateId: ecd28e82-d9eb-11ef-9165-73923693377a 11 | sorters: 12 | - dir: asc 13 | prop: name 14 | store: 15 | proxy: 16 | service: Compose 17 | get: 18 | method: getExampleList 19 | columns: 20 | - name: " " 21 | prop: image 22 | flexGrow: 0.15 23 | cellTemplateName: image 24 | cellTemplateConfig: 25 | class: "mat-icon notranslate mat-icon-no-color" 26 | alt: " " 27 | src: "{{ image }}" 28 | - name: _("Name") 29 | prop: name 30 | flexGrow: 1 31 | sortable: true 32 | - name: _("Description") 33 | prop: description 34 | flexGrow: 3 35 | sortable: true 36 | actions: 37 | - type: iconButton 38 | icon: mdi:plus-box 39 | tooltip: _("Add example compose file") 40 | enabledConstraints: 41 | minSelected: 1 42 | maxSelected: 1 43 | execute: 44 | type: formDialog 45 | formDialog: 46 | title: _("Add...") 47 | fields: 48 | - type: hidden 49 | name: example 50 | value: "{{ _selected[0].name }}" 51 | - type: textInput 52 | name: name 53 | label: _("Name") 54 | value: "{{ _selected[0].name }}" 55 | - type: textInput 56 | name: description 57 | label: _("Description") 58 | value: "" 59 | buttons: 60 | submit: 61 | text: _("Add") 62 | execute: 63 | type: request 64 | request: 65 | service: Compose 66 | method: setExample 67 | progressMessage: _("Adding an example compose file ...") 68 | successNotification: _("Example compose file has been added.") 69 | successUrl: /services/compose/files 70 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-networks-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-network-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | post: 10 | method: setNetwork 11 | fields: 12 | - type: confObjUuid 13 | - type: textInput 14 | name: name 15 | label: _("Name") 16 | value: "" 17 | validators: 18 | required: true 19 | - type: select 20 | name: driver 21 | label: _("Driver") 22 | value: "bridge" 23 | store: 24 | data: 25 | - ["bridge", "bridge"] 26 | - ["ipvlan", "ipvlan"] 27 | - ["macvlan", "macvlan"] 28 | - ["overlay", "overlay"] 29 | - type: textInput 30 | name: parentnetwork 31 | label: _("Parent network") 32 | value: '' 33 | suggestions: true 34 | store: 35 | proxy: 36 | service: Compose 37 | get: 38 | method: enumerateNetworkList 39 | modifiers: 40 | - type: visible 41 | constraint: 42 | operator: eq 43 | arg0: 44 | prop: driver 45 | arg1: "macvlan" 46 | - type: container 47 | fields: 48 | - type: textInput 49 | name: subnet 50 | label: _("Subnet") 51 | value: "" 52 | hint: _("e.g. 172.20.0.0/16") 53 | - type: textInput 54 | name: gateway 55 | label: _("Gateway") 56 | value: "" 57 | hint: _("e.g. 172.20.0.1") 58 | validators: 59 | patternType: ipv4 60 | - type: textInput 61 | name: iprange 62 | label: _("IP range") 63 | value: "" 64 | hint: _("e.g. 172.20.10.128/25") 65 | - type: textInput 66 | name: auxaddress 67 | label: _("Aux address") 68 | value: "" 69 | hint: _("Format is name=ip. Comma separate multiple entries.
e.g. my-router=192.168.10.5,my-nas=192.168.20.6") 70 | buttons: 71 | - template: submit 72 | execute: 73 | type: url 74 | url: "/services/compose/networks" 75 | - template: cancel 76 | execute: 77 | type: url 78 | url: "/services/compose/networks" 79 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-volumes-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-volumes-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | rowId: name 11 | stateId: 834f2d10-0591-11ee-bcac-d35108090f95 12 | sorters: 13 | - dir: asc 14 | prop: name 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getVolumesBg 20 | task: true 21 | columns: 22 | - name: _("Name") 23 | prop: name 24 | flexGrow: 2 25 | sortable: true 26 | - name: _("Size") 27 | prop: size 28 | flexGrow: 1 29 | sortable: true 30 | cellTemplateName: template 31 | cellTemplateConfig: '{{ size | tobytes | binaryunit | notavailable("-") }}' 32 | - name: _("Mountpoint") 33 | prop: mountpoint 34 | flexGrow: 4 35 | sortable: true 36 | - name: _("Driver") 37 | prop: driver 38 | flexGrow: 1 39 | sortable: true 40 | actions: 41 | - type: iconButton 42 | icon: mdi:delete 43 | tooltip: _("Delete") 44 | enabledConstraints: 45 | minSelected: 1 46 | maxSelected: 1 47 | execute: 48 | type: taskDialog 49 | taskDialog: 50 | config: 51 | title: _("docker volume rm ...") 52 | startOnInit: true 53 | request: 54 | service: Compose 55 | method: doDockerVolumeCmd 56 | params: 57 | name: "{{ _selected[0].name }}" 58 | command: "rm" 59 | - type: iconButton 60 | icon: mdi:magnify 61 | tooltip: _("Inspect") 62 | enabledConstraints: 63 | minSelected: 1 64 | maxSelected: 1 65 | execute: 66 | type: taskDialog 67 | taskDialog: 68 | config: 69 | title: _("docker volume inspect ...") 70 | startOnInit: true 71 | request: 72 | service: Compose 73 | method: doDockerVolumeCmd 74 | params: 75 | name: "{{ _selected[0].name }}" 76 | command: "inspect" 77 | buttons: 78 | stop: 79 | hidden: true 80 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/40job.sls: -------------------------------------------------------------------------------- 1 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 2 | # @author OpenMediaVault Plugin Developers 3 | # @copyright Copyright (c) 2024 OpenMediaVault Plugin Developers 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | {% set config = salt['omv_conf.get']('conf.service.compose.job') %} 19 | 20 | configure_compose_scheduled_backup: 21 | file.managed: 22 | - name: "/etc/cron.d/omv-compose-backup" 23 | - source: 24 | - salt://{{ tpldir }}/files/backup.j2 25 | - template: jinja 26 | - context: 27 | jobs: {{ config | json }} 28 | - user: root 29 | - group: root 30 | - mode: 644 31 | 32 | configure_compose_scheduled_update: 33 | file.managed: 34 | - name: "/etc/cron.d/omv-compose-update" 35 | - source: 36 | - salt://{{ tpldir }}/files/update.j2 37 | - template: jinja 38 | - context: 39 | jobs: {{ config | json }} 40 | - user: root 41 | - group: root 42 | - mode: 644 43 | 44 | configure_compose_scheduled_prune: 45 | file.managed: 46 | - name: "/etc/cron.d/omv-compose-prune" 47 | - source: 48 | - salt://{{ tpldir }}/files/prune.j2 49 | - template: jinja 50 | - context: 51 | jobs: {{ config | json }} 52 | - user: root 53 | - group: root 54 | - mode: 644 55 | 56 | configure_compose_scheduled_start: 57 | file.managed: 58 | - name: "/etc/cron.d/omv-compose-start" 59 | - source: 60 | - salt://{{ tpldir }}/files/start.j2 61 | - template: jinja 62 | - context: 63 | jobs: {{ config | json }} 64 | - user: root 65 | - group: root 66 | - mode: 644 67 | 68 | configure_compose_scheduled_stop: 69 | file.managed: 70 | - name: "/etc/cron.d/omv-compose-stop" 71 | - source: 72 | - salt://{{ tpldir }}/files/stop.j2 73 | - template: jinja 74 | - context: 75 | jobs: {{ config | json }} 76 | - user: root 77 | - group: root 78 | - mode: 644 79 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-stop: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2086,SC2207 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.1 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i timeout=180 16 | declare -i i=0 17 | 18 | . /usr/share/openmediavault/scripts/helper-functions 19 | 20 | # logging location 21 | logDir="/var/log/" 22 | logFile="${logDir}/omv-compose-stop.log" 23 | 24 | _log() 25 | { 26 | msg=${1} 27 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composestop] ${msg}" | tee -a ${logFile} >&2 28 | } 29 | 30 | compose="${1}" 31 | if [ -z "${compose}" ]; then 32 | _log "No compose name set. Exiting..." 33 | exit 10 34 | fi 35 | _log "compose :: ${compose}" 36 | 37 | # Get docker storage path 38 | dockerStorage=$(omv_config_get "/config/services/compose/dockerStorage") 39 | _log "Docker storage :: ${dockerStorage}" 40 | 41 | # Get the shared folder reference and path 42 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 43 | if ! omv_isuuid "${sfref}"; then 44 | _log "No compose sharedfolder set." 45 | exit 11 46 | fi 47 | sfpath="$(omv_get_sharedfolder_path "${sfref}")" 48 | if [ ! -d "${sfpath}" ]; then 49 | _log "Shared folder directory does not exist. Exiting..." 50 | exit 12 51 | fi 52 | sfpath="${sfpath/%\/}" 53 | sfpath="${sfpath//\/\//\/}" 54 | _log "Compose file path :: ${sfpath}" 55 | 56 | # set path for yml and env files 57 | composepath="${sfpath}/${compose}" 58 | env="${composepath}/${compose}.env" 59 | globalenv="${sfpath}/global.env" 60 | ovr="${composepath}/compose.override.yml" 61 | yml="${composepath}/${compose}.yml" 62 | if [ ! -f "${yml}" ]; then 63 | _log "Compose file '${yml}' does not exist. Exiting..." 64 | exit 13 65 | fi 66 | _log "Compose file :: ${yml}" 67 | 68 | while pgrep -f omv-compose-backup -l > /dev/null; do 69 | _log "Backup is running. Waiting ... ${i}" 70 | sleep 10 71 | (( i++ )) 72 | if [ ${i} -gt ${timeout} ]; then 73 | _log "Timed out waiting. Exiting." 74 | exit 15 75 | fi 76 | done 77 | 78 | # build compose arguments 79 | dockerComposeArgs=("--file" "${yml}") 80 | if [ -f "${ovr}" ]; then 81 | dockerComposeArgs+=("--file" "${ovr}") 82 | fi 83 | if [ -f "${globalenv}" ]; then 84 | dockerComposeArgs+=("--env-file" "${globalenv}") 85 | fi 86 | dockerComposeArgs+=("--env-file" "${env}") 87 | 88 | # stop container(s) 89 | _log "Stopping container(s) ..." 90 | docker compose "${dockerComposeArgs[@]}" stop 91 | 92 | _log "Done." 93 | 94 | exit 0 95 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2086,SC2207 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.0.1 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i timeout=180 16 | declare -i i=0 17 | 18 | . /usr/share/openmediavault/scripts/helper-functions 19 | 20 | # logging location 21 | logDir="/var/log/" 22 | logFile="${logDir}/omv-compose-start.log" 23 | 24 | _log() 25 | { 26 | msg=${1} 27 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composestart] ${msg}" | tee -a ${logFile} >&2 28 | } 29 | 30 | compose="${1}" 31 | if [ -z "${compose}" ]; then 32 | _log "No compose name set. Exiting..." 33 | exit 10 34 | fi 35 | _log "compose :: ${compose}" 36 | 37 | # Get docker storage path 38 | dockerStorage=$(omv_config_get "/config/services/compose/dockerStorage") 39 | _log "Docker storage :: ${dockerStorage}" 40 | 41 | # Get the shared folder reference and path 42 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 43 | if ! omv_isuuid "${sfref}"; then 44 | _log "No compose sharedfolder set." 45 | exit 11 46 | fi 47 | sfpath="$(omv_get_sharedfolder_path "${sfref}")" 48 | if [ ! -d "${sfpath}" ]; then 49 | _log "Shared folder directory does not exist. Exiting..." 50 | exit 12 51 | fi 52 | sfpath="${sfpath/%\/}" 53 | sfpath="${sfpath//\/\//\/}" 54 | _log "Compose file path :: ${sfpath}" 55 | 56 | # set path for yml and env files 57 | composepath="${sfpath}/${compose}" 58 | env="${composepath}/${compose}.env" 59 | globalenv="${sfpath}/global.env" 60 | ovr="${composepath}/compose.override.yml" 61 | yml="${composepath}/${compose}.yml" 62 | if [ ! -f "${yml}" ]; then 63 | _log "Compose file '${yml}' does not exist. Exiting..." 64 | exit 13 65 | fi 66 | _log "Compose file :: ${yml}" 67 | 68 | while pgrep -f omv-compose-backup -l > /dev/null; do 69 | _log "Backup is running. Waiting ... ${i}" 70 | sleep 10 71 | (( i++ )) 72 | if [ ${i} -gt ${timeout} ]; then 73 | _log "Timed out waiting. Exiting." 74 | exit 15 75 | fi 76 | done 77 | 78 | # build compose arguments 79 | dockerComposeArgs=("--file" "${yml}") 80 | if [ -f "${ovr}" ]; then 81 | dockerComposeArgs+=("--file" "${ovr}") 82 | fi 83 | if [ -f "${globalenv}" ]; then 84 | dockerComposeArgs+=("--env-file" "${globalenv}") 85 | fi 86 | dockerComposeArgs+=("--env-file" "${env}") 87 | 88 | # start container(s) 89 | _log "Starting container(s) ..." 90 | docker compose "${dockerComposeArgs[@]}" start 91 | 92 | _log "Done." 93 | 94 | exit 0 95 | -------------------------------------------------------------------------------- /usr/share/openmediavault/engined/inc/90composebackup.inc: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | require_once("openmediavault/functions.inc"); 20 | 21 | \OMV\System\LogFileSpec::registerSpecification("omv-compose-backup", [ 22 | "filename" => "omv-compose-backup.log", 23 | "filepath" => "/var/log/omv-compose-backup.log", 24 | "regex" => "/^\[((.*?)\s+(.*?))\]\s+\[(.*?)\]\s+(.*?)$/", 25 | "columns" => [ 26 | "date" => 1, 27 | "action" => 4, 28 | "message" => 5 29 | ] 30 | ]); 31 | 32 | \OMV\System\LogFileSpec::registerSpecification("omv-compose-restore", [ 33 | "filename" => "omv-compose-restore.log", 34 | "filepath" => "/var/log/omv-compose-restore.log", 35 | "regex" => "/^\[((.*?)\s+(.*?))\]\s+\[(.*?)\]\s+(.*?)$/", 36 | "columns" => [ 37 | "date" => 1, 38 | "action" => 4, 39 | "message" => 5 40 | ] 41 | ]); 42 | 43 | \OMV\System\LogFileSpec::registerSpecification("omv-compose-update", [ 44 | "filename" => "omv-compose-update.log", 45 | "filepath" => "/var/log/omv-compose-update.log", 46 | "regex" => "/^\[((.*?)\s+(.*?))\]\s+\[(.*?)\]\s+(.*?)$/", 47 | "columns" => [ 48 | "date" => 1, 49 | "action" => 4, 50 | "message" => 5 51 | ] 52 | ]); 53 | 54 | \OMV\System\LogFileSpec::registerSpecification("omv-compose-start", [ 55 | "filename" => "omv-compose-start.log", 56 | "filepath" => "/var/log/omv-compose-start.log", 57 | "regex" => "/^\[((.*?)\s+(.*?))\]\s+\[(.*?)\]\s+(.*?)$/", 58 | "columns" => [ 59 | "date" => 1, 60 | "action" => 4, 61 | "message" => 5 62 | ] 63 | ]); 64 | 65 | \OMV\System\LogFileSpec::registerSpecification("omv-compose-stop", [ 66 | "filename" => "omv-compose-stop.log", 67 | "filepath" => "/var/log/omv-compose-stop.log", 68 | "regex" => "/^\[((.*?)\s+(.*?))\]\s+\[(.*?)\]\s+(.*?)$/", 69 | "columns" => [ 70 | "date" => 1, 71 | "action" => 4, 72 | "message" => 5 73 | ] 74 | ]); 75 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-restore-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-restore-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | rowId: backup 10 | stateId: d4f9aeb6-1b7f-11ee-8b96-b71aa60c188d 11 | store: 12 | proxy: 13 | service: Compose 14 | get: 15 | method: getRestoreList 16 | columns: 17 | - name: _('Backup') 18 | prop: backup 19 | flexGrow: 1 20 | sortable: true 21 | - name: _("Size") 22 | prop: backupsize 23 | flexGrow: 1 24 | sortable: true 25 | cellTemplateName: template 26 | cellTemplateConfig: '{{ backupsize | tobytes | binaryunit | notavailable("-") }}' 27 | - name: _('Backup Time') 28 | prop: time 29 | flexGrow: 1 30 | sortable: true 31 | actions: 32 | - type: iconButton 33 | tooltip: _("Restore") 34 | icon: mdi:restore 35 | enabledConstraints: 36 | minSelected: 1 37 | maxSelected: 1 38 | execute: 39 | type: taskDialog 40 | taskDialog: 41 | config: 42 | title: _("Restore Compose ...") 43 | startOnInit: false 44 | request: 45 | service: Compose 46 | method: doRestore 47 | params: 48 | backup: "{{ _selected[0].backup }}" 49 | - type: iconButton 50 | tooltip: _("Restore global.env") 51 | icon: mdi:file-restore-outline 52 | confirmationDialogConfig: 53 | template: confirmation-danger 54 | message: _("Are you sure you want to restore global.env from backup?") 55 | execute: 56 | type: request 57 | request: 58 | service: Compose 59 | method: restoreGlobalEnv 60 | progressMessage: _("Restoring global.env from backup ...") 61 | successNotification: _("global.env has been restored from backup.") 62 | - type: iconButton 63 | icon: mdi:delete 64 | tooltip: _("Delete") 65 | confirmationDialogConfig: 66 | template: confirmation-danger 67 | message: _("Are you sure you want to delete this backup?") 68 | enabledConstraints: 69 | minSelected: 1 70 | maxSelected: 1 71 | execute: 72 | type: taskDialog 73 | taskDialog: 74 | config: 75 | title: _("Delete backup ...") 76 | startOnInit: true 77 | request: 78 | service: Compose 79 | method: deleteBackup 80 | params: 81 | name: "{{ _selected[0].backup }}" 82 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.job.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose.job", 4 | "title": "compose backup scheduled job", 5 | "queryinfo": { 6 | "xpath": "//services/compose/jobs/job", 7 | "iterable": true, 8 | "idproperty": "uuid" 9 | }, 10 | "properties": { 11 | "uuid": { 12 | "type": "string", 13 | "format": "uuidv4" 14 | }, 15 | "enable": { 16 | "type": "boolean", 17 | "default": false 18 | }, 19 | "filter": { 20 | "type": "string", 21 | "default": "" 22 | }, 23 | "backup": { 24 | "type": "boolean", 25 | "default": true 26 | }, 27 | "prebackup": { 28 | "type": "string", 29 | "default": "" 30 | }, 31 | "postbackup": { 32 | "type": "string", 33 | "default": "" 34 | }, 35 | "update": { 36 | "type": "boolean", 37 | "default": false 38 | }, 39 | "prune": { 40 | "type": "boolean", 41 | "default": false 42 | }, 43 | "filestart": { 44 | "type": "boolean", 45 | "default": false 46 | }, 47 | "filestop": { 48 | "type": "boolean", 49 | "default": false 50 | }, 51 | "sendemail": { 52 | "type": "boolean", 53 | "default": false 54 | }, 55 | "verbose": { 56 | "type": "boolean", 57 | "default": true 58 | }, 59 | "comment": { 60 | "type": "string", 61 | "default": "" 62 | }, 63 | "excludes": { 64 | "type": "string", 65 | "default": "" 66 | }, 67 | "execution": { 68 | "type": "string", 69 | "enum": [ 70 | "exactly", 71 | "hourly", 72 | "daily", 73 | "weekly", 74 | "monthly", 75 | "yearly", 76 | "reboot" 77 | ], 78 | "default": "exactly" 79 | }, 80 | "minute": { 81 | "type": "string", 82 | "pattern": "^[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[*]$", 83 | "default": 0 84 | }, 85 | "everynminute": { 86 | "type": "boolean", 87 | "default": false 88 | }, 89 | "hour": { 90 | "type": "string", 91 | "pattern": "^[0-9]|1[0-9]|2[0-3]|[*]$", 92 | "default": 0 93 | }, 94 | "everynhour": { 95 | "type": "boolean", 96 | "default": false 97 | }, 98 | "month": { 99 | "type": "string", 100 | "pattern": "^[1-9]|1[0-2]|[*]$", 101 | "default": "*" 102 | }, 103 | "dayofmonth": { 104 | "type": "string", 105 | "pattern": "^[1-9]|1[0-9]|2[0-9]|3[0-1]|[*]$", 106 | "default": "*" 107 | }, 108 | "everyndayofmonth": { 109 | "type": "boolean", 110 | "default": false 111 | }, 112 | "dayofweek": { 113 | "type": "string", 114 | "pattern": "^[1-7]|[*]$", 115 | "default": "*" 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/20dockerfile.sls: -------------------------------------------------------------------------------- 1 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 2 | # @author OpenMediaVault Plugin Developers 3 | # @copyright Copyright (c) 2022-2025 openmediavault plugin developers 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | {% set config = salt['omv_conf.get']('conf.service.compose') %} 19 | {% if config.sharedfolderref | length > 0 %} 20 | {% set sfpath = salt['omv_conf.get_sharedfolder_path'](config.sharedfolderref).rstrip('/') %} 21 | 22 | {% for file in config.dockerfiles.dockerfile %} 23 | {% set dockerfileDir = sfpath ~ '/' ~ file.name %} 24 | {% set dockerFile = dockerfileDir ~ '/Dockerfile' %} 25 | 26 | configure_compose_dockerfile_dir_{{ file.name }}: 27 | file.directory: 28 | - name: "{{ dockerfileDir }}" 29 | - user: "{{ config.composeowner }}" 30 | - group: "{{ config.composegroup }}" 31 | - mode: "{{ config.mode }}" 32 | - makedirs: True 33 | 34 | configure_dockerfile_{{ dockerFile }}: 35 | file.managed: 36 | - name: '{{ dockerFile }}' 37 | - source: 38 | - salt://{{ tpldir }}/files/dockerfile.j2 39 | - context: 40 | file: {{ file | json }} 41 | - template: jinja 42 | - user: "{{ config.composeowner }}" 43 | - group: "{{ config.composegroup }}" 44 | - mode: "{{ config.fileperms }}" 45 | 46 | {% if file.script | length > 0 %} 47 | {% set scriptFile = dockerfileDir ~ '/' ~ file.script %} 48 | 49 | configure_dockerfile_script_{{ scriptFile }}: 50 | file.managed: 51 | - name: '{{ scriptFile }}' 52 | - source: 53 | - salt://{{ tpldir }}/files/dockerfile_script.j2 54 | - context: 55 | file: {{ file | json }} 56 | - template: jinja 57 | - user: "{{ config.composeowner }}" 58 | - group: "{{ config.composegroup }}" 59 | - mode: "{{ config.fileperms }}" 60 | 61 | {% endif %} 62 | 63 | {% if file.conf | length > 0 %} 64 | {% set confFile = dockerfileDir ~ '/' ~ file.conf %} 65 | 66 | configure_dockerfile_conf_{{ confFile }}: 67 | file.managed: 68 | - name: '{{ confFile }}' 69 | - source: 70 | - salt://{{ tpldir }}/files/dockerfile_conf.j2 71 | - context: 72 | file: {{ file | json }} 73 | - template: jinja 74 | - user: "{{ config.composeowner }}" 75 | - group: "{{ config.composegroup }}" 76 | - mode: "{{ config.fileperms }}" 77 | 78 | {% endif %} 79 | 80 | {% endfor %} 81 | {% endif %} 82 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/30docker.sls: -------------------------------------------------------------------------------- 1 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 2 | # @author OpenMediaVault Plugin Developers 3 | # @copyright Copyright (c) 2022-2025 openmediavault plugin developers 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | {% set config = salt['omv_conf.get']('conf.service.compose') %} 19 | {% set nocterm = salt['pillar.get']('default:OMV_NO_CTERM_DEPENDENCY', 'no') -%} 20 | 21 | # create daemon.json file if docker storage path is specified 22 | {% if config.dockerStorage | length > 1 %} 23 | 24 | configure_etc_docker_dir: 25 | file.directory: 26 | - name: "/etc/docker" 27 | - user: "root" 28 | - group: "root" 29 | - mode: "0755" 30 | - makedirs: True 31 | 32 | /etc/docker/daemon.json: 33 | file.serialize: 34 | - dataset: 35 | data-root: "{{ config.dockerStorage }}" 36 | storage-driver: "overlay2" 37 | {% if config.logmaxsize|int > 0 %} 38 | log-driver: "json-file" 39 | log-opts: 40 | max-size: "{{ config.logmaxsize }}m" 41 | max-file: "3" 42 | {% endif %} 43 | {% if config.liverestore %} 44 | live-restore: true 45 | {% endif %} 46 | - serializer: json 47 | - user: root 48 | - group: root 49 | - mode: "0600" 50 | 51 | {% endif %} 52 | 53 | docker_install_packages: 54 | pkg.installed: 55 | - pkgs: 56 | - docker-ce: '>=27.2.1' 57 | 58 | docker_compose_install_packages: 59 | pkg.installed: 60 | - pkgs: 61 | - docker-compose-plugin: '>=2.29.2' 62 | - containerd.io: '>=1.7.21' 63 | - docker-ce-cli: '>=27.2.1' 64 | - docker-buildx-plugin: '>=0.16.2' 65 | {% if not nocterm | to_bool %} 66 | - openmediavault-cterm: '>= 7.8.5' 67 | {% endif %} 68 | 69 | docker_purged_packages: 70 | pkg.purged: 71 | - pkgs: 72 | - docker-compose 73 | - docker.io 74 | 75 | {% if config.dockerStorage | length > 1 %} 76 | 77 | docker: 78 | service.running: 79 | - enable: True 80 | - watch: 81 | - file: /etc/docker/daemon.json 82 | 83 | {% endif %} 84 | 85 | {% set mounts = salt['cmd.shell']('systemctl list-units --type=mount | awk \'$5 ~ "/srv" { printf "%s ",$1 }\'') %} 86 | {% set waitConf = '/etc/systemd/system/docker.service.d/waitAllMounts.conf' %} 87 | 88 | {{ waitConf }}: 89 | file.managed: 90 | - contents: | 91 | [Unit] 92 | After=local-fs.target {{ mounts }} 93 | - mode: "0644" 94 | - makedirs: True 95 | 96 | systemd_daemon_reload_docker: 97 | cmd.run: 98 | - name: systemctl daemon-reload 99 | - onchanges: 100 | - file: {{ waitConf }} 101 | 102 | create_usr_local_bin_dir: 103 | file.directory: 104 | - name: "/usr/local/bin" 105 | - user: root 106 | - group: root 107 | - mode: "0755" 108 | - makedirs: True 109 | -------------------------------------------------------------------------------- /usr/share/openmediavault/engined/module/compose.inc: -------------------------------------------------------------------------------- 1 | . 18 | */ 19 | 20 | class OMVModuleCompose extends \OMV\Engine\Module\ServiceAbstract 21 | implements \OMV\Engine\Notify\IListener, \OMV\Engine\Module\IServiceStatus 22 | { 23 | public function getName() 24 | { 25 | return "compose"; 26 | } 27 | 28 | public function getStatus() 29 | { 30 | if (file_exists("/lib/systemd/system/docker.service")) { 31 | $systemCtl = new \OMV\System\SystemCtl("docker"); 32 | $enabled = $systemCtl->isEnabled(); 33 | $running = $systemCtl->isActive(); 34 | } else { 35 | $enabled = false; 36 | $running = false; 37 | } 38 | return array( 39 | "name" => "Docker", 40 | "title" => "Docker", 41 | "enabled" => $enabled, 42 | "running" => $running 43 | ); 44 | } 45 | 46 | final public function onSharedFolder($type, $path, $object) 47 | { 48 | $db = \OMV\Config\Database::getInstance(); 49 | if (TRUE === $db->exists("conf.service.compose.file", [ 50 | "operator" => "stringEquals", 51 | "arg0" => "sharedfolderref", 52 | "arg1" => $object['uuid'] 53 | ])) { 54 | $this->setDirty(); 55 | } 56 | } 57 | 58 | public function bindListeners(\OMV\Engine\Notify\Dispatcher $dispatcher) 59 | { 60 | $dispatcher->addListener( 61 | OMV_NOTIFY_MODIFY, 62 | "org.openmediavault.conf.service.compose", 63 | [ $this, "setDirty" ] 64 | ); 65 | $dispatcher->addListener( 66 | OMV_NOTIFY_CREATE | OMV_NOTIFY_DELETE, 67 | "org.openmediavault.conf.service.compose.file", 68 | [ $this, "setDirty" ] 69 | ); 70 | $dispatcher->addListener( 71 | OMV_NOTIFY_MODIFY, 72 | "org.openmediavault.conf.service.compose.globalenv", 73 | [ $this, "setDirty" ] 74 | ); 75 | $dispatcher->addListener( 76 | OMV_NOTIFY_CREATE | OMV_NOTIFY_DELETE, 77 | "org.openmediavault.conf.service.compose.dockerfile", 78 | [ $this, "setDirty" ] 79 | ); 80 | $dispatcher->addListener( 81 | OMV_NOTIFY_CREATE | OMV_NOTIFY_MODIFY | OMV_NOTIFY_DELETE, 82 | "org.openmediavault.conf.service.compose.job", 83 | [ $this, "setDirty" ] 84 | ); 85 | $dispatcher->addListener( 86 | OMV_NOTIFY_MODIFY, 87 | "org.openmediavault.conf.system.sharedfolder", 88 | [ $this, "onSharedFolder" ] 89 | ); 90 | $dispatcher->addListener( 91 | OMV_NOTIFY_MODIFY, 92 | "org.openmediavault.conf.system.sharedfolder.privilege", 93 | [ $this, "onSharedFolder" ] 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-schedule-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-schedule-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | stateId: 721723ee-1843-11ee-8845-f794b6ff9154 10 | store: 11 | proxy: 12 | service: Compose 13 | get: 14 | method: getJobList 15 | columns: 16 | - name: _('Enabled') 17 | prop: enable 18 | flexGrow: 1 19 | sortable: true 20 | cellTemplateName: checkIcon 21 | - name: _("Filter") 22 | prop: filter 23 | flexGrow: 1 24 | sortable: true 25 | - name: _("Excludes") 26 | prop: excludes 27 | flexGrow: 1 28 | hidden: true 29 | - name: _('Backup') 30 | prop: backup 31 | flexGrow: 1 32 | sortable: true 33 | cellTemplateName: checkIcon 34 | - name: _('Update') 35 | prop: update 36 | flexGrow: 1 37 | sortable: true 38 | cellTemplateName: checkIcon 39 | - name: _('Prune') 40 | prop: prune 41 | flexGrow: 1 42 | sortable: true 43 | cellTemplateName: checkIcon 44 | - name: _('Start') 45 | prop: filestart 46 | flexGrow: 1 47 | sortable: true 48 | cellTemplateName: checkIcon 49 | - name: _('Stop') 50 | prop: filestop 51 | flexGrow: 1 52 | sortable: true 53 | cellTemplateName: checkIcon 54 | - name: _('Verbose') 55 | prop: verbose 56 | flexGrow: 1 57 | sortable: true 58 | cellTemplateName: checkIcon 59 | hidden: true 60 | - name: _('Scheduling') 61 | prop: '' 62 | flexGrow: 1 63 | cellTemplateName: template 64 | cellTemplateConfig: | 65 | {% if execution == "exactly" %} 66 | {% set _minute = minute %} 67 | {% set _hour = hour %} 68 | {% set _dayofmonth = dayofmonth %} 69 | {% if everynminute %}{% set _minute %}*/{{ minute }}{% endset %}{% endif %} 70 | {% if everynhour %}{% set _hour %}*/{{ hour }}{% endset %}{% endif %} 71 | {% if everyndayofmonth %}{% set _dayofmonth %}*/{{ dayofmonth }}{% endset %}{% endif %} 72 | {{ [_minute, _hour, _dayofmonth, month, dayofweek] | join(" ") | cron2human }} 73 | {% else %} 74 | {{ execution | capitalize | translate }} 75 | {% endif %} 76 | actions: 77 | - template: create 78 | execute: 79 | type: url 80 | url: "/services/compose/schedule/create" 81 | - template: edit 82 | execute: 83 | type: url 84 | url: "/services/compose/schedule/edit/{{ _selected[0].uuid }}" 85 | - template: delete 86 | execute: 87 | type: request 88 | request: 89 | service: Compose 90 | method: deleteJob 91 | params: 92 | uuid: "{{ _selected[0].uuid }}" 93 | - type: iconButton 94 | tooltip: _("Run") 95 | icon: mdi:play-box-outline 96 | enabledConstraints: 97 | minSelected: 1 98 | maxSelected: 1 99 | execute: 100 | type: taskDialog 101 | taskDialog: 102 | config: 103 | title: _("Execute scheduled job ...") 104 | startOnInit: false 105 | showCompletion: false 106 | request: 107 | service: Compose 108 | method: doJob 109 | params: 110 | uuid: '{{ _selected[0].uuid }}' 111 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2086,SC2207 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.1.2 12 | 13 | export LC_ALL=C.UTF-8 14 | 15 | declare -i timeout=180 16 | declare -i i=0 17 | declare -A before_versions 18 | 19 | . /usr/share/openmediavault/scripts/helper-functions 20 | 21 | # logging location 22 | logDir="/var/log/" 23 | logFile="${logDir}/omv-compose-update.log" 24 | 25 | _log() 26 | { 27 | msg=${1} 28 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composeupdate] ${msg}" | tee -a ${logFile} >&2 29 | } 30 | 31 | compose="${1}" 32 | if [ -z "${compose}" ]; then 33 | _log "No compose name set. Exiting..." 34 | exit 10 35 | fi 36 | _log "compose :: ${compose}" 37 | 38 | # Get docker storage path 39 | dockerStorage=$(omv_config_get "/config/services/compose/dockerStorage") 40 | _log "Docker storage :: ${dockerStorage}" 41 | 42 | # Get the shared folder reference and path 43 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 44 | if ! omv_isuuid "${sfref}"; then 45 | _log "No compose sharedfolder set." 46 | exit 11 47 | fi 48 | sfpath="$(omv_get_sharedfolder_path "${sfref}")" 49 | if [ ! -d "${sfpath}" ]; then 50 | _log "Shared folder directory does not exist. Exiting..." 51 | exit 12 52 | fi 53 | sfpath="${sfpath/%\/}" 54 | sfpath="${sfpath//\/\//\/}" 55 | _log "Compose file path :: ${sfpath}" 56 | 57 | # set path for yml and env files 58 | composepath="${sfpath}/${compose}" 59 | env="${composepath}/${compose}.env" 60 | globalenv="${sfpath}/global.env" 61 | ovr="${composepath}/compose.override.yml" 62 | yml="${composepath}/${compose}.yml" 63 | if [ ! -f "${yml}" ]; then 64 | _log "Compose file '${yml}' does not exist. Exiting..." 65 | exit 13 66 | fi 67 | _log "Compose file :: ${yml}" 68 | 69 | yq="/usr/local/bin/yq" 70 | if [ ! -f "${yq}" ]; then 71 | _log "'${yq}' does not exist. Exiting..." 72 | exit 14 73 | fi 74 | 75 | while pgrep -f omv-compose-backup -l > /dev/null; do 76 | _log "Backup is running. Waiting ... ${i}" 77 | sleep 10 78 | (( i++ )) 79 | if [ ${i} -gt ${timeout} ]; then 80 | _log "Timed out waiting. Exiting." 81 | exit 15 82 | fi 83 | done 84 | 85 | # save status 86 | status="$(mktemp)" 87 | docker compose ls --all --format json | jq -r ".[] | select(.ConfigFiles | contains(\"${yml}\")) | .Status" | tee "${status}" 88 | _log "status :: $(cat ${status})" 89 | 90 | # log current image versions 91 | for image in $(sudo ${yq} .services.[].image "${yml}"); do 92 | version_before=$(docker image ls --format '{{.ID}},{{.Repository}}:{{.Tag}}' "${image}") 93 | _log "version before :: ${version_before}" 94 | before_versions["${image}"]="${version_before}" 95 | done 96 | 97 | # build compose arguments 98 | dockerComposeArgs=("--file" "${yml}") 99 | if [ -f "${ovr}" ]; then 100 | dockerComposeArgs+=("--file" "${ovr}") 101 | fi 102 | if [ -f "${globalenv}" ]; then 103 | dockerComposeArgs+=("--env-file" "${globalenv}") 104 | fi 105 | dockerComposeArgs+=("--env-file" "${env}") 106 | 107 | # pull new images 108 | _log "Pulling new images ..." 109 | docker compose "${dockerComposeArgs[@]}" pull 110 | 111 | # log image versions after update 112 | for image in $(sudo ${yq} .services.[].image "${yml}"); do 113 | version_after=$(docker image ls --format '{{.ID}},{{.Repository}}:{{.Tag}}' "${image}") 114 | if [ "${version_after}" != "${before_versions["${image}"]}" ]; then 115 | _log "version after :: ${version_after}" 116 | fi 117 | done 118 | 119 | # recreate containers with new images if running 120 | if grep -q "running" "${status}"; then 121 | _log "Recreating containers with new images ..." 122 | docker compose "${dockerComposeArgs[@]}" up -d 123 | fi 124 | 125 | rm -f "${status}" 126 | 127 | _log "Done." 128 | 129 | exit 0 130 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-dockerfiles-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-dockerfiles-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | rowId: name 10 | stateId: 6ab36dd0-7698-11ed-b240-a73c8036aead 11 | sorters: 12 | - dir: asc 13 | prop: name 14 | store: 15 | proxy: 16 | service: Compose 17 | get: 18 | method: getDockerfileList 19 | columns: 20 | - name: _("Name") 21 | prop: name 22 | flexGrow: 1 23 | sortable: true 24 | - name: _("Description") 25 | prop: description 26 | flexGrow: 1 27 | sortable: true 28 | - name: _("Script") 29 | prop: script 30 | flexGrow: 1 31 | sortable: true 32 | hidden: true 33 | - name: _("Conf") 34 | prop: conf 35 | flexGrow: 1 36 | sortable: true 37 | hidden: true 38 | actions: 39 | - template: create 40 | execute: 41 | type: url 42 | url: "/services/compose/dockerfiles/create" 43 | - template: edit 44 | execute: 45 | type: url 46 | url: "/services/compose/dockerfiles/edit/{{ _selected[0].uuid }}" 47 | - template: delete 48 | execute: 49 | type: request 50 | request: 51 | service: Compose 52 | method: deleteDockerfile 53 | params: 54 | uuid: "{{ _selected[0].uuid }}" 55 | - type: iconButton 56 | icon: mdi:wrench-outline 57 | tooltip: _("Build") 58 | enabledConstraints: 59 | minSelected: 1 60 | maxSelected: 1 61 | execute: 62 | type: taskDialog 63 | taskDialog: 64 | config: 65 | title: _("dockerfile build ...") 66 | startOnInit: true 67 | request: 68 | service: Compose 69 | method: doBuild 70 | params: 71 | name: "{{ _selected[0].name }}" 72 | options: "" 73 | - type: iconButton 74 | icon: mdi:wrench-cog-outline 75 | tooltip: _("Pull and Build") 76 | enabledConstraints: 77 | minSelected: 1 78 | maxSelected: 1 79 | execute: 80 | type: taskDialog 81 | taskDialog: 82 | config: 83 | title: _("dockerfile build ...") 84 | startOnInit: true 85 | request: 86 | service: Compose 87 | method: doBuild 88 | params: 89 | name: "{{ _selected[0].name }}" 90 | options: "pull" 91 | - type: iconButton 92 | icon: mdi:tag-plus-outline 93 | tooltip: _("Tag") 94 | enabledConstraints: 95 | minSelected: 0 96 | maxSelected: 1 97 | execute: 98 | type: formDialog 99 | formDialog: 100 | title: _("Create new image tag ...") 101 | fields: 102 | - type: textInput 103 | name: repo 104 | label: _('Repository') 105 | value: '{{ _selected[0].name }}' 106 | - type: textInput 107 | name: tag 108 | label: _('Tag') 109 | value: '' 110 | - type: hidden 111 | name: srcid 112 | label: _('Image ID') 113 | value: '' 114 | - type: textInput 115 | name: tgtimg 116 | label: _('Target repo') 117 | value: '' 118 | - type: textInput 119 | name: tgttag 120 | label: _('Target tag') 121 | value: '' 122 | buttons: 123 | submit: 124 | text: _("Tag") 125 | execute: 126 | type: request 127 | request: 128 | service: Compose 129 | method: doTag 130 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-configs-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-configs-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | stateId: 284c8cb0-9b99-11ef-b1fd-f7b945601cd3 10 | sorters: 11 | - dir: asc 12 | prop: name 13 | store: 14 | proxy: 15 | service: Compose 16 | get: 17 | method: getConfigList 18 | columns: 19 | - name: "uuid" 20 | prop: uuid 21 | flexGrow: 1 22 | sortable: true 23 | hidden: true 24 | - name: _("Name") 25 | prop: name 26 | flexGrow: 1 27 | sortable: true 28 | - name: _("Description") 29 | prop: description 30 | flexGrow: 1 31 | sortable: true 32 | - name: _("Compose File") 33 | prop: composefile 34 | flexGrow: 1 35 | sortable: true 36 | - name: _("Path") 37 | prop: fullpath 38 | flexGrow: 2 39 | sortable: true 40 | cellTemplateName: copyToClipboard 41 | actions: 42 | - type: menu 43 | icon: add 44 | tooltip: _("Add") 45 | actions: 46 | - text: _("Add") 47 | icon: add 48 | execute: 49 | type: url 50 | url: "/services/compose/configs/create" 51 | - text: _("Import") 52 | icon: mdi:file-import-outline 53 | execute: 54 | type: formDialog 55 | formDialog: 56 | title: _("Import config files from folders ...") 57 | fields: 58 | - type: hint 59 | hintType: info 60 | text: _("This will import config files in from the selected folder.
It will not import any config files that already exist in the plugin.") 61 | - type: hidden 62 | name: rootfsref 63 | value: "79684322-3eac-11ea-a974-63a080abab18" 64 | submitValue: false 65 | - type: folderBrowser 66 | name: path 67 | label: _("Path") 68 | value: "" 69 | dirType: mntent 70 | dirRefIdField: rootfsref 71 | - type: select 72 | name: fileref 73 | label: _('Compose File') 74 | textField: name 75 | valueField: uuid 76 | store: 77 | proxy: 78 | service: Compose 79 | get: 80 | method: enumerateFiles 81 | hint: _("Compose file to be associate imported files with") 82 | buttons: 83 | submit: 84 | text: _("Import") 85 | execute: 86 | type: request 87 | request: 88 | service: Compose 89 | method: importConfig 90 | - template: edit 91 | execute: 92 | type: url 93 | url: "/services/compose/configs/edit/{{ _selected[0].uuid }}" 94 | - template: delete 95 | execute: 96 | type: request 97 | request: 98 | service: Compose 99 | method: deleteConfig 100 | params: 101 | uuid: "{{ _selected[0].uuid }}" 102 | - type: iconButton 103 | icon: mdi:vector-difference 104 | tooltip: _("Show config changes") 105 | execute: 106 | type: taskDialog 107 | taskDialog: 108 | config: 109 | title: _("Show all file changes ...") 110 | startOnInit: true 111 | autoScroll: false 112 | request: 113 | service: Compose 114 | method: doGit 115 | params: 116 | uuid: "{{ _selected[0].uuid }}" 117 | command: "diffc" 118 | enabledConstraints: 119 | minSelected: 1 120 | maxSelected: 1 121 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-backup-multi: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2053,SC2086,SC2162 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 0.2.0 12 | 13 | . /usr/share/openmediavault/scripts/helper-functions 14 | 15 | export LC_ALL=C.UTF-8 16 | 17 | declare -i count=0 18 | declare -i index=0 19 | 20 | filter="" 21 | postbackup="" 22 | prebackup="" 23 | uuid="" 24 | 25 | # logging location 26 | logDir="/var/log/" 27 | logFile="${logDir}/omv-compose-backup.log" 28 | 29 | _log() 30 | { 31 | msg=${1} 32 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composebackup] ${msg}" | tee -a ${logFile} >&2 33 | } 34 | 35 | # loop through options 36 | while [[ $# -gt 0 ]]; do 37 | case "$1" in 38 | -f) 39 | filter="$2" 40 | shift 2 41 | ;; 42 | -u) 43 | uuid="$2" 44 | _log "uuid :: ${uuid}" 45 | shift 2 46 | ;; 47 | -h) 48 | echo "Use the following flags:" 49 | echo " -f 'filter'" 50 | echo " will be used over filter specified without -f flag" 51 | echo " -h" 52 | echo " show this help" 53 | echo " -u uuid of job" 54 | echo "" 55 | echo "Examples:" 56 | echo " omv-compile-backup-multi -u '41f4e112-cda9-11ee-938c-6715fcf387b0'" 57 | echo " omv-compile-backup-multi -f '*' -u '41f4e112-cda9-11ee-938c-6715fcf387b0'" 58 | echo " omv-compile-backup-multi -f 'tst' -u '41f4e112-cda9-11ee-938c-6715fcf387b0'" 59 | echo " omv-compile-backup-multi -f 'tst1,tst2,tst3' -u '41f4e112-cda9-11ee-938c-6715fcf387b0'" 60 | echo " omv-compile-backup-multi tst" 61 | echo "" 62 | exit 100 63 | ;; 64 | -*) 65 | echo "Invalid option: $1" 66 | exit 1 67 | ;; 68 | *) 69 | # if no dash in front, treat as a filter 70 | if [[ -z "${filter}" ]]; then 71 | filter="$1" 72 | fi 73 | shift 74 | ;; 75 | esac 76 | done 77 | 78 | if [[ "${filter}" == "*" ]]; then 79 | filter="" 80 | elif [[ "${filter}" == *","* ]]; then 81 | filter="@(${filter//,/|})" 82 | fi 83 | _log "filter :: ${filter}" 84 | 85 | if omv_isuuid "${uuid}"; then 86 | job="/config/services/compose/jobs/job[uuid='${uuid}']" 87 | prebackup=$(omv_config_get "${job}/prebackup") 88 | postbackup=$(omv_config_get "${job}/postbackup") 89 | fi 90 | 91 | # execute pre-backup 92 | if [ -f "${prebackup}" ] && [ -x "${prebackup}" ]; then 93 | _log "Executing pre-backup script :: ${prebackup}" 94 | ${prebackup} 95 | _log "pre-backup script complete." 96 | else 97 | if [ -n "${prebackup}" ]; then 98 | _log "pre-backup script not found." 99 | fi 100 | fi 101 | 102 | # Get the shared folder reference and path 103 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 104 | if ! omv_isuuid "${sfref}"; then 105 | _log "No compose sharedfolder set." 106 | exit 11 107 | fi 108 | sfpath="$(omv_get_sharedfolder_path "${sfref}")" 109 | if [ ! -d "${sfpath}" ]; then 110 | _log "Shared folder directory does not exist. Exiting..." 111 | exit 12 112 | fi 113 | sfpath="${sfpath/%\/}" 114 | sfpath="${sfpath//\/\//\/}" 115 | _log "Compose file path :: ${sfpath}" 116 | 117 | # Get the backup shared folder reference and path 118 | sfref=$(omv_config_get "/config/services/compose/backupsharedfolderref") 119 | if ! omv_isuuid "${sfref}"; then 120 | _log "No backup sharedfolder set." 121 | exit 13 122 | fi 123 | backuppath="$(omv_get_sharedfolder_path "${sfref}")" 124 | if [ ! -d "${backuppath}" ]; then 125 | _log "Backup shared folder directory does not exist. Exiting..." 126 | exit 14 127 | fi 128 | backuppath="${backuppath/%\/}" 129 | backuppath="${backuppath//\/\//\/}" 130 | _log "Backup path :: ${backuppath}" 131 | 132 | # backup global.env 133 | globalenv="${sfpath}/global.env" 134 | if [ -f "${globalenv}" ]; then 135 | _log "Backup global.env..." 136 | cp -pv "${globalenv}" "${backuppath}/" 137 | fi 138 | 139 | xpath="/config/services/compose/files/file" 140 | count=$(omv_config_get_count "${xpath}") 141 | index=1 142 | while [ ${index} -le ${count} ]; do 143 | pos="${xpath}[position()=${index}]" 144 | name=$(omv_config_get "${pos}/name") 145 | if [ -n "${filter}" ] && [[ "${name}" != ${filter} ]]; then 146 | index=$(( index + 1 )) 147 | continue 148 | fi 149 | echo ${name} 150 | omv-compose-backup "${name}" "${uuid:+${uuid}}" 151 | index=$(( index + 1 )) 152 | done; 153 | 154 | # execute post-backup 155 | if [ -f "${postbackup}" ] && [ -x "${postbackup}" ]; then 156 | _log "Executing post-backup script :: ${postbackup}" 157 | ${postbackup} 158 | _log "post-backup script complete." 159 | else 160 | if [ -n "${postbackup}" ]; then 161 | _log "post-backup script not found." 162 | fi 163 | fi 164 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-containers-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-containers-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | stateId: 21c6ac02-0e98-11ee-99b7-d3fb1b8b430a 11 | rowId: name 12 | sorters: 13 | - dir: asc 14 | prop: name 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getContainerListBg 20 | task: true 21 | columns: 22 | - name: _("ID") 23 | prop: id 24 | flexGrow: 1 25 | sortable: true 26 | hidden: true 27 | - name: _("Name") 28 | prop: name 29 | flexGrow: 1 30 | sortable: true 31 | - name: _("Image") 32 | prop: image 33 | flexGrow: 2 34 | sortable: true 35 | - name: _("State") 36 | prop: state 37 | flexGrow: 1 38 | sortable: true 39 | - name: _("Status") 40 | prop: status 41 | flexGrow: 1 42 | sortable: true 43 | - name: _("Created") 44 | prop: created 45 | flexGrow: 1 46 | sortable: true 47 | hidden: true 48 | - name: _("Running For") 49 | prop: running 50 | flexGrow: 1 51 | sortable: true 52 | hidden: true 53 | - name: _("Terminal Link") 54 | prop: execurl 55 | flexGrow: 1 56 | sortable: true 57 | - name: _("Ports") 58 | prop: ports 59 | flexGrow: 1 60 | sortable: true 61 | - name: _("Mounts") 62 | prop: mounts 63 | flexGrow: 2 64 | sortable: true 65 | - name: _("Command") 66 | prop: command 67 | flexGrow: 2 68 | sortable: true 69 | hidden: true 70 | - name: _("Network") 71 | prop: network 72 | flexGrow: 1 73 | sortable: true 74 | hidden: true 75 | actions: 76 | - type: iconButton 77 | icon: mdi:restart 78 | tooltip: _("restart") 79 | enabledConstraints: 80 | minSelected: 1 81 | maxSelected: 1 82 | execute: 83 | type: taskDialog 84 | taskDialog: 85 | config: 86 | title: _("docker restart ...") 87 | startOnInit: true 88 | request: 89 | service: Compose 90 | method: doContainerCommand 91 | params: 92 | command: "restart" 93 | command2: "" 94 | id: "{{ _selected[0].id }}" 95 | - type: iconButton 96 | icon: mdi:note-search-outline 97 | tooltip: _("inspect") 98 | enabledConstraints: 99 | minSelected: 1 100 | maxSelected: 1 101 | execute: 102 | type: taskDialog 103 | taskDialog: 104 | config: 105 | title: _("docker inspect ...") 106 | startOnInit: true 107 | request: 108 | service: Compose 109 | method: doContainerCommand 110 | params: 111 | command: "inspect" 112 | command2: "" 113 | id: "{{ _selected[0].id }}" 114 | - type: iconButton 115 | icon: mdi:file-document-outline 116 | tooltip: _("logs") 117 | enabledConstraints: 118 | minSelected: 1 119 | maxSelected: 1 120 | execute: 121 | type: taskDialog 122 | taskDialog: 123 | config: 124 | title: _("docker logs ...") 125 | startOnInit: true 126 | request: 127 | service: Compose 128 | method: doContainerCommand 129 | params: 130 | command: "logs" 131 | command2: "" 132 | id: "{{ _selected[0].id }}" 133 | - type: iconButton 134 | icon: mdi:file-document-refresh-outline 135 | tooltip: _("follow logs") 136 | enabledConstraints: 137 | minSelected: 1 138 | maxSelected: 1 139 | execute: 140 | type: taskDialog 141 | taskDialog: 142 | config: 143 | title: _("docker logs --follow ...") 144 | startOnInit: true 145 | request: 146 | service: Compose 147 | method: doContainerCommand 148 | params: 149 | command: "logs" 150 | command2: "--follow" 151 | id: "{{ _selected[0].id }}" 152 | - type: iconButton 153 | icon: mdi:download 154 | tooltip: _("Download log") 155 | enabledConstraints: 156 | minSelected: 1 157 | maxSelected: 1 158 | execute: 159 | type: url 160 | url: '/download?service=Compose&method=getContainerLog¶ms={"id":"{{ _selected[0].id }}","name":"{{ _selected[0].name }}"}' 161 | -------------------------------------------------------------------------------- /usr/share/openmediavault/confdb/create.d/conf.service.compose.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 4 | # @author OpenMediaVault Plugin Developers 5 | # @copyright Copyright (c) 2022-2025 openmediavault plugin developers 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | set -e 21 | 22 | . /etc/default/openmediavault 23 | . /usr/share/openmediavault/scripts/helper-functions 24 | 25 | if ! omv_config_exists "/config/services/compose"; then 26 | dockerPath="/var/lib/docker" 27 | if [ -f "/usr/bin/docker" ]; then 28 | dockerRoot="$(docker info | grep "Docker Root Dir:" | awk '{ print $4 }')" 29 | if [ -d "${dockerRoot}" ]; then 30 | dockerPath="${dockerRoot}" 31 | fi 32 | fi 33 | daemonJson="/etc/docker/daemon.json" 34 | if [ -f "${daemonJson}" ]; then 35 | if grep -qi nvidia ${daemonJson}; then 36 | dockerPath="" 37 | fi 38 | fi 39 | omv_config_add_node "/config/services" "compose" 40 | omv_config_add_key "/config/services/compose" "sharedfolderref" "" 41 | omv_config_add_key "/config/services/compose" "composeowner" "root" 42 | omv_config_add_key "/config/services/compose" "composegroup" "root" 43 | omv_config_add_key "/config/services/compose" "mode" "700" 44 | omv_config_add_key "/config/services/compose" "fileperms" "600" 45 | omv_config_add_key "/config/services/compose" "datasharedfolderref" "" 46 | omv_config_add_key "/config/services/compose" "backupsharedfolderref" "" 47 | omv_config_add_key "/config/services/compose" "backupmaxsize" "1" 48 | omv_config_add_key "/config/services/compose" "dockerStorage" "${dockerPath}" 49 | omv_config_add_key "/config/services/compose" "dockersharedfolderref" "" 50 | omv_config_add_key "/config/services/compose" "logmaxsize" "50" 51 | omv_config_add_key "/config/services/compose" "liverestore" "0" 52 | omv_config_add_key "/config/services/compose" "urlHostname" "" 53 | omv_config_add_key "/config/services/compose" "cachetimefiles" "60" 54 | omv_config_add_key "/config/services/compose" "cachetimeservices" "60" 55 | omv_config_add_key "/config/services/compose" "cachetimestats" "60" 56 | omv_config_add_key "/config/services/compose" "cachetimeimages" "60" 57 | omv_config_add_key "/config/services/compose" "cachetimenetworks" "60" 58 | omv_config_add_key "/config/services/compose" "cachetimevolumes" "60" 59 | omv_config_add_key "/config/services/compose" "cachetimecontainers" "60" 60 | omv_config_add_key "/config/services/compose" "showcmd" "0" 61 | omv_config_add_key "/config/services/compose" "runconfig" "0" 62 | omv_config_add_node "/config/services/compose" "files" 63 | omv_config_add_node "/config/services/compose" "configs" 64 | omv_config_add_node "/config/services/compose" "dockerfiles" 65 | omv_config_add_node "/config/services/compose" "jobs" 66 | omv_config_add_node "/config/services/compose" "globalenv" 67 | omv_config_add_key "/config/services/compose/globalenv" "enabled" "1" 68 | omv_config_add_key "/config/services/compose/globalenv" "globalenv" "" 69 | fi 70 | 71 | # download yq 72 | version="v4.49.2" 73 | bindir="/usr/local/bin" 74 | yq="${bindir}/yq" 75 | arch="$(dpkg --print-architecture)" 76 | repo_url=${OMV_EXTRAS_YQ_URL:-"https://github.com/mikefarah/yq/releases/download"} 77 | if [ ! -d "${bindir}" ]; then 78 | mkdir -p ${bindir} 79 | fi 80 | if [ ! -f "${yq}" ]; then 81 | echo "Downloading yq ..." 82 | wget -O ${yq} "${repo_url}/${version}/yq_linux_${arch}" 83 | else 84 | echo "Checking yq version ..." 85 | chmod 755 ${yq} 86 | yqvers="$(${yq} -V | awk '{ print $4 }')" 87 | if [ ! "${version}" = "${yqvers}" ]; then 88 | wget -O ${yq} "${repo_url}/${version}/yq_linux_${arch}" 89 | else 90 | echo "Correct version of yq installed - '${version}'" 91 | fi 92 | fi 93 | chmod 755 ${yq} 94 | 95 | # download regctl 96 | arch="$(dpkg --print-architecture)" 97 | version="v0.11.1" 98 | bindir="/usr/local/bin" 99 | regctl="${bindir}/regctl" 100 | repo_url=${OMV_EXTRAS_REGCTL_URL:-"https://github.com/regclient/regclient/releases/download"} 101 | if [ ! -f "${regctl}" ]; then 102 | echo "Downloading regctl ..." 103 | wget -O ${regctl} "${repo_url}/${version}/regctl-linux-${arch}" 104 | else 105 | echo "Checking regctl version ..." 106 | chmod 755 ${regctl} 107 | regctlvers="$(${regctl} version | awk '$1 == "VCSTag:" { print $2 }')" 108 | if [ ! "${version}" = "${regctlvers}" ]; then 109 | wget -O ${regctl} "${repo_url}/${version}/regctl-linux-${arch}" 110 | else 111 | echo "Correct version of regctl installed - '${version}'" 112 | fi 113 | fi 114 | chmod 755 ${regctl} 115 | 116 | # make sure log files exist to eliminate log viewer error 117 | for log in backup restore update; do 118 | file="/var/log/omv-compose-${log}.log" 119 | if [ ! -f "${file}" ]; then 120 | touch ${file} 121 | fi 122 | done 123 | 124 | # download icons 125 | echo "Downloading example file icons ..." 126 | omv-compose-download-icons 127 | 128 | exit 0 129 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-stats-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-stats-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | rowId: id 11 | stateId: 8050106c-797c-11ed-bbaf-bb845b3510b9 12 | sorters: 13 | - dir: asc 14 | prop: name 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getStatsBg 20 | task: true 21 | columns: 22 | - name: _("ID") 23 | prop: id 24 | flexGrow: 1 25 | sortable: true 26 | hidden: true 27 | - name: _("Name") 28 | prop: name 29 | flexGrow: 1 30 | sortable: true 31 | - name: _("CPU %") 32 | prop: cpu 33 | flexGrow: 1 34 | sortable: true 35 | cellTemplateName: progressBar 36 | cellTemplateConfig: 37 | text: '{{ cpu | tofixed(2) }}' 38 | - name: _("Mem Usage") 39 | prop: memuse 40 | flexGrow: 1 41 | sortable: true 42 | cellTemplateName: template 43 | cellTemplateConfig: '{{ memuse | tobytes | binaryunit | notavailable("-") }}' 44 | - name: _("Mem Limit") 45 | prop: memlim 46 | flexGrow: 1 47 | sortable: true 48 | cellTemplateName: template 49 | cellTemplateConfig: '{{ memlim | tobytes | binaryunit | notavailable("-") }}' 50 | - name: _("Mem %") 51 | prop: mem 52 | flexGrow: 1 53 | sortable: true 54 | cellTemplateName: template 55 | cellTemplateConfig: '{{ mem | tofixed(2) }}' 56 | - name: _("Net In") 57 | prop: netin 58 | flexGrow: 1 59 | sortable: true 60 | cellTemplateName: template 61 | cellTemplateConfig: '{{ netin | tobytes | binaryunit | notavailable("-") }}' 62 | - name: _("Net Out") 63 | prop: netout 64 | flexGrow: 1 65 | sortable: true 66 | cellTemplateName: template 67 | cellTemplateConfig: '{{ netout | tobytes | binaryunit | notavailable("-") }}' 68 | - name: _("Block In") 69 | prop: blockin 70 | flexGrow: 1 71 | sortable: true 72 | cellTemplateName: template 73 | cellTemplateConfig: '{{ blockin | tobytes | binaryunit | notavailable("-") }}' 74 | - name: _("Block Out") 75 | prop: blockout 76 | flexGrow: 1 77 | sortable: true 78 | cellTemplateName: template 79 | cellTemplateConfig: '{{ blockout | tobytes | binaryunit | notavailable("-") }}' 80 | - name: _("PIDs") 81 | prop: pids 82 | flexGrow: 1 83 | sortable: true 84 | actions: 85 | - type: iconButton 86 | icon: mdi:magnify 87 | tooltip: _("Inspect") 88 | enabledConstraints: 89 | minSelected: 1 90 | maxSelected: 1 91 | execute: 92 | type: taskDialog 93 | taskDialog: 94 | config: 95 | title: _("docker inspect ...") 96 | startOnInit: true 97 | request: 98 | service: Compose 99 | method: doDockerCmd 100 | params: 101 | id: "{{ _selected[0].id }}" 102 | cmd: "inspect" 103 | cmd2: "" 104 | buttons: 105 | stop: 106 | hidden: true 107 | - type: iconButton 108 | icon: mdi:file-document-outline 109 | tooltip: _("logs") 110 | enabledConstraints: 111 | minSelected: 1 112 | maxSelected: 1 113 | execute: 114 | type: taskDialog 115 | taskDialog: 116 | config: 117 | title: _("docker logs ...") 118 | startOnInit: true 119 | request: 120 | service: Compose 121 | method: doDockerCmd 122 | params: 123 | id: "{{ _selected[0].id }}" 124 | cmd: "logs" 125 | cmd2: "" 126 | buttons: 127 | stop: 128 | hidden: true 129 | - type: iconButton 130 | icon: mdi:file-document-refresh-outline 131 | tooltip: _("follow logs") 132 | enabledConstraints: 133 | minSelected: 1 134 | maxSelected: 1 135 | execute: 136 | type: taskDialog 137 | taskDialog: 138 | config: 139 | title: _("docker logs --follow ...") 140 | startOnInit: true 141 | request: 142 | service: Compose 143 | method: doDockerCmd 144 | params: 145 | id: "{{ _selected[0].id }}" 146 | cmd: "logs" 147 | cmd2: "--follow" 148 | - type: iconButton 149 | icon: mdi:restart 150 | tooltip: _("restart") 151 | enabledConstraints: 152 | minSelected: 1 153 | maxSelected: 1 154 | execute: 155 | type: taskDialog 156 | taskDialog: 157 | config: 158 | title: _("docker restart ...") 159 | startOnInit: true 160 | request: 161 | service: Compose 162 | method: doDockerCmd 163 | params: 164 | cmd: "restart" 165 | cmd2: "" 166 | id: "{{ _selected[0].id }}" 167 | - type: iconButton 168 | icon: mdi:download 169 | tooltip: _("Download log") 170 | enabledConstraints: 171 | minSelected: 1 172 | maxSelected: 1 173 | execute: 174 | type: url 175 | url: '/download?service=Compose&method=getContainerLog¶ms={"id":"{{ _selected[0].id }}","name":"{{ _selected[0].name }}"}' 176 | -------------------------------------------------------------------------------- /usr/sbin/omv-compose-restore: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # shellcheck disable=SC1091,SC2086 4 | # 5 | # Copyright (c) 2023-2025 openmediavault plugin developers 6 | # 7 | # This file is licensed under the terms of the GNU General Public 8 | # License version 2. This program is licensed "as is" without any 9 | # warranty of any kind, whether express or implied. 10 | # 11 | # version: 1.0.3 12 | 13 | . /usr/share/openmediavault/scripts/helper-functions 14 | 15 | declare -i skip=0 16 | 17 | # logging location 18 | logDir="/var/log/" 19 | logFile="${logDir}/omv-compose-restore.log" 20 | 21 | _log() 22 | { 23 | msg=${1} 24 | echo "[$(date +'%Y-%m-%d %H:%M:%S%z')] [composerestore] ${msg}" | tee -a ${logFile} >&2 25 | } 26 | 27 | compose="${1}" 28 | if [ -z "${compose}" ]; then 29 | _log "No compose name set. Exiting..." 30 | exit 10 31 | fi 32 | _log "compose :: ${compose}" 33 | 34 | # Get docker storage path 35 | dockerStorage=$(omv_config_get "/config/services/compose/dockerStorage") 36 | _log "Docker storage :: ${dockerStorage}" 37 | 38 | # Get the shared folder reference and path 39 | sfref=$(omv_config_get "/config/services/compose/sharedfolderref") 40 | if ! omv_isuuid "${sfref}"; then 41 | _log "No compose sharedfolder set." 42 | exit 11 43 | fi 44 | sfpath="$(omv_get_sharedfolder_path "${sfref}")" 45 | if [ ! -d "${sfpath}" ]; then 46 | _log "Shared folder directory does not exist. Exiting..." 47 | exit 12 48 | fi 49 | sfpath="${sfpath/%\/}" 50 | sfpath="${sfpath//\/\//\/}" 51 | _log "Compose file path :: ${sfpath}" 52 | 53 | # Get the backup shared folder reference and path 54 | sfref=$(omv_config_get "/config/services/compose/backupsharedfolderref") 55 | if ! omv_isuuid "${sfref}"; then 56 | _log "No backup sharedfolder set." 57 | exit 13 58 | fi 59 | backuppath="$(omv_get_sharedfolder_path "${sfref}")" 60 | if [ ! -d "${backuppath}" ]; then 61 | _log "Backup shared folder directory does not exist. Exiting..." 62 | exit 14 63 | fi 64 | backuppath="${backuppath/%\/}" 65 | _log "Backup path :: ${backuppath}" 66 | 67 | # set path for yml and env files 68 | composepath="${sfpath}/${compose}" 69 | env="${composepath}/${compose}.env" 70 | globalenv="${sfpath}/global.env" 71 | ovr="${composepath}/compose.override.yml" 72 | yml="${composepath}/${compose}.yml" 73 | if [ ! -f "${yml}" ]; then 74 | _log "Compose file '${yml}' does not exist." 75 | skip=1 76 | fi 77 | _log "Compose file :: ${yml}" 78 | 79 | # build compose arguments 80 | dockerComposeArgs=("--file" "${yml}") 81 | if [ -f "${ovr}" ]; then 82 | dockerComposeArgs+=("--file" "${ovr}") 83 | fi 84 | if [ -f "${globalenv}" ]; then 85 | dockerComposeArgs+=("--env-file" "${globalenv}") 86 | fi 87 | dockerComposeArgs+=("--env-file" "${env}") 88 | 89 | if [ ${skip} -ne 1 ]; then 90 | # save status 91 | status="$(mktemp)" 92 | docker compose ls --all --format json | jq -r ".[] | select(.ConfigFiles | contains(\"${yml}\")) | .Status" | tee "${status}" 93 | _log "status :: $(cat ${status})" 94 | 95 | # stop compose if running 96 | if grep -q "running" "${status}"; then 97 | docker compose "${dockerComposeArgs[@]}" stop 98 | else 99 | _log "${compose} is not running" 100 | fi 101 | fi 102 | 103 | path="${backuppath}/${compose}" 104 | _log "path :: ${path}" 105 | 106 | echo 107 | vollist="${path}/vol.list" 108 | if [ -f "${vollist}" ]; then 109 | while read -r line; do 110 | volnum="${line%%,*}" 111 | volpath="${line#*,}" 112 | args=(-avr) 113 | if [[ ! "${volpath}" == */ ]]; then 114 | volpath="$(dirname "${volpath}")" 115 | else 116 | args+=(--delete) 117 | fi 118 | src="${path}/${volnum}/" 119 | if [ ${volnum} -eq 0 ]; then 120 | volpath="${volpath}/${compose}/" 121 | volpath="${volpath//\/\//\/}" 122 | fi 123 | _log "Source :: ${src}" 124 | _log "Dest :: ${volpath}" 125 | if [ ${volnum} -eq 0 ]; then 126 | cp -v "${src}/${compose}.yml" "${src}/compose.override.yml" "${src}/${compose}.env" "${volpath}" 127 | else 128 | rsync "${args[@]}" "${src}" "${volpath}" 129 | fi 130 | done < "${vollist}" 131 | else 132 | _log "No volume list found." 133 | fi 134 | 135 | if [ ${skip} -ne 1 ]; then 136 | # start compose if running before backup 137 | if grep -q "running" "${status}"; then 138 | docker compose "${dockerComposeArgs[@]}" start 139 | fi 140 | 141 | rm -f "${status}" 142 | else 143 | # add compose file to database if it doesn't exist already 144 | xpath="//services/compose/files" 145 | if ! omv_config_exists "${xpath}/file[name='${compose}']"; then 146 | _log "Adding '${compose}' to the database..." 147 | composepath="${path}/0/${compose}" 148 | envfile="${composepath}.env" 149 | ymlfile="${composepath}.yml" 150 | ovrfile="${path}/0/compose.override.yml" 151 | descfile="${path}/omv_file_desc.txt" 152 | envtxt="" 153 | ymltxt="" 154 | ovrtxt="" 155 | desctxt="" 156 | # escape for xml 157 | if [ -f "${envfile}" ]; then 158 | envtxt=$(sed -e 's/&/\&/g' -e 's//\>/g' "${envfile}") 159 | fi 160 | if [ -f "${ymlfile}" ]; then 161 | ymltxt=$(sed -e 's/&/\&/g' -e 's//\>/g' "${ymlfile}") 162 | fi 163 | if [ -f "${ovrfile}" ]; then 164 | ovrtxt=$(sed -e 's/&/\&/g' -e 's//\>/g' "${ovrfile}") 165 | fi 166 | if [ -f "${descfile}" ]; then 167 | desctxt=$(sed -e 's/&/\&/g' -e 's//\>/g' "${descfile}") 168 | fi 169 | if [ -n "${ymltxt}" ]; then 170 | # create backup of omv database 171 | date="$(date +'%Y-%m-%d_%H-%M-%S')" 172 | cp -fv "${OMV_CONFIG_FILE}" "/root/config_${date}.xml" 173 | # create database entry 174 | object="$(uuid)" 175 | object="${object}${compose}" 176 | object="${object}@@DESC_DATA@@" 177 | object="${object}@@YAML_DATA@@" 178 | object="${object}0" 179 | object="${object}@@ENV_DATA@@" 180 | object="${object}0" 181 | object="${object}@@OVR_DATA@@" 182 | # add new entry to database 183 | omv_config_add_node_data "${xpath}" "file" "${object}" 184 | # repalce temp variables with compose yaml and environment file 185 | omvdb=$(<"${OMV_CONFIG_FILE}") 186 | omvdbmod=$( 187 | awk \ 188 | -v env="${envtxt}" \ 189 | -v yml="${ymltxt}" \ 190 | -v ovr="${ovrtxt}" \ 191 | -v desc="${desctxt}" \ 192 | '{ 193 | gsub(/@@DESC_DATA@@/, desc) 194 | gsub(/@@ENV_DATA@@/, env) 195 | gsub(/@@YAML_DATA@@/, yml) 196 | gsub(/@@OVR_DATA@@/, ovr) 197 | } 198 | 1 199 | ' <<< "${omvdb}" 200 | ) 201 | echo "${omvdbmod}" > "${OMV_CONFIG_FILE}" 202 | # mark compose module dirty 203 | omv_module_set_dirty compose 204 | fi 205 | else 206 | _log "File exists in database." 207 | fi 208 | fi 209 | 210 | _log "Done." 211 | 212 | exit 0 213 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-images-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-images-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | rowId: id 11 | stateId: c2e1baf6-0a19-11ee-9a0a-1bbcfb3e6ead 12 | sorters: 13 | - dir: asc 14 | prop: repo 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getImagesBg 20 | task: true 21 | columns: 22 | - name: _("ID") 23 | prop: id 24 | flexGrow: 2 25 | sortable: true 26 | hidden: true 27 | - name: _("Repository") 28 | prop: repo 29 | flexGrow: 2 30 | sortable: true 31 | - name: _("Tag") 32 | prop: tag 33 | flexGrow: 1 34 | sortable: true 35 | - name: _("Digest") 36 | prop: digest 37 | flexGrow: 2 38 | sortable: true 39 | hidden: true 40 | - name: _("Created At") 41 | prop: createat 42 | flexGrow: 2 43 | sortable: true 44 | hidden: true 45 | - name: _("Created") 46 | prop: createsince 47 | flexGrow: 1 48 | - name: _("Size") 49 | prop: size 50 | flexGrow: 1 51 | sortable: true 52 | cellTemplateName: template 53 | cellTemplateConfig: '{{ size | tobytes | binaryunit | notavailable("-") }}' 54 | - name: _("Virtual Size") 55 | prop: virtualsize 56 | flexGrow: 1 57 | sortable: true 58 | cellTemplateName: template 59 | cellTemplateConfig: '{{ virtualsize | tobytes | binaryunit | notavailable("-") }}' 60 | hidden: true 61 | - name: _("Status") 62 | prop: status 63 | flexGrow: 1 64 | sortable: true 65 | cellTemplateName: chip 66 | cellTemplateConfig: 67 | map: 68 | AVAILABLE: 69 | value: _("Available") 70 | class: omv-background-color-pair-yellow 71 | CURRENT: 72 | value: _("Current") 73 | class: omv-background-color-pair-green 74 | NA: 75 | value: _("n/a") 76 | class: omv-background-color-pair-blue 77 | NOIMAGE: 78 | value: _("No Image") 79 | class: omv-background-color-pair-blue 80 | FAILED: 81 | value: _("Failed") 82 | class: omv-background-color-pair-red 83 | - name: _("In Use") 84 | prop: inuse 85 | flexGrow: 1 86 | sortable: true 87 | cellTemplateName: checkIcon 88 | actions: 89 | - type: iconButton 90 | icon: mdi:delete 91 | tooltip: _("Delete") 92 | enabledConstraints: 93 | minSelected: 1 94 | maxSelected: 1 95 | execute: 96 | type: taskDialog 97 | taskDialog: 98 | config: 99 | title: _("docker image rm ...") 100 | startOnInit: true 101 | request: 102 | service: Compose 103 | method: doDockerImageCmd 104 | params: 105 | id: "{{ _selected[0].id }}" 106 | command: "rm" 107 | - type: iconButton 108 | icon: mdi:magnify 109 | tooltip: _("Inspect") 110 | enabledConstraints: 111 | minSelected: 1 112 | maxSelected: 1 113 | execute: 114 | type: taskDialog 115 | taskDialog: 116 | config: 117 | title: _("docker image inspect ...") 118 | startOnInit: true 119 | request: 120 | service: Compose 121 | method: doDockerImageCmd 122 | params: 123 | id: "{{ _selected[0].id }}" 124 | command: "inspect" 125 | buttons: 126 | stop: 127 | hidden: true 128 | - type: iconButton 129 | icon: mdi:download-network-outline 130 | tooltip: _("pull image") 131 | enabledConstraints: 132 | minSelected: 1 133 | maxSelected: 1 134 | execute: 135 | type: taskDialog 136 | taskDialog: 137 | config: 138 | title: _("docker image pull ...") 139 | startOnInit: true 140 | request: 141 | service: Compose 142 | method: doDockerImageCmd 143 | params: 144 | id: "{{ _selected[0].repo }}" 145 | command: "pull" 146 | - type: iconButton 147 | icon: mdi:download-network 148 | tooltip: _("pull image+tag") 149 | enabledConstraints: 150 | minSelected: 1 151 | maxSelected: 1 152 | execute: 153 | type: taskDialog 154 | taskDialog: 155 | config: 156 | title: _("docker image pull ...") 157 | startOnInit: true 158 | request: 159 | service: Compose 160 | method: doDockerImageCmd 161 | params: 162 | id: "{{ _selected[0].repo }}:{{ _selected[0].tag }}" 163 | command: "pull" 164 | - type: iconButton 165 | icon: mdi:tag-plus-outline 166 | tooltip: _("Tag") 167 | enabledConstraints: 168 | minSelected: 1 169 | maxSelected: 1 170 | execute: 171 | type: formDialog 172 | formDialog: 173 | title: _("Create new image tag ...") 174 | fields: 175 | - type: textInput 176 | name: repo 177 | label: _('Repository') 178 | value: '{{ _selected[0].repo }}' 179 | readonly: true 180 | submitValue: false 181 | - type: textInput 182 | name: tag 183 | label: _('Tag') 184 | value: '{{ _selected[0].tag }}' 185 | readonly: true 186 | submitValue: false 187 | - type: textInput 188 | name: srcid 189 | label: _('Image ID') 190 | value: '{{ _selected[0].id }}' 191 | readonly: true 192 | - type: textInput 193 | name: tgtimg 194 | label: _('Target repo') 195 | value: '{{ _selected[0].repo }}' 196 | - type: textInput 197 | name: tgttag 198 | label: _('Target tag') 199 | value: '' 200 | buttons: 201 | submit: 202 | text: _("Tag") 203 | execute: 204 | type: request 205 | request: 206 | service: Compose 207 | method: doTag 208 | - type: iconButton 209 | icon: mdi:tag-arrow-up-outline 210 | tooltip: _("Push") 211 | enabledConstraints: 212 | minSelected: 1 213 | maxSelected: 1 214 | execute: 215 | type: taskDialog 216 | taskDialog: 217 | config: 218 | title: _("Push to Docker Hub ...") 219 | startOnInit: true 220 | request: 221 | service: Compose 222 | method: doHubPush 223 | params: 224 | imgname: "{{ _selected[0].repo }}" 225 | imgtag: "{{ _selected[0].tag }}" 226 | - type: iconButton 227 | icon: mdi:image-minus-outline 228 | tooltip: _("Prune Images") 229 | execute: 230 | type: taskDialog 231 | taskDialog: 232 | config: 233 | title: _("docker image prune ...") 234 | startOnInit: true 235 | request: 236 | service: Compose 237 | method: doPrune 238 | params: 239 | command: "image prune" 240 | -------------------------------------------------------------------------------- /srv/salt/omv/deploy/compose/10compose.sls: -------------------------------------------------------------------------------- 1 | # @license http://www.gnu.org/licenses/gpl.html GPL Version 3 2 | # @author OpenMediaVault Plugin Developers 3 | # @copyright Copyright (c) 2022-2025 openmediavault plugin developers 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | {% set sf_list = salt['omv_conf.get']('conf.system.sharedfolder') %} 19 | {% set sfmap = {} %} 20 | {% for sf in sf_list %} 21 | {% set name = sf.name %} 22 | {% set uuid = sf.uuid %} 23 | {% set path = salt['omv_conf.get_sharedfolder_path'](uuid).rstrip('/') %} 24 | {% do sfmap.update({name: path}) %} 25 | {% endfor %} 26 | 27 | {%- set uidmap = {} -%} 28 | {%- set uent = salt['user.getent']() -%} 29 | 30 | {%- for info in uent -%} 31 | {%- if info is mapping -%} 32 | {%- set uname = info.get('name') -%} 33 | {%- set uid = info.get('uid') -%} 34 | {%- if uname and uid is not none -%} 35 | {%- do uidmap.update({(uname|string): (uid|string)}) -%} 36 | {%- endif -%} 37 | {%- endif -%} 38 | {%- endfor -%} 39 | 40 | {%- set gidmap = {} -%} 41 | {%- set gent = salt['group.getent']() -%} 42 | 43 | {%- for info in gent -%} 44 | {%- if info is mapping -%} 45 | {%- set gname = info.get('name') -%} 46 | {%- set gid = info.get('gid') -%} 47 | {%- if gname and gid is not none -%} 48 | {%- do gidmap.update({(gname|string): (gid|string)}) -%} 49 | {%- endif -%} 50 | {%- endif -%} 51 | {%- endfor -%} 52 | 53 | {%- set timezone = salt['timezone.get_zone']() -%} 54 | 55 | {%- macro resolve_sf(body) -%} 56 | {%- if not body -%} 57 | {{ "" }} 58 | {%- else -%} 59 | {%- set ns = namespace(text=body) -%} 60 | {%- for name, path in sfmap.items() -%} 61 | {%- set placeholder = '${{ sf:"' ~ name ~ '" }}' -%} 62 | {%- set ns.text = ns.text | replace(placeholder, path) -%} 63 | {%- endfor -%} 64 | {{ ns.text }} 65 | {%- endif -%} 66 | {%- endmacro -%} 67 | 68 | {%- macro replace_uid_tokens(text) -%} 69 | {%- set ns = namespace(out=text) -%} 70 | {%- for part in ns.out.split('${{ uid:"') -%} 71 | {%- if loop.first -%}{%- continue -%}{%- endif -%} 72 | {%- set uname = part.split('" }}', 1)[0] if '" }}' in part else None -%} 73 | {%- if uname -%} 74 | {%- set uname = (uname|string).strip() -%} 75 | {%- set uid = uidmap.get(uname) -%} 76 | {%- if uid is not none -%} 77 | {%- set ns.out = ns.out | replace('${{ uid:"' ~ uname ~ '" }}', uid|string) -%} 78 | {%- endif -%} 79 | {%- endif -%} 80 | {%- endfor -%} 81 | {{ ns.out }} 82 | {%- endmacro -%} 83 | 84 | {%- macro replace_gid_tokens(text) -%} 85 | {%- set ns = namespace(out=text) -%} 86 | {%- for part in ns.out.split('${{ gid:"') -%} 87 | {%- if loop.first -%}{%- continue -%}{%- endif -%} 88 | {%- set gname = part.split('" }}', 1)[0] if '" }}' in part else None -%} 89 | {%- if gname -%} 90 | {%- set gname = (gname|string).strip() -%} 91 | {%- set gid = gidmap.get(gname) -%} 92 | {%- if gid is not none -%} 93 | {%- set ns.out = ns.out | replace('${{ gid:"' ~ gname ~ '" }}', gid|string) -%} 94 | {%- endif -%} 95 | {%- endif -%} 96 | {%- endfor -%} 97 | {{ ns.out }} 98 | {%- endmacro -%} 99 | 100 | {%- macro render_body(raw, name, datapath) -%} 101 | {%- if not raw -%} 102 | {{ "" }} 103 | {%- else -%} 104 | {%- set b = resolve_sf(raw) -%} 105 | {%- set b = b | replace("CHANGE_TO_COMPOSE_NAME", name) -%} 106 | {%- if datapath is not none and datapath|length > 0 -%} 107 | {%- set b = b | replace("CHANGE_TO_COMPOSE_DATA_PATH", datapath) -%} 108 | {%- endif -%} 109 | {%- if '${{ uid:"' in b -%} 110 | {%- set b = replace_uid_tokens(b) -%} 111 | {%- endif -%} 112 | {%- if '${{ gid:"' in b -%} 113 | {%- set b = replace_gid_tokens(b) -%} 114 | {%- endif -%} 115 | {%- if '${{ tz' in b -%} 116 | {%- set b = b | replace('${{ tz }}', timezone) -%} 117 | {%- endif -%} 118 | {{ b }} 119 | {%- endif -%} 120 | {%- endmacro -%} 121 | 122 | 123 | {% set config = salt['omv_conf.get']('conf.service.compose') %} 124 | {% if config.sharedfolderref | length > 0 %} 125 | {% set sfpath = salt['omv_conf.get_sharedfolder_path'](config.sharedfolderref).rstrip('/') %} 126 | {% set datapath = "" %} 127 | {% if config.datasharedfolderref | string | length > 1 %} 128 | {% set datapath = salt['omv_conf.get_sharedfolder_path'](config.datasharedfolderref).rstrip('/') %} 129 | {% if not salt['file.directory_exists'](datapath) %} 130 | {% set datapath = "" %} 131 | {% endif %} 132 | {% endif %} 133 | 134 | {% for file in config.files.file %} 135 | {% set composeDir = sfpath ~ '/' ~ file.name %} 136 | {% set composeFile = composeDir ~ '/' ~ file.name ~ '.yml' %} 137 | {% set overrideFile = composeDir ~ '/compose.override.yml' %} 138 | {% set envFile = composeDir ~ '/' ~ file.name ~ '.env' %} 139 | 140 | configure_compose_file_dir_{{ file.name }}: 141 | file.directory: 142 | - name: "{{ composeDir }}" 143 | - user: "{{ config.composeowner }}" 144 | - group: "{{ config.composegroup }}" 145 | - mode: "{{ config.mode }}" 146 | - makedirs: True 147 | 148 | {% set file_body = render_body(file.body, file.name, datapath) %} 149 | configure_compose_{{ file.name }}_file: 150 | file.managed: 151 | - name: '{{ composeFile }}' 152 | - source: 153 | - salt://{{ tpldir }}/files/compose_yml.j2 154 | - context: 155 | file: {{ file | json }} 156 | datapath: {{ datapath }} 157 | body: {{ file_body.strip() | json }} 158 | - template: jinja 159 | - user: "{{ config.composeowner }}" 160 | - group: "{{ config.composegroup }}" 161 | - mode: "{{ config.fileperms }}" 162 | 163 | {% set file_override = render_body(file.override, file.name, datapath) %} 164 | configure_compose_{{ file.name }}_override: 165 | file.managed: 166 | - name: '{{ overrideFile }}' 167 | - source: 168 | - salt://{{ tpldir }}/files/override_yml.j2 169 | - context: 170 | file: {{ file | json }} 171 | datapath: {{ datapath }} 172 | body: {{ file_override.strip() | json }} 173 | - template: jinja 174 | - user: "{{ config.composeowner }}" 175 | - group: "{{ config.composegroup }}" 176 | - mode: "{{ config.fileperms }}" 177 | 178 | {% set file_env = render_body(file.env, file.name, datapath) %} 179 | configure_compose_env_{{ file.name }}_file: 180 | file.managed: 181 | - name: '{{ envFile }}' 182 | - source: 183 | - salt://{{ tpldir }}/files/compose_env_yml.j2 184 | - context: 185 | file: {{ file | json }} 186 | datapath: {{ datapath }} 187 | body: {{ file_env.strip() | json }} 188 | - template: jinja 189 | - user: "{{ config.composeowner }}" 190 | - group: "{{ config.composegroup }}" 191 | - mode: "{{ config.fileperms }}" 192 | 193 | {%- for cnf in config.configs.config | selectattr("fileref", "equalto", file.uuid) %} 194 | 195 | {% set cnfFile = composeDir ~ '/' ~ cnf.name %} 196 | {% set cnf_body = resolve_sf(cnf.body) %} 197 | 198 | configure_compose_{{ file.name }}_config_{{ cnf.uuid }}: 199 | file.managed: 200 | - name: '{{ cnfFile }}' 201 | - source: 202 | - salt://{{ tpldir }}/files/compose_cnf.j2 203 | - context: 204 | file: {{ cnf | json }} 205 | body: {{ cnf_body | json }} 206 | - template: jinja 207 | - user: "{{ config.composeowner }}" 208 | - group: "{{ config.composegroup }}" 209 | - mode: "{{ config.fileperms }}" 210 | 211 | {% endfor %} 212 | {% endfor %} 213 | 214 | {% set globalenv = salt['omv_conf.get']('conf.service.compose.globalenv') %} 215 | {% set globalEnvFile = sfpath ~ '/global.env' %} 216 | 217 | {% if globalenv.enabled | to_bool %} 218 | 219 | {% set file_global = render_body(globalenv.globalenv, '', datapath) %} 220 | configure_compose_global_env_file: 221 | file.managed: 222 | - name: '{{ globalEnvFile }}' 223 | - source: 224 | - salt://{{ tpldir }}/files/global_env_yml.j2 225 | - context: 226 | body: {{ file_global.strip() | json }} 227 | - template: jinja 228 | - user: "{{ config.composeowner }}" 229 | - group: "{{ config.composegroup }}" 230 | - mode: "{{ config.fileperms }}" 231 | 232 | {% else %} 233 | 234 | {% set datapath = "" %} 235 | remove_compose_global_env_file: 236 | file.absent: 237 | - name: '{{ globalEnvFile }}' 238 | 239 | {% endif %} 240 | {% endif %} 241 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-services-datatable-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-services-datatable-page 5 | type: datatablePage 6 | config: 7 | autoReload: false 8 | hasSearchField: true 9 | remoteSorting: true 10 | stateId: f350a8e8-9997-11ed-8f7b-83968f3f3a4f 11 | rowId: name 12 | sorters: 13 | - dir: asc 14 | prop: name 15 | store: 16 | proxy: 17 | service: Compose 18 | get: 19 | method: getServicesListBg 20 | task: true 21 | columns: 22 | - name: _("Name") 23 | prop: name 24 | flexGrow: 1 25 | sortable: true 26 | - name: _("Image") 27 | prop: image 28 | flexGrow: 2 29 | sortable: true 30 | - name: _("Project") 31 | prop: project 32 | flexGrow: 1 33 | sortable: true 34 | - name: _("Service") 35 | prop: service 36 | flexGrow: 1 37 | sortable: true 38 | - name: _("State") 39 | prop: state 40 | flexGrow: 1 41 | sortable: true 42 | - name: _("Status") 43 | prop: status 44 | flexGrow: 1 45 | sortable: true 46 | - name: _("Terminal Link") 47 | prop: execurl 48 | flexGrow: 1 49 | sortable: true 50 | - name: _("Ports") 51 | prop: ports 52 | flexGrow: 1 53 | sortable: true 54 | - name: _("Created") 55 | prop: created 56 | flexGrow: 1 57 | sortable: true 58 | hidden: true 59 | - name: _("Path") 60 | prop: filepath 61 | flexGrow: 1 62 | sortable: true 63 | hidden: true 64 | cellTemplateName: copyToClipboard 65 | - name: _("Env Path") 66 | prop: envpath 67 | flexGrow: 1 68 | sortable: true 69 | hidden: true 70 | cellTemplateName: copyToClipboard 71 | - name: _("Override Path") 72 | prop: overridepath 73 | flexGrow: 1 74 | sortable: true 75 | hidden: true 76 | cellTemplateName: copyToClipboard 77 | actions: 78 | - type: iconButton 79 | icon: mdi:download-network-outline 80 | tooltip: _("pull") 81 | enabledConstraints: 82 | minSelected: 1 83 | maxSelected: 1 84 | execute: 85 | type: taskDialog 86 | taskDialog: 87 | config: 88 | title: _("docker compose pull ...") 89 | startOnInit: true 90 | request: 91 | service: Compose 92 | method: doServiceCommand 93 | params: 94 | command: "pull" 95 | command2: "" 96 | service: "{{ _selected[0].service }}" 97 | path: "{{ _selected[0].filepath }}" 98 | envpath: "{{ _selected[0].envpath }}" 99 | overridepath: "{{ _selected[0].overridepath }}" 100 | - type: iconButton 101 | icon: mdi:arrow-up-circle-outline 102 | tooltip: _("up") 103 | enabledConstraints: 104 | minSelected: 1 105 | maxSelected: 1 106 | execute: 107 | type: taskDialog 108 | taskDialog: 109 | config: 110 | title: _("docker compose up -d ...") 111 | startOnInit: true 112 | request: 113 | service: Compose 114 | method: doServiceCommand 115 | params: 116 | command: "up -d" 117 | command2: "" 118 | service: "{{ _selected[0].service }}" 119 | path: "{{ _selected[0].filepath }}" 120 | envpath: "{{ _selected[0].envpath }}" 121 | overridepath: "{{ _selected[0].overridepath }}" 122 | - type: iconButton 123 | icon: mdi:stop 124 | tooltip: _("stop") 125 | enabledConstraints: 126 | minSelected: 1 127 | maxSelected: 1 128 | execute: 129 | type: taskDialog 130 | taskDialog: 131 | config: 132 | title: _("docker compose stop ...") 133 | startOnInit: true 134 | request: 135 | service: Compose 136 | method: doServiceCommand 137 | params: 138 | command: "stop" 139 | command2: "" 140 | service: "{{ _selected[0].service }}" 141 | path: "{{ _selected[0].filepath }}" 142 | envpath: "{{ _selected[0].envpath }}" 143 | overridepath: "{{ _selected[0].overridepath }}" 144 | - type: iconButton 145 | icon: mdi:arrow-down-circle-outline 146 | tooltip: _("down") 147 | enabledConstraints: 148 | minSelected: 1 149 | maxSelected: 1 150 | execute: 151 | type: taskDialog 152 | taskDialog: 153 | config: 154 | title: _("docker compose down ...") 155 | startOnInit: true 156 | request: 157 | service: Compose 158 | method: doServiceCommand 159 | params: 160 | command: "down" 161 | command2: "" 162 | service: "{{ _selected[0].service }}" 163 | path: "{{ _selected[0].filepath }}" 164 | envpath: "{{ _selected[0].envpath }}" 165 | overridepath: "{{ _selected[0].overridepath }}" 166 | - type: iconButton 167 | icon: mdi:restart 168 | tooltip: _("restart") 169 | enabledConstraints: 170 | minSelected: 1 171 | maxSelected: 1 172 | execute: 173 | type: taskDialog 174 | taskDialog: 175 | config: 176 | title: _("docker compose restart ...") 177 | startOnInit: true 178 | request: 179 | service: Compose 180 | method: doServiceCommand 181 | params: 182 | command: "restart" 183 | command2: "" 184 | service: "{{ _selected[0].service }}" 185 | path: "{{ _selected[0].filepath }}" 186 | envpath: "{{ _selected[0].envpath }}" 187 | overridepath: "{{ _selected[0].overridepath }}" 188 | - type: iconButton 189 | icon: mdi:note-search-outline 190 | tooltip: _("inspect") 191 | enabledConstraints: 192 | minSelected: 1 193 | maxSelected: 1 194 | execute: 195 | type: taskDialog 196 | taskDialog: 197 | config: 198 | title: _("docker inspect ...") 199 | startOnInit: true 200 | request: 201 | service: Compose 202 | method: doContainerCommand 203 | params: 204 | command: "inspect" 205 | command2: "" 206 | id: "{{ _selected[0].name }}" 207 | - type: iconButton 208 | icon: mdi:file-document-outline 209 | tooltip: _("logs") 210 | enabledConstraints: 211 | minSelected: 1 212 | maxSelected: 1 213 | execute: 214 | type: taskDialog 215 | taskDialog: 216 | config: 217 | title: _("docker compose logs ...") 218 | startOnInit: true 219 | request: 220 | service: Compose 221 | method: doServiceCommand 222 | params: 223 | command: "logs" 224 | command2: "" 225 | service: "{{ _selected[0].service }}" 226 | path: "{{ _selected[0].filepath }}" 227 | envpath: "{{ _selected[0].envpath }}" 228 | overridepath: "{{ _selected[0].overridepath }}" 229 | - type: iconButton 230 | icon: mdi:file-document-refresh-outline 231 | tooltip: _("follow logs") 232 | enabledConstraints: 233 | minSelected: 1 234 | maxSelected: 1 235 | execute: 236 | type: taskDialog 237 | taskDialog: 238 | config: 239 | title: _("docker compose logs --follow ...") 240 | startOnInit: true 241 | request: 242 | service: Compose 243 | method: doServiceCommand 244 | params: 245 | command: "logs" 246 | command2: "--follow" 247 | service: "{{ _selected[0].service }}" 248 | path: "{{ _selected[0].filepath }}" 249 | envpath: "{{ _selected[0].envpath }}" 250 | overridepath: "{{ _selected[0].overridepath }}" 251 | - type: iconButton 252 | icon: mdi:download 253 | tooltip: _("Download log") 254 | enabledConstraints: 255 | minSelected: 1 256 | maxSelected: 1 257 | execute: 258 | type: url 259 | url: '/download?service=Compose&method=getServiceLog¶ms={"service":"{{ _selected[0].service }}","name":"{{ _selected[0].name }}","path":"{{ _selected[0].filepath }}","envpath":"{{ _selected[0].envpath }}","overridepath":"{{ _selected[0].overridepath }}"}' 260 | -------------------------------------------------------------------------------- /usr/share/openmediavault/datamodels/conf.service.compose.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "config", 3 | "id": "conf.service.compose", 4 | "title": "compose", 5 | "queryinfo": { 6 | "xpath": "//services/compose", 7 | "iterable": false 8 | }, 9 | "properties": { 10 | "sharedfolderref": { 11 | "type": "string", 12 | "oneOf": [ 13 | { 14 | "type": "string", 15 | "format": "uuidv4" 16 | }, 17 | { 18 | "type": "string", 19 | "maxLength": 0 20 | } 21 | ] 22 | }, 23 | "composeowner": { 24 | "type": "string", 25 | "default": "root" 26 | }, 27 | "composegroup": { 28 | "type": "string", 29 | "default": "root" 30 | }, 31 | "mode": { 32 | "type": "string", 33 | "default": "700" 34 | }, 35 | "fileperms": { 36 | "type": "string", 37 | "default": "600" 38 | }, 39 | "datasharedfolderref": { 40 | "type": "string", 41 | "oneOf": [ 42 | { 43 | "type": "string", 44 | "format": "uuidv4" 45 | }, 46 | { 47 | "type": "string", 48 | "maxLength": 0 49 | } 50 | ] 51 | }, 52 | "backupsharedfolderref": { 53 | "type": "string", 54 | "oneOf": [ 55 | { 56 | "type": "string", 57 | "format": "uuidv4" 58 | }, 59 | { 60 | "type": "string", 61 | "maxLength": 0 62 | } 63 | ] 64 | }, 65 | "backupmaxsize": { 66 | "type": "integer", 67 | "minimum": 0, 68 | "maximum": 65535, 69 | "default": 1 70 | }, 71 | "dockerStorage": { 72 | "type": "string", 73 | "default": "/var/lib/docker" 74 | }, 75 | "dockersharedfolderref": { 76 | "type": "string", 77 | "oneOf": [ 78 | { 79 | "type": "string", 80 | "format": "uuidv4" 81 | }, 82 | { 83 | "type": "string", 84 | "maxLength": 0 85 | } 86 | ] 87 | }, 88 | "logmaxsize": { 89 | "type": "integer", 90 | "minimum": 0, 91 | "maximum": 65535, 92 | "default": 50 93 | }, 94 | "liverestore": { 95 | "type": "boolean" 96 | }, 97 | "urlHostname": { 98 | "type": "string", 99 | "default": "" 100 | }, 101 | "cachetimefiles": { 102 | "type": "integer", 103 | "minimum": 0, 104 | "maximum": 65535, 105 | "default": 60 106 | }, 107 | "cachetimeservices": { 108 | "type": "integer", 109 | "minimum": 0, 110 | "maximum": 65535, 111 | "default": 60 112 | }, 113 | "cachetimestats": { 114 | "type": "integer", 115 | "minimum": 0, 116 | "maximum": 65535, 117 | "default": 60 118 | }, 119 | "cachetimeimages": { 120 | "type": "integer", 121 | "minimum": 0, 122 | "maximum": 65535, 123 | "default": 60 124 | }, 125 | "cachetimenetworks": { 126 | "type": "integer", 127 | "minimum": 0, 128 | "maximum": 65535, 129 | "default": 60 130 | }, 131 | "cachetimevolumes": { 132 | "type": "integer", 133 | "minimum": 0, 134 | "maximum": 65535, 135 | "default": 60 136 | }, 137 | "cachetimecontainers": { 138 | "type": "integer", 139 | "minimum": 0, 140 | "maximum": 65535, 141 | "default": 60 142 | }, 143 | "showcmd": { 144 | "type": "boolean" 145 | }, 146 | "runconfig": { 147 | "type": "boolean" 148 | }, 149 | "files": { 150 | "type": "object", 151 | "properties": { 152 | "file": { 153 | "type": "array", 154 | "items": { 155 | "type": "object", 156 | "properties": { 157 | "uuid": { 158 | "type": "string", 159 | "format": "uuidv4" 160 | }, 161 | "name": { 162 | "type": "string" 163 | }, 164 | "description": { 165 | "type": "string" 166 | }, 167 | "body": { 168 | "type": "string" 169 | }, 170 | "showenv": { 171 | "type": "boolean" 172 | }, 173 | "env": { 174 | "type": "string" 175 | }, 176 | "showoverride": { 177 | "type": "boolean" 178 | }, 179 | "override": { 180 | "type": "string" 181 | } 182 | } 183 | } 184 | } 185 | } 186 | }, 187 | "configs": { 188 | "type": "object", 189 | "properties": { 190 | "config": { 191 | "type": "array", 192 | "items": { 193 | "type": "object", 194 | "properties": { 195 | "uuid": { 196 | "type": "string", 197 | "format": "uuidv4" 198 | }, 199 | "name": { 200 | "type": "string" 201 | }, 202 | "description": { 203 | "type": "string" 204 | }, 205 | "fileref": { 206 | "type": "string", 207 | "format": "uuidv4" 208 | }, 209 | "body": { 210 | "type": "string" 211 | } 212 | } 213 | } 214 | } 215 | } 216 | }, 217 | "dockerfiles": { 218 | "type": "object", 219 | "properties": { 220 | "dockerfile": { 221 | "type": "array", 222 | "items": { 223 | "type": "object", 224 | "properties": { 225 | "uuid": { 226 | "type": "string", 227 | "format": "uuidv4" 228 | }, 229 | "name": { 230 | "type": "string" 231 | }, 232 | "description": { 233 | "type": "string" 234 | }, 235 | "body": { 236 | "type": "string" 237 | }, 238 | "script": { 239 | "type": "string" 240 | }, 241 | "scriptfile": { 242 | "type": "string" 243 | }, 244 | "conf": { 245 | "type": "string" 246 | }, 247 | "conffile": { 248 | "type": "string" 249 | } 250 | } 251 | } 252 | } 253 | } 254 | }, 255 | "jobs": { 256 | "type": "object", 257 | "properties": { 258 | "job": { 259 | "type": "array", 260 | "items": { 261 | "type": "object", 262 | "properties": { 263 | "uuid": { 264 | "type": "string", 265 | "format": "uuidv4" 266 | }, 267 | "enable": { 268 | "type": "boolean" 269 | }, 270 | "filter": { 271 | "type": "string" 272 | }, 273 | "backup": { 274 | "type": "boolean" 275 | }, 276 | "prebackup": { 277 | "type": "string" 278 | }, 279 | "postbackup": { 280 | "type": "string" 281 | }, 282 | "update": { 283 | "type": "boolean" 284 | }, 285 | "prune": { 286 | "type": "boolean" 287 | }, 288 | "filestart": { 289 | "type": "boolean" 290 | }, 291 | "filestop": { 292 | "type": "boolean" 293 | }, 294 | "sendemail": { 295 | "type": "boolean" 296 | }, 297 | "verbose": { 298 | "type": "boolean" 299 | }, 300 | "comment": { 301 | "type": "string" 302 | }, 303 | "excludes": { 304 | "type": "string" 305 | }, 306 | "execution": { 307 | "type": "string", 308 | "enum": [ 309 | "exactly", 310 | "hourly", 311 | "daily", 312 | "weekly", 313 | "monthly", 314 | "yearly", 315 | "reboot" 316 | ], 317 | "default": "exactly" 318 | }, 319 | "minute": { 320 | "type": "string", 321 | "pattern": "^[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[*]$", 322 | "default": 0 323 | }, 324 | "everynminute": { 325 | "type": "boolean", 326 | "default": false 327 | }, 328 | "hour": { 329 | "type": "string", 330 | "pattern": "^[0-9]|1[0-9]|2[0-3]|[*]$", 331 | "default": 0 332 | }, 333 | "everynhour": { 334 | "type": "boolean", 335 | "default": false 336 | }, 337 | "month": { 338 | "type": "string", 339 | "pattern": "^[1-9]|1[0-2]|[*]$", 340 | "default": "*" 341 | }, 342 | "dayofmonth": { 343 | "type": "string", 344 | "pattern": "^[1-9]|1[0-9]|2[0-9]|3[0-1]|[*]$", 345 | "default": "*" 346 | }, 347 | "everyndayofmonth": { 348 | "type": "boolean", 349 | "default": false 350 | }, 351 | "dayofweek": { 352 | "type": "string", 353 | "pattern": "^[1-7]|[*]$", 354 | "default": "*" 355 | } 356 | } 357 | } 358 | } 359 | } 360 | }, 361 | "globalenv": { 362 | "type": "object", 363 | "properties": { 364 | "enabled": { 365 | "type": "boolean" 366 | }, 367 | "globalenv": { 368 | "type": "string" 369 | } 370 | } 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /usr/share/openmediavault/workbench/component.d/omv-services-compose-settings-form-page.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | type: component 3 | data: 4 | name: omv-services-compose-settings-form-page 5 | type: formPage 6 | config: 7 | request: 8 | service: Compose 9 | get: 10 | method: get 11 | post: 12 | method: set 13 | fields: 14 | - type: divider 15 | title: _("Compose Files") 16 | - type: sharedFolderSelect 17 | name: sharedfolderref 18 | label: _("Shared folder") 19 | hasEmptyOption: true 20 | hint: _("Location of compose files") 21 | - type: container 22 | fields: 23 | - type: select 24 | name: composeowner 25 | label: _("Owner of directories and files") 26 | placeholder: _("Select a user ...") 27 | value: "root" 28 | valueField: "name" 29 | textField: "name" 30 | store: 31 | proxy: 32 | service: UserMgmt 33 | get: 34 | method: enumerateAllUsers 35 | sorters: 36 | - dir: asc 37 | prop: name 38 | validators: 39 | required: true 40 | - type: select 41 | name: composegroup 42 | label: _("Group of directories and files") 43 | placeholder: _("Select a group ...") 44 | value: "root" 45 | valueField: "name" 46 | textField: "name" 47 | store: 48 | proxy: 49 | service: UserMgmt 50 | get: 51 | method: enumerateAllGroups 52 | sorters: 53 | - dir: asc 54 | prop: name 55 | validators: 56 | required: true 57 | - type: select 58 | name: mode 59 | label: _("Permissions of directories and files") 60 | value: "700" 61 | store: 62 | data: 63 | - - "700" 64 | - _("Administrator - read/write, Users - no access, Others - no access") 65 | - - "750" 66 | - _("Administrator - read/write, Users - read-only, Others - no access") 67 | - - "770" 68 | - _("Administrator - read/write, Users - read/write, Others - no access") 69 | - - "755" 70 | - _("Administrator - read/write, Users - read-only, Others - read-only") 71 | - - "775" 72 | - _("Administrator - read/write, Users - read/write, Others - read-only") 73 | - - "777" 74 | - _("Everyone - read/write") 75 | - type: divider 76 | title: _("Data") 77 | - type: sharedFolderSelect 78 | name: datasharedfolderref 79 | label: _("Shared folder") 80 | hasEmptyOption: true 81 | hint: _("Optional - Location of persistent container data
Only used to substitute CHANGE_TO_COMPOSE_DATA_PATH in compose and env files with this shared folder path.") 82 | - type: divider 83 | title: _("Backup") 84 | - type: sharedFolderSelect 85 | name: backupsharedfolderref 86 | label: _("Shared folder") 87 | hasEmptyOption: true 88 | hint: _("Location of backups") 89 | - type: numberInput 90 | name: backupmaxsize 91 | label: _("Max Size") 92 | value: 1 93 | validators: 94 | min: 0 95 | max: 65536 96 | patternType: integer 97 | required: true 98 | hint: _("Units in GB. Backup will skip volumes larger than this size.
Set to 0 for unlimited.") 99 | - type: divider 100 | title: _("Docker config") 101 | - type: container 102 | fields: 103 | - type: textInput 104 | name: dockerStorage 105 | label: _("Docker storage") 106 | value: "/var/lib/docker" 107 | hint: _("Leave blank to use a custom /etc/docker/daemon.json") 108 | - type: select 109 | name: dockersharedfolderref 110 | label: _("Shared folder") 111 | hint: _("Optional. If selected, the absolute path of this shared folder will be used to override the Docker storage field path when saved. The Docker storage field path will not be overriden when this shared folder is set to None or when Docker storage field path is blank.") 112 | valueField: uuid 113 | textField: description 114 | hasEmptyOption: true 115 | value: "" 116 | store: 117 | proxy: 118 | service: ShareMgmt 119 | get: 120 | method: enumerateSharedFolders 121 | modifiers: 122 | - type: disabled 123 | constraint: 124 | operator: eq 125 | arg0: 126 | prop: dockerStorage 127 | arg1: "" 128 | - type: numberInput 129 | name: logmaxsize 130 | label: _("Log file max size") 131 | value: 50 132 | validators: 133 | min: 0 134 | max: 65536 135 | patternType: integer 136 | required: true 137 | hint: _("Units in MB. Set to 0 for unlimited.") 138 | modifiers: 139 | - type: disabled 140 | constraint: 141 | operator: eq 142 | arg0: 143 | prop: dockerStorage 144 | arg1: "" 145 | - type: checkbox 146 | name: liverestore 147 | label: _("Live restore") 148 | hint: _("Keep containers running across Docker daemon updates") 149 | value: false 150 | modifiers: 151 | - type: disabled 152 | constraint: 153 | operator: eq 154 | arg0: 155 | prop: dockerStorage 156 | arg1: "" 157 | - type: divider 158 | title: _("Docker info") 159 | - type: textInput 160 | name: dockerStatus 161 | label: _("Status") 162 | submitValue: false 163 | readonly: true 164 | - type: textInput 165 | name: dockerVersion 166 | label: _("Docker version") 167 | submitValue: false 168 | readonly: true 169 | - type: textInput 170 | name: composeVersion 171 | label: _("Compose version") 172 | submitValue: false 173 | readonly: true 174 | - type: divider 175 | title: _("Overrides") 176 | - type: textInput 177 | name: urlHostname 178 | label: _("URL hostname") 179 | value: "" 180 | hint: _("Override hostname when opening ports in Files, Services, and Containers tabs") 181 | - type: checkbox 182 | name: showcmd 183 | label: _("Show commands") 184 | hint: _("Show docker command for task in task dialog") 185 | value: false 186 | - type: checkbox 187 | name: runconfig 188 | label: _("Run config") 189 | hint: _("Run docker compose config to automatically check syntax when saving file") 190 | value: false 191 | - type: divider 192 | title: _("Cache times for tabs") 193 | - type: container 194 | fields: 195 | - type: numberInput 196 | name: cachetimefiles 197 | label: _("Files") 198 | value: 60 199 | hint: _("Units in seconds.
Set to 0 for no caching.") 200 | validators: 201 | min: 0 202 | max: 65536 203 | patternType: integer 204 | required: true 205 | - type: numberInput 206 | name: cachetimeservices 207 | label: _("Services") 208 | value: 60 209 | validators: 210 | min: 0 211 | max: 65536 212 | patternType: integer 213 | required: true 214 | - type: numberInput 215 | name: cachetimestats 216 | label: _("Stats") 217 | value: 60 218 | validators: 219 | min: 0 220 | max: 65536 221 | patternType: integer 222 | required: true 223 | - type: numberInput 224 | name: cachetimeimages 225 | label: _("Images") 226 | value: 60 227 | validators: 228 | min: 0 229 | max: 65536 230 | patternType: integer 231 | required: true 232 | - type: numberInput 233 | name: cachetimenetworks 234 | label: _("Networks") 235 | value: 60 236 | validators: 237 | min: 0 238 | max: 65536 239 | patternType: integer 240 | required: true 241 | - type: numberInput 242 | name: cachetimevolumes 243 | label: _("Volumes") 244 | value: 60 245 | validators: 246 | min: 0 247 | max: 65536 248 | patternType: integer 249 | required: true 250 | - type: numberInput 251 | name: cachetimecontainers 252 | label: _("Containers") 253 | value: 60 254 | validators: 255 | min: 0 256 | max: 65536 257 | patternType: integer 258 | required: true 259 | buttons: 260 | - template: submit 261 | execute: 262 | type: url 263 | url: "/services/compose" 264 | - template: cancel 265 | execute: 266 | type: url 267 | url: "/services/compose" 268 | - text: _("Enable Docker repo") 269 | execute: 270 | type: taskDialog 271 | taskDialog: 272 | config: 273 | title: _("Enable Docker repo ...") 274 | startOnInit: true 275 | request: 276 | service: Compose 277 | method: enableDockerRepo 278 | buttons: 279 | stop: 280 | hidden: true 281 | - text: _("Reinstall Docker") 282 | confirmationDialogConfig: 283 | template: confirmation-danger 284 | message: _("Are you sure you want to reinstall?") 285 | execute: 286 | type: taskDialog 287 | taskDialog: 288 | config: 289 | title: _("Reinstall Docker ...") 290 | startOnInit: true 291 | request: 292 | service: Compose 293 | method: reinstallDocker 294 | buttons: 295 | stop: 296 | hidden: true 297 | - text: _("Restart Docker") 298 | confirmationDialogConfig: 299 | template: confirmation-danger 300 | message: _("Are you sure you want to restart?") 301 | execute: 302 | type: request 303 | request: 304 | service: Compose 305 | method: restartDocker 306 | progressMessage: _("Docker is restarting ...") 307 | successNotification: _("Docker has been restarted.") 308 | - text: _("Clear cache") 309 | execute: 310 | type: request 311 | request: 312 | service: Compose 313 | method: clearCacheFiles 314 | progressMessage: _("Clearing cache ...") 315 | successNotification: _("Cache has been cleared.") 316 | -------------------------------------------------------------------------------- /usr/bin/autocompose.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | import argparse 3 | import datetime 4 | import re 5 | import sys 6 | 7 | from collections import OrderedDict 8 | 9 | import docker 10 | import pyaml 11 | 12 | IGNORE_VALUES = [None, "", [], "null", {}, "default", 0, ",", "no"] 13 | 14 | 15 | def list_container_names(): 16 | c = docker.from_env() 17 | return [container.name for container in c.containers.list(all=True)] 18 | 19 | 20 | def list_network_names(): 21 | c = docker.from_env() 22 | return [network.name for network in c.networks.list()] 23 | 24 | 25 | def generate_network_info(): 26 | networks = {} 27 | 28 | for network_name in list_network_names(): 29 | connection = docker.from_env() 30 | network_attributes = connection.networks.get(network_name).attrs 31 | 32 | values = { 33 | "name": network_attributes.get("Name"), 34 | "scope": network_attributes.get("Scope", "local"), 35 | "driver": network_attributes.get("Driver", None), 36 | "enable_ipv6": network_attributes.get("EnableIPv6", False), 37 | "internal": network_attributes.get("Internal", False), 38 | "ipam": { 39 | "driver": network_attributes.get("IPAM", {}).get("Driver", "default"), 40 | "config": [ 41 | {key.lower(): value for key, value in config.items()} 42 | for config in network_attributes.get("IPAM", {}).get("Config", []) 43 | ], 44 | }, 45 | } 46 | 47 | networks[network_name] = {key: value for key, value in values.items()} 48 | 49 | return networks 50 | 51 | 52 | def main(): 53 | parser = argparse.ArgumentParser( 54 | description="Generate docker-compose yaml definition from running container.", 55 | ) 56 | parser.add_argument( 57 | "-a", 58 | "--all", 59 | action="store_true", 60 | help="Include all active containers", 61 | ) 62 | parser.add_argument( 63 | "-v", 64 | "--version", 65 | type=int, 66 | default=3, 67 | help="Compose file version (1 or 3)", 68 | ) 69 | parser.add_argument( 70 | "cnames", 71 | nargs="*", 72 | type=str, 73 | help="The name of the container to process.", 74 | ) 75 | parser.add_argument( 76 | "-c", 77 | "--createvolumes", 78 | action="store_true", 79 | help="Create new volumes instead of reusing existing ones", 80 | ) 81 | parser.add_argument( 82 | "-f", 83 | "--filter", 84 | type=str, 85 | help="Filter containers by regex", 86 | ) 87 | args = parser.parse_args() 88 | 89 | container_names = args.cnames 90 | 91 | if args.all: 92 | container_names.extend(list_container_names()) 93 | 94 | if args.filter: 95 | cfilter = re.compile(args.filter) 96 | container_names = [c for c in container_names if cfilter.search(c)] 97 | 98 | struct = {} 99 | networks = {} 100 | volumes = {} 101 | containers = {} 102 | 103 | for cname in container_names: 104 | cfile, c_networks, c_volumes = generate(cname, createvolumes=args.createvolumes) 105 | 106 | struct.update(cfile) 107 | 108 | if not c_networks == None: 109 | networks.update(c_networks) 110 | if not c_volumes == None: 111 | volumes.update(c_volumes) 112 | 113 | # moving the networks = None statements outside of the for loop. Otherwise any container could reset it. 114 | if len(networks) == 0: 115 | networks = None 116 | if len(volumes) == 0: 117 | volumes = None 118 | 119 | if args.all: 120 | host_networks = generate_network_info() 121 | networks = host_networks 122 | 123 | render(struct, args, networks, volumes) 124 | 125 | 126 | def render(struct, args, networks, volumes): 127 | # Render yaml file 128 | if args.version == 1: 129 | pyaml.p(OrderedDict(struct)) 130 | else: 131 | ans = {"version": '3.6', "services": struct} 132 | 133 | if networks is not None: 134 | ans["networks"] = networks 135 | 136 | if volumes is not None: 137 | ans["volumes"] = volumes 138 | 139 | pyaml.p(OrderedDict(ans), string_val_style='"') 140 | 141 | 142 | def generate(cname, createvolumes=False): 143 | c = docker.from_env() 144 | 145 | try: 146 | cid = [x.short_id for x in c.containers.list(all=True) if cname == x.name or x.short_id in cname][0] 147 | except IndexError: 148 | print("That container is not available.", file=sys.stderr) 149 | sys.exit(1) 150 | 151 | cattrs = c.containers.get(cid).attrs 152 | 153 | # Build yaml dict structure 154 | 155 | cfile = {} 156 | cfile[cattrs.get("Name")[1:]] = {} 157 | ct = cfile[cattrs.get("Name")[1:]] 158 | 159 | default_networks = ["bridge", "host", "none"] 160 | 161 | values = { 162 | "cap_drop": cattrs.get("HostConfig", {}).get("CapDrop", None), 163 | "cgroup_parent": cattrs.get("HostConfig", {}).get("CgroupParent", None), 164 | "container_name": cattrs.get("Name")[1:], 165 | "devices": [], 166 | "dns": cattrs.get("HostConfig", {}).get("Dns", None), 167 | "dns_search": cattrs.get("HostConfig", {}).get("DnsSearch", None), 168 | "environment": cattrs.get("Config", {}).get("Env", None), 169 | "extra_hosts": cattrs.get("HostConfig", {}).get("ExtraHosts", None), 170 | "image": cattrs.get("Config", {}).get("Image", None), 171 | "labels": cattrs.get("Config", {}).get("Labels", {}), 172 | "links": cattrs.get("HostConfig", {}).get("Links"), 173 | #'log_driver': cattrs.get('HostConfig']['LogConfig']['Type'], 174 | #'log_opt': cattrs.get('HostConfig']['LogConfig']['Config'], 175 | "logging": { 176 | "driver": cattrs.get("HostConfig", {}).get("LogConfig", {}).get("Type", None), 177 | "options": cattrs.get("HostConfig", {}).get("LogConfig", {}).get("Config", None), 178 | }, 179 | "networks": { 180 | x for x in cattrs.get("NetworkSettings", {}).get("Networks", {}).keys() if x not in default_networks 181 | }, 182 | "security_opt": cattrs.get("HostConfig", {}).get("SecurityOpt"), 183 | "ulimits": cattrs.get("HostConfig", {}).get("Ulimits"), 184 | # the line below would not handle type bind 185 | # 'volumes': [f'{m["Name"]}:{m["Destination"]}' for m in cattrs.get('Mounts'] if m['Type'] == 'volume'], 186 | "mounts": cattrs.get("Mounts"), # this could be moved outside of the dict. will only use it for generate 187 | "volume_driver": cattrs.get("HostConfig", {}).get("VolumeDriver", None), 188 | "volumes_from": cattrs.get("HostConfig", {}).get("VolumesFrom", None), 189 | "entrypoint": cattrs.get("Config", {}).get("Entrypoint", None), 190 | "user": cattrs.get("Config", {}).get("User", None), 191 | "working_dir": cattrs.get("Config", {}).get("WorkingDir", None), 192 | "domainname": cattrs.get("Config", {}).get("Domainname", None), 193 | "hostname": cattrs.get("Config", {}).get("Hostname", None), 194 | "ipc": cattrs.get("HostConfig", {}).get("IpcMode", None), 195 | "mac_address": cattrs.get("NetworkSettings", {}).get("MacAddress", None), 196 | "privileged": cattrs.get("HostConfig", {}).get("Privileged", None), 197 | "restart": cattrs.get("HostConfig", {}).get("RestartPolicy", {}).get("Name", None), 198 | "read_only": cattrs.get("HostConfig", {}).get("ReadonlyRootfs", None), 199 | "stdin_open": cattrs.get("Config", {}).get("OpenStdin", None), 200 | "tty": cattrs.get("Config", {}).get("Tty", None), 201 | } 202 | 203 | # Populate devices key if device values are present 204 | if cattrs.get("HostConfig", {}).get("Devices"): 205 | values["devices"] = [ 206 | x["PathOnHost"] + ":" + x["PathInContainer"] for x in cattrs.get("HostConfig", {}).get("Devices") 207 | ] 208 | 209 | networks = {} 210 | if values["networks"] == set(): 211 | del values["networks"] 212 | 213 | if len(cattrs.get("NetworkSettings", {}).get("Networks", {}).keys()) > 0: 214 | assumed_default_network = list(cattrs.get("NetworkSettings", {}).get("Networks", {}).keys())[0] 215 | values["network_mode"] = assumed_default_network 216 | networks = None 217 | else: 218 | networklist = c.networks.list() 219 | for network in networklist: 220 | if network.attrs["Name"] in values["networks"]: 221 | networks[network.attrs["Name"]] = { 222 | "external": (not network.attrs["Internal"]), 223 | "name": network.attrs["Name"], 224 | } 225 | # volumes = {} 226 | # if values['volumes'] is not None: 227 | # for volume in values['volumes']: 228 | # volume_name = volume.split(':')[0] 229 | # volumes[volume_name] = {'external': True} 230 | # else: 231 | # volumes = None 232 | 233 | # handles both the returned values['volumes'] (in c_file) and volumes for both, the bind and volume types 234 | # also includes the read only option 235 | volumes = {} 236 | mountpoints = [] 237 | if values["mounts"] is not None: 238 | for mount in values["mounts"]: 239 | destination = mount["Destination"] 240 | if not mount["RW"]: 241 | destination = destination + ":ro" 242 | if mount["Type"] == "volume": 243 | mountpoints.append(mount["Name"] + ":" + destination) 244 | if not createvolumes: 245 | volumes[mount["Name"]] = { 246 | "external": True 247 | } # to reuse an existing volume ... better to make that a choice? (cli argument) 248 | elif mount["Type"] == "bind": 249 | mountpoints.append(mount["Source"] + ":" + destination) 250 | values["volumes"] = mountpoints 251 | if len(volumes) == 0: 252 | volumes = None 253 | values["mounts"] = None # remove this temporary data from the returned data 254 | 255 | # Check for command and add it if present. 256 | if cattrs.get("Config", {}).get("Cmd") is not None: 257 | values["command"] = cattrs.get("Config", {}).get("Cmd") 258 | 259 | # Check for exposed/bound ports and add them if needed. 260 | try: 261 | expose_value = list(cattrs.get("Config", {}).get("ExposedPorts", {}).keys()) 262 | ports_value = [ 263 | cattrs.get("HostConfig", {}).get("PortBindings", {})[key][0]["HostIp"] 264 | + ":" 265 | + cattrs.get("HostConfig", {}).get("PortBindings", {})[key][0]["HostPort"] 266 | + ":" 267 | + key 268 | for key in cattrs.get("HostConfig", {}).get("PortBindings") 269 | ] 270 | 271 | # If bound ports found, don't use the 'expose' value. 272 | if ports_value not in IGNORE_VALUES: 273 | for index, port in enumerate(ports_value): 274 | if port[0] == ":": 275 | ports_value[index] = port[1:] 276 | 277 | values["ports"] = ports_value 278 | else: 279 | values["expose"] = expose_value 280 | 281 | except (KeyError, TypeError): 282 | # No ports exposed/bound. Continue without them. 283 | ports = None 284 | 285 | # Iterate through values to finish building yaml dict. 286 | for key in values: 287 | value = values[key] 288 | if value not in IGNORE_VALUES: 289 | ct[key] = value 290 | 291 | return cfile, networks, volumes 292 | 293 | 294 | if __name__ == "__main__": 295 | main() 296 | --------------------------------------------------------------------------------