├── .auto-changelog ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── changlog_template.hbs ├── custom-handlebars.js ├── stale.yml └── workflows │ └── build_addon.yml ├── .gitignore ├── .release-it.yml ├── LICENSE ├── README.md ├── images └── screenshot.png ├── nextcloud_backup ├── .README.md.j2 ├── .base_version ├── .dockerignore ├── DOCS.md ├── Dockerfile ├── README.md ├── backend │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ ├── settings.json │ │ └── tasks.json │ ├── error_code.md │ ├── eslint.config.js │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── app.ts │ │ ├── config │ │ │ └── winston.ts │ │ ├── env.ts │ │ ├── postInit.ts │ │ ├── routes │ │ │ ├── action.ts │ │ │ ├── apiV2.ts │ │ │ ├── config.ts │ │ │ ├── homeAssistant.ts │ │ │ ├── messages.ts │ │ │ ├── status.ts │ │ │ └── webdav.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── backupConfigService.ts │ │ │ ├── cronService.ts │ │ │ ├── homeAssistantService.ts │ │ │ ├── orchestrator.ts │ │ │ ├── webdavConfigService.ts │ │ │ └── webdavService.ts │ │ ├── tools │ │ │ ├── cronTools.ts │ │ │ ├── messageManager.ts │ │ │ ├── pathTools.ts │ │ │ ├── status.ts │ │ │ └── toolbox.ts │ │ └── types │ │ │ ├── message.ts │ │ │ ├── services │ │ │ ├── backupConfig.ts │ │ │ ├── backupConfigValidation.ts │ │ │ ├── ha_os_payload.ts │ │ │ ├── ha_os_response.ts │ │ │ ├── orchecstrator.ts │ │ │ ├── webdav.ts │ │ │ ├── webdavConfig.ts │ │ │ ├── webdavConfigValidation.ts │ │ │ └── webdavValidation.ts │ │ │ ├── settings.ts │ │ │ └── status.ts │ └── tsconfig.json ├── build.json ├── config.yml ├── frontend │ ├── .env.development │ ├── .env.production │ ├── .eslintrc-auto-import.json │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierrc.json │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── auto-imports.d.ts │ ├── components.d.ts │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ ├── base.css │ │ │ ├── logo.svg │ │ │ └── main.css │ │ ├── components │ │ │ ├── AlertManager.vue │ │ │ ├── MessageBar.vue │ │ │ ├── NavbarComponent.vue │ │ │ ├── cloud │ │ │ │ ├── CloudDeleteDialog.vue │ │ │ │ ├── CloudList.vue │ │ │ │ └── CloudListItem.vue │ │ │ ├── homeAssistant │ │ │ │ ├── HaDeleteDialog.vue │ │ │ │ ├── HaList.vue │ │ │ │ ├── HaListItem.vue │ │ │ │ └── HaListItemContent.vue │ │ │ ├── settings │ │ │ │ ├── BackupConfig │ │ │ │ │ ├── BackupConfigAddon.vue │ │ │ │ │ ├── BackupConfigAutoBackup.vue │ │ │ │ │ ├── BackupConfigAutoClean.vue │ │ │ │ │ ├── BackupConfigAutoStop.vue │ │ │ │ │ ├── BackupConfigFolder.vue │ │ │ │ │ └── BackupConfigSecurity.vue │ │ │ │ ├── BackupConfigForm.vue │ │ │ │ ├── BackupConfigMenu.vue │ │ │ │ ├── WebdavConfigForm.vue │ │ │ │ └── WebdavConfigMenu.vue │ │ │ └── statusBar │ │ │ │ ├── ActionComponent.vue │ │ │ │ ├── BackupStatus.vue │ │ │ │ ├── ConnectionStatus.vue │ │ │ │ └── StatusBar.vue │ │ ├── composable │ │ │ ├── ConfigForm.ts │ │ │ └── menuSize.ts │ │ ├── env.d.ts │ │ ├── main.ts │ │ ├── plugins │ │ │ ├── index.ts │ │ │ └── vuetify.ts │ │ ├── services │ │ │ ├── actionService.ts │ │ │ ├── configService.ts │ │ │ ├── homeAssistantService.ts │ │ │ ├── kyClient.ts │ │ │ ├── messageService.ts │ │ │ ├── statusService.ts │ │ │ └── webdavService.ts │ │ ├── store │ │ │ ├── alert.ts │ │ │ ├── backupConfig.ts │ │ │ ├── dialogStatus.ts │ │ │ ├── index.ts │ │ │ └── message.ts │ │ ├── styles │ │ │ └── settings.scss │ │ └── types │ │ │ ├── alert.ts │ │ │ ├── backupConfig.ts │ │ │ ├── homeAssistant.ts │ │ │ ├── messages.ts │ │ │ ├── status.ts │ │ │ ├── webdav.ts │ │ │ └── webdavConfig.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── typed-router.d.ts │ └── vite.config.mts ├── icon.png ├── logo.png ├── naming_template.md └── rootfs │ ├── etc │ └── services.d │ │ └── nextcould_backup │ │ └── run │ └── usr │ └── bin │ └── nextcloud_backup.sh └── renovate.json /.auto-changelog: -------------------------------------------------------------------------------- 1 | { 2 | "template": ".github/changlog_template.hbs", 3 | "commitLimit": false 4 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | 4 | 5 | custom: ['buymeacoffee.com/seb6596', 'paypal.me/seb6596'] 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You found a bug ? Please use this template ! 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **System information** 20 | - Home Assistant Version : 21 | - Home Assistant Supervisor Version : 22 | - Nextcloud Version : 23 | - Addon version : 24 | - If any, reverse proxy in fort on Nextcloud : 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **System information** 20 | - Home Assistant Version : 21 | - Home Assistant Supervisor Version : 22 | - Nextcloud Version : 23 | - Addon version : 24 | - If any, reverse proxy in fort on Nextcloud : 25 | 26 | **Additional context** 27 | Add any other context or screenshots about the feature request here. 28 | -------------------------------------------------------------------------------- /.github/changlog_template.hbs: -------------------------------------------------------------------------------- 1 | {{#each releases}} 2 | {{#if @first}} 3 | {{#if href}} 4 | ## [{{title}}]({{href}}){{#if tag}} - {{isoDate}}{{/if}} 5 | {{else}} 6 | ## {{title}}{{#if tag}} - {{isoDate}}{{/if}} 7 | {{/if}} 8 | 9 | {{#commit-list commits heading='## 💥 Breaking changes' message=':boom:'}} 10 | - {{subject}} [`{{shorthash}}`]({{href}}) {{setVar "fix" "true"}} 11 | {{/commit-list}} 12 | {{#unless @root.fix }} 13 | {{#commit-list merges heading='## 💥 Breaking changes' message=':boom:'}} 14 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 15 | {{/commit-list}} 16 | {{/unless}} 17 | {{#if @root.fix }} 18 | {{#commit-list merges heading='' message=':boom:'}} 19 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 20 | {{/commit-list}} 21 | {{/if}} 22 | 23 | 24 | {{#commit-list commits heading='## 🚑 Fixs' message=':ambulance:'}} 25 | - {{subject}} [`{{shorthash}}`]({{href}}) {{setVar "fix" "true"}} 26 | {{/commit-list}} 27 | {{#unless @root.fix }} 28 | {{#commit-list merges heading='## 🚑 Fixs' message=':ambulance:'}} 29 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 30 | {{/commit-list}} 31 | {{/unless}} 32 | {{#if @root.fix }} 33 | {{#commit-list merges heading='' message=':ambulance:'}} 34 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 35 | {{/commit-list}} 36 | {{/if}} 37 | 38 | 39 | {{#commit-list commits heading='## ✏️ Other changes' exclude=':boom:|:ambulance:|:heavy_plus_sign:'}} 40 | - {{subject}} [`{{shorthash}}`]({{href}}) {{setVar "change" "true"}} 41 | {{/commit-list}} 42 | {{#unless @root.change }} 43 | {{#commit-list merges heading='## ✏️ Other changes' exclude=':boom:|:ambulance:|:heavy_plus_sign:'}} 44 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 45 | {{/commit-list}} 46 | {{/unless}} 47 | {{#if @root.change }} 48 | {{#commit-list merges heading='' exclude=':boom:|:ambulance:|:heavy_plus_sign:'}} 49 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 50 | {{/commit-list}} 51 | {{/if}} 52 | 53 | 54 | {{#commit-list commits heading='## ⬆️ Dependency updates' message=':arrow_up:'}} 55 | - {{subject}} [`{{shorthash}}`]({{href}}) {{setVar "dep" "true"}} 56 | {{/commit-list}} 57 | {{#unless @root.dep }} 58 | {{#commit-list merges heading='## ⬆️ Dependency updates' message=':arrow_up:'}} 59 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 60 | {{/commit-list}} 61 | {{/unless}} 62 | {{#if @root.dep }} 63 | {{#commit-list merges heading='' message=':arrow_up:'}} 64 | - {{message}} [`{{id}}`]({{href}}) `{{author}}` 65 | {{/commit-list}} 66 | {{/if}} 67 | {{/if}} 68 | {{/each}} -------------------------------------------------------------------------------- /.github/custom-handlebars.js: -------------------------------------------------------------------------------- 1 | module.exports = function (Handlebars) { 2 | Handlebars.registerHelper("setVar", function(varName, varValue, options) { 3 | options.data.root[varName] = varValue; 4 | }); 5 | } -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 30 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - no-stale 10 | - enhancement 11 | # Label to use when marking an issue as stale 12 | staleLabel: stale 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | **/.yarn/cache 44 | **/.yarn/install-state.gz 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | status.json 107 | conf.json 108 | webdav_conf.json 109 | 110 | .idea 111 | -------------------------------------------------------------------------------- /.release-it.yml: -------------------------------------------------------------------------------- 1 | github: 2 | release: true 3 | draft: true 4 | releaseName: "V${version}" 5 | 6 | 7 | git: 8 | changelog: "auto-changelog --stdout --handlebars-setup .github/custom-handlebars.js" 9 | push: false 10 | tag: false 11 | commit: false 12 | 13 | npm: 14 | publish: false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-ons: Nextcloud Backup 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | ![Project Stage][project-stage-shield] 5 | [![License][license-shield]](LICENSE) 6 | 7 | ![Supports aarch64 Architecture][aarch64-shield] 8 | ![Supports amd64 Architecture][amd64-shield] 9 | ![Supports armhf Architecture][armhf-shield] 10 | ![Supports armv7 Architecture][armv7-shield] 11 | ![Supports i386 Architecture][i386-shield] 12 | 13 | ![Project Maintenance][maintenance-shield] 14 | 15 | [![Community Forum][forum-shield]][forum] 16 | 17 | 18 | 19 | 20 | 21 | Easily backup your Home Assistant snapshots to Nextcloud 22 | 23 | ![Nextcloud Backup Screenshot](images/screenshot.png) 24 | 25 | ## About 26 | 27 | Easily backup your Home Assistant snapshots to Nextcloud. 28 | Auto backup can be configured via the web interface. 29 | ### Features 30 | 31 | - __Auto Backup__ : Configure this add-on to automatically backup your HassIO instance 32 | - __Selective Backup__ : You can specify witch folder and add-on you want to backup. 33 | - __Password protected Backup__ : this add-on can use the Home Assistant snapshot encryption. 34 | - __Auto Clean__ : You can specify the maximum number of local snapshots and (__ONLY__) auto backed-up snapshots. 35 | - __Restore__ : Upload backed-up snapshot to Home assistant. 36 | - __Auto Stop__ : This addon can stop addons before backup and restart them after backup 37 | - __Web UI__ : All the configuration is based on an easy-to-use web interface, no yaml needed. 38 | - ~~__Home Assistant State Entities__ : This addon create 2 entite in HA : `binary_sensor.nextcloud_backup_error` and `sensor.nextcloud_backup_status`~~ 39 | > __Info:__ 40 | > Auto Clean is executed after every upload and every day at 00h30 41 | 42 | ## Installation 43 | 44 | The installation of this add-on is pretty straightforward and not different in 45 | comparison to installing any other Hass.io add-on. 46 | 47 | 1. [Add our Home Assisant add-ons repository][repository] to your HassOS instance. 48 | 1. Install the "Nextcloud Backup" add-on. 49 | 1. Start the "Nextcloud Backup" add-on 50 | 1. Check the logs of the "Nextcloud Backup" add-on to see if everything went well. 51 | 1. Open the web UI for the "Nextcloud Backup" to configure the add-on. 52 | 53 | > **NOTE**: Do not add this repository to HassOS, please use: `https://github.com/Sebclem/sebclem-hassio-addon-repository`. 54 | 55 | ## Configuration 56 | The configuration documention can be found [here][config_doc] 57 | 58 | ## Support 59 | 60 | Got questions? 61 | 62 | You have several options to get them answered: 63 | 64 | - The [Home Assistant Discord chat server][discord-ha] for general Home 65 | Assistant discussions and questions. 66 | - The Home Assistant [Community Forum][forum]. 67 | - Join the [Reddit subreddit][reddit] in [/r/homeassistant][reddit] 68 | 69 | You could also [open an issue here][issue] GitHub. 70 | 71 | 80 | 81 | ## Authors & contributors 82 | 83 | The original setup of this repository is by [Sebastien Clement][Sebclem]. 84 | 85 | For a full list of all authors and contributors, 86 | check [the contributor's page][contributors]. 87 | 88 | 89 | [aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg 90 | [amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg 91 | [armhf-shield]: https://img.shields.io/badge/armhf-no-red.svg 92 | [armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg 93 | [buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg 94 | [buymeacoffee]: https://www.buymeacoffee.com/seb6596 95 | [Sebclem]: https://github.com/Sebclem 96 | [discord-ha]: https://discord.gg/c5DvZ4e 97 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg 98 | [forum]: https://community.home-assistant.io/ 99 | [i386-shield]: https://img.shields.io/badge/i386-no-red.svg 100 | [issue]: https://github.com/Sebclem/hassio-nextcloud-backup/issues 101 | [license-shield]: https://img.shields.io/github/license/Sebclem/hassio-nextcloud-backup.svg 102 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2024.svg 103 | [project-stage-shield]: https://img.shields.io/badge/project%20stage-Beta-red.svg 104 | [reddit]: https://reddit.com/r/homeassistant 105 | [releases-shield]: https://img.shields.io/github/release/Sebclem/hassio-nextcloud-backup.svg?include_prereleases 106 | [releases]: https://github.com/Sebclem/hassio-nextcloud-backup/releases 107 | [repository]: https://github.com/Sebclem/sebclem-hassio-addon-repository 108 | [contributors]: https://github.com/Sebclem/hassio-nextcloud-backup/graphs/contributors 109 | [semver]: https://semver.org/spec/v2.0.0.htm 110 | [config_doc]: https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/nextcloud_backup/DOCS.md 111 | 112 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sebclem/hassio-nextcloud-backup/82d3473761094e3fff39bf1432ba79fca16dcd96/images/screenshot.png -------------------------------------------------------------------------------- /nextcloud_backup/.README.md.j2: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-ons: Nextcloud Backup 2 | ![Nextcloud Backup Logo][logo] 3 | 4 | [![Release][release-shield]][release] ![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield] 5 | 6 | 7 | 8 | ## About 9 | 10 | Easily backup your Home Assistant snapshots to Nextcloud. 11 | 12 | ### Features 13 | - __Auto Backup__ : Configure this add-on to automatically backup your HassIO instance 14 | - __Selective Backup__ : You can specify witch folder and add-on you want to backup. 15 | - __Password protected Backup__ : this add-on can use the Home Assistant snapshot encryption. 16 | - __Auto Clean__ : You can specify the maximum number of local snapshots and (__ONLY__) auto backed-up snapshots. 17 | - __Auto Stop__ : This addon can stop addons before backup and restart them after backup 18 | - __Restore__ : Upload backed-up snapshot to Home assistant. 19 | - __Web UI__ : All the configuration is based on an easy-to-use web interface, no yaml needed. 20 | - __Home Assistant State Entities__ : This addon create 2 entite in HA : `binary_sensor.nextcloud_backup_error` and `sensor.nextcloud_backup_status` 21 | 22 | 23 | [Click here for the full documentation][docs] 24 | 25 | ![Nextcloud Backup Screenshot][image] 26 | 27 | [docs]: https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/README.md 28 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg 29 | [forum]: https://community.home-assistant.io/ 30 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2024.svg 31 | [project-stage-shield]: https://img.shields.io/badge/project%20stage-beta-red.svg 32 | [release-shield]: https://img.shields.io/badge/version-{{ version }}-blue.svg 33 | [release]: https://img.shields.io/badge/version-{{ version }}-blue.svg 34 | [image]: https://github.com/Sebclem/hassio-nextcloud-backup/raw/master/images/screenshot.png 35 | [logo]: https://github.com/Sebclem/hassio-nextcloud-backup/raw/master/nextcloud_backup/logo.png 36 | -------------------------------------------------------------------------------- /nextcloud_backup/.base_version: -------------------------------------------------------------------------------- 1 | 3.18 -------------------------------------------------------------------------------- /nextcloud_backup/.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | 11 | # Diagnostic reports (https://nodejs.org/api/report.html) 12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | *.pid.lock 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | **/node_modules/ 44 | jspm_packages/ 45 | 46 | # Snowpack dependency directory (https://snowpack.dev/) 47 | web_modules/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | .parcel-cache 80 | 81 | # Next.js build output 82 | .next 83 | out 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # yarn v2 114 | **/.yarn/cache 115 | **/.yarn/unplugged 116 | **/.yarn/build-state.yml 117 | **/.yarn/install-state.gz 118 | **/.pnp.* 119 | 120 | -------------------------------------------------------------------------------- /nextcloud_backup/DOCS.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | The installation of this add-on is pretty straightforward and not different in 4 | comparison to installing any other Hass.io add-on. 5 | 6 | 1. [Add our Home Assisant add-ons repository][repository] to your HassOS instance. 7 | 1. Install the "Nextcloud Backup" add-on. 8 | 1. Start the "Nextcloud Backup" add-on 9 | 1. Check the logs of the "Nextcloud Backup" add-on to see if everything went well. 10 | 1. Open the web UI for the "Nextcloud Backup" to configure the add-on. 11 | 12 | > **NOTE**: Do not add this repository to HassOS, please use: `https://github.com/Sebclem/sebclem-hassio-addon-repository`. 13 | 14 | 15 | ## NextCloud config 16 | 17 | First, you need to configure all your Nextcloud information. 18 | 19 | 1. Open the add-on Web UI 20 | 1. Open NextCloud config menu (Top right gear, and Nextcloud) 21 | 1. If your NextCloud instance uses `HTTPS`, enable the `SSL` lever 22 | 1. Enter the `hostname` of the NextCloud instance. You can specify a custom port by adding `:[port]` at the end of the hostname (`exemple.com:8080`) 23 | 1. Now enter the Nextcloud username that you would like this add-on to use. 24 | 1. For the password, we highly recommend using an `App Password`. 25 | 26 | >To generate a `App Password`, go into your personal setting into Nextcloud, Security page. You can generate one via the `Devices & sessions` section. Simply enter a name and hit `Create new app password`. 27 | 1. You can change the backup directory in Nextcloud. Default is `/Hassio Backup/`. 28 | 29 | ## Backup config 30 | You can now configure the automatic backup. 31 | 32 | 1. Open the add-on Web UI 33 | 1. Open Backup config menu (Top right gear, and Backup) 34 | 1. Specify the backup naming template, this will define how your backup will be named. 35 | On this field, you can use some variables that are documented [here][variable_doc]. 36 | The default value is `{type}-{ha_version}-{date}_{hour}`. 37 | 1. You can now choose witch folder and add-on you want to include in your backup. 38 | 1. If you want to protect your backup with a password, enable `Password Protected` and specify the password. 39 | 1. Now select the backup frequency. 40 | 1. If you want to auto stop addons before backup, select then in `Auto Stop Addons` (*Note: These addons will be re-started after backup*) 41 | 1. You can finally enable Auto clean for Local Snapshot (Snapshot in Home Assistant) and Nextcloud Backups. 42 | If enabled, you can specify how much Local Snapshot and Nextcloud Backup you want to keep before deleting the older one. 43 | > __Info:__ 44 | > Auto Clean is executed after every upload and every day at 00h30 45 | 46 | 47 | ## Home Assitant Os Configuration 48 | 49 | **Note**: _Remember to restart the add-on when the configuration is changed._ 50 | 51 | Example add-on configuration: 52 | 53 | ```json 54 | { 55 | "log_level": "info" 56 | } 57 | ``` 58 | 59 | ### Option: `log_level` 60 | 61 | The `log_level` option controls the level of log output by the addon and can 62 | be changed to be more or less verbose, which might be useful when you are 63 | dealing with an unknown issue. Possible values are: 64 | 65 | - `trace`: Show every detail, like all called internal functions. 66 | - `debug`: Shows detailed debug information. 67 | - `info`: Normal (usually) interesting events. 68 | - `warning`: Exceptional occurrences that are not errors. 69 | - `error`: Runtime errors that do not require immediate action. 70 | - `fatal`: Something went terribly wrong. Add-on becomes unusable. 71 | 72 | Please note that each level automatically includes log messages from a 73 | more severe level, e.g., `debug` also shows `info` messages. By default, 74 | the `log_level` is set to `info`, which is the recommended setting unless 75 | you are troubleshooting. 76 | 77 | [variable_doc]: https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/nextcloud_backup/naming_template.md 78 | [repository]: https://github.com/Sebclem/sebclem-hassio-addon-repository 79 | -------------------------------------------------------------------------------- /nextcloud_backup/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BUILD_FROM=ghcr.io/home-assistant/amd64-base:3.20 2 | 3 | FROM node:20 AS frontend-builder 4 | 5 | WORKDIR /app 6 | 7 | COPY frontend/package.json frontend/pnpm-lock.yaml ./ 8 | RUN corepack enable && pnpm install 9 | 10 | COPY frontend/ . 11 | 12 | RUN pnpm build 13 | 14 | FROM node:20 AS backend-builder 15 | 16 | WORKDIR /app 17 | 18 | COPY backend/package.json backend/pnpm-lock.yaml ./ 19 | RUN corepack enable && pnpm install 20 | 21 | COPY backend/ . 22 | 23 | RUN pnpm build 24 | 25 | FROM $BUILD_FROM 26 | 27 | COPY rootfs/etc /etc/ 28 | COPY rootfs/usr /usr/ 29 | 30 | RUN apk add --no-cache nodejs-current && mkdir -p /usr/local/sbin/ && ln -s /usr/bin/node /usr/local/sbin/node 31 | 32 | WORKDIR /opt/nextcloud_backup/ 33 | 34 | COPY backend/package.json backend/pnpm-lock.yaml ./ 35 | 36 | COPY --from=backend-builder /app/dist . 37 | COPY --from=backend-builder /app/node_modules ./node_modules 38 | COPY --from=frontend-builder /app/dist ./public 39 | -------------------------------------------------------------------------------- /nextcloud_backup/README.md: -------------------------------------------------------------------------------- 1 | # Home Assistant Add-ons: Nextcloud Backup 2 | ![Nextcloud Backup Logo][logo] 3 | 4 | [![Release][release-shield]][release] ![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield] 5 | 6 | 7 | 8 | 9 | ## About 10 | 11 | Easily backup your Home Assistant snapshots to Nextcloud. 12 | ### Features 13 | 14 | - __Auto Backup__ : Configure this add-on to automatically backup your HassIO instance 15 | - __Selective Backup__ : You can specify witch folder and add-on you want to backup. 16 | - __Password protected Backup__ : this add-on can use the Home Assistant snapshot encryption. 17 | - __Auto Clean__ : You can specify the maximum number of local snapshots and (__ONLY__) auto backed-up snapshots. 18 | - __Auto Stop__ : This addon can stop addons before backup and restart them after backup 19 | - __Restore__ : Upload backed-up snapshot to Home assistant. 20 | - __Web UI__ : All the configuration is based on an easy-to-use web interface, no yaml needed. 21 | - __Home Assistant State Entities__ : This addon create 2 entite in HA : `binary_sensor.nextcloud_backup_error` and `sensor.nextcloud_backup_status` 22 | 23 | 24 | 25 | [Click here for the full documentation][docs] 26 | 27 | ![Nextcloud Backup Screenshot](../images/screenshot.png) 28 | 29 | 30 | 31 | [docs]: https://github.com/Sebclem/hassio-nextcloud-backup/blob/master/README.md 32 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg 33 | [forum]: https://community.home-assistant.io/ 34 | [maintenance-shield]: https://img.shields.io/maintenance/yes/2022.svg 35 | [project-stage-shield]: https://img.shields.io/badge/project%20stage-Beta-red.svg 36 | [release-shield]: https://img.shields.io/github/release/Sebclem/hassio-nextcloud-backup.svg 37 | [release]: https://github.com/Sebclem/hassio-nextcloud-backup/releases 38 | [logo]: https://github.com/Sebclem/hassio-nextcloud-backup/raw/master/nextcloud_backup/logo.png 39 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/.gitignore: -------------------------------------------------------------------------------- 1 | dist/** 2 | .vscode/* 3 | !.vscode/settings.json 4 | !.vscode/launch.json 5 | !.vscode/tasks.json -------------------------------------------------------------------------------- /nextcloud_backup/backend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "console": "integratedTerminal", 9 | "internalConsoleOptions": "neverOpen", 10 | "name": "nodemon", 11 | "program": "${workspaceFolder}/dist/server.js", 12 | "request": "launch", 13 | "restart": true, 14 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/nodemon", 15 | "skipFiles": ["/**"], 16 | "type": "node", 17 | "preLaunchTask": "npm: build:watch" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.inlayHints.enumMemberValues.enabled": true, 3 | "typescript.inlayHints.propertyDeclarationTypes.enabled": true, 4 | "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true, 5 | "typescript.inlayHints.parameterNames.enabled": "literals", 6 | "typescript.inlayHints.functionLikeReturnTypes.enabled": true, 7 | "javascript.inlayHints.enumMemberValues.enabled": true, 8 | "javascript.inlayHints.propertyDeclarationTypes.enabled": true, 9 | "javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": true, 10 | "javascript.inlayHints.parameterNames.enabled": "literals", 11 | "editor.formatOnSave": true 12 | } 13 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build:watch", 7 | "group": "build", 8 | "label": "npm: build:watch", 9 | "detail": "tsc -w", 10 | "isBackground": true, 11 | "problemMatcher": "$tsc-watch" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/error_code.md: -------------------------------------------------------------------------------- 1 | # Error Codes 2 | 3 | - `1` => Fail to download snap 4 | - `2` => Nextcloud config invalid 5 | - `3` => Can't connect to Nextcloud 6 | - `4` => Upload snap fail 7 | - `5` => Fail create new snap 8 | - `6` => Fail to clean 9 | - `7` => Fail to download snap 10 | - `8` => Fail to stop addon 11 | - `9` => Fail to start addon 12 | 13 | 14 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | 6 | export default tseslint.config( 7 | eslint.configs.recommended, 8 | ...tseslint.configs.recommendedTypeChecked, 9 | { 10 | languageOptions: { 11 | parserOptions: { 12 | project: true, 13 | tsconfigRootDir: import.meta.dirname, 14 | } 15 | } 16 | }, 17 | { 18 | ignores: ["dist/", "eslint.config.js"] 19 | } 20 | ); -------------------------------------------------------------------------------- /nextcloud_backup/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexcloud-backup", 3 | "version": "0.8.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build:watch": "tsc -w", 8 | "build": "tsc --build --verbose", 9 | "dev": "pnpm build && concurrently -n tsc,node \"pnpm build:watch\" \"pnpm serve:watch\"", 10 | "lint": "tsc --noEmit && eslint --quiet --fix", 11 | "serve:watch": "nodemon dist/server.js", 12 | "serve": "node dist/server.js" 13 | }, 14 | "dependencies": { 15 | "app-root-path": "3.1.0", 16 | "cookie-parser": "1.4.6", 17 | "cors": "^2.8.5", 18 | "cron": "3.1.7", 19 | "debug": "4.3.6", 20 | "errorhandler": "^1.5.1", 21 | "express": "4.20.0", 22 | "fast-xml-parser": "^4.4.1", 23 | "figlet": "^1.7.0", 24 | "form-data": "4.0.0", 25 | "got": "14.4.2", 26 | "http-errors": "2.0.0", 27 | "joi": "^17.13.3", 28 | "jquery": "3.7.1", 29 | "kleur": "^4.1.5", 30 | "luxon": "3.5.0", 31 | "morgan": "1.10.0", 32 | "url-join": "^5.0.0", 33 | "webdav": "5.7.1", 34 | "winston": "3.14.1" 35 | }, 36 | "packageManager": "pnpm@9.7.0", 37 | "devDependencies": { 38 | "@eslint/js": "^9.9.0", 39 | "@tsconfig/recommended": "^1.0.7", 40 | "@types/cookie-parser": "^1.4.7", 41 | "@types/cors": "^2.8.17", 42 | "@types/errorhandler": "^1.5.3", 43 | "@types/eslint__js": "^8.42.3", 44 | "@types/express": "^4.17.21", 45 | "@types/figlet": "^1.5.8", 46 | "@types/http-errors": "^2.0.4", 47 | "@types/luxon": "^3.4.2", 48 | "@types/morgan": "^1.9.9", 49 | "@types/node": "^22.3.0", 50 | "concurrently": "9.0.1", 51 | "dotenv": "^16.4.5", 52 | "eslint": "^9.9.0", 53 | "nodemon": "^3.1.4", 54 | "ts-node": "^10.9.2", 55 | "typescript": "^5.5.4", 56 | "typescript-eslint": "8.1.0" 57 | } 58 | } -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/app.ts: -------------------------------------------------------------------------------- 1 | import cookieParser from "cookie-parser"; 2 | import cors from "cors"; 3 | import express from "express"; 4 | 5 | import morgan from "morgan"; 6 | import path from "path"; 7 | import { fileURLToPath } from "url"; 8 | import logger from "./config/winston.js"; 9 | import apiV2Router from "./routes/apiV2.js"; 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | 13 | const app = express(); 14 | 15 | app.use( 16 | cors({ 17 | origin: true, 18 | }) 19 | ); 20 | 21 | app.set("port", process.env.PORT || 3000); 22 | if (process.env.ACCESS_LOG == "true") { 23 | app.use( 24 | morgan("dev", { stream: { write: (message) => logger.debug(message) } }) 25 | ); 26 | } 27 | app.use(express.json()); 28 | app.use(express.urlencoded({ extended: false })); 29 | app.use(cookieParser()); 30 | app.use(express.static(path.join(__dirname, "public"))); 31 | 32 | app.use("/v2/api/", apiV2Router); 33 | app.get("/", (req, res) => { 34 | res.sendFile(path.join(__dirname, "public/index.html")); 35 | }); 36 | /* 37 | ----------------------------------------------------------- 38 | Error handler 39 | ---------------------------------------------------------- 40 | */ 41 | 42 | export default app; 43 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/config/winston.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | const logger = winston.createLogger({ 3 | level: process.env.LOG_LEVEL || 'info', 4 | format: winston.format.combine( 5 | winston.format.timestamp({ 6 | format: "YYYY-MM-DD HH:mm:ss", 7 | }), 8 | winston.format.errors({ stack: true }), 9 | // winston.format.splat(), 10 | winston.format.colorize(), 11 | winston.format.align(), 12 | winston.format.printf(({ level, message, timestamp }) => { 13 | return `[${timestamp}] [${level}]: ${message}`; 14 | }) 15 | ), 16 | transports: [ 17 | // 18 | // - Write to all logs with level `info` and below to `quick-start-combined.log`. 19 | // - Write all logs error (and below) to `quick-start-error.log`. 20 | // 21 | new winston.transports.Console({ handleExceptions: true }), 22 | // new winston.transports.File({filename: '/data/NCB.log', handleExceptions: true}) 23 | ], 24 | }); 25 | 26 | 27 | export default logger; -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/env.ts: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | dotenv.config(); 3 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/postInit.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, mkdirSync } from "fs"; 2 | import logger from "./config/winston.js"; 3 | import * as homeAssistantService from "./services/homeAssistantService.js"; 4 | import * as statusTools from "./tools/status.js"; 5 | import kleur from "kleur"; 6 | import { 7 | checkWebdavLogin, 8 | createBackupFolder, 9 | } from "./services/webdavService.js"; 10 | import { 11 | getWebdavConfig, 12 | validateWebdavConfig, 13 | } from "./services/webdavConfigService.js"; 14 | import messageManager from "./tools/messageManager.js"; 15 | import { initCron } from "./services/cronService.js"; 16 | import { getBackupConfig } from "./services/backupConfigService.js"; 17 | 18 | function postInit() { 19 | logger.info(`Log level: ${process.env.LOG_LEVEL}`); 20 | 21 | logger.info( 22 | `Backup timeout: ${ 23 | (process.env.CREATE_BACKUP_TIMEOUT 24 | ? parseInt(process.env.CREATE_BACKUP_TIMEOUT) 25 | : false) || 90 * 60 * 1000 26 | }` 27 | ); 28 | 29 | if (!existsSync("/data")) mkdirSync("/data"); 30 | statusTools.init(); 31 | logger.info("Satus : " + kleur.green().bold("Go !")); 32 | 33 | homeAssistantService.getBackups().then( 34 | () => { 35 | logger.info("Hassio API : " + kleur.green().bold("Go !")); 36 | }, 37 | (err) => { 38 | logger.error("Hassio API : " + kleur.red().bold("FAIL !")); 39 | logger.error("... " + err); 40 | } 41 | ); 42 | 43 | const webdavConf = getWebdavConfig(); 44 | validateWebdavConfig(webdavConf) 45 | .then( 46 | () => { 47 | logger.info("Webdav config: " + kleur.green().bold("Go !")); 48 | return checkWebdavLogin(webdavConf); 49 | }, 50 | (reason: Error) => { 51 | logger.error("Webdav config: " + kleur.red().bold("FAIL !")); 52 | logger.error(reason); 53 | messageManager.error("Invalid webdav config", reason.message); 54 | } 55 | ) 56 | .then( 57 | () => { 58 | logger.info("Webdav : " + kleur.green().bold("Go !")); 59 | return createBackupFolder(webdavConf); 60 | }, 61 | (reason) => { 62 | logger.error("Webdav : " + kleur.red().bold("FAIL !")); 63 | logger.error(reason); 64 | } 65 | ) 66 | .then( 67 | () => { 68 | logger.info("Webdav fodlers: " + kleur.green().bold("Go !")); 69 | return initCron(getBackupConfig()); 70 | }, 71 | (reason) => { 72 | logger.error("Webdav folders: " + kleur.red().bold("FAIL !")); 73 | logger.error(reason); 74 | } 75 | ) 76 | .then( 77 | () => { 78 | logger.info("Cron: " + kleur.green().bold("Go !")); 79 | }, 80 | () => { 81 | logger.info("Cron: " + kleur.red().bold("FAIL !")); 82 | } 83 | ); 84 | } 85 | 86 | export default postInit; 87 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/action.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { doBackupWorkflow } from "../services/orchestrator.js"; 3 | import { WorkflowType } from "../types/services/orchecstrator.js"; 4 | import logger from "../config/winston.js"; 5 | import { clean as webdavClean } from "../services/webdavService.js"; 6 | import { getBackupConfig } from "../services/backupConfigService.js"; 7 | import { getWebdavConfig } from "../services/webdavConfigService.js"; 8 | import { clean } from "../services/homeAssistantService.js"; 9 | 10 | const actionRouter = express.Router(); 11 | 12 | actionRouter.post("/backup", (req, res) => { 13 | doBackupWorkflow(WorkflowType.MANUAL) 14 | .then(() => { 15 | logger.info("All good !"); 16 | }) 17 | .catch(() => { 18 | logger.error("Something wrong !"); 19 | }); 20 | res.sendStatus(202); 21 | }); 22 | 23 | actionRouter.post("/clean", (req, res) => { 24 | const backupConfig = getBackupConfig(); 25 | const webdavConfig = getWebdavConfig(); 26 | webdavClean(backupConfig, webdavConfig) 27 | .then(() => { 28 | return clean(backupConfig); 29 | }) 30 | .then(() => { 31 | return webdavClean(backupConfig, webdavConfig); 32 | }) 33 | .then(() => { 34 | logger.info("All good !"); 35 | }) 36 | .catch(() => { 37 | logger.error("Something wrong !"); 38 | }); 39 | res.sendStatus(202); 40 | }); 41 | 42 | export default actionRouter; 43 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/apiV2.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import configRouter from "./config.js"; 3 | import homeAssistant from "./homeAssistant.js"; 4 | import messageRouter from "./messages.js"; 5 | import webdavRouter from "./webdav.js"; 6 | import statusRouter from "./status.js"; 7 | import actionRouter from "./action.js"; 8 | 9 | const router = express.Router(); 10 | 11 | router.use("/homeAssistant", homeAssistant); 12 | router.use("/config", configRouter); 13 | router.use("/webdav", webdavRouter); 14 | router.use("/messages", messageRouter); 15 | router.use("/status", statusRouter); 16 | router.use("/action", actionRouter); 17 | 18 | export default router; 19 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/config.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | getBackupConfig, 4 | saveBackupConfig, 5 | validateBackupConfig, 6 | } from "../services/backupConfigService.js"; 7 | import { 8 | getWebdavConfig, 9 | saveWebdavConfig, 10 | validateWebdavConfig, 11 | } from "../services/webdavConfigService.js"; 12 | import { 13 | checkWebdavLogin, 14 | createBackupFolder, 15 | } from "../services/webdavService.js"; 16 | import type { BackupConfig } from "../types/services/backupConfig.js"; 17 | import type { ValidationError } from "joi"; 18 | import type { WebdavConfig } from "../types/services/webdavConfig.js"; 19 | 20 | const configRouter = express.Router(); 21 | 22 | configRouter.get("/backup", (req, res) => { 23 | res.json(getBackupConfig()); 24 | }); 25 | 26 | configRouter.put("/backup", (req, res) => { 27 | validateBackupConfig(req.body as BackupConfig) 28 | .then(() => { 29 | return saveBackupConfig(req.body as BackupConfig); 30 | }) 31 | .then(() => { 32 | res.status(204).send(); 33 | }) 34 | .catch((error: ValidationError) => { 35 | if (error.details) { 36 | res.status(400).json({ type: "validation", errors: error.details }); 37 | } else { 38 | res.status(400).json({ type: "cron", errors: [error.message] }); 39 | } 40 | }); 41 | }); 42 | 43 | configRouter.get("/webdav", (req, res) => { 44 | res.json(getWebdavConfig()); 45 | }); 46 | 47 | configRouter.put("/webdav", (req, res) => { 48 | validateWebdavConfig(req.body as WebdavConfig) 49 | .then(() => { 50 | return checkWebdavLogin(req.body as WebdavConfig, true); 51 | }) 52 | .then(() => { 53 | return createBackupFolder(req.body as WebdavConfig); 54 | }) 55 | .then(() => { 56 | saveWebdavConfig(req.body as WebdavConfig); 57 | res.status(204).send(); 58 | }) 59 | .catch((error: ValidationError) => { 60 | if (error.details) { 61 | res.status(400).json({ type: "validation", errors: error.details }); 62 | } else { 63 | res.status(400).json({ type: "validation", errors: error }); 64 | } 65 | }); 66 | }); 67 | 68 | export default configRouter; 69 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/homeAssistant.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import * as haOsService from "../services/homeAssistantService.js"; 3 | import { uploadToCloud } from "../services/orchestrator.js"; 4 | import logger from "../config/winston.js"; 5 | 6 | const homeAssistantRouter = express.Router(); 7 | 8 | homeAssistantRouter.get("/backups/", (req, res) => { 9 | haOsService 10 | .getBackups() 11 | .then((value) => { 12 | res.json( 13 | value.body.data.backups.sort( 14 | (a, b) => Date.parse(b.date) - Date.parse(a.date) 15 | ) 16 | ); 17 | }) 18 | .catch((reason) => { 19 | res.status(500).json(reason); 20 | }); 21 | }); 22 | 23 | homeAssistantRouter.get("/backup/:slug", (req, res) => { 24 | haOsService 25 | .getBackupInfo(req.params.slug) 26 | .then((value) => { 27 | res.json(value.body.data); 28 | }) 29 | .catch((reason) => { 30 | res.status(500).json(reason); 31 | }); 32 | }); 33 | 34 | homeAssistantRouter.delete("/backup/:slug", (req, res) => { 35 | haOsService 36 | .delSnap(req.params.slug) 37 | .then((value) => { 38 | res.json(value.body); 39 | }) 40 | .catch((reason) => { 41 | res.status(500).json(reason); 42 | }); 43 | }); 44 | 45 | homeAssistantRouter.post("/backup/:slug/upload", (req, res) => { 46 | uploadToCloud(req.params.slug) 47 | .then(() => { 48 | logger.info("All good !"); 49 | }) 50 | .catch(() => { 51 | logger.error("Something wrong !"); 52 | }); 53 | res.sendStatus(202); 54 | }); 55 | 56 | homeAssistantRouter.get("/addons", (req, res) => { 57 | haOsService 58 | .getAddonList() 59 | .then((value) => { 60 | res.json(value.body.data); 61 | }) 62 | .catch((reason) => { 63 | res.status(500).json(reason); 64 | }); 65 | }); 66 | 67 | homeAssistantRouter.get("/folders", (req, res) => { 68 | res.json(haOsService.getFolderList()); 69 | }); 70 | 71 | export default homeAssistantRouter; 72 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/messages.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import messageManager from "../tools/messageManager.js"; 3 | 4 | const messageRouter = express.Router(); 5 | 6 | messageRouter.get("/", (req, res) => { 7 | res.json(messageManager.get()); 8 | }); 9 | 10 | messageRouter.patch("/:messageId/readed", (req, res) => { 11 | if (messageManager.markReaded(req.params.messageId)) { 12 | res.json(messageManager.get()); 13 | } else { 14 | res.sendStatus(404); 15 | } 16 | }); 17 | 18 | messageRouter.post("/allReaded", (req, res) => { 19 | messageManager.markAllReaded(); 20 | res.json(messageManager.get()); 21 | }); 22 | 23 | export default messageRouter; 24 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/status.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { getStatus } from "../tools/status.js"; 3 | 4 | const statusRouter = express.Router(); 5 | 6 | 7 | statusRouter.get('/', (req, res) => { 8 | res.json(getStatus()); 9 | }) 10 | 11 | 12 | export default statusRouter 13 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/routes/webdav.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import Joi from "joi"; 3 | import { getBackupConfig } from "../services/backupConfigService.js"; 4 | import { 5 | getWebdavConfig, 6 | validateWebdavConfig, 7 | } from "../services/webdavConfigService.js"; 8 | import * as webdavService from "../services/webdavService.js"; 9 | import * as pathTools from "../tools/pathTools.js"; 10 | import type { WebdavGenericPath } from "../types/services/webdav.js"; 11 | import { WebdavDeleteValidation } from "../types/services/webdavValidation.js"; 12 | import { restoreToHA } from "../services/orchestrator.js"; 13 | import path from "path"; 14 | import logger from "../config/winston.js"; 15 | import { getStatus } from "../tools/status.js"; 16 | 17 | const webdavRouter = express.Router(); 18 | 19 | webdavRouter.get("/backup/auto", (req, res) => { 20 | const config = getWebdavConfig(); 21 | const backupConf = getBackupConfig(); 22 | validateWebdavConfig(config) 23 | .then(() => { 24 | return webdavService.checkWebdavLogin(config); 25 | }) 26 | .then(() => { 27 | if (!getStatus().webdav.folder_created) { 28 | return webdavService.createBackupFolder(config); 29 | } else { 30 | return Promise.resolve(); 31 | } 32 | }) 33 | .then(async () => { 34 | const value = await webdavService.getBackups( 35 | pathTools.auto, 36 | config, 37 | backupConf.nameTemplate 38 | ); 39 | res.json(value); 40 | }) 41 | .catch((reason) => { 42 | res.status(500); 43 | res.json(reason); 44 | }); 45 | }); 46 | 47 | webdavRouter.get("/backup/manual", (req, res) => { 48 | const config = getWebdavConfig(); 49 | const backupConf = getBackupConfig(); 50 | validateWebdavConfig(config) 51 | .then(() => { 52 | return webdavService.checkWebdavLogin(config); 53 | }) 54 | .then(() => { 55 | if (!getStatus().webdav.folder_created) { 56 | return webdavService.createBackupFolder(config); 57 | } else { 58 | return Promise.resolve(); 59 | } 60 | }) 61 | .then(async () => { 62 | const value = await webdavService.getBackups( 63 | pathTools.manual, 64 | config, 65 | backupConf.nameTemplate 66 | ); 67 | res.json(value); 68 | }) 69 | .catch((reason) => { 70 | res.status(500); 71 | res.json(reason); 72 | }); 73 | }); 74 | 75 | webdavRouter.delete("/", (req, res) => { 76 | const body = req.body as WebdavGenericPath; 77 | const validator = Joi.object(WebdavDeleteValidation); 78 | const config = getWebdavConfig(); 79 | validateWebdavConfig(config) 80 | .then(() => { 81 | return validator.validateAsync(body); 82 | }) 83 | .then(() => { 84 | return webdavService.checkWebdavLogin(config); 85 | }) 86 | .then(() => { 87 | if (!getStatus().webdav.folder_created) { 88 | return webdavService.createBackupFolder(config); 89 | } else { 90 | return Promise.resolve(); 91 | } 92 | }) 93 | .then(() => { 94 | webdavService 95 | .deleteBackup(body.path, config) 96 | .then(() => { 97 | res.status(201).send(); 98 | }) 99 | .catch((reason) => { 100 | res.status(500).json(reason); 101 | }); 102 | }) 103 | .catch((reason) => { 104 | res.status(400).json(reason); 105 | }); 106 | }); 107 | 108 | webdavRouter.post("/restore", (req, res) => { 109 | const body = req.body as WebdavGenericPath; 110 | const validator = Joi.object(WebdavDeleteValidation); 111 | validator 112 | .validateAsync(body) 113 | .then(() => { 114 | return restoreToHA(body.path, path.basename(body.path)); 115 | }) 116 | .then(() => { 117 | logger.info("All good !"); 118 | }) 119 | .catch(() => { 120 | logger.error("Something wrong !"); 121 | }); 122 | res.sendStatus(202); 123 | }); 124 | 125 | export default webdavRouter; 126 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/server.ts: -------------------------------------------------------------------------------- 1 | import "./env.js"; 2 | 3 | import errorHandler from "errorhandler"; 4 | import figlet from "figlet"; 5 | import createError from "http-errors"; 6 | import kleur from "kleur"; 7 | import app from "./app.js"; 8 | import logger from "./config/winston.js"; 9 | import postInit from "./postInit.js"; 10 | 11 | /** 12 | * Error Handler. Provides full stack 13 | */ 14 | if (process.env.NODE_ENV === "development") { 15 | app.use(errorHandler()); 16 | app.use((req, res, next) => { 17 | next(createError(404)); 18 | }); 19 | } 20 | 21 | /** 22 | * Start Express server. 23 | */ 24 | const server = app.listen(app.get("port"), () => { 25 | console.log(kleur.yellow().bold(figlet.textSync("NC Backup"))); 26 | logger.info( 27 | `App is running at ` + 28 | kleur.green().bold(`http://localhost:${app.get("port")}`) + 29 | " in " + 30 | kleur.green().bold(process.env.NODE_ENV || "production") + 31 | " mode" 32 | ); 33 | logger.info(kleur.red().bold("Press CTRL-C to stop")); 34 | postInit(); 35 | }); 36 | 37 | export default server; 38 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/services/backupConfigService.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import Joi from "joi"; 3 | import logger from "../config/winston.js"; 4 | import { 5 | type BackupConfig, 6 | BackupType, 7 | } from "../types/services/backupConfig.js"; 8 | import backupConfigValidation from "../types/services/backupConfigValidation.js"; 9 | import { DateTime } from "luxon"; 10 | import { WorkflowType } from "../types/services/orchecstrator.js"; 11 | import { initCron } from "./cronService.js"; 12 | 13 | const backupConfigPath = "/data/backupConfigV2.json"; 14 | 15 | export function validateBackupConfig(config: BackupConfig) { 16 | const validator = Joi.object(backupConfigValidation); 17 | return validator.validateAsync(config, { 18 | abortEarly: false, 19 | }); 20 | } 21 | 22 | export function saveBackupConfig(config: BackupConfig) { 23 | fs.writeFileSync(backupConfigPath, JSON.stringify(config, undefined, 2)); 24 | return initCron(config); 25 | } 26 | 27 | export function getBackupConfig(): BackupConfig { 28 | if (!fs.existsSync(backupConfigPath)) { 29 | logger.warn("Config file not found, creating default one !"); 30 | const defaultConfig = getBackupDefaultConfig(); 31 | saveBackupConfig(defaultConfig).catch(() => {}); 32 | return defaultConfig; 33 | } else { 34 | return JSON.parse( 35 | fs.readFileSync(backupConfigPath).toString() 36 | ) as BackupConfig; 37 | } 38 | } 39 | 40 | export function getBackupDefaultConfig(): BackupConfig { 41 | return { 42 | nameTemplate: "{type}-{ha_version}-{date}_{hour}", 43 | cron: [], 44 | autoClean: { 45 | homeAssistant: { 46 | enabled: false, 47 | }, 48 | webdav: { 49 | enabled: false, 50 | }, 51 | }, 52 | backupType: BackupType.FULL, 53 | autoStopAddon: [], 54 | password: { 55 | enabled: false, 56 | }, 57 | }; 58 | } 59 | 60 | export function templateToRegexp(template: string) { 61 | let regexp = template.replace("{date}", "(?\\d{4}-\\d{2}-\\d{2})"); 62 | regexp = regexp.replace("{hour}", "(?\\d{4})"); 63 | regexp = regexp.replace("{hour_12}", "(?\\d{4}(AM|PM))"); 64 | regexp = regexp.replace("{type}", "(?Auto|Manual|)"); 65 | regexp = regexp.replace("{type_low}", "(?auto|manual|)"); 66 | return regexp.replace( 67 | "{ha_version}", 68 | "(?\\d+\\.\\d+\\.\\d+(b\\d+)?)" 69 | ); 70 | } 71 | 72 | export function getFormatedName( 73 | workflowType: WorkflowType, 74 | ha_version: string 75 | ) { 76 | const setting = getBackupConfig(); 77 | let template = setting.nameTemplate; 78 | template = template.replace( 79 | "{type_low}", 80 | workflowType == WorkflowType.MANUAL ? "manual" : "auto" 81 | ); 82 | template = template.replace( 83 | "{type}", 84 | workflowType == WorkflowType.MANUAL ? "Manual" : "Auto" 85 | ); 86 | template = template.replace("{ha_version}", ha_version); 87 | const now = DateTime.now().setLocale("en"); 88 | template = template.replace("{hour_12}", now.toFormat("hhmma")); 89 | template = template.replace("{hour}", now.toFormat("HHmm")); 90 | template = template.replace("{date}", now.toFormat("yyyy-MM-dd")); 91 | return template; 92 | } 93 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/services/cronService.ts: -------------------------------------------------------------------------------- 1 | import { CronJob } from "cron"; 2 | import { 3 | CronMode, 4 | type BackupConfig, 5 | type CronConfig, 6 | } from "../types/services/backupConfig.js"; 7 | import { WorkflowType } from "../types/services/orchecstrator.js"; 8 | import { doBackupWorkflow } from "./orchestrator.js"; 9 | import { DateTime } from "luxon"; 10 | import { getStatus, setStatus } from "../tools/status.js"; 11 | import logger from "../config/winston.js"; 12 | 13 | let cronList: Map; 14 | 15 | export function initCron(backupConfig: BackupConfig) { 16 | return new Promise((res, rej) => { 17 | const fn = doBackupWorkflow; 18 | if (cronList) { 19 | stopAllCron(cronList); 20 | } 21 | cronList = new Map(); 22 | for (const cronItem of backupConfig.cron) { 23 | try { 24 | if (cronItem.mode == CronMode.DAILY) { 25 | cronList.set(cronItem.id, getDailyCron(cronItem, fn)); 26 | } else if (cronItem.mode == CronMode.WEEKLY) { 27 | cronList.set(cronItem.id, getWeeklyCron(cronItem, fn)); 28 | } else if (cronItem.mode == CronMode.MONTHLY) { 29 | cronList.set(cronItem.id, getMonthlyCron(cronItem, fn)); 30 | } else if (cronItem.mode == CronMode.CUSTOM) { 31 | cronList.set(cronItem.id, getCustomCron(cronItem, fn)); 32 | } 33 | } catch { 34 | logger.error(`Fail to init CRON ${cronItem.id} (${cronItem.mode})`); 35 | stopAllCron(cronList); 36 | rej(Error(cronItem.id)); 37 | } 38 | } 39 | const nextDate = getNextDate(); 40 | const status = getStatus(); 41 | status.next_backup = nextDate; 42 | setStatus(status); 43 | res(null); 44 | }); 45 | } 46 | 47 | export function getNextDate() { 48 | let nextDate: DateTime | undefined = undefined; 49 | for (const item of cronList) { 50 | const thisDate = item[1].nextDate(); 51 | if (!nextDate) { 52 | nextDate = thisDate; 53 | } 54 | if (nextDate > thisDate) { 55 | nextDate = thisDate; 56 | } 57 | } 58 | return nextDate; 59 | } 60 | 61 | function stopAllCron(cronList: Map) { 62 | for (const item of cronList) { 63 | item[1].stop(); 64 | } 65 | } 66 | function getDailyCron( 67 | config: CronConfig, 68 | fn: (type: WorkflowType) => Promise 69 | ) { 70 | const splited = (config.hour as string).split(":"); 71 | return new CronJob( 72 | `${splited[1]} ${splited[0]} * * *`, 73 | () => fn(WorkflowType.AUTO), 74 | null, 75 | true, 76 | Intl.DateTimeFormat().resolvedOptions().timeZone 77 | ); 78 | } 79 | 80 | function getWeeklyCron( 81 | config: CronConfig, 82 | fn: (type: WorkflowType) => Promise 83 | ) { 84 | const splited = (config.hour as string).split(":"); 85 | return new CronJob( 86 | `${splited[1]} ${splited[0]} * * ${config.weekday}`, 87 | () => fn(WorkflowType.AUTO), 88 | null, 89 | true, 90 | Intl.DateTimeFormat().resolvedOptions().timeZone 91 | ); 92 | } 93 | 94 | function getMonthlyCron( 95 | config: CronConfig, 96 | fn: (type: WorkflowType) => Promise 97 | ) { 98 | const splited = (config.hour as string).split(":"); 99 | return new CronJob( 100 | `${splited[1]} ${splited[0]} ${config.monthDay} * *`, 101 | () => fn(WorkflowType.AUTO), 102 | null, 103 | true, 104 | Intl.DateTimeFormat().resolvedOptions().timeZone 105 | ); 106 | } 107 | 108 | function getCustomCron( 109 | config: CronConfig, 110 | fn: (type: WorkflowType) => Promise 111 | ) { 112 | return new CronJob( 113 | config.custom as string, 114 | () => fn(WorkflowType.AUTO), 115 | null, 116 | true, 117 | Intl.DateTimeFormat().resolvedOptions().timeZone 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/services/webdavConfigService.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import Joi from "joi"; 3 | import logger from "../config/winston.js"; 4 | import * as pathTools from "../tools/pathTools.js"; 5 | import { default_root } from "../tools/pathTools.js"; 6 | import { WorkflowType } from "../types/services/orchecstrator.js"; 7 | import { 8 | type WebdavConfig, 9 | WebdavEndpointType, 10 | } from "../types/services/webdavConfig.js"; 11 | import WebdavConfigValidation from "../types/services/webdavConfigValidation.js"; 12 | 13 | const webdavConfigPath = "/data/webdavConfigV2.json"; 14 | const NEXTCLOUD_ENDPOINT = "/remote.php/dav/files/$username"; 15 | const NEXTCLOUD_CHUNK_ENDPOINT = "/remote.php/dav/uploads/$username"; 16 | 17 | export function validateWebdavConfig(config: WebdavConfig) { 18 | const validator = Joi.object(WebdavConfigValidation); 19 | return validator.validateAsync(config, { 20 | abortEarly: false, 21 | }); 22 | } 23 | 24 | export function saveWebdavConfig(config: WebdavConfig) { 25 | fs.writeFileSync(webdavConfigPath, JSON.stringify(config, undefined, 2)); 26 | } 27 | 28 | export function getWebdavConfig(): WebdavConfig { 29 | if (!fs.existsSync(webdavConfigPath)) { 30 | logger.warn("Webdav Config file not found, creating default one !"); 31 | const defaultConfig = getWebdavDefaultConfig(); 32 | saveWebdavConfig(defaultConfig); 33 | return defaultConfig; 34 | } else { 35 | return JSON.parse( 36 | fs.readFileSync(webdavConfigPath).toString() 37 | ) as WebdavConfig; 38 | } 39 | } 40 | 41 | export function getEndpoint(config: WebdavConfig) { 42 | let endpoint: string; 43 | 44 | if (config.webdavEndpoint.type == WebdavEndpointType.NEXTCLOUD) { 45 | endpoint = NEXTCLOUD_ENDPOINT.replace("$username", config.username); 46 | } else if (config.webdavEndpoint.customEndpoint) { 47 | endpoint = config.webdavEndpoint.customEndpoint.replace( 48 | "$username", 49 | config.username 50 | ); 51 | } else { 52 | return ""; 53 | } 54 | if (!endpoint.startsWith("/")) { 55 | endpoint = "/" + endpoint; 56 | } 57 | 58 | if (!endpoint.endsWith("/")) { 59 | return endpoint + "/"; 60 | } 61 | 62 | return endpoint; 63 | } 64 | 65 | export function getChunkEndpoint(config: WebdavConfig) { 66 | let endpoint: string; 67 | 68 | if (config.webdavEndpoint.type == WebdavEndpointType.NEXTCLOUD) { 69 | endpoint = NEXTCLOUD_CHUNK_ENDPOINT.replace("$username", config.username); 70 | } else if (config.webdavEndpoint.customChunkEndpoint) { 71 | endpoint = config.webdavEndpoint.customChunkEndpoint.replace( 72 | "$username", 73 | config.username 74 | ); 75 | } else { 76 | return ""; 77 | } 78 | if (!endpoint.startsWith("/")) { 79 | endpoint = "/" + endpoint; 80 | } 81 | 82 | if (!endpoint.endsWith("/")) { 83 | return endpoint + "/"; 84 | } 85 | 86 | return endpoint; 87 | } 88 | 89 | export function getBackupFolder(type: WorkflowType, config: WebdavConfig) { 90 | const end = type == WorkflowType.AUTO ? pathTools.auto : pathTools.manual; 91 | return config.backupDir.endsWith("/") 92 | ? config.backupDir + end 93 | : config.backupDir + "/" + end; 94 | } 95 | 96 | export function getWebdavDefaultConfig(): WebdavConfig { 97 | return { 98 | url: "", 99 | username: "", 100 | password: "", 101 | backupDir: default_root, 102 | allowSelfSignedCerts: false, 103 | chunckedUpload: false, 104 | webdavEndpoint: { 105 | type: WebdavEndpointType.NEXTCLOUD, 106 | }, 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/tools/cronTools.ts: -------------------------------------------------------------------------------- 1 | // import { CronJob } from "cron"; 2 | // import * as settingsTools from "./settingsTools.js"; 3 | // import * as hassioApiTools from "../services/haOsService.js"; 4 | // import * as statusTools from "./status.js"; 5 | // import * as pathTools from "./pathTools.js"; 6 | // // import webdav from "../services/webdavService.js"; 7 | 8 | // import logger from "../config/winston.js"; 9 | 10 | // class CronContainer { 11 | // cronJob: CronJob | undefined; 12 | // cronClean: CronJob | undefined; 13 | 14 | // init() { 15 | // const settings = settingsTools.getSettings(); 16 | // let cronStr = ""; 17 | // if (!this.cronClean) { 18 | // logger.info("Starting auto clean cron..."); 19 | // this.cronClean = new CronJob( 20 | // "0 1 * * *", 21 | // this._clean, 22 | // null, 23 | // false, 24 | // Intl.DateTimeFormat().resolvedOptions().timeZone 25 | // ); 26 | // this.cronClean.start(); 27 | // } 28 | // if (this.cronJob) { 29 | // logger.info("Stopping Cron..."); 30 | // this.cronJob.stop(); 31 | // this.cronJob = undefined; 32 | // } 33 | // if (!settingsTools.check_cron(settingsTools.getSettings())) { 34 | // logger.warn("No Cron settings available."); 35 | // return; 36 | // } 37 | 38 | // switch (settings.cron_base) { 39 | // case "0": 40 | // logger.warn("No Cron settings available."); 41 | // return; 42 | // case "1": { 43 | // const splited = settings.cron_hour.split(":"); 44 | // cronStr = "" + splited[1] + " " + splited[0] + " * * *"; 45 | // break; 46 | // } 47 | 48 | // case "2": { 49 | // const splited = settings.cron_hour.split(":"); 50 | // cronStr = 51 | // "" + splited[1] + " " + splited[0] + " * * " + settings.cron_weekday; 52 | // break; 53 | // } 54 | 55 | // case "3": { 56 | // const splited = settings.cron_hour.split(":"); 57 | // cronStr = 58 | // "" + 59 | // splited[1] + 60 | // " " + 61 | // splited[0] + 62 | // " " + 63 | // settings.cron_month_day + 64 | // " * *"; 65 | // break; 66 | // } 67 | // case "4": { 68 | // cronStr = settings.cron_custom; 69 | // break; 70 | // } 71 | // } 72 | // logger.info("Starting Cron..."); 73 | // this.cronJob = new CronJob( 74 | // cronStr, 75 | // this._createBackup, 76 | // null, 77 | // false, 78 | // Intl.DateTimeFormat().resolvedOptions().timeZone 79 | // ); 80 | // this.cronJob.start(); 81 | // this.updateNextDate(); 82 | // } 83 | 84 | // updateNextDate() { 85 | // let date; 86 | // if (this.cronJob) { 87 | // date = this.cronJob 88 | // .nextDate() 89 | // .setLocale("en") 90 | // .toFormat("dd MMM yyyy, HH:mm"); 91 | // } 92 | // const status = statusTools.getStatus(); 93 | // status.next_backup = date; 94 | // statusTools.setStatus(status); 95 | // } 96 | 97 | // _createBackup() { 98 | // logger.debug("Cron triggered !"); 99 | // const status = statusTools.getStatus(); 100 | // if ( 101 | // status.status === "creating" || 102 | // status.status === "upload" || 103 | // status.status === "download" || 104 | // status.status === "stopping" || 105 | // status.status === "starting" 106 | // ) 107 | // return; 108 | // hassioApiTools 109 | // .stopAddons() 110 | // .then(() => { 111 | // hassioApiTools.getVersion().then((version) => { 112 | // const name = settingsTools.getFormatedName(false, version); 113 | // hassioApiTools.createNewBackup(name).then((id) => { 114 | // hassioApiTools.downloadSnapshot(id).then(() => { 115 | // webdav 116 | // .uploadFile( 117 | // id, 118 | // webdav.getConf()?.back_dir + pathTools.auto + name + ".tar" 119 | // ) 120 | // .then(() => { 121 | // hassioApiTools.startAddons().catch(() => { 122 | // // Skip 123 | // }); 124 | // }); 125 | // }); 126 | // }); 127 | // }); 128 | // }) 129 | // .catch(() => { 130 | // hassioApiTools.startAddons().catch(() => { 131 | // // Skip 132 | // }); 133 | // }); 134 | // } 135 | 136 | // _clean() { 137 | // const autoCleanlocal = settingsTools.getSettings().auto_clean_local; 138 | // if (autoCleanlocal && autoCleanlocal == "true") { 139 | // hassioApiTools.clean().catch(); 140 | // } 141 | // const autoCleanCloud = settingsTools.getSettings().auto_clean_backup; 142 | // if (autoCleanCloud && autoCleanCloud == "true") { 143 | // webdav.clean().catch(); 144 | // } 145 | // } 146 | // } 147 | 148 | // const INSTANCE = new CronContainer(); 149 | // export default INSTANCE; 150 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/tools/messageManager.ts: -------------------------------------------------------------------------------- 1 | import { randomUUID } from "crypto"; 2 | import { DateTime } from "luxon"; 3 | import { type Message, MessageType } from "../types/message.js"; 4 | 5 | const maxMessageLength = 255; 6 | 7 | class MessageManager { 8 | private messages: Message[] = []; 9 | 10 | public addMessage( 11 | type: MessageType, 12 | message: string, 13 | detail?: string, 14 | isImportant = false 15 | ) { 16 | this.messages.unshift({ 17 | id: randomUUID(), 18 | message: message, 19 | type: type, 20 | time: DateTime.now(), 21 | viewed: !isImportant, 22 | detail: detail, 23 | }); 24 | if (this.messages.length > maxMessageLength) { 25 | this.messages.shift(); 26 | } 27 | } 28 | 29 | public error(message: string, detail?: string) { 30 | this.addMessage(MessageType.ERROR, message, detail, true); 31 | } 32 | 33 | public warn(message: string, detail?: string) { 34 | this.addMessage(MessageType.WARN, message, detail); 35 | } 36 | 37 | public info(message: string, detail?: string) { 38 | this.addMessage(MessageType.INFO, message, detail); 39 | } 40 | 41 | public success(message: string, detail?: string) { 42 | this.addMessage(MessageType.SUCCESS, message, detail); 43 | } 44 | 45 | public get() { 46 | return this.messages; 47 | } 48 | 49 | public getById(id: string) { 50 | return this.messages.find((value) => value.id == id); 51 | } 52 | 53 | public markReaded(id: string) { 54 | const index = this.messages.findIndex((value) => value.id == id); 55 | if (index == -1) { 56 | return false; 57 | } 58 | this.messages[index].viewed = true; 59 | return true; 60 | } 61 | public markAllReaded() { 62 | this.messages.forEach((value: Message) => { 63 | value.viewed = true; 64 | }); 65 | } 66 | } 67 | 68 | const messageManager = new MessageManager(); 69 | export default messageManager; 70 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/tools/pathTools.ts: -------------------------------------------------------------------------------- 1 | const default_root = "/Hassio Backup/"; 2 | export { default_root }; 3 | export const manual = "Manual/"; 4 | export const auto = "Auto/"; 5 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/tools/status.ts: -------------------------------------------------------------------------------- 1 | import { States, type Status } from "../types/status.js"; 2 | import { DateTime } from "luxon"; 3 | 4 | let status: Status = { 5 | status: States.IDLE, 6 | last_backup: {}, 7 | next_backup: undefined, 8 | progress: undefined, 9 | webdav: { 10 | logged_in: false, 11 | folder_created: false, 12 | last_check: DateTime.now(), 13 | }, 14 | hass: { 15 | ok: false, 16 | last_check: DateTime.now(), 17 | }, 18 | }; 19 | 20 | export function init() { 21 | if (status.status !== States.IDLE) { 22 | status.status = States.IDLE; 23 | status.progress = undefined; 24 | } 25 | } 26 | 27 | export function getStatus() { 28 | return status; 29 | } 30 | 31 | export function setStatus(new_state: Status) { 32 | const old_state_str = JSON.stringify(status); 33 | if (old_state_str !== JSON.stringify(new_state)) { 34 | status = new_state; 35 | // publish_state(status); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/tools/toolbox.ts: -------------------------------------------------------------------------------- 1 | // Found on Stackoverflow, perfect code :D https://stackoverflow.com/a/14919494/8654475 2 | function humanFileSize(bytes: number, si = false, dp = 1) { 3 | const thresh = si ? 1000 : 1024; 4 | 5 | if (Math.abs(bytes) < thresh) { 6 | return bytes + " B"; 7 | } 8 | 9 | const units = si ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]; 10 | let u = -1; 11 | const r = 10 ** dp; 12 | 13 | do { 14 | bytes /= thresh; 15 | ++u; 16 | } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); 17 | 18 | return bytes.toFixed(dp) + " " + units[u]; 19 | } 20 | 21 | export { humanFileSize } ; 22 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/message.ts: -------------------------------------------------------------------------------- 1 | import type { DateTime } from "luxon"; 2 | 3 | export enum MessageType { 4 | ERROR = "ERROR", 5 | WARN = "WARN", 6 | INFO = "INFO", 7 | SUCCESS = "SUCCESS" 8 | } 9 | 10 | 11 | export interface Message { 12 | id: string; 13 | time: DateTime; 14 | type: MessageType; 15 | message: string; 16 | viewed: boolean; 17 | detail?: string; 18 | } -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/backupConfig.ts: -------------------------------------------------------------------------------- 1 | export enum CronMode { 2 | DAILY = "DAILY", 3 | WEEKLY = "WEEKLY", 4 | MONTHLY = "MONTHLY", 5 | CUSTOM = "CUSTOM" 6 | } 7 | 8 | export enum Weekday { 9 | SUNDAY, 10 | MONDAY, 11 | TUESDAY, 12 | WEDNESDAY, 13 | THURSDAY, 14 | FRIDAY, 15 | SATURDAY 16 | } 17 | 18 | export enum BackupType { 19 | FULL= "FULL", 20 | PARTIAL = "PARTIAL" 21 | } 22 | 23 | 24 | export interface BackupConfig { 25 | nameTemplate: string; 26 | cron: CronConfig[]; 27 | autoClean: { 28 | homeAssistant: AutoCleanConfig; 29 | webdav: AutoCleanConfig; 30 | }, 31 | backupType: BackupType; 32 | exclude?: { 33 | addon: string[]; 34 | folder: string[]; 35 | } 36 | autoStopAddon: string[]; 37 | password: { 38 | enabled: boolean; 39 | value?: string; 40 | } 41 | } 42 | 43 | 44 | export interface CronConfig { 45 | id: string; 46 | mode: CronMode; 47 | hour?: string; 48 | weekday?: Weekday; 49 | monthDay: string; 50 | custom?: string; 51 | } 52 | 53 | 54 | export interface AutoCleanConfig { 55 | enabled: boolean; 56 | nbrToKeep?: number; 57 | } 58 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/backupConfigValidation.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | import { BackupType, CronMode } from "./backupConfig.js"; 3 | 4 | const CronConfigValidation = { 5 | id: Joi.string().required().not().empty(), 6 | mode: Joi.string() 7 | .required() 8 | .valid(CronMode.CUSTOM, CronMode.DAILY, CronMode.MONTHLY, CronMode.WEEKLY), 9 | hour: Joi.alternatives().conditional("mode", { 10 | is: CronMode.CUSTOM, 11 | then: Joi.forbidden(), 12 | otherwise: Joi.string() 13 | .pattern(/^\d{2}:\d{2}$/) 14 | .required(), 15 | }), 16 | weekday: Joi.alternatives().conditional("mode", { 17 | is: CronMode.WEEKLY, 18 | then: Joi.number().min(0).max(6).required(), 19 | otherwise: Joi.forbidden(), 20 | }), 21 | monthDay: Joi.alternatives().conditional("mode", { 22 | is: CronMode.MONTHLY, 23 | then: Joi.number().min(1).max(28).required(), 24 | otherwise: Joi.forbidden(), 25 | }), 26 | custom: Joi.alternatives().conditional("mode", { 27 | is: CronMode.CUSTOM, 28 | then: Joi.string().required(), 29 | otherwise: Joi.forbidden(), 30 | }), 31 | }; 32 | 33 | const AutoCleanConfig = { 34 | enabled: Joi.boolean().required(), 35 | nbrToKeep: Joi.alternatives().conditional("enabled", { 36 | is: true, 37 | then: Joi.number().required().min(0), 38 | otherwise: Joi.forbidden(), 39 | }), 40 | }; 41 | 42 | const backupConfigValidation = { 43 | nameTemplate: Joi.string().required().not().empty(), 44 | cron: Joi.array().items(CronConfigValidation).required(), 45 | autoClean: Joi.object({ 46 | homeAssistant: Joi.object(AutoCleanConfig).required(), 47 | webdav: Joi.object(AutoCleanConfig).required(), 48 | }).required(), 49 | backupType: Joi.string() 50 | .required() 51 | .valid(BackupType.FULL, BackupType.PARTIAL), 52 | exclude: Joi.alternatives().conditional("backupType", { 53 | is: BackupType.PARTIAL, 54 | then: Joi.object({ 55 | addon: Joi.array().items(Joi.string().not().empty()).required(), 56 | folder: Joi.array().items(Joi.string().not().empty()).required(), 57 | }).required(), 58 | otherwise: Joi.forbidden(), 59 | }), 60 | autoStopAddon: Joi.array().items(Joi.string().not().empty()), 61 | password: Joi.object({ 62 | enabled: Joi.boolean().required(), 63 | value: Joi.alternatives().conditional("enabled", { 64 | is: true, 65 | then: Joi.string().required().not().empty(), 66 | otherwise: Joi.forbidden(), 67 | }), 68 | }), 69 | }; 70 | 71 | export default backupConfigValidation; 72 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/ha_os_payload.ts: -------------------------------------------------------------------------------- 1 | export interface NewBackupPayload { 2 | name?: string; 3 | password?: string; 4 | homeassistant?: boolean; 5 | addons?: string[]; 6 | folders?: string[]; 7 | compressed?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/ha_os_response.ts: -------------------------------------------------------------------------------- 1 | export interface SupervisorResponse { 2 | result: string; 3 | data: T; 4 | } 5 | 6 | export interface CoreInfoBody { 7 | version: string; 8 | version_latest: string; 9 | update_available: boolean; 10 | arch: string; 11 | machine: string; 12 | ip_address: string; 13 | image: string; 14 | boot: boolean; 15 | port: number; 16 | ssl: boolean; 17 | watchdog: boolean; 18 | wait_boot: number; 19 | audio_input: string; 20 | audio_output: string; 21 | } 22 | 23 | export interface AddonData { 24 | addons: AddonModel[]; 25 | } 26 | 27 | export interface AddonModel { 28 | name: string; 29 | slug: string; 30 | advanced: boolean; 31 | description: string; 32 | repository: string; 33 | version: string; 34 | version_latest: string; 35 | update_available: boolean; 36 | installed: string; 37 | available: boolean; 38 | icon: boolean; 39 | logo: boolean; 40 | state: string; 41 | } 42 | 43 | export interface BackupData { 44 | backups: BackupModel[]; 45 | } 46 | 47 | export interface BackupModel { 48 | slug: string; 49 | date: string; 50 | name: string; 51 | type: "full" | "partial"; 52 | protected: boolean; 53 | content: BackupContent; 54 | compressed: boolean; 55 | } 56 | 57 | export interface BackupContent { 58 | homeassistant: boolean; 59 | addons: string[]; 60 | folders: string[]; 61 | } 62 | 63 | export interface BackupDetailModel { 64 | slug: string; 65 | type: "full" | "partial"; 66 | name: string; 67 | date: string; 68 | size: string; 69 | protected: boolean; 70 | homeassistant: string; 71 | addons: { 72 | slug: string; 73 | name: string; 74 | version: string; 75 | size: number; 76 | }[]; 77 | repositories: string[]; 78 | folders: string[]; 79 | } 80 | 81 | export interface BackupUpload { 82 | slug: string; 83 | job_id: string; 84 | } 85 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/orchecstrator.ts: -------------------------------------------------------------------------------- 1 | export enum WorkflowType { 2 | AUTO, 3 | MANUAL, 4 | } 5 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/webdav.ts: -------------------------------------------------------------------------------- 1 | import type { DateTime } from "luxon"; 2 | 3 | export interface WebdavBackup { 4 | id: string; 5 | name: string; 6 | size: number; 7 | lastEdit: DateTime; 8 | path: string; 9 | haVersion?: string; 10 | creationDate?: DateTime; 11 | } 12 | 13 | export interface WebdavGenericPath { 14 | path: string; 15 | } 16 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/webdavConfig.ts: -------------------------------------------------------------------------------- 1 | export enum WebdavEndpointType { 2 | NEXTCLOUD = "NEXTCLOUD", 3 | CUSTOM = "CUSTOM", 4 | } 5 | 6 | export interface WebdavConfig { 7 | url: string; 8 | username: string; 9 | password: string; 10 | backupDir: string; 11 | allowSelfSignedCerts: boolean; 12 | chunckedUpload: boolean; 13 | webdavEndpoint: { 14 | type: WebdavEndpointType; 15 | customEndpoint?: string; 16 | customChunkEndpoint?: string; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/webdavConfigValidation.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | import { WebdavEndpointType } from "./webdavConfig.js"; 3 | 4 | const WebdavConfigValidation = { 5 | url: Joi.string().not().empty().uri().required().label("Url"), 6 | username: Joi.string().not().empty().label("Username"), 7 | password: Joi.string().not().empty().label("Password"), 8 | backupDir: Joi.string().required().label("Backup directory"), 9 | allowSelfSignedCerts: Joi.boolean().label("Allow self signed certificate"), 10 | chunckedUpload: Joi.boolean().required().label("Chuncked upload"), 11 | webdavEndpoint: Joi.object({ 12 | type: Joi.string() 13 | .valid(WebdavEndpointType.CUSTOM, WebdavEndpointType.NEXTCLOUD) 14 | .required(), 15 | customEndpoint: Joi.alternatives().conditional("type", { 16 | is: WebdavEndpointType.CUSTOM, 17 | then: Joi.string().not().empty().required(), 18 | otherwise: Joi.disallow(), 19 | }), 20 | customChunkEndpoint: Joi.alternatives().conditional("type", { 21 | is: WebdavEndpointType.CUSTOM, 22 | then: Joi.string().not().empty().required(), 23 | otherwise: Joi.disallow(), 24 | }), 25 | }) 26 | .required() 27 | .label("Webdav endpoint"), 28 | }; 29 | 30 | export default WebdavConfigValidation; 31 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/services/webdavValidation.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export const WebdavDeleteValidation = { 4 | path: Joi.string().not().empty().required() 5 | } -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/settings.ts: -------------------------------------------------------------------------------- 1 | export interface Settings { 2 | name_template?: string; 3 | cron_base?: string; 4 | cron_hour?: string; 5 | cron_weekday?: number; 6 | cron_month_day?: number; 7 | cron_custom?: string; 8 | auto_clean_local?: string; 9 | auto_clean_local_keep?: string; 10 | auto_clean_backup?: string; 11 | auto_clean_backup_keep?: string; 12 | auto_stop_addon?: string[]; 13 | password_protected?: string; 14 | password_protect_value?: string; 15 | exclude_addon: string[]; 16 | exclude_folder: string[]; 17 | } 18 | 19 | export interface WebdavSettings { 20 | ssl: boolean; 21 | host: string; 22 | username: string; 23 | password: string; 24 | back_dir: string; 25 | self_signed: boolean; 26 | } -------------------------------------------------------------------------------- /nextcloud_backup/backend/src/types/status.ts: -------------------------------------------------------------------------------- 1 | import type { DateTime } from "luxon"; 2 | 3 | export enum States { 4 | IDLE = "IDLE", 5 | BKUP_CREATION = "BKUP_CREATION", 6 | BKUP_DOWNLOAD_HA = "BKUP_DOWNLOAD_HA", 7 | BKUP_DOWNLOAD_CLOUD = "BKUP_DOWNLOAD_CLOUD", 8 | BKUP_UPLOAD_HA = "BKUP_UPLOAD_HA", 9 | BKUP_UPLOAD_CLOUD = "BKUP_UPLOAD_CLOUD", 10 | STOP_ADDON = "STOP_ADDON", 11 | START_ADDON = "START_ADDON", 12 | CLEAN_CLOUD = "CLEAN_CLOUD", 13 | CLEAN_HA = "CLEAN_HA", 14 | } 15 | 16 | export interface Status { 17 | status: States; 18 | progress?: number; 19 | last_backup: { 20 | success?: boolean; 21 | last_success?: DateTime; 22 | last_try?: DateTime; 23 | }; 24 | next_backup?: DateTime; 25 | webdav: { 26 | logged_in: boolean; 27 | folder_created: boolean; 28 | last_check: DateTime; 29 | }; 30 | hass: { 31 | ok: boolean; 32 | last_check: DateTime; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /nextcloud_backup/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "outDir": "./dist", 6 | "module": "nodenext", 7 | "moduleResolution": "nodenext", 8 | "target": "ES2022", 9 | "sourceMap": true, 10 | "verbatimModuleSyntax": true, 11 | "strict": true 12 | }, 13 | "include": ["src/**/*"] 14 | } 15 | -------------------------------------------------------------------------------- /nextcloud_backup/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "squash": false, 3 | "build_from": { 4 | "aarch64": "hassioaddons/base-aarch64:5.0.2", 5 | "amd64": "hassioaddons/base-amd64:5.0.2", 6 | "armhf": "hassioaddons/base-armhf:5.0.2", 7 | "armv7": "hassioaddons/base-armv7:5.0.2", 8 | "i386": "hassioaddons/base-i386:5.0.2" 9 | }, 10 | "args": {} 11 | } 12 | -------------------------------------------------------------------------------- /nextcloud_backup/config.yml: -------------------------------------------------------------------------------- 1 | name: Nextcloud Backup 2 | version: dev 3 | slug: nextcloud_backup 4 | description: Easily upload your Home Assistant backups to Nextcloud 5 | url: https://github.com/Sebclem/hassio-nextcloud-backup 6 | webui: "[PROTO:ssl]://[HOST]:[PORT:3000]/" 7 | ingress: true 8 | ingress_port: 3000 9 | panel_icon: mdi:cloud-upload 10 | panel_title: NC Backup 11 | panel_admin: true 12 | startup: application 13 | stage: experimental 14 | init: false 15 | arch: 16 | - aarch64 17 | - amd64 18 | - armv7 19 | boot: auto 20 | hassio_api: true 21 | hassio_role: admin 22 | homeassistant_api: true 23 | options: 24 | log_level: info 25 | create_backup_timeout: 5400000 26 | schema: 27 | log_level: list(trace|debug|info|notice|warning|error|fatal) 28 | create_backup_timeout: int 29 | ports: 30 | 3000/tcp: null 31 | ports_description: 32 | 3000/tcp: Web interface (Not required for Home Assistant Ingress) 33 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.env.development: -------------------------------------------------------------------------------- 1 | VITE_API_URL="http://localhost:3000/" -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.env.production: -------------------------------------------------------------------------------- 1 | VITE_API_URL="./" -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.eslintrc-auto-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "Component": true, 4 | "ComponentPublicInstance": true, 5 | "ComputedRef": true, 6 | "EffectScope": true, 7 | "ExtractDefaultPropTypes": true, 8 | "ExtractPropTypes": true, 9 | "ExtractPublicPropTypes": true, 10 | "InjectionKey": true, 11 | "PropType": true, 12 | "Ref": true, 13 | "VNode": true, 14 | "WritableComputedRef": true, 15 | "computed": true, 16 | "createApp": true, 17 | "customRef": true, 18 | "defineAsyncComponent": true, 19 | "defineComponent": true, 20 | "effectScope": true, 21 | "getCurrentInstance": true, 22 | "getCurrentScope": true, 23 | "h": true, 24 | "inject": true, 25 | "isProxy": true, 26 | "isReactive": true, 27 | "isReadonly": true, 28 | "isRef": true, 29 | "markRaw": true, 30 | "nextTick": true, 31 | "onActivated": true, 32 | "onBeforeMount": true, 33 | "onBeforeRouteLeave": true, 34 | "onBeforeRouteUpdate": true, 35 | "onBeforeUnmount": true, 36 | "onBeforeUpdate": true, 37 | "onDeactivated": true, 38 | "onErrorCaptured": true, 39 | "onMounted": true, 40 | "onRenderTracked": true, 41 | "onRenderTriggered": true, 42 | "onScopeDispose": true, 43 | "onServerPrefetch": true, 44 | "onUnmounted": true, 45 | "onUpdated": true, 46 | "provide": true, 47 | "reactive": true, 48 | "readonly": true, 49 | "ref": true, 50 | "resolveComponent": true, 51 | "shallowReactive": true, 52 | "shallowReadonly": true, 53 | "shallowRef": true, 54 | "toRaw": true, 55 | "toRef": true, 56 | "toRefs": true, 57 | "toValue": true, 58 | "triggerRef": true, 59 | "unref": true, 60 | "useAttrs": true, 61 | "useCssModule": true, 62 | "useCssVars": true, 63 | "useLink": true, 64 | "useRoute": true, 65 | "useRouter": true, 66 | "useSlots": true, 67 | "watch": true, 68 | "watchEffect": true, 69 | "watchPostEffect": true, 70 | "watchSyncEffect": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /nextcloud_backup/frontend/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). 8 | 9 | ## Type Support for `.vue` Imports in TS 10 | 11 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. 12 | 13 | If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: 14 | 15 | 1. Disable the built-in TypeScript Extension 16 | 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette 17 | 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 18 | 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 19 | 20 | ## Customize configuration 21 | 22 | See [Vite Configuration Reference](https://vitejs.dev/config/). 23 | 24 | ## Project Setup 25 | 26 | ```sh 27 | pnpm install 28 | ``` 29 | 30 | ### Compile and Hot-Reload for Development 31 | 32 | ```sh 33 | pnpm dev 34 | ``` 35 | 36 | ### Type-Check, Compile and Minify for Production 37 | 38 | ```sh 39 | pnpm build 40 | ``` 41 | 42 | ### Lint with [ESLint](https://eslint.org/) 43 | 44 | ```sh 45 | pnpm lint 46 | ``` 47 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // @ts-nocheck 3 | // Generated by unplugin-vue-components 4 | // Read more: https://github.com/vuejs/core/pull/3399 5 | export {} 6 | 7 | /* prettier-ignore */ 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | ActionComponent: typeof import('./src/components/statusBar/ActionComponent.vue')['default'] 11 | AlertManager: typeof import('./src/components/AlertManager.vue')['default'] 12 | BackupConfigAddon: typeof import('./src/components/settings/BackupConfig/BackupConfigAddon.vue')['default'] 13 | BackupConfigAutoBackup: typeof import('./src/components/settings/BackupConfig/BackupConfigAutoBackup.vue')['default'] 14 | BackupConfigAutoClean: typeof import('./src/components/settings/BackupConfig/BackupConfigAutoClean.vue')['default'] 15 | BackupConfigAutoStop: typeof import('./src/components/settings/BackupConfig/BackupConfigAutoStop.vue')['default'] 16 | BackupConfigFolder: typeof import('./src/components/settings/BackupConfig/BackupConfigFolder.vue')['default'] 17 | BackupConfigForm: typeof import('./src/components/settings/BackupConfigForm.vue')['default'] 18 | BackupConfigMenu: typeof import('./src/components/settings/BackupConfigMenu.vue')['default'] 19 | BackupConfigSecurity: typeof import('./src/components/settings/BackupConfig/BackupConfigSecurity.vue')['default'] 20 | BackupStatus: typeof import('./src/components/statusBar/BackupStatus.vue')['default'] 21 | CloudDeleteDialog: typeof import('./src/components/cloud/CloudDeleteDialog.vue')['default'] 22 | CloudList: typeof import('./src/components/cloud/CloudList.vue')['default'] 23 | CloudListItem: typeof import('./src/components/cloud/CloudListItem.vue')['default'] 24 | ConnectionStatus: typeof import('./src/components/statusBar/ConnectionStatus.vue')['default'] 25 | HaDeleteDialog: typeof import('./src/components/homeAssistant/HaDeleteDialog.vue')['default'] 26 | HaList: typeof import('./src/components/homeAssistant/HaList.vue')['default'] 27 | HaListItem: typeof import('./src/components/homeAssistant/HaListItem.vue')['default'] 28 | HaListItemContent: typeof import('./src/components/homeAssistant/HaListItemContent.vue')['default'] 29 | MessageBar: typeof import('./src/components/MessageBar.vue')['default'] 30 | NavbarComponent: typeof import('./src/components/NavbarComponent.vue')['default'] 31 | RouterLink: typeof import('vue-router')['RouterLink'] 32 | RouterView: typeof import('vue-router')['RouterView'] 33 | StatusBar: typeof import('./src/components/statusBar/StatusBar.vue')['default'] 34 | WebdavConfigForm: typeof import('./src/components/settings/WebdavConfigForm.vue')['default'] 35 | WebdavConfigMenu: typeof import('./src/components/settings/WebdavConfigMenu.vue')['default'] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Nextcloud Backup 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "cross-env NODE_OPTIONS='--no-warnings' vite", 6 | "build": "vue-tsc --noEmit && vite build", 7 | "preview": "vite preview", 8 | "lint": "eslint . --fix --ignore-path .gitignore", 9 | "type-check": "vue-tsc --noEmit", 10 | "build-to-back": "vue-tsc --noEmit && vite build --outDir ../backend/dist/public --emptyOutDir true" 11 | }, 12 | "dependencies": { 13 | "@mdi/font": "7.4.47", 14 | "core-js": "^3.38.0", 15 | "ky": "^1.6.0", 16 | "luxon": "^3.5.0", 17 | "pretty-bytes": "^6.1.1", 18 | "roboto-fontface": "^0.10.0", 19 | "uuid": "^10.0.0", 20 | "vue": "^3.4.37", 21 | "vuetify": "3.6.14" 22 | }, 23 | "devDependencies": { 24 | "@babel/types": "^7.25.2", 25 | "@types/luxon": "^3.4.2", 26 | "@types/node": "^20.14.15", 27 | "@types/uuid": "^10.0.0", 28 | "@vitejs/plugin-vue": "^5.0.0", 29 | "@vue/eslint-config-typescript": "^13.0.0", 30 | "cross-env": "^7.0.3", 31 | "eslint": "^8.57.0", 32 | "eslint-config-standard": "^17.1.0", 33 | "eslint-config-vuetify": "^1.0.0", 34 | "eslint-plugin-import": "^2.29.1", 35 | "eslint-plugin-n": "^17.0.0", 36 | "eslint-plugin-promise": "^7.0.0", 37 | "eslint-plugin-vue": "^9.27.0", 38 | "pinia": "^2.2.1", 39 | "sass": "^1.77.8", 40 | "typescript": "^5.5.4", 41 | "unplugin-auto-import": "^0.18.0", 42 | "unplugin-fonts": "^1.1.1", 43 | "unplugin-vue-components": "^0.27.0", 44 | "unplugin-vue-router": "^0.10.0", 45 | "vite": "^5.4.0", 46 | "vite-plugin-vue-layouts": "^0.11.0", 47 | "vite-plugin-vuetify": "^2.0.4", 48 | "vue-router": "^4.4.3", 49 | "vue-tsc": "^2.0.26" 50 | }, 51 | "packageManager": "pnpm@9.7.0" 52 | } -------------------------------------------------------------------------------- /nextcloud_backup/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sebclem/hassio-nextcloud-backup/82d3473761094e3fff39bf1432ba79fca16dcd96/nextcloud_backup/frontend/public/favicon.ico -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | position: relative; 59 | font-weight: normal; 60 | } 61 | 62 | body { 63 | min-height: 100vh; 64 | color: var(--color-text); 65 | background: var(--color-background); 66 | transition: color 0.5s, background-color 0.5s; 67 | line-height: 1.6; 68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 70 | font-size: 15px; 71 | text-rendering: optimizeLegibility; 72 | -webkit-font-smoothing: antialiased; 73 | -moz-osx-font-smoothing: grayscale; 74 | } 75 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import "./base.css"; 2 | 3 | #app { 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | padding: 2rem; 7 | 8 | font-weight: normal; 9 | } 10 | 11 | a, 12 | .green { 13 | text-decoration: none; 14 | color: hsla(160, 100%, 37%, 1); 15 | transition: 0.4s; 16 | } 17 | 18 | @media (hover: hover) { 19 | a:hover { 20 | background-color: hsla(160, 100%, 37%, 0.2); 21 | } 22 | } 23 | 24 | @media (min-width: 1024px) { 25 | body { 26 | display: flex; 27 | place-items: center; 28 | } 29 | 30 | #app { 31 | display: grid; 32 | grid-template-columns: 1fr 1fr; 33 | padding: 0 2rem; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/AlertManager.vue: -------------------------------------------------------------------------------- 1 | 39 | 50 | 66 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/MessageBar.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 183 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/NavbarComponent.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 56 | 91 | @/store/dialogStatus@/store/message -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/cloud/CloudDeleteDialog.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 73 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/cloud/CloudList.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 140 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/cloud/CloudListItem.vue: -------------------------------------------------------------------------------- 1 | 114 | 115 | 132 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/homeAssistant/HaDeleteDialog.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 74 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/homeAssistant/HaList.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 109 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/homeAssistant/HaListItem.vue: -------------------------------------------------------------------------------- 1 | 176 | 177 | 204 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/homeAssistant/HaListItemContent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigAddon.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 51 | 52 | 53 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigAutoBackup.vue: -------------------------------------------------------------------------------- 1 | 184 | 185 | 207 | 208 | 209 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigAutoClean.vue: -------------------------------------------------------------------------------- 1 | 82 | 90 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigAutoStop.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigFolder.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 52 | 53 | 54 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfig/BackupConfigSecurity.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 57 | @/store/backupConfig -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfigForm.vue: -------------------------------------------------------------------------------- 1 | 91 | 135 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/BackupConfigMenu.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 72 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/WebdavConfigForm.vue: -------------------------------------------------------------------------------- 1 | 172 | 236 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/settings/WebdavConfigMenu.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 80 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/statusBar/ActionComponent.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 58 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/statusBar/BackupStatus.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 130 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/statusBar/ConnectionStatus.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 127 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/components/statusBar/StatusBar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 47 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/composable/ConfigForm.ts: -------------------------------------------------------------------------------- 1 | import { HTTPError } from "ky"; 2 | import { ref, type Ref } from "vue"; 3 | 4 | export function useConfigForm( 5 | saveService: (data: any) => Promise, 6 | loadService: () => Promise, 7 | dataRef: Ref, 8 | errorsRef: Ref, 9 | emit: { 10 | (e: "success"): void; 11 | (e: "fail"): void; 12 | (e: "loaded"): void; 13 | (e: "loading"): void; 14 | } 15 | ) { 16 | const loading = ref(true); 17 | 18 | function save() { 19 | loading.value = true; 20 | clearErrors(); 21 | saveService(dataRef.value) 22 | .then(() => { 23 | loading.value = false; 24 | emit("success"); 25 | }) 26 | .catch(async (reason) => { 27 | if (reason instanceof HTTPError) { 28 | const response: any = await reason.response.json(); 29 | if (response["type"] == "validation") { 30 | for (const elem of response["errors"]) { 31 | errorsRef.value[ 32 | elem.context.key as keyof typeof errorsRef.value 33 | ] = elem.message; 34 | } 35 | } 36 | else if (response["type"] == "cron") { 37 | for (const elem of response["errors"]) { 38 | errorsRef.value["cron"].push(elem) 39 | } 40 | } 41 | } 42 | loading.value = false; 43 | emit("fail"); 44 | }); 45 | } 46 | 47 | function clearErrors() { 48 | for (const elem in errorsRef.value) { 49 | errorsRef.value[elem as keyof typeof errorsRef.value] = []; 50 | } 51 | } 52 | 53 | function loadData() { 54 | emit("loading"); 55 | loadService().then(() => { 56 | loading.value = false; 57 | emit("loaded"); 58 | }); 59 | } 60 | 61 | loadData(); 62 | return { save, loading }; 63 | } 64 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/composable/menuSize.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | import { useDisplay } from "vuetify"; 3 | 4 | export function useMenuSize() { 5 | const { xs, mdAndDown } = useDisplay(); 6 | const width = computed(() => { 7 | if (xs.value) { 8 | return undefined; 9 | } else if (mdAndDown.value) { 10 | return "80%"; 11 | } else { 12 | return "50%"; 13 | } 14 | }); 15 | const isFullScreen = xs; 16 | return { width, isFullScreen }; 17 | } 18 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_API_URL: string; 5 | } 6 | interface ImportMeta { 7 | readonly env: ImportMetaEnv; 8 | } 9 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | import { registerPlugins } from "@/plugins"; 4 | 5 | const app = createApp(App); 6 | 7 | registerPlugins(app); 8 | 9 | app.mount("#app"); 10 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * plugins/index.ts 3 | * 4 | * Automatically included in `./src/main.ts` 5 | */ 6 | 7 | // Plugins 8 | import vuetify from "./vuetify"; 9 | import pinia from "../store"; 10 | // import router from '../router' 11 | 12 | // Types 13 | import type { App } from "vue"; 14 | 15 | export function registerPlugins(app: App) { 16 | app 17 | .use(vuetify) 18 | // .use(router) 19 | .use(pinia); 20 | } 21 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/plugins/vuetify.ts: -------------------------------------------------------------------------------- 1 | // Styles 2 | import "@mdi/font/css/materialdesignicons.css"; 3 | import "vuetify/styles"; 4 | 5 | // Vuetify 6 | import { createVuetify } from "vuetify"; 7 | 8 | const darkTheme = { 9 | dark: true, 10 | colors: { 11 | primary: "#0091ea", //light-blue accent-4 12 | accent: "#FF6F00", //amber darken-4 13 | }, 14 | }; 15 | 16 | export default createVuetify( 17 | // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides 18 | { 19 | theme: { 20 | defaultTheme: "darkTheme", 21 | themes: { 22 | darkTheme, 23 | }, 24 | }, 25 | } 26 | ); 27 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/actionService.ts: -------------------------------------------------------------------------------- 1 | import kyClient from "./kyClient"; 2 | 3 | export function backupNow() { 4 | return kyClient.post("action/backup"); 5 | } 6 | 7 | export function clean() { 8 | return kyClient.post("action/clean"); 9 | } -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/configService.ts: -------------------------------------------------------------------------------- 1 | import { BackupType, type BackupConfig } from "@/types/backupConfig"; 2 | import type { WebdavConfig } from "@/types/webdavConfig"; 3 | import kyClient from "./kyClient"; 4 | 5 | export function getWebdavConfig() { 6 | return kyClient.get("config/webdav").json(); 7 | } 8 | 9 | export function saveWebdavConfig(config: WebdavConfig) { 10 | return kyClient 11 | .put("config/webdav", { 12 | json: config, 13 | }) 14 | .json(); 15 | } 16 | 17 | export function getBackupConfig() { 18 | return kyClient.get("config/backup").json(); 19 | } 20 | 21 | export function saveBackupConfig(config: BackupConfig) { 22 | return kyClient 23 | .put("config/backup", { 24 | json: cleanupConfig(config), 25 | }) 26 | .json(); 27 | } 28 | 29 | function cleanupConfig(config: BackupConfig) { 30 | if (config.backupType == BackupType.Full) { 31 | config.exclude = undefined; 32 | } else if (!config.exclude) { 33 | config.exclude = { addon: [], folder: [] }; 34 | } 35 | 36 | if (!config.autoClean.homeAssistant.enabled) { 37 | config.autoClean.homeAssistant.nbrToKeep = undefined; 38 | } 39 | if (!config.autoClean.webdav.enabled) { 40 | config.autoClean.webdav.nbrToKeep = undefined; 41 | } 42 | 43 | if (!config.password.enabled) { 44 | config.password.value = undefined; 45 | } 46 | return config; 47 | } 48 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/homeAssistantService.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | BackupDetailModel, 3 | AddonData, 4 | BackupModel, 5 | Folder, 6 | } from "@/types/homeAssistant"; 7 | import kyClient from "./kyClient"; 8 | 9 | export function getFolders() { 10 | return kyClient.get("homeAssistant/folders").json(); 11 | } 12 | 13 | export function getAddons() { 14 | return kyClient.get("homeAssistant/addons").json(); 15 | } 16 | 17 | export function getBackups() { 18 | return kyClient.get("homeAssistant/backups").json(); 19 | } 20 | 21 | export function getBackupDetail(slug: string) { 22 | return kyClient.get(`homeAssistant/backup/${slug}`).json(); 23 | } 24 | 25 | export function uploadHomeAssistantBackup(slug: string) { 26 | return kyClient.post(`homeAssistant/backup/${slug}/upload`); 27 | } 28 | 29 | export function deleteHomeAssistantBackup(slug: string) { 30 | return kyClient.delete(`homeAssistant/backup/${slug}`); 31 | } 32 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/kyClient.ts: -------------------------------------------------------------------------------- 1 | import ky from "ky"; 2 | const kyClient = ky.create({ 3 | prefixUrl: `${import.meta.env.VITE_API_URL}v2/api`, 4 | }); 5 | 6 | export default kyClient; 7 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/messageService.ts: -------------------------------------------------------------------------------- 1 | import type { Message } from "@/types/messages"; 2 | import kyClient from "./kyClient"; 3 | 4 | export function getMessages() { 5 | return kyClient.get("messages").json(); 6 | } 7 | 8 | export function markRead(id: string) { 9 | return kyClient.patch(`messages/${encodeURI(id)}/readed`).json(); 10 | } 11 | 12 | export function markAllRead(){ 13 | return kyClient.post(`messages/allReaded`).json(); 14 | } -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/statusService.ts: -------------------------------------------------------------------------------- 1 | import kyClient from "./kyClient"; 2 | import { Status } from "@/types/status"; 3 | 4 | export function getStatus() { 5 | return kyClient.get("status").json(); 6 | } 7 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/services/webdavService.ts: -------------------------------------------------------------------------------- 1 | import type { WebdavBackup } from "@/types/webdav"; 2 | import kyClient from "./kyClient"; 3 | 4 | export function getAutoBackupList() { 5 | return kyClient.get("webdav/backup/auto").json(); 6 | } 7 | 8 | export function getManualBackupList() { 9 | return kyClient.get("webdav/backup/manual").json(); 10 | } 11 | 12 | export function deleteWebdabBackup(path: string) { 13 | return kyClient 14 | .delete("webdav", { 15 | json: { 16 | path: path, 17 | }, 18 | }) 19 | .text(); 20 | } 21 | 22 | export function restoreWebdavBackup(path: string) { 23 | return kyClient 24 | .post("webdav/restore", { 25 | json: { 26 | path: path, 27 | }, 28 | }) 29 | .text(); 30 | } 31 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/store/alert.ts: -------------------------------------------------------------------------------- 1 | import type { Alert } from "@/types/alert"; 2 | import { defineStore } from "pinia"; 3 | import { ref, type Ref } from "vue"; 4 | import { v4 as uuidv4 } from "uuid"; 5 | 6 | export const useAlertStore = defineStore("alert", () => { 7 | const alertList = ref([]) as Ref; 8 | const timeOutValue = ref(100); 9 | 10 | function add( 11 | type: "error" | "success" | "warning" | "info", 12 | message: string 13 | ) { 14 | const alert: Alert = { 15 | id: uuidv4(), 16 | timeOut: ref(timeOutValue.value), 17 | interval: window.setInterval(() => { 18 | timeout(alert); 19 | }, 50), 20 | type: type, 21 | message: message, 22 | }; 23 | alertList.value.push(alert); 24 | } 25 | 26 | function timeout(alert: Alert) { 27 | if (alert.timeOut.value <= 0) { 28 | remove(alert.id!); 29 | } else { 30 | alert.timeOut.value--; 31 | } 32 | } 33 | 34 | function remove(id: string) { 35 | const alertToRemove = alertList.value.find((value) => value.id == id); 36 | if (alertToRemove) { 37 | clearInterval(alertToRemove.interval); 38 | } 39 | alertList.value = alertList.value.filter((value) => value.id != id); 40 | } 41 | return { alertList, timeOutValue, add, remove }; 42 | }); 43 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/store/backupConfig.ts: -------------------------------------------------------------------------------- 1 | import { getBackupConfig } from "@/services/configService"; 2 | import { getAddons, getFolders } from "@/services/homeAssistantService"; 3 | import { BackupType, CronMode, type BackupConfig } from "@/types/backupConfig"; 4 | import type { Folder, AddonModel } from "@/types/homeAssistant"; 5 | import { defineStore } from "pinia"; 6 | import { ref } from "vue"; 7 | import { v4 as uuidv4 } from "uuid"; 8 | 9 | export const useBackupConfigStore = defineStore("backupConfig", () => { 10 | const data = ref({} as BackupConfig); 11 | const addons = ref([]); 12 | const folders = ref([]); 13 | 14 | // This represent the oposite of excluded => if prensent on this list, backup it 15 | const invertedFolders = ref([]); 16 | const invertedAddons = ref([]); 17 | 18 | function loadAll() { 19 | const conf = getBackupConfig(); 20 | const foldersProm = getFolders(); 21 | const addonsProm = getAddons(); 22 | return Promise.all([conf, foldersProm, addonsProm]).then((value) => { 23 | if (value[0].backupType == BackupType.Partial && value[0].exclude) { 24 | for (const folder of value[1]) { 25 | if (!value[0].exclude.folder.includes(folder.slug)) { 26 | invertedFolders.value.push(folder.slug); 27 | } 28 | } 29 | for (const addon of value[2].addons) { 30 | if (!value[0].exclude.addon.includes(addon.slug)) { 31 | invertedAddons.value.push(addon.slug); 32 | } 33 | } 34 | } 35 | 36 | data.value = value[0]; 37 | folders.value = value[1]; 38 | addons.value = value[2].addons; 39 | }); 40 | } 41 | 42 | function addEmptyCron() { 43 | data.value.cron.push({ 44 | id: uuidv4(), 45 | mode: CronMode.Daily, 46 | }); 47 | } 48 | 49 | function initExcludes() { 50 | data.value.exclude = { addon: [], folder: [] }; 51 | addons.value.forEach((value) => { 52 | invertedAddons.value.push(value.slug); 53 | }); 54 | folders.value.forEach((value) => { 55 | invertedFolders.value.push(value.slug); 56 | }); 57 | } 58 | 59 | function removeCron(id: string) { 60 | data.value.cron = data.value.cron.filter((value) => value.id != id); 61 | } 62 | 63 | return { 64 | data, 65 | addons, 66 | folders, 67 | invertedFolders, 68 | invertedAddons, 69 | loadAll, 70 | addEmptyCron, 71 | removeCron, 72 | initExcludes, 73 | }; 74 | }); 75 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/store/dialogStatus.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import { defineStore } from "pinia"; 3 | 4 | export const useDialogStatusStore = defineStore("dialogStatus", () => { 5 | const webdav = ref(false); 6 | const backup = ref(false); 7 | return { webdav, backup }; 8 | }); 9 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/store/index.ts: -------------------------------------------------------------------------------- 1 | // Utilities 2 | import { createPinia } from "pinia"; 3 | 4 | export default createPinia(); 5 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/store/message.ts: -------------------------------------------------------------------------------- 1 | import { computed, ref } from "vue"; 2 | import { defineStore } from "pinia"; 3 | import type { Message } from "@/types/messages"; 4 | 5 | export const useMessageStore = defineStore("message", () => { 6 | const messages = ref([]); 7 | const countUnreadMessages = computed(() => { 8 | return messages.value.filter((value) => !value.viewed).length; 9 | }); 10 | 11 | const haveUnreadMessages = computed(() => { 12 | let unread = false; 13 | for (const mess of messages.value) { 14 | if (!mess.viewed) { 15 | unread = true; 16 | break; 17 | } 18 | } 19 | return unread; 20 | }); 21 | 22 | return { messages, countUnreadMessages, haveUnreadMessages }; 23 | }); 24 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/styles/settings.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * src/styles/settings.scss 3 | * 4 | * Configures SASS variables and Vuetify overwrites 5 | */ 6 | 7 | // https://vuetifyjs.com/features/sass-variables/` 8 | // @use 'vuetify/settings' with ( 9 | // $color-pack: false 10 | // ); 11 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/alert.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | 3 | export interface Alert { 4 | id: string; 5 | type: "error" | "success" | "warning" | "info"; 6 | message: string; 7 | timeOut: Ref; 8 | interval: number; 9 | } 10 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/backupConfig.ts: -------------------------------------------------------------------------------- 1 | export enum CronMode { 2 | Daily = "DAILY", 3 | Weekly = "WEEKLY", 4 | Monthly = "MONTHLY", 5 | Custom = "CUSTOM", 6 | } 7 | 8 | export enum CronModeFriendly { 9 | DAILY = "Daily", 10 | WEEKLY = "Weekly", 11 | MONTHLY = "Monthly", 12 | CUSTOM = "Custom", 13 | } 14 | 15 | export enum Weekday { 16 | SUNDAY, 17 | MONDAY, 18 | TUESDAY, 19 | WEDNESDAY, 20 | THURSDAY, 21 | FRIDAY, 22 | SATURDAY, 23 | } 24 | 25 | export enum BackupType { 26 | Full = "FULL", 27 | Partial = "PARTIAL", 28 | } 29 | 30 | export const weekdayFriendly = [ 31 | "Sunday", 32 | "Monday", 33 | "Tuesday", 34 | "Wednesday", 35 | "Thursday", 36 | "Friday", 37 | "Saturday", 38 | ]; 39 | 40 | export interface BackupConfig { 41 | nameTemplate: string; 42 | cron: CronConfig[]; 43 | autoClean: { 44 | homeAssistant: AutoCleanConfig; 45 | webdav: AutoCleanConfig; 46 | }; 47 | backupType: BackupType; 48 | exclude?: { 49 | addon: string[]; 50 | folder: string[]; 51 | }; 52 | autoStopAddon: string[]; 53 | password: { 54 | enabled: boolean; 55 | value?: string; 56 | }; 57 | } 58 | 59 | export interface CronConfig { 60 | id: string; 61 | mode: CronMode; 62 | hour?: string; 63 | weekday?: Weekday; 64 | monthDay?: string; 65 | custom?: string; 66 | } 67 | 68 | export interface AutoCleanConfig { 69 | enabled: boolean; 70 | nbrToKeep?: number; 71 | } 72 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/homeAssistant.ts: -------------------------------------------------------------------------------- 1 | export interface Folder { 2 | name: string; 3 | slug: string; 4 | } 5 | 6 | export interface AddonData { 7 | addons: AddonModel[]; 8 | } 9 | 10 | export interface AddonModel { 11 | name: string; 12 | slug: string; 13 | advanced: boolean; 14 | description: string; 15 | repository: string; 16 | version: string; 17 | version_latest: string; 18 | update_available: boolean; 19 | installed: string; 20 | available: boolean; 21 | icon: boolean; 22 | logo: boolean; 23 | state: string; 24 | } 25 | 26 | export interface BackupModel { 27 | slug: string; 28 | date: string; 29 | name: string; 30 | type: "full" | "partial"; 31 | protected: boolean; 32 | content: BackupContent; 33 | compressed: boolean; 34 | } 35 | 36 | export interface BackupContent { 37 | homeassistant: boolean; 38 | addons: string[]; 39 | folders: string[]; 40 | } 41 | 42 | export interface BackupDetailModel { 43 | slug: string; 44 | type: "full" | "partial"; 45 | name: string; 46 | date: string; 47 | size: string; 48 | protected: boolean; 49 | homeassistant: string; 50 | addons: { 51 | slug: string; 52 | name: string; 53 | version: string; 54 | size: number; 55 | }[]; 56 | repositories: string[]; 57 | folders: string[]; 58 | } 59 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/messages.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export enum MessageType { 4 | ERROR = "ERROR", 5 | WARN = "WARN", 6 | INFO = "INFO", 7 | SUCCESS = "SUCCESS", 8 | } 9 | 10 | export interface Message { 11 | id: string; 12 | time: string; 13 | type: MessageType; 14 | message: string; 15 | viewed: boolean; 16 | detail?: string; 17 | } 18 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/status.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum States { 3 | IDLE = "IDLE", 4 | BKUP_CREATION = "BKUP_CREATION", 5 | BKUP_DOWNLOAD_HA = "BKUP_DOWNLOAD_HA", 6 | BKUP_DOWNLOAD_CLOUD = "BKUP_DOWNLOAD_CLOUD", 7 | BKUP_UPLOAD_HA = "BKUP_UPLOAD_HA", 8 | BKUP_UPLOAD_CLOUD = "BKUP_UPLOAD_CLOUD", 9 | STOP_ADDON = "STOP_ADDON", 10 | START_ADDON = "START_ADDON", 11 | } 12 | 13 | export interface Status { 14 | status: States; 15 | progress?: number; 16 | last_backup: { 17 | success?: boolean; 18 | last_success?: string; 19 | last_try?: string; 20 | }; 21 | next_backup?: string; 22 | webdav: { 23 | logged_in: boolean; 24 | folder_created: boolean; 25 | last_check: string; 26 | }; 27 | hass: { 28 | ok: boolean; 29 | last_check: string; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/webdav.ts: -------------------------------------------------------------------------------- 1 | export interface WebdavBackup { 2 | id: string; 3 | name: string; 4 | size: number; 5 | lastEdit: string; 6 | path: string; 7 | haVersion?: string; 8 | creationDate?: string; 9 | } 10 | 11 | export interface WebdavDeletePayload { 12 | path: string; 13 | } 14 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/src/types/webdavConfig.ts: -------------------------------------------------------------------------------- 1 | export enum WebdavEndpointType { 2 | NEXTCLOUD = "NEXTCLOUD", 3 | CUSTOM = "CUSTOM", 4 | } 5 | 6 | export interface WebdavConfig { 7 | url: string; 8 | username: string; 9 | password: string; 10 | backupDir: string; 11 | allowSelfSignedCerts: boolean; 12 | chunckedUpload: boolean; 13 | webdavEndpoint: { 14 | type: WebdavEndpointType; 15 | customEndpoint?: string; 16 | customChunkEndpoint?: string; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "jsx": "preserve", 5 | "lib": ["DOM", "ESNext"], 6 | "baseUrl": ".", 7 | "module": "ESNext", 8 | "moduleResolution": "bundler", 9 | "paths": { 10 | "@/*": ["src/*"] 11 | }, 12 | "resolveJsonModule": true, 13 | "types": [ 14 | "vite/client", 15 | "vite-plugin-vue-layouts/client", 16 | "unplugin-vue-router/client" 17 | ], 18 | "allowJs": true, 19 | "strict": true, 20 | "strictNullChecks": true, 21 | "noUnusedLocals": true, 22 | "esModuleInterop": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "isolatedModules": true, 25 | "skipLibCheck": true 26 | }, 27 | "include": [ 28 | "src/**/*", 29 | "src/**/*.vue" 30 | ], 31 | "exclude": ["dist", "node_modules", "cypress"], 32 | "references": [{ "path": "./tsconfig.node.json" }], 33 | } -------------------------------------------------------------------------------- /nextcloud_backup/frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.mts"] 9 | } 10 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/typed-router.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ 5 | // It's recommended to commit this file. 6 | // Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. 7 | 8 | declare module 'vue-router/auto-routes' { 9 | import type { 10 | RouteRecordInfo, 11 | ParamValue, 12 | ParamValueOneOrMore, 13 | ParamValueZeroOrMore, 14 | ParamValueZeroOrOne, 15 | } from 'vue-router' 16 | 17 | /** 18 | * Route name map generated by unplugin-vue-router 19 | */ 20 | export interface RouteNamedMap { 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /nextcloud_backup/frontend/vite.config.mts: -------------------------------------------------------------------------------- 1 | // Plugins 2 | import AutoImport from "unplugin-auto-import/vite"; 3 | import Components from "unplugin-vue-components/vite"; 4 | import Fonts from "unplugin-fonts/vite"; 5 | import Layouts from "vite-plugin-vue-layouts"; 6 | import Vue from "@vitejs/plugin-vue"; 7 | import VueRouter from "unplugin-vue-router/vite"; 8 | import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify"; 9 | 10 | // Utilities 11 | import { defineConfig } from "vite"; 12 | import { fileURLToPath, URL } from "node:url"; 13 | 14 | // https://vitejs.dev/config/ 15 | export default defineConfig({ 16 | plugins: [ 17 | VueRouter(), 18 | Layouts(), 19 | Vue({ 20 | template: { transformAssetUrls }, 21 | }), 22 | // https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme 23 | Vuetify({ 24 | autoImport: true, 25 | styles: { 26 | configFile: "src/styles/settings.scss", 27 | }, 28 | }), 29 | Components(), 30 | Fonts({ 31 | google: { 32 | families: [ 33 | { 34 | name: "Roboto", 35 | styles: "wght@100;300;400;500;700;900", 36 | }, 37 | ], 38 | }, 39 | }), 40 | AutoImport({ 41 | imports: ["vue", "vue-router"], 42 | dts: true, 43 | eslintrc: { 44 | enabled: true, 45 | }, 46 | vueTemplate: true, 47 | }), 48 | ], 49 | define: { "process.env": {} }, 50 | resolve: { 51 | alias: { 52 | "@": fileURLToPath(new URL("./src", import.meta.url)), 53 | }, 54 | extensions: [".js", ".json", ".jsx", ".mjs", ".ts", ".tsx", ".vue"], 55 | }, 56 | server: { 57 | port: 3000, 58 | }, 59 | base: "./" 60 | }); 61 | -------------------------------------------------------------------------------- /nextcloud_backup/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sebclem/hassio-nextcloud-backup/82d3473761094e3fff39bf1432ba79fca16dcd96/nextcloud_backup/icon.png -------------------------------------------------------------------------------- /nextcloud_backup/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sebclem/hassio-nextcloud-backup/82d3473761094e3fff39bf1432ba79fca16dcd96/nextcloud_backup/logo.png -------------------------------------------------------------------------------- /nextcloud_backup/naming_template.md: -------------------------------------------------------------------------------- 1 | # Variable reference for `naming template` 2 | 3 | 4 | | variable | Example | Descrition | 5 | | :---: | :---:| :---:| 6 | | `{date}` | `2020-12-24` | Current date | 7 | | `{hour}` | `20:05` | Current hour in 24h format | 8 | | `{hour_12}` | `08:05PM` | Current hour in 12h format | 9 | | `{ha_version}` | `0.117.5`| Home assistant Version | 10 | | `{type}` | `Manual` | Backup type, value can be `Manual` or `Auto` | 11 | | `{type_low}` | `manual` | Same as `{type}` but value is lowercase | 12 | -------------------------------------------------------------------------------- /nextcloud_backup/rootfs/etc/services.d/nextcould_backup/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bashio 2 | # ============================================================================== 3 | # Community Hass.io Add-ons: Example 4 | # Runs example1 script 5 | # ============================================================================== 6 | 7 | 8 | bashio::log.info "Starting Node..." 9 | 10 | exec /usr/bin/nextcloud_backup.sh 11 | -------------------------------------------------------------------------------- /nextcloud_backup/rootfs/usr/bin/nextcloud_backup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bashio 2 | # ============================================================================== 3 | # 4 | # Community Hass.io Add-ons: Example 5 | # 6 | # Example add-on for Hass.io. 7 | # This add-on displays a random quote every X seconds. 8 | # 9 | # ============================================================================== 10 | 11 | cd /opt/nextcloud_backup/ 12 | if bashio::config.exists 'log_level'; then 13 | LOG_LEVEL=$(bashio::config 'log_level') 14 | else 15 | LOG_LEVEL='info' 16 | fi 17 | 18 | if bashio::config.exists 'create_backup_timeout'; then 19 | CREATE_BACKUP_TIMEOUT=$(bashio::config 'create_backup_timeout') 20 | else 21 | CREATE_BACKUP_TIMEOUT='info' 22 | fi 23 | 24 | LOG_LEVEL=$LOG_LEVEL CREATE_BACKUP_TIMEOUT=$CREATE_BACKUP_TIMEOUT node server.js -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":prHourlyLimitNone", 5 | ":prConcurrentLimitNone", 6 | "group:allNonMajor" 7 | ], 8 | "commitMessagePrefix": ":arrow_up:", 9 | "regexManagers": [ 10 | { 11 | "fileMatch": ["nextcloud_backup/.base_version"], 12 | "matchStrings": [ 13 | "(?.*)\\s" 14 | ], 15 | "depNameTemplate": "ghcr.io/home-assistant/amd64-base", 16 | "datasourceTemplate": "docker", 17 | "versioningTemplate": "semver" 18 | } 19 | ], 20 | "vulnerabilityAlerts": { 21 | "enabled": true 22 | } 23 | } 24 | --------------------------------------------------------------------------------