mirror of https://github.com/Requarks/wiki.git
NGPixel
4 years ago
17 changed files with 412 additions and 59 deletions
Split View
Diff Options
-
2client/graph/admin/storage/storage-query-targets.gql
-
2package.json
-
19server/graph/resolvers/storage.js
-
6server/graph/schemas/storage.graphql
-
1server/helpers/common.js
-
8server/models/storage.js
-
6server/modules/storage/azure/definition.yml
-
47server/modules/storage/azure/storage.js
-
6server/modules/storage/digitalocean/definition.yml
-
2server/modules/storage/git/definition.yml
-
44server/modules/storage/s3/common.js
-
5server/modules/storage/s3/definition.yml
-
16server/modules/storage/scp/definition.yml
-
23server/modules/storage/scp/storage.js
-
71server/modules/storage/sftp/definition.yml
-
160server/modules/storage/sftp/storage.js
-
53yarn.lock
@ -1,16 +0,0 @@ |
|||
key: scp |
|||
title: SCP (SSH) |
|||
description: SSH is a software package that enables secure system administration and file transfers over insecure networks. |
|||
author: requarks.io |
|||
logo: https://static.requarks.io/logo/ssh.svg |
|||
website: https://www.ssh.com/ssh/ |
|||
props: |
|||
host: String |
|||
port: |
|||
type: Number |
|||
default: 22 |
|||
username: String |
|||
privateKeyPath: String |
|||
basePath: |
|||
type: String |
|||
default: '~' |
@ -1,23 +0,0 @@ |
|||
module.exports = { |
|||
async activated() { |
|||
|
|||
}, |
|||
async deactivated() { |
|||
|
|||
}, |
|||
async init() { |
|||
|
|||
}, |
|||
async created() { |
|||
|
|||
}, |
|||
async updated() { |
|||
|
|||
}, |
|||
async deleted() { |
|||
|
|||
}, |
|||
async renamed() { |
|||
|
|||
} |
|||
} |
@ -0,0 +1,71 @@ |
|||
key: sftp |
|||
title: SFTP |
|||
description: SFTP (SSH File Transfer Protocol) is a secure file transfer protocol. It runs over the SSH protocol. It supports the full security and authentication functionality of SSH. |
|||
author: requarks.io |
|||
logo: https://static.requarks.io/logo/ssh.svg |
|||
website: https://www.ssh.com/ssh/sftp |
|||
isAvailable: true |
|||
supportedModes: |
|||
- push |
|||
defaultMode: push |
|||
schedule: false |
|||
props: |
|||
host: |
|||
type: String |
|||
title: Host |
|||
default: '' |
|||
hint: Hostname or IP of the remote SSH server. |
|||
order: 1 |
|||
port: |
|||
type: Number |
|||
title: Port |
|||
default: 22 |
|||
hint: SSH port of the remote server. |
|||
order: 2 |
|||
authMode: |
|||
type: String |
|||
title: Authentication Method |
|||
default: 'privateKey' |
|||
hint: Whether to use Private Key or Password-based authentication. A private key is highly recommended for best security. |
|||
enum: |
|||
- privateKey |
|||
- password |
|||
order: 3 |
|||
username: |
|||
type: String |
|||
title: Username |
|||
default: '' |
|||
hint: Username for authentication. |
|||
order: 4 |
|||
privateKey: |
|||
type: String |
|||
title: Private Key Contents |
|||
default: '' |
|||
hint: (Private Key Authentication Only) - Contents of the private key |
|||
multiline: true |
|||
sensitive: true |
|||
order: 5 |
|||
passphrase: |
|||
type: String |
|||
title: Private Key Passphrase |
|||
default: '' |
|||
hint: (Private Key Authentication Only) - Passphrase if the private key is encrypted, leave empty otherwise |
|||
sensitive: true |
|||
order: 6 |
|||
password: |
|||
type: String |
|||
title: Password |
|||
default: '' |
|||
hint: (Password-based Authentication Only) - Password for authentication |
|||
sensitive: true |
|||
order: 6 |
|||
basePath: |
|||
type: String |
|||
title: Base Directory Path |
|||
default: '/root/wiki' |
|||
hint: Base directory where files will be transferred to. The path must already exists and be writable by the user. |
|||
actions: |
|||
- handler: exportAll |
|||
label: Export All |
|||
hint: Output all content from the DB to the remote SSH server, overwriting any existing data. If you enabled SFTP after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. |
|||
|
@ -0,0 +1,160 @@ |
|||
const SSH2Promise = require('ssh2-promise') |
|||
const _ = require('lodash') |
|||
const path = require('path') |
|||
const stream = require('stream') |
|||
const Promise = require('bluebird') |
|||
const pipeline = Promise.promisify(stream.pipeline) |
|||
const pageHelper = require('../../../helpers/page.js') |
|||
|
|||
/* global WIKI */ |
|||
|
|||
const getFilePath = (page, pathKey) => { |
|||
const fileName = `${page[pathKey]}.${pageHelper.getFileExtension(page.contentType)}` |
|||
const withLocaleCode = WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode |
|||
return withLocaleCode ? `${page.localeCode}/${fileName}` : fileName |
|||
} |
|||
|
|||
module.exports = { |
|||
client: null, |
|||
sftp: null, |
|||
async activated() { |
|||
|
|||
}, |
|||
async deactivated() { |
|||
|
|||
}, |
|||
async init() { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Initializing...`) |
|||
this.client = new SSH2Promise({ |
|||
host: this.config.host, |
|||
port: this.config.port || 22, |
|||
username: this.config.username, |
|||
password: (this.config.authMode === 'password') ? this.config.password : null, |
|||
privateKey: (this.config.authMode === 'privateKey') ? this.config.privateKey : null, |
|||
passphrase: (this.config.authMode === 'privateKey') ? this.config.passphrase : null |
|||
}) |
|||
await this.client.connect() |
|||
this.sftp = this.client.sftp() |
|||
try { |
|||
await this.sftp.readdir(this.config.basePath) |
|||
} catch (err) { |
|||
WIKI.logger.warn(`(STORAGE/SFTP) ${err.message}`) |
|||
throw new Error(`Unable to read specified base directory: ${err.message}`) |
|||
} |
|||
WIKI.logger.info(`(STORAGE/SFTP) Initialization completed.`) |
|||
}, |
|||
async created(page) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Creating file ${page.path}...`) |
|||
const filePath = getFilePath(page, 'path') |
|||
await this.ensureDirectory(filePath) |
|||
await this.sftp.writeFile(path.posix.join(this.config.basePath, filePath), page.injectMetadata()) |
|||
}, |
|||
async updated(page) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Updating file ${page.path}...`) |
|||
const filePath = getFilePath(page, 'path') |
|||
await this.ensureDirectory(filePath) |
|||
await this.sftp.writeFile(path.posix.join(this.config.basePath, filePath), page.injectMetadata()) |
|||
}, |
|||
async deleted(page) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Deleting file ${page.path}...`) |
|||
const filePath = getFilePath(page, 'path') |
|||
await this.sftp.unlink(path.posix.join(this.config.basePath, filePath)) |
|||
}, |
|||
async renamed(page) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Renaming file ${page.path} to ${page.destinationPath}...`) |
|||
let sourceFilePath = getFilePath(page, 'path') |
|||
let destinationFilePath = getFilePath(page, 'destinationPath') |
|||
if (WIKI.config.lang.namespacing) { |
|||
if (WIKI.config.lang.code !== page.localeCode) { |
|||
sourceFilePath = `${page.localeCode}/${sourceFilePath}` |
|||
} |
|||
if (WIKI.config.lang.code !== page.destinationLocaleCode) { |
|||
destinationFilePath = `${page.destinationLocaleCode}/${destinationFilePath}` |
|||
} |
|||
} |
|||
await this.ensureDirectory(destinationFilePath) |
|||
await this.sftp.rename(path.posix.join(this.config.basePath, sourceFilePath), path.posix.join(this.config.basePath, destinationFilePath)) |
|||
}, |
|||
/** |
|||
* ASSET UPLOAD |
|||
* |
|||
* @param {Object} asset Asset to upload |
|||
*/ |
|||
async assetUploaded (asset) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Creating new file ${asset.path}...`) |
|||
await this.ensureDirectory(asset.path) |
|||
await this.sftp.writeFile(path.posix.join(this.config.basePath, asset.path), asset.data) |
|||
}, |
|||
/** |
|||
* ASSET DELETE |
|||
* |
|||
* @param {Object} asset Asset to delete |
|||
*/ |
|||
async assetDeleted (asset) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Deleting file ${asset.path}...`) |
|||
await this.sftp.unlink(path.posix.join(this.config.basePath, asset.path)) |
|||
}, |
|||
/** |
|||
* ASSET RENAME |
|||
* |
|||
* @param {Object} asset Asset to rename |
|||
*/ |
|||
async assetRenamed (asset) { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Renaming file from ${asset.path} to ${asset.destinationPath}...`) |
|||
await this.ensureDirectory(asset.destinationPath) |
|||
await this.sftp.rename(path.posix.join(this.config.basePath, asset.path), path.posix.join(this.config.basePath, asset.destinationPath)) |
|||
}, |
|||
/** |
|||
* HANDLERS |
|||
*/ |
|||
async exportAll() { |
|||
WIKI.logger.info(`(STORAGE/SFTP) Exporting all content to the remote server...`) |
|||
|
|||
// -> Pages
|
|||
await pipeline( |
|||
WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'contentType', 'content', 'isPublished', 'updatedAt').select().from('pages').where({ |
|||
isPrivate: false |
|||
}).stream(), |
|||
new stream.Transform({ |
|||
objectMode: true, |
|||
transform: async (page, enc, cb) => { |
|||
const filePath = getFilePath(page, 'path') |
|||
WIKI.logger.info(`(STORAGE/SFTP) Adding page ${filePath}...`) |
|||
await this.ensureDirectory(filePath) |
|||
await this.sftp.writeFile(path.posix.join(this.config.basePath, filePath), pageHelper.injectPageMetadata(page)) |
|||
cb() |
|||
} |
|||
}) |
|||
) |
|||
|
|||
// -> Assets
|
|||
const assetFolders = await WIKI.models.assetFolders.getAllPaths() |
|||
|
|||
await pipeline( |
|||
WIKI.models.knex.column('filename', 'folderId', 'data').select().from('assets').join('assetData', 'assets.id', '=', 'assetData.id').stream(), |
|||
new stream.Transform({ |
|||
objectMode: true, |
|||
transform: async (asset, enc, cb) => { |
|||
const filename = (asset.folderId && asset.folderId > 0) ? `${_.get(assetFolders, asset.folderId)}/${asset.filename}` : asset.filename |
|||
WIKI.logger.info(`(STORAGE/SFTP) Adding asset ${filename}...`) |
|||
await this.ensureDirectory(filename) |
|||
await this.sftp.writeFile(path.posix.join(this.config.basePath, filename), asset.data) |
|||
cb() |
|||
} |
|||
}) |
|||
) |
|||
|
|||
WIKI.logger.info('(STORAGE/SFTP) All content has been pushed to the remote server.') |
|||
}, |
|||
async ensureDirectory(filePath) { |
|||
if (filePath.indexOf('/') >= 0) { |
|||
try { |
|||
const folderPaths = _.dropRight(filePath.split('/')) |
|||
for (let i = 1; i <= folderPaths.length; i++) { |
|||
const folderSection = _.take(folderPaths, i).join('/') |
|||
await this.sftp.mkdir(path.posix.join(this.config.basePath, folderSection)) |
|||
} |
|||
} catch (err) {} |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save