mirror of
https://github.com/Sebclem/hassio-nextcloud-backup.git
synced 2024-11-22 01:02:59 +01:00
Add base code
This commit is contained in:
parent
e008c62c32
commit
6e609f387c
1
.gitignore
vendored
1
.gitignore
vendored
@ -102,3 +102,4 @@ dist
|
|||||||
|
|
||||||
# TernJS port file
|
# TernJS port file
|
||||||
.tern-port
|
.tern-port
|
||||||
|
.vscode
|
67
nextcloud_backup/.README.j2
Normal file
67
nextcloud_backup/.README.j2
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Community Hass.io Add-ons: Nextcloud Backup
|
||||||
|
|
||||||
|
[![Release][release-shield]][release] ![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield]
|
||||||
|
|
||||||
|
[![Discord][discord-shield]][discord] [![Community Forum][forum-shield]][forum]
|
||||||
|
|
||||||
|
[![Buy me a coffee][buymeacoffee-shield]][buymeacoffee]
|
||||||
|
|
||||||
|
Hass.io snapshot backup to Nextcloud
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
Easily backup you'r Hass.io snapshots to Nextcloud.
|
||||||
|
Auto backup can be configure with the web interface.
|
||||||
|
|
||||||
|
[Click here for the full documentation][docs]
|
||||||
|
|
||||||
|
{% if channel == "edge" %}
|
||||||
|
## WARNING! THIS IS AN EDGE VERSION!
|
||||||
|
|
||||||
|
This Hass.io Add-ons repository contains edge builds of add-ons. Edge builds
|
||||||
|
add-ons are based upon the latest development version.
|
||||||
|
|
||||||
|
- They may not work at all.
|
||||||
|
- They might stop working at any time.
|
||||||
|
- They could have a negative impact on your system.
|
||||||
|
|
||||||
|
This repository was created for:
|
||||||
|
|
||||||
|
- Anybody willing to test.
|
||||||
|
- Anybody interested in trying out upcoming add-ons or add-on features.
|
||||||
|
- Developers.
|
||||||
|
|
||||||
|
If you are more interested in stable releases of our add-ons:
|
||||||
|
|
||||||
|
<https://github.com/hassio-addons/repository>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% if channel == "beta" %}
|
||||||
|
## WARNING! THIS IS A BETA VERSION!
|
||||||
|
|
||||||
|
This Hass.io Add-ons repository contains beta releases of add-ons.
|
||||||
|
|
||||||
|
- They might stop working at any time.
|
||||||
|
- They could have a negative impact on your system.
|
||||||
|
|
||||||
|
This repository was created for:
|
||||||
|
|
||||||
|
- Anybody willing to test.
|
||||||
|
- Anybody interested in trying out upcoming add-ons or add-on features.
|
||||||
|
|
||||||
|
If you are more interested in stable releases of our add-ons:
|
||||||
|
|
||||||
|
<https://github.com/hassio-addons/repository>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/guidelines/download-assets-sm-2.svg
|
||||||
|
[buymeacoffee]: https://www.buymeacoffee.com/seb6596
|
||||||
|
[discord-shield]: https://img.shields.io/discord/478094546522079232.svg
|
||||||
|
[discord]: https://discord.me/hassioaddons
|
||||||
|
[docs]: {{ repo }}/blob/{{ version }}/README.md
|
||||||
|
[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg
|
||||||
|
[forum]: https://community.home-assistant.io/
|
||||||
|
[maintenance-shield]: https://img.shields.io/maintenance/yes/2019.svg
|
||||||
|
[project-stage-shield]: https://img.shields.io/badge/project%20stage-developpement-yellow.svg
|
||||||
|
[release-shield]: https://img.shields.io/badge/version-{{ version }}-blue.svg
|
||||||
|
[release]: {{ repo }}/tree/{{ version }}
|
39
nextcloud_backup/Dockerfile
Normal file
39
nextcloud_backup/Dockerfile
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
ARG BUILD_FROM=hassioaddons/base:5.0.2
|
||||||
|
# hadolint ignore=DL3006
|
||||||
|
FROM ${BUILD_FROM}
|
||||||
|
|
||||||
|
# Copy root filesystem
|
||||||
|
COPY rootfs /
|
||||||
|
|
||||||
|
# Setup base
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
nodejs \
|
||||||
|
npm
|
||||||
|
|
||||||
|
WORKDIR /opt/nextcloud_backup
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Build arguments
|
||||||
|
ARG BUILD_ARCH
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG BUILD_REF
|
||||||
|
ARG BUILD_VERSION
|
||||||
|
|
||||||
|
# Labels
|
||||||
|
LABEL \
|
||||||
|
io.hass.name="Nextcloud Backup" \
|
||||||
|
io.hass.description="Addon that backup your snapshot to a Nextcloud server" \
|
||||||
|
io.hass.arch="${BUILD_ARCH}" \
|
||||||
|
io.hass.type="addon" \
|
||||||
|
io.hass.version=${BUILD_VERSION} \
|
||||||
|
maintainer="Sebclem" \
|
||||||
|
org.label-schema.description="Addon that backup your snapshot to a Nextcloud server" \
|
||||||
|
org.label-schema.build-date=${BUILD_DATE} \
|
||||||
|
org.label-schema.name="Nextcloud Backup" \
|
||||||
|
org.label-schema.schema-version="1.0" \
|
||||||
|
org.label-schema.url="https://addons.community" \
|
||||||
|
org.label-schema.usage="https://github.com/hassio-addons/addon-example/tree/master/README.md" \
|
||||||
|
org.label-schema.vcs-ref=${BUILD_REF} \
|
||||||
|
org.label-schema.vcs-url="https://github.com/hassio-addons/addon-example" \
|
||||||
|
org.label-schema.vendor="Sebclem"
|
11
nextcloud_backup/build.json
Normal file
11
nextcloud_backup/build.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"squash": false,
|
||||||
|
"build_from": {
|
||||||
|
"aarch64": "hassioaddons/base-aarch64:5.0.2",
|
||||||
|
"amd64": "hassioaddons/base-amd64:5.0.2",
|
||||||
|
"armhf": "hassioaddons/base-armhf:5.0.2",
|
||||||
|
"armv7": "hassioaddons/base-armv7:5.0.2",
|
||||||
|
"i386": "hassioaddons/base-i386:5.0.2"
|
||||||
|
},
|
||||||
|
"args": {}
|
||||||
|
}
|
37
nextcloud_backup/config.json
Normal file
37
nextcloud_backup/config.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "Nextcloud Backup",
|
||||||
|
"version": "dev",
|
||||||
|
"slug": "nextcloud_backup",
|
||||||
|
"description": "Addon that backup your snapshot to a Nextcloud server",
|
||||||
|
"url": "https://github.com/hassio-addons/addon-example",
|
||||||
|
"webui": "[PROTO:ssl]://[HOST]:[PORT:3000]/",
|
||||||
|
"ingress": true,
|
||||||
|
"ingress_port": 3000,
|
||||||
|
"startup": "application",
|
||||||
|
"arch": [
|
||||||
|
"aarch64",
|
||||||
|
"amd64",
|
||||||
|
"armhf",
|
||||||
|
"armv7",
|
||||||
|
"i386"
|
||||||
|
],
|
||||||
|
"boot": "auto",
|
||||||
|
"hassio_api": true,
|
||||||
|
"hassio_role": "backup",
|
||||||
|
"options": {
|
||||||
|
"ssl": false,
|
||||||
|
"certfile": "fullchain.pem",
|
||||||
|
"keyfile": "privkey.pem"
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"log_level": "match(^(trace|debug|info|notice|warning|error|fatal)$)?",
|
||||||
|
"ssl": "bool",
|
||||||
|
"certfile": "str",
|
||||||
|
"keyfile": "str",
|
||||||
|
"leave_front_door_open": "bool?"
|
||||||
|
},
|
||||||
|
"ports": {
|
||||||
|
"3000/tcp": 3000,
|
||||||
|
"9226/tcp": 9226
|
||||||
|
}
|
||||||
|
}
|
BIN
nextcloud_backup/icon.png
Normal file
BIN
nextcloud_backup/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
BIN
nextcloud_backup/logo.png
Normal file
BIN
nextcloud_backup/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
1
nextcloud_backup/rootfs/etc/fix-attrs.d/10-run
Normal file
1
nextcloud_backup/rootfs/etc/fix-attrs.d/10-run
Normal file
@ -0,0 +1 @@
|
|||||||
|
/usr/bin/run.sh false root 0755 0755
|
10
nextcloud_backup/rootfs/etc/services.d/nextcould_backup/run
Normal file
10
nextcloud_backup/rootfs/etc/services.d/nextcould_backup/run
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
# ==============================================================================
|
||||||
|
# Community Hass.io Add-ons: Example
|
||||||
|
# Runs example1 script
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
bashio::log.info "Starting Node..."
|
||||||
|
|
||||||
|
exec /usr/bin/nextcloud_backup.sh
|
20
nextcloud_backup/rootfs/opt/nextcloud_backup/.eslintrc.js
Normal file
20
nextcloud_backup/rootfs/opt/nextcloud_backup/.eslintrc.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2018
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": "warn"
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
69
nextcloud_backup/rootfs/opt/nextcloud_backup/app.js
Normal file
69
nextcloud_backup/rootfs/opt/nextcloud_backup/app.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
const createError = require('http-errors');
|
||||||
|
const express = require('express');
|
||||||
|
const path = require('path');
|
||||||
|
const cookieParser = require('cookie-parser');
|
||||||
|
const logger = require('morgan');
|
||||||
|
|
||||||
|
|
||||||
|
const indexRouter = require('./routes/index');
|
||||||
|
const apiRouter = require('./routes/api');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
// view engine setup
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
|
||||||
|
app.use(logger('dev', { skip: function(req, res) { return res.statusCode = 304 } }));
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
app.use('/api', apiRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
// set locals, only providing error in development
|
||||||
|
res.locals.message = err.message;
|
||||||
|
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||||
|
|
||||||
|
// render the error page
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.render('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const statusTools = require('./tools/status');
|
||||||
|
statusTools.init();
|
||||||
|
console.log("Satus : \x1b[32mGo !\x1b[0m")
|
||||||
|
|
||||||
|
const hassioApiTools = require('./tools/hassioApiTools');
|
||||||
|
hassioApiTools.getSnapshots().then(
|
||||||
|
() => {
|
||||||
|
console.log("Hassio API : \x1b[32mGo !\x1b[0m")
|
||||||
|
|
||||||
|
}, (err) => {
|
||||||
|
console.log("Hassio API : \x1b[31;1mFAIL !\x1b[0m")
|
||||||
|
console.log("... " + err);
|
||||||
|
});
|
||||||
|
|
||||||
|
const WebdavTools = require('./tools/webdavTools');
|
||||||
|
const webdav = new WebdavTools().getInstance();
|
||||||
|
webdav.confIsValid().then(
|
||||||
|
() => {
|
||||||
|
console.log("Nextcloud connection : \x1b[32mGo !\x1b[0m")
|
||||||
|
}, (err) => {
|
||||||
|
console.log("Nextcloud connection : \x1b[31;1mFAIL !\x1b[0m")
|
||||||
|
console.log("... " + err);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = app;
|
@ -0,0 +1 @@
|
|||||||
|
{"status":"idle","last_upload":null,"next_upload":null,"message":null,"error_code":null}
|
90
nextcloud_backup/rootfs/opt/nextcloud_backup/bin/www
Executable file
90
nextcloud_backup/rootfs/opt/nextcloud_backup/bin/www
Executable file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var app = require('../app');
|
||||||
|
var debug = require('debug')('nexcloud-backup:server');
|
||||||
|
var http = require('http');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get port from environment and store in Express.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var port = normalizePort(process.env.PORT || '3000');
|
||||||
|
app.set('port', port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create HTTP server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var server = http.createServer(app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen on provided port, on all network interfaces.
|
||||||
|
*/
|
||||||
|
|
||||||
|
server.listen(port);
|
||||||
|
server.on('error', onError);
|
||||||
|
server.on('listening', onListening);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a port into a number, string, or false.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function normalizePort(val) {
|
||||||
|
var port = parseInt(val, 10);
|
||||||
|
|
||||||
|
if (isNaN(port)) {
|
||||||
|
// named pipe
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port >= 0) {
|
||||||
|
// port number
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "error" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onError(error) {
|
||||||
|
if (error.syscall !== 'listen') {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bind = typeof port === 'string'
|
||||||
|
? 'Pipe ' + port
|
||||||
|
: 'Port ' + port;
|
||||||
|
|
||||||
|
// handle specific listen errors with friendly messages
|
||||||
|
switch (error.code) {
|
||||||
|
case 'EACCES':
|
||||||
|
console.error(bind + ' requires elevated privileges');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
case 'EADDRINUSE':
|
||||||
|
console.error(bind + ' is already in use');
|
||||||
|
process.exit(1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for HTTP server "listening" event.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onListening() {
|
||||||
|
var addr = server.address();
|
||||||
|
var bind = typeof addr === 'string'
|
||||||
|
? 'pipe ' + addr
|
||||||
|
: 'port ' + addr.port;
|
||||||
|
debug('Listening on ' + bind);
|
||||||
|
}
|
1
nextcloud_backup/rootfs/opt/nextcloud_backup/config.json
Normal file
1
nextcloud_backup/rootfs/opt/nextcloud_backup/config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"hostname":"test","username":"test","password":"test"}
|
1772
nextcloud_backup/rootfs/opt/nextcloud_backup/package-lock.json
generated
Normal file
1772
nextcloud_backup/rootfs/opt/nextcloud_backup/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
nextcloud_backup/rootfs/opt/nextcloud_backup/package.json
Normal file
23
nextcloud_backup/rootfs/opt/nextcloud_backup/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "nexcloud-backup",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node --inspect=0.0.0.0:9226 ./bin/www "
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"debug": "~2.6.9",
|
||||||
|
"ejs": "~2.6.1",
|
||||||
|
"express": "~4.16.1",
|
||||||
|
"http-errors": "~1.6.3",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"morgan": "~1.9.1",
|
||||||
|
"superagent": "^5.1.2",
|
||||||
|
"webdav": "^2.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-config-google": "^0.14.0"
|
||||||
|
}
|
||||||
|
}
|
13
nextcloud_backup/rootfs/opt/nextcloud_backup/public/css/materialize.min.css
vendored
Normal file
13
nextcloud_backup/rootfs/opt/nextcloud_backup/public/css/materialize.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
nextcloud_backup/rootfs/opt/nextcloud_backup/public/js/jquery-3.4.1.min.js
vendored
Normal file
2
nextcloud_backup/rootfs/opt/nextcloud_backup/public/js/jquery-3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
nextcloud_backup/rootfs/opt/nextcloud_backup/public/js/materialize.min.js
vendored
Normal file
6
nextcloud_backup/rootfs/opt/nextcloud_backup/public/js/materialize.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
61
nextcloud_backup/rootfs/opt/nextcloud_backup/routes/api.js
Normal file
61
nextcloud_backup/rootfs/opt/nextcloud_backup/routes/api.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const moment = require('moment');
|
||||||
|
const statusTools = require('../tools/status');
|
||||||
|
const WebdavTools = require('../tools/webdavTools')
|
||||||
|
const webdav = new WebdavTools().getInstance();
|
||||||
|
const hassioApiTools = require('../tools/hassioApiTools');
|
||||||
|
const settingsTools = require('../tools/settingsTools');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/status', (req, res, next) => {
|
||||||
|
let status = statusTools.getStatus();
|
||||||
|
res.json(status);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
router.get('/formated-local-snap', function(req, res, next) {
|
||||||
|
hassioApiTools.getSnapshots().then(
|
||||||
|
(snaps) => {
|
||||||
|
res.render('localSnaps', { snaps: snaps, moment: moment });
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(500);
|
||||||
|
res.send('');
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/formated-remote-manual', function(req, res, next) {
|
||||||
|
webdav.init(true, 'cloud.seb6596.ovh', 'admin', 'WPHRG-4jwCw-i8eqg-mtiao-Kmwrw').then(() => {
|
||||||
|
console.log('success');
|
||||||
|
}, (err) => {
|
||||||
|
console.log('failure');
|
||||||
|
console.log(err);
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
router.post('/nextcloud-settings', function(req, res, next){
|
||||||
|
console.log("ok");
|
||||||
|
let settings = req.body;
|
||||||
|
if(settings.host !== null && settings.host !== "" && settings.username !== null && settings.password !== null){
|
||||||
|
settingsTools.setSettings(settings);
|
||||||
|
res.status(201);
|
||||||
|
res.send();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
res.status(400);
|
||||||
|
res.send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
11
nextcloud_backup/rootfs/opt/nextcloud_backup/routes/index.js
Normal file
11
nextcloud_backup/rootfs/opt/nextcloud_backup/routes/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
/* GET home page. */
|
||||||
|
router.get('/', function(req, res, next) {
|
||||||
|
res.render('index');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = router;
|
1
nextcloud_backup/rootfs/opt/nextcloud_backup/status.json
Normal file
1
nextcloud_backup/rootfs/opt/nextcloud_backup/status.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"status":"Error","last_upload":null,"next_upload":null,"message":"Nextcloud config not found !","error_code":2}
|
@ -0,0 +1,41 @@
|
|||||||
|
const superagent = require('superagent');
|
||||||
|
const statusTools = require('./status');
|
||||||
|
|
||||||
|
// !!! FOR DEV PURPOSE ONLY !!!
|
||||||
|
//put token here for dev (ssh port tunelling 'sudo ssh -L 80:hassio:80 root@`hassoi_ip`' + put 127.0.0.1 hassio into host)
|
||||||
|
const fallbackToken = "75c7a712290f47a7513ee75a24072f2a5f44745d9b9c4e1f9fe6d44e55da2715e7c4341de239ec1c79a5f7178dd4376e27a98ebb7b4b029a"
|
||||||
|
|
||||||
|
|
||||||
|
function getSnapshots() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let token = process.env.HASSIO_TOKEN;
|
||||||
|
if (token == null) {
|
||||||
|
token = fallbackToken
|
||||||
|
}
|
||||||
|
let status = statusTools.getStatus();
|
||||||
|
superagent.get('http://hassio/snapshots')
|
||||||
|
.set('X-HASSIO-KEY', token)
|
||||||
|
.then(data => {
|
||||||
|
if (status.error_code == 1) {
|
||||||
|
status.status = "idle";
|
||||||
|
status.message = null;
|
||||||
|
status.error_code = null;
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
}
|
||||||
|
let snaps = data.body.data.snapshots;
|
||||||
|
// console.log(snaps);
|
||||||
|
resolve(snaps);
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
status.status = "error";
|
||||||
|
status.message = "Fail to fetch Hassio snapshot (" + err + ")";
|
||||||
|
status.error_code = 1;
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getSnapshots = getSnapshots;
|
@ -0,0 +1,26 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const settingsPath = "./config.json"
|
||||||
|
|
||||||
|
|
||||||
|
function getSettings(){
|
||||||
|
|
||||||
|
if (!fs.existsSync(settingsPath)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let rawSettings = fs.readFileSync(settingsPath);
|
||||||
|
return JSON.parse(rawSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setSettings(settings){
|
||||||
|
fs.writeFileSync(settingsPath, JSON.stringify(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.getSettings = getSettings;
|
||||||
|
exports.setSettings = setSettings;
|
||||||
|
|
||||||
|
|
45
nextcloud_backup/rootfs/opt/nextcloud_backup/tools/status.js
Normal file
45
nextcloud_backup/rootfs/opt/nextcloud_backup/tools/status.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const statusPath = './status.json'
|
||||||
|
|
||||||
|
let baseStatus = {
|
||||||
|
status: "idle",
|
||||||
|
last_upload: null,
|
||||||
|
next_upload: null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (!fs.existsSync(statusPath)) {
|
||||||
|
fs.writeFileSync(statusPath, JSON.stringify(baseStatus));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
let content = getStatus();
|
||||||
|
if(content.status !== "idle"){
|
||||||
|
content.status = "idle";
|
||||||
|
content.message = null;
|
||||||
|
setStatus(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStatus(){
|
||||||
|
if (!fs.existsSync(statusPath)) {
|
||||||
|
fs.writeFileSync(statusPath, JSON.stringify(baseStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = fs.readFileSync(statusPath);
|
||||||
|
return JSON.parse(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(state){
|
||||||
|
fs.writeFileSync(statusPath, JSON.stringify(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.init = init;
|
||||||
|
exports.getStatus = getStatus;
|
||||||
|
exports.setStatus = setStatus;
|
@ -0,0 +1,104 @@
|
|||||||
|
const { createClient } = require("webdav");
|
||||||
|
const fs = require("fs");
|
||||||
|
const statusTools = require('./status');
|
||||||
|
const endpoint = "/remote.php/webdav"
|
||||||
|
const configPath = "./webdav_conf.json"
|
||||||
|
|
||||||
|
class WebdavTools {
|
||||||
|
constructor() {
|
||||||
|
this.client = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(ssl, host, username, password) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log("Initilizing and checking webdav client...")
|
||||||
|
let url = (ssl ? "https" : "http") + "://" + host + endpoint;
|
||||||
|
try {
|
||||||
|
this.client = createClient(url, { username: username, password: password });
|
||||||
|
this.client.getDirectoryContents("/").then(() => {
|
||||||
|
if (status.error_code == 3) {
|
||||||
|
status.status = "idle";
|
||||||
|
status.message = null;
|
||||||
|
status.error_code = null;
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}).catch((error) => {
|
||||||
|
status.status = "Error";
|
||||||
|
status.error_code = 3;
|
||||||
|
status.message = "Can't connect to Nextcloud (" + error + ") !"
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
this.client = null;
|
||||||
|
reject("Can't connect to Nextcloud (" + error + ") !");
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
status.status = "Error";
|
||||||
|
status.error_code = 3;
|
||||||
|
status.message = "Can't connect to Nextcloud (" + err + ") !"
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
this.client = null;
|
||||||
|
reject("Can't connect to Nextcloud (" + err + ") !");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confIsValid() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let status = statusTools.getStatus();
|
||||||
|
let conf = this.loadConf();
|
||||||
|
if (conf !== null) {
|
||||||
|
if (conf.ssl !== null && conf.host !== null && conf !== null && conf !== null) {
|
||||||
|
if (status.error_code == 2) {
|
||||||
|
status.status = "idle";
|
||||||
|
status.message = null;
|
||||||
|
status.error_code = null;
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
}
|
||||||
|
//TODO init connection
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
status.status = "Error";
|
||||||
|
status.error_code = 2;
|
||||||
|
status.message = "Nextcloud config invalid !"
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
reject("Nextcloud config invalid !");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
status.status = "Error";
|
||||||
|
status.error_code = 2;
|
||||||
|
status.message = "Nextcloud config not found !"
|
||||||
|
statusTools.setStatus(status);
|
||||||
|
reject("Nextcloud config not found !");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConf() {
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
let content = JSON.parse(fs.readFileSync(configPath));
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton {
|
||||||
|
constructor() {
|
||||||
|
if (!Singleton.instance) {
|
||||||
|
Singleton.instance = new WebdavTools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstance() {
|
||||||
|
return Singleton.instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Singleton;
|
||||||
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<h1><%= message %></h1>
|
||||||
|
<h2><%= error.status %></h2>
|
||||||
|
<pre><%= error.stack %></pre>
|
284
nextcloud_backup/rootfs/opt/nextcloud_backup/views/index.ejs
Normal file
284
nextcloud_backup/rootfs/opt/nextcloud_backup/views/index.ejs
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<title>Nextcloud Backup</title>
|
||||||
|
|
||||||
|
<!-- <link rel='stylesheet' href='/stylesheets/style.css' /> -->
|
||||||
|
<link rel="stylesheet" href="./css/materialize.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.modal input[disabled] {
|
||||||
|
color: #757575 !important;
|
||||||
|
border-color: #616161 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .input-field span.helper-text {
|
||||||
|
color: #9e9e9e;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal div.row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal div.col:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-box {
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-box .col {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-box .col .card {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-box .card-content {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-box .card-content h5 {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.dropdown-content a:hover {
|
||||||
|
background-color: #101619 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.dropdown-content li:hover {
|
||||||
|
background-color: #101619 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* change autocomplete color */
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
-webkit-box-shadow: 0 0 0 30px #263238 inset !important;
|
||||||
|
-webkit-text-fill-color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="blue-grey darken-4">
|
||||||
|
<nav class=" light-blue accent-4">
|
||||||
|
<div class="nav-wrapper container">
|
||||||
|
<a href="#" class="brand-logo"><img src="https://cloud.seb6596.ovh/svg/core/logo/logo?color=fff&v=1" height="54"
|
||||||
|
style="margin: 5px"></a>
|
||||||
|
<ul class="right">
|
||||||
|
<li id="setting-trigger"><a class="dropdown-trigger" href="#" data-target="dropdown-settings"><i
|
||||||
|
class="material-icons">settings</i></a></li>
|
||||||
|
</ul>
|
||||||
|
<div style="height: 64px; display: table; margin-left: 130px;">
|
||||||
|
<h4 style="display: table-cell; vertical-align: middle;">Nextcloud Backup</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<ul id="dropdown-settings" class="dropdown-content blue-grey darken-4">
|
||||||
|
<li><a href="#modal-settings-nextcloud" class="modal-trigger center">Nextcloud</a></li>
|
||||||
|
<li><a href="#modal-settings-backup" class="modal-trigger center">Backup</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="container ">
|
||||||
|
|
||||||
|
<div class="row header-box">
|
||||||
|
<div class="col s12 m3">
|
||||||
|
<div class="card cyan darken-3">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title white-text" style="font-weight: bold;">Status </span>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<h5 id="status" class="white-text"></h5>
|
||||||
|
<div id="status-second-line" class="truncate tooltipped" data-position="bottom" data-tooltip=""></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col s12 m3">
|
||||||
|
<div class="card cyan darken-3">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title white-text" style="font-weight: bold;">Last Backup</span>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<h5 id="last_back_status"></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m3">
|
||||||
|
<div class="card cyan darken-3 ">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title white-text" style="font-weight: bold;">Next Backup </span>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<h5 id="next_back_status"></h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col s12 m3">
|
||||||
|
<div class="card cyan darken-3">
|
||||||
|
<div class="card-content center">
|
||||||
|
<a class="btn green">Backup Now</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 m12 l6 ">
|
||||||
|
<div class="card cyan darken-3">
|
||||||
|
<div class="card-content">
|
||||||
|
<span class="card-title center white-text">Local Snapshots</span>
|
||||||
|
<div id="local_snaps"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="modal-settings-nextcloud" class="modal modal-fixed-footer blue-grey darken-4 white-text">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 center">
|
||||||
|
<h4>Nextcloud Settings</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 center divider">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input id="hostname" type="text" class="white-text">
|
||||||
|
<label for="hostname">Hostname</label>
|
||||||
|
<span class="helper-text">exemple.com:8080</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input id="username" type="text" class="white-text">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-field col s6">
|
||||||
|
<input id="password" type="password" class="white-text">
|
||||||
|
<label for="password">Password</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-footer blue-grey darken-4">
|
||||||
|
<a href="#" class="modal-close waves-effect btn red"><b>Cancel</b></a>
|
||||||
|
<a href="#" class="btn green waves-effect" style="margin-left: 5px;" id="save-nextcloud-settings"><b>Save</b></a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="modal-loading" class="modal blue-grey darken-4 white-text">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
<script src="./js/materialize.min.js"></script>
|
||||||
|
<script src="./js/jquery-3.4.1.min.js"></script>
|
||||||
|
<script src="./js/index.js"></script>
|
||||||
|
<script>
|
||||||
|
var last_status = "";
|
||||||
|
|
||||||
|
var loadingModal = null;
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
updateLocalSnaps();
|
||||||
|
update_status();
|
||||||
|
let tooltips = document.querySelectorAll('.tooltipped');
|
||||||
|
M.Tooltip.init(tooltips, {});
|
||||||
|
let drops = document.querySelectorAll('.dropdown-trigger');
|
||||||
|
M.Dropdown.init(drops, { constrainWidth: false, coverTrigger: false, alignment: 'right', onOpenStart: () => $('#setting-trigger').addClass('active'), onCloseEnd: () => $('#setting-trigger').removeClass('active') });
|
||||||
|
setInterval(update_status, 500);
|
||||||
|
listeners();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateLocalSnaps() {
|
||||||
|
$.get('./api/formated-local-snap', (data) => {
|
||||||
|
$('#local_snaps').empty();
|
||||||
|
$('#local_snaps').html(data);
|
||||||
|
|
||||||
|
var elems = document.querySelectorAll('.collapsible');
|
||||||
|
M.Collapsible.init(elems, { accordion: true });
|
||||||
|
var modals = document.querySelectorAll('.modal:not(#modal-loading)');
|
||||||
|
M.Modal.init(modals, {});
|
||||||
|
|
||||||
|
let loadingModals = document.querySelectorAll('#modal-loading');
|
||||||
|
M.Modal.init( loadingModals, {dismissible: false});
|
||||||
|
|
||||||
|
loadingModal = M.Modal.getInstance(document.querySelector('#modal-loading'));
|
||||||
|
debugger;
|
||||||
|
$('.local-snap-listener').click(function() {
|
||||||
|
let id = this.getAttribute('data-id');
|
||||||
|
console.log(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_status() {
|
||||||
|
$.get('./api/status', (data) => {
|
||||||
|
if (JSON.stringify(data) !== last_status) {
|
||||||
|
last_status = JSON.stringify(data);
|
||||||
|
switch (data.status) {
|
||||||
|
case "error":
|
||||||
|
printStatus('Error', data.message);
|
||||||
|
break;
|
||||||
|
case "idle":
|
||||||
|
printStatus('Idle', "Waiting for next backup.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function printStatus(status, secondLine) {
|
||||||
|
$('#status').empty();
|
||||||
|
$('#status').html(status);
|
||||||
|
$('#status-second-line').empty();
|
||||||
|
$('#status-second-line').html(secondLine);
|
||||||
|
$('#status-second-line').attr('data-tooltip', secondLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
function listeners() {
|
||||||
|
$('#save-nextcloud-settings').click(sendNextcloudSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function sendNextcloudSettings() {
|
||||||
|
loadingModal.open();
|
||||||
|
let hostname = $('#hostname').val();
|
||||||
|
let username = $('#username').val();
|
||||||
|
let password = $('#password').val();
|
||||||
|
$.post('./api/nextcloud-settings', { hostname: hostname, username: username, password: password })
|
||||||
|
.done((data) => {
|
||||||
|
console.log('Saved');
|
||||||
|
}).fail((data) => {
|
||||||
|
console.log('Fail');
|
||||||
|
}).always(()=>{
|
||||||
|
loadingModal.close();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
@ -0,0 +1,61 @@
|
|||||||
|
<% if (locals.snaps) { %>
|
||||||
|
<div class="collection">
|
||||||
|
<% for(const index in snaps) { %>
|
||||||
|
<a class="collection-item local-snap-listener modal-trigger" href="#modal-<%=snaps[index].slug%>"
|
||||||
|
data-id="<%= snaps[index].slug %>">
|
||||||
|
<div><%= snaps[index].name ? snaps[index].name : 'No name' %><div class="secondary-content">
|
||||||
|
<%= moment(snaps[index].date).format('lll') %></div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div id="modal-<%=snaps[index].slug%>" class="modal modal-fixed-footer blue-grey darken-4 white-text">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 center">
|
||||||
|
<h4>Snapshot Detail</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12 center divider">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input disabled type="text" id="name-<%=snaps[index].slug%>" value="<%= snaps[index].name ? snaps[index].name : 'No name' %>" />
|
||||||
|
<label for="name-<%=snaps[index].slug%>" class="white-text active">Name</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input disabled type="text" id="date-<%=snaps[index].slug%>"
|
||||||
|
value="<%=moment(snaps[index].date).format('lll')%>" />
|
||||||
|
<label for="date-<%=snaps[index].slug%>" class="white-text active">Date</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input disabled type="text" id="protected-<%=snaps[index].slug%>"
|
||||||
|
value="<%=snaps[index].protected%>" />
|
||||||
|
<label for="protected-<%=snaps[index].slug%>" class="white-text active">Protected</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-field col s12">
|
||||||
|
<input disabled type="text" id="type-<%=snaps[index].slug%>" value="<%=snaps[index].type%>" />
|
||||||
|
<label for="type-<%=snaps[index].slug%>" class="white-text active">Type</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer blue-grey darken-4">
|
||||||
|
<a href="#!" class="waves-effect waves-green btn light-blue accent-4">Backup now</a>
|
||||||
|
<a href="#!" class="modal-close waves-effect waves-green btn red">Close</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<% } %>
|
12
nextcloud_backup/rootfs/usr/bin/nextcloud_backup.sh
Executable file
12
nextcloud_backup/rootfs/usr/bin/nextcloud_backup.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/with-contenv bashio
|
||||||
|
# ==============================================================================
|
||||||
|
#
|
||||||
|
# Community Hass.io Add-ons: Example
|
||||||
|
#
|
||||||
|
# Example add-on for Hass.io.
|
||||||
|
# This add-on displays a random quote every X seconds.
|
||||||
|
#
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
cd /opt/nextcloud_backup/
|
||||||
|
npm start
|
1
nextcloud_backup/run.sh
Normal file
1
nextcloud_backup/run.sh
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
Loading…
Reference in New Issue
Block a user