diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index e3ba0cb48..46ea8a993 100755
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -27,97 +27,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -134,36 +48,36 @@
- createDomain
- submitDomain
- getpass
- addRule
- firewalld
- normal
- Removing OpenLiteSpeed..
- def decideServer(
- mirror.cyberpanel.net
- http
- loginAPI
- verify
- verifylogins
- https
- kvm
- openvz
- virtualenv
- createWebsiteAPI
- selectedEmail
- emailForwarding
- emailFor
- selectForwardingEmail
- submitForwardDeletion
- getEmailsForDomain
- forwardEmail
- export
restoreRemoteBackupsInc
- backupData
- backupData(
- sftpFunction
+ status
+ liteSpeedStatus
+ saveRewrite
+ Dashboard
+ onerror
+ sendCommand
+ trans_forward
+ invoke_shell
+ self.ssh
+ webssh
+ _shell
+ [
+ _check_init_param
+ Result from paramiko
+ trans_back
+ get data
+ parse_pkey
+ privaterKey
+ init
+ print(self.verifyPath)
+ CloudLinux
+ verifyPath
+ charWidth
+ sendClientData
+ cpec
+ cpecs
+ emailMig
+ apt install build-essential libssl-dev libffi-dev python3-dev
+ python4
admin.api == 1
@@ -194,62 +108,63 @@
+
@@ -258,7 +173,6 @@
-
@@ -269,57 +183,32 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+
+
+
+
+
+
-
-
-
-
-
@@ -509,18 +398,19 @@
-
+
+
-
+
-
+
@@ -544,7 +434,7 @@
-
+
@@ -584,7 +474,7 @@
file://$PROJECT_DIR$/serverStatus/views.py
- 474
+ 489
@@ -604,640 +494,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -1246,9 +506,9 @@
-
+
-
+
@@ -1256,15 +516,65 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
@@ -1272,42 +582,372 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CyberCP/settings.py b/CyberCP/settings.py
index 95b03f62e..60f2bcc06 100755
--- a/CyberCP/settings.py
+++ b/CyberCP/settings.py
@@ -65,7 +65,8 @@ INSTALLED_APPS = [
'dockerManager',
'containerization',
'CLManager',
- 'IncBackups'
+ 'IncBackups',
+ 'WebTerminal'
]
MIDDLEWARE = [
diff --git a/CyberCP/urls.py b/CyberCP/urls.py
index 121b0b468..dde85b823 100755
--- a/CyberCP/urls.py
+++ b/CyberCP/urls.py
@@ -44,4 +44,5 @@ urlpatterns = [
url(r'^container/', include('containerization.urls')),
url(r'^CloudLinux/', include('CLManager.urls')),
url(r'^IncrementalBackups/', include('IncBackups.urls')),
+ url(r'^Terminal/', include('WebTerminal.urls')),
]
diff --git a/IncBackups/IncBackupsControl.py b/IncBackups/IncBackupsControl.py
index 63d68c783..a93ee0bb9 100644
--- a/IncBackups/IncBackupsControl.py
+++ b/IncBackups/IncBackupsControl.py
@@ -29,6 +29,7 @@ from mailServer.models import Domains as eDomains
from random import randint
import json
from django.shortcuts import HttpResponse
+from plogical.mailUtilities import mailUtilities
try:
from plogical.virtualHostUtilities import virtualHostUtilities
@@ -50,6 +51,8 @@ class IncJobs(multi.Thread):
self.backupDestinations = ''
self.jobid = 0
self.metaPath = ''
+ self.path = ''
+ self.reconstruct = ''
def run(self):
@@ -61,18 +64,15 @@ class IncJobs(multi.Thread):
self.restorePoint()
def getRemoteBackups(self):
- if self.jobid.destination == 'local':
- path = '/home/%s/incbackup' % (self.website)
- command = 'export RESTIC_PASSWORD=%s && restic -r %s snapshots' % (self.passwordFile, path)
- return ProcessUtilities.outputExecutioner(command).split('\n')
- elif self.jobid.destination[:4] == 'sftp':
+ if self.backupDestinations[:4] == 'sftp':
path = '/home/backup/%s' % (self.website)
command = 'export RESTIC_PASSWORD=%s PATH=${PATH}:/usr/bin && restic -r %s:%s snapshots' % (
self.passwordFile, self.backupDestinations, path)
return ProcessUtilities.outputExecutioner(command).split('\n')
else:
- path = '/home/%s/incbackup' % (self.website)
- command = 'export RESTIC_PASSWORD=%s && restic -r %s snapshots' % (self.passwordFile, path)
+ key, secret = self.getAWSData()
+ command = 'export RESTIC_PASSWORD=%s AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s snapshots' % (
+ self.passwordFile, key, secret, self.website)
return ProcessUtilities.outputExecutioner(command).split('\n')
def fetchCurrentBackups(self):
@@ -81,39 +81,36 @@ class IncJobs(multi.Thread):
self.backupDestinations = self.extraArgs['backupDestinations']
self.passwordFile = self.extraArgs['password']
- remotePath = '/home/backup/%s' % (self.website)
-
- command = 'export RESTIC_PASSWORD=%s PATH=${PATH}:/usr/bin && restic -r %s:%s snapshots' % (
- self.passwordFile, self.backupDestinations, remotePath)
- result = ProcessUtilities.outputExecutioner(command).split('\n')
+ result = self.getRemoteBackups()
activator = 0
json_data = "["
checker = 0
- for items in result:
- if items.find('---------------') > -1:
- if activator == 0:
- activator = 1
- continue
- else:
- activator = 0
+ if result[0].find('unable to open config file') == -1:
+ for items in reversed(result):
- if activator:
- entry = items.split(' ')
- logging.writeToFile(str(entry))
+ if items.find('---------------') > -1:
+ if activator == 0:
+ activator = 1
+ continue
+ else:
+ activator = 0
- dic = {'id': entry[0],
- 'date': "%s %s" % (entry[2], entry[3]),
- 'host': entry[5],
- 'path': entry[-1]
- }
+ if activator:
+ entry = items.split(' ')
- if checker == 0:
- json_data = json_data + json.dumps(dic)
- checker = 1
- else:
- json_data = json_data + ',' + json.dumps(dic)
+ dic = {'id': entry[0],
+ 'date': "%s %s" % (entry[2], entry[3]),
+ 'host': entry[5],
+ 'path': entry[-1]
+ }
+
+ if checker == 0:
+ json_data = json_data + json.dumps(dic)
+ checker = 1
+ else:
+ json_data = json_data + ',' + json.dumps(dic)
json_data = json_data + ']'
final_json = json.dumps({'status': 1, 'error_message': "None", "data": json_data})
@@ -150,12 +147,21 @@ class IncJobs(multi.Thread):
destination=self.backupDestinations)
newSnapshot.save()
else:
- self.backupDestinations = self.jobid.destination
- key, secret = self.getAWSData()
- command = 'export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s restore %s --password-file %s --target /' % (
- key, secret, self.website, snapshotID, self.passwordFile)
- result = ProcessUtilities.outputExecutioner(command)
- logging.statusWriter(self.statusPath, result, 1)
+ if self.reconstruct == 'remote':
+ self.backupDestinations = self.backupDestinations
+ key, secret = self.getAWSData()
+ command = 'export RESTIC_PASSWORD=%s AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s restore %s --target /' % (
+ self.passwordFile,
+ key, secret, self.website, snapshotID)
+ result = ProcessUtilities.outputExecutioner(command)
+ logging.statusWriter(self.statusPath, result, 1)
+ else:
+ self.backupDestinations = self.jobid.destination
+ key, secret = self.getAWSData()
+ command = 'export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s restore %s --password-file %s --target /' % (
+ key, secret, self.website, snapshotID, self.passwordFile)
+ result = ProcessUtilities.outputExecutioner(command)
+ logging.statusWriter(self.statusPath, result, 1)
except BaseException, msg:
@@ -164,7 +170,8 @@ class IncJobs(multi.Thread):
def localFunction(self, backupPath, type, restore=None):
if restore == None:
- command = 'restic -r %s backup %s --password-file %s --exclude %s' % (self.repoPath, backupPath, self.passwordFile, self.repoPath)
+ command = 'restic -r %s backup %s --password-file %s --exclude %s' % (
+ self.repoPath, backupPath, self.passwordFile, self.repoPath)
result = ProcessUtilities.outputExecutioner(command)
logging.statusWriter(self.statusPath, result, 1)
snapShotid = result.split(' ')[-2]
@@ -204,21 +211,35 @@ class IncJobs(multi.Thread):
destination=self.backupDestinations)
newSnapshot.save()
else:
- repoLocation = '/home/backup/%s' % (self.website)
- command = 'export PATH=${PATH}:/usr/bin && restic -r %s:%s restore %s --target / --password-file %s' % (
- self.jobid.destination, repoLocation, self.jobid.snapshotid, self.passwordFile)
- result = ProcessUtilities.outputExecutioner(command)
- logging.statusWriter(self.statusPath, result, 1)
+ if self.reconstruct == 'remote':
+ repoLocation = '/home/backup/%s' % (self.website)
+ command = 'export RESTIC_PASSWORD=%s PATH=${PATH}:/usr/bin && restic -r %s:%s restore %s --target /' % (
+ self.passwordFile,
+ self.backupDestinations, repoLocation, self.jobid)
+ result = ProcessUtilities.outputExecutioner(command)
+ logging.statusWriter(self.statusPath, result, 1)
+ else:
+ repoLocation = '/home/backup/%s' % (self.website)
+ command = 'export PATH=${PATH}:/usr/bin && restic -r %s:%s restore %s --target / --password-file %s' % (
+ self.jobid.destination, repoLocation, self.jobid.snapshotid, self.passwordFile)
+ result = ProcessUtilities.outputExecutioner(command)
+ logging.statusWriter(self.statusPath, result, 1)
def restoreData(self):
try:
- if self.jobid.destination == 'local':
- self.localFunction('none', 'none', 1)
- elif self.jobid.destination[:4] == 'sftp':
- self.sftpFunction('none', 'none', 1)
+ if self.reconstruct == 'remote':
+ if self.backupDestinations[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid)
else:
- self.awsFunction('restore', '', self.jobid.snapshotid)
+ if self.jobid.destination == 'local':
+ self.localFunction('none', 'none', 1)
+ elif self.jobid.destination[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid.snapshotid)
except BaseException, msg:
logging.statusWriter(self.statusPath, "%s [138][5009]" % (str(msg)), 1)
@@ -227,19 +248,33 @@ class IncJobs(multi.Thread):
def restoreDatabase(self):
try:
- if self.jobid.destination == 'local':
- self.localFunction('none', 'none', 1)
- elif self.jobid.destination[:4] == 'sftp':
- self.sftpFunction('none', 'none', 1)
- else:
- self.awsFunction('restore', '', self.jobid.snapshotid)
+ if self.reconstruct == 'remote':
+ if self.backupDestinations[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid)
- if mysqlUtilities.mysqlUtilities.restoreDatabaseBackup(self.jobid.type.split(':')[1].rstrip('.sql'),
- '/home/cyberpanel', 'dummy', 'dummy') == 0:
- raise BaseException
+ if mysqlUtilities.mysqlUtilities.restoreDatabaseBackup(self.path.split('/')[-1].rstrip('.sql'),
+ '/home/cyberpanel', 'dummy', 'dummy') == 0:
+ raise BaseException
+ else:
+
+ if self.jobid.destination == 'local':
+ self.localFunction('none', 'none', 1)
+ elif self.jobid.destination[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid.snapshotid)
+
+ if mysqlUtilities.mysqlUtilities.restoreDatabaseBackup(self.jobid.type.split(':')[1].rstrip('.sql'),
+ '/home/cyberpanel', 'dummy', 'dummy') == 0:
+ raise BaseException
try:
- os.remove('/home/cyberpanel/%s.sql' % (self.jobid.type.split(':')[1]))
+ if self.reconstruct == 'remote':
+ os.remove('/home/cyberpanel/%s' % (self.path.split('/')[-1]))
+ else:
+ os.remove('/home/cyberpanel/%s.sql' % (self.jobid.type.split(':')[1]))
except BaseException, msg:
logging.writeToFile(str(msg))
@@ -250,12 +285,18 @@ class IncJobs(multi.Thread):
def restoreEmail(self):
try:
- if self.jobid.destination == 'local':
- self.localFunction('none', 'none', 1)
- elif self.jobid.destination[:4] == 'sftp':
- self.sftpFunction('none', 'none', 1)
+ if self.reconstruct == 'remote':
+ if self.backupDestinations[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid)
else:
- self.awsFunction('restore', '', self.jobid.snapshotid)
+ if self.jobid.destination == 'local':
+ self.localFunction('none', 'none', 1)
+ elif self.jobid.destination[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid.snapshotid)
except BaseException, msg:
logging.statusWriter(self.statusPath, "%s [46][5009]" % (str(msg)), 1)
@@ -264,12 +305,18 @@ class IncJobs(multi.Thread):
def reconstructWithMeta(self):
try:
- if self.jobid.destination == 'local':
- self.localFunction('none', 'none', 1)
- elif self.jobid.destination[:4] == 'sftp':
- self.sftpFunction('none', 'none', 1)
+ if self.reconstruct == 'remote':
+ if self.backupDestinations[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid)
else:
- self.awsFunction('restore', '', self.jobid.snapshotid)
+ if self.jobid.destination == 'local':
+ self.localFunction('none', 'none', 1)
+ elif self.jobid.destination[:4] == 'sftp':
+ self.sftpFunction('none', 'none', 1)
+ else:
+ self.awsFunction('restore', '', self.jobid.snapshotid)
metaPathNew = '/home/%s/meta.xml' % (self.website)
execPath = "nice -n 10 /usr/local/CyberCP/bin/python2 " + virtualHostUtilities.cyberPanel + "/IncBackups/restoreMeta.py"
@@ -277,6 +324,11 @@ class IncJobs(multi.Thread):
result = ProcessUtilities.outputExecutioner(execPath)
logging.statusWriter(self.statusPath, result, 1)
+ try:
+ os.remove(metaPathNew)
+ except:
+ pass
+
except BaseException, msg:
logging.statusWriter(self.statusPath, "%s [46][5009]" % (str(msg)), 1)
return 0
@@ -286,35 +338,67 @@ class IncJobs(multi.Thread):
self.statusPath = self.extraArgs['tempPath']
self.website = self.extraArgs['website']
jobid = self.extraArgs['jobid']
+ self.reconstruct = self.extraArgs['reconstruct']
- self.jobid = JobSnapshots.objects.get(pk=jobid)
+ if self.reconstruct == 'remote':
+ self.jobid = self.extraArgs['jobid']
+ self.backupDestinations = self.extraArgs['backupDestinations']
+ self.passwordFile = self.extraArgs['password']
+ self.path = self.extraArgs['path']
- message = 'Starting restore of %s for %s.' % (self.jobid.snapshotid, self.website)
- logging.statusWriter(self.statusPath, message, 1)
- self.passwordFile = '/home/%s/%s' % (self.website, self.website)
+ if self.path.find('.sql') > -1:
+ message = 'Restoring database..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.restoreDatabase()
+ message = 'Database restored.'
+ logging.statusWriter(self.statusPath, message, 1)
+ elif self.path == '/home/%s' % (self.website):
+ message = 'Restoring data..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.restoreData()
+ message = 'Data restored..'
+ logging.statusWriter(self.statusPath, message, 1)
+ elif self.path.find('vmail') > -1:
+ message = 'Restoring email..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.restoreEmail()
+ message = 'Emails restored.'
+ logging.statusWriter(self.statusPath, message, 1)
+ elif self.path.find('meta.xml') > -1:
+ message = 'Reconstructing with meta..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.reconstructWithMeta()
+ message = 'Reconstructed'
+ logging.statusWriter(self.statusPath, message, 1)
+ else:
+ self.jobid = JobSnapshots.objects.get(pk=jobid)
- ##
+ message = 'Starting restore of %s for %s.' % (self.jobid.snapshotid, self.website)
+ logging.statusWriter(self.statusPath, message, 1)
+ self.passwordFile = '/home/%s/%s' % (self.website, self.website)
- if self.jobid.type[:8] == 'database':
- message = 'Restoring database..'
- logging.statusWriter(self.statusPath, message, 1)
- self.restoreDatabase()
- message = 'Database restored.'
- logging.statusWriter(self.statusPath, message, 1)
- elif self.jobid.type[:4] == 'data':
- self.restoreData()
- elif self.jobid.type[:5] == 'email':
- message = 'Restoring email..'
- logging.statusWriter(self.statusPath, message, 1)
- self.restoreEmail()
- message = 'Emails restored.'
- logging.statusWriter(self.statusPath, message, 1)
- elif self.jobid.type[:4] == 'meta':
- message = 'Reconstructing with meta..'
- logging.statusWriter(self.statusPath, message, 1)
- self.reconstructWithMeta()
- message = 'Reconstructed'
- logging.statusWriter(self.statusPath, message, 1)
+ ##
+
+ if self.jobid.type[:8] == 'database':
+ message = 'Restoring database..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.restoreDatabase()
+ message = 'Database restored.'
+ logging.statusWriter(self.statusPath, message, 1)
+ elif self.jobid.type[:4] == 'data':
+ self.restoreData()
+ elif self.jobid.type[:5] == 'email':
+ message = 'Restoring email..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.restoreEmail()
+ message = 'Emails restored.'
+ logging.statusWriter(self.statusPath, message, 1)
+ elif self.jobid.type[:4] == 'meta':
+ message = 'Reconstructing with meta..'
+ logging.statusWriter(self.statusPath, message, 1)
+ self.reconstructWithMeta()
+ message = 'Reconstructed'
+ logging.statusWriter(self.statusPath, message, 1)
logging.statusWriter(self.statusPath, 'Completed', 1)
except BaseException, msg:
@@ -594,13 +678,13 @@ class IncJobs(multi.Thread):
elif self.backupDestinations[:4] == 'sftp':
remotePath = '/home/backup/%s' % (self.website.domain)
command = 'export PATH=${PATH}:/usr/bin && restic init --repo %s:%s --password-file %s' % (
- self.backupDestinations, remotePath, self.passwordFile)
+ self.backupDestinations, remotePath, self.passwordFile)
result = ProcessUtilities.outputExecutioner(command)
logging.statusWriter(self.statusPath, result, 1)
else:
key, secret = self.getAWSData()
command = 'export AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s && restic -r s3:s3.amazonaws.com/%s init --password-file %s' % (
- key, secret, self.website.domain, self.passwordFile)
+ key, secret, self.website.domain, self.passwordFile)
result = ProcessUtilities.outputExecutioner(command)
logging.statusWriter(self.statusPath, result, 1)
return 1
@@ -639,6 +723,21 @@ class IncJobs(multi.Thread):
command = 'chmod 600 %s' % (self.passwordFile)
ProcessUtilities.executioner(command)
+ SUBJECT = "Backup Repository password for %s" % (self.website)
+ text = """Password: %s
+This is password for your incremental backup repository, please save it in safe place as it will be required when you want to restore backup for this site on remote server.
+"""
+
+ sender = 'cyberpanel@%s' % (self.website.domain)
+ TO = [self.website.adminEmail]
+ message = """\
+From: %s
+To: %s
+Subject: %s
+
+%s
+""" % (sender, ", ".join(TO), SUBJECT, text)
+ mailUtilities.SendEmail(sender, TO, message)
if self.initiateRepo() == 0:
return
diff --git a/IncBackups/restoreMeta.py b/IncBackups/restoreMeta.py
index 858fdfa24..863733514 100644
--- a/IncBackups/restoreMeta.py
+++ b/IncBackups/restoreMeta.py
@@ -127,6 +127,12 @@ class restoreMeta():
else:
logging.statusWriter(statusPath, 'Database: %s successfully created.' % (dbName))
+ mysqlUtilities.mysqlUtilities.changePassword(dbUser, dbPassword, 1)
+ if mysqlUtilities.mysqlUtilities.changePassword(dbUser, dbPassword, 1) == 0:
+ logging.statusWriter(statusPath, 'Failed changing password for database: %s' % (dbName))
+ else:
+ logging.statusWriter(statusPath, 'Password successfully changed for database: %s.' % (dbName))
+
try:
newDB = Databases(website=website, dbName=dbName, dbUser=dbUser)
diff --git a/IncBackups/static/IncBackups/IncBackups.js b/IncBackups/static/IncBackups/IncBackups.js
index 8bc93dbb0..438065c89 100644
--- a/IncBackups/static/IncBackups/IncBackups.js
+++ b/IncBackups/static/IncBackups/IncBackups.js
@@ -759,7 +759,6 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
};
$scope.fetchDetails = function () {
- getBackupStatus();
$scope.populateCurrentRecords();
};
@@ -806,7 +805,12 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
$scope.runningBackup = false;
$scope.fileName = response.data.fileName;
- $scope.status = response.data.status;
+ if(response.data.status === 1){
+ $scope.status = 'Fetching status..'
+ }else{
+ $scope.status = response.data.status;
+ }
+
$timeout(getBackupStatus, 2000);
}
@@ -870,54 +874,7 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
};
- $scope.restore = function (id) {
-
- $scope.cyberpanelLoading = false;
-
-
- url = "/IncrementalBackups/fetchRestorePoints";
-
- var data = {
- id: id,
- websiteToBeBacked: $scope.websiteToBeBacked
- };
-
- var config = {
- headers: {
- 'X-CSRFToken': getCookie('csrftoken')
- }
- };
-
-
- $http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
-
-
- function ListInitialDatas(response) {
- $scope.cyberpanelLoading = true;
- if (response.data.status === 1) {
- $scope.jobs = JSON.parse(response.data.data);
- } else {
- new PNotify({
- title: 'Operation Failed!',
- text: response.data.error_message,
- type: 'error'
- });
- }
-
- }
-
- function cantLoadInitialDatas(response) {
- $scope.cyberpanelLoading = true;
- new PNotify({
- title: 'Operation Failed!',
- text: 'Could not connect to server, please refresh this page',
- type: 'error'
- });
- }
-
- };
-
- $scope.restorePoint = function (id, reconstruct) {
+ $scope.restorePoint = function (id, path) {
$scope.status = '';
@@ -930,7 +887,10 @@ app.controller('restoreRemoteBackupsInc', function ($scope, $http, $timeout) {
var data = {
websiteToBeBacked: $scope.websiteToBeBacked,
jobid: id,
- reconstruct: reconstruct
+ reconstruct: 'remote',
+ path: path,
+ backupDestinations: $scope.backupDestinations,
+ password: $scope.password
};
diff --git a/IncBackups/templates/IncBackups/restoreRemoteBackups.html b/IncBackups/templates/IncBackups/restoreRemoteBackups.html
index 69dd71d69..4a9f75a5a 100755
--- a/IncBackups/templates/IncBackups/restoreRemoteBackups.html
+++ b/IncBackups/templates/IncBackups/restoreRemoteBackups.html
@@ -72,6 +72,19 @@
+
+
+
+
+
+
+
@@ -95,8 +108,7 @@
|
|
- Restore
|
diff --git a/IncBackups/views.py b/IncBackups/views.py
index 8276e02ad..b6a42569d 100644
--- a/IncBackups/views.py
+++ b/IncBackups/views.py
@@ -550,11 +550,21 @@ def restorePoint(request):
tempPath = "/home/cyberpanel/" + str(randint(1000, 9999))
- extraArgs = {}
- extraArgs['website'] = backupDomain
- extraArgs['jobid'] = jobid
- extraArgs['tempPath'] = tempPath
- extraArgs['reconstruct'] = data['reconstruct']
+ if data['reconstruct'] == 'remote':
+ extraArgs = {}
+ extraArgs['website'] = backupDomain
+ extraArgs['jobid'] = jobid
+ extraArgs['tempPath'] = tempPath
+ extraArgs['reconstruct'] = data['reconstruct']
+ extraArgs['backupDestinations'] = data['backupDestinations']
+ extraArgs['password'] = data['password']
+ extraArgs['path'] = data['path']
+ else:
+ extraArgs = {}
+ extraArgs['website'] = backupDomain
+ extraArgs['jobid'] = jobid
+ extraArgs['tempPath'] = tempPath
+ extraArgs['reconstruct'] = data['reconstruct']
startJob = IncJobs('restorePoint', extraArgs)
@@ -718,7 +728,6 @@ def restoreRemoteBackups(request):
websitesName = ACLManager.findAllSites(currentACL, userID)
destinations = []
- destinations.append('local')
path = '/home/cyberpanel/sftp'
diff --git a/WebTerminal/CPWebSocket.py b/WebTerminal/CPWebSocket.py
new file mode 100644
index 000000000..7dd379fe1
--- /dev/null
+++ b/WebTerminal/CPWebSocket.py
@@ -0,0 +1,137 @@
+import os
+import asyncio
+import websockets
+import paramiko
+import json
+import ssl
+
+
+class WebSocketServer():
+
+ def loadPublicKey(self):
+ pubkey = '/root/.ssh/cyberpanel.pub'
+
+ data = open(pubkey, 'r').read()
+
+ authFile = '/root/.ssh/authorized_keys'
+
+ authData = open(authFile, 'r').read()
+
+ checker = 1
+
+ if authData.find(data) > -1:
+ checker = 0
+
+ if checker:
+ writeToFile = open(authFile, 'a')
+ writeToFile.writelines(data)
+ writeToFile.close()
+
+
+ def __init__(self, websocket, path):
+ self.websockets = websocket
+ self.path = path
+ self.sshclient = paramiko.SSHClient()
+ self.sshclient.load_system_host_keys()
+ self.sshclient.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ k = paramiko.RSAKey.from_private_key_file('/root/.ssh/cyberpanel')
+
+ ## Load Public Key
+ self.loadPublicKey()
+
+ self.sshclient.connect('127.0.0.1', 22, username='root', pkey=k)
+ self.shell = self.sshclient.invoke_shell(term='xterm')
+ self.shell.settimeout(0)
+ self.verifyPath = ''
+
+ async def consumer_handler(self):
+ try:
+ async for message in self.websockets:
+ await self.sendData(message)
+ except:
+ print(self.verifyPath)
+ os.remove(self.verifyPath)
+
+ async def producer_handler(self):
+ try:
+ while True:
+ message = await self.recvData()
+ if os.path.exists(self.verifyPath):
+ await self.websockets.send(message)
+ else:
+ await self.websockets.send('Authentication failed.')
+ except:
+ print(self.verifyPath)
+ os.remove(self.verifyPath)
+
+ async def recvData(self):
+ try:
+ print ('recvData')
+ try:
+ while True:
+ if self.shell.recv_ready():
+ return self.shell.recv(9000).decode("utf-8")
+ else:
+ await asyncio.sleep(0.1)
+ continue
+ except:
+ pass
+ except:
+ print(self.verifyPath)
+ os.remove(self.verifyPath)
+
+ async def sendData(self, message):
+ try:
+ print ('sendData')
+ print (str(message))
+ try:
+ data = json.loads(message)
+ if str(message).find('"tp":"init"') > -1:
+ self.verifyPath = str(data['data']['verifyPath'])
+ else:
+ if os.path.exists(self.verifyPath):
+ self.shell.send(str(data['data']))
+ except:
+ pass
+ except:
+ print(self.verifyPath)
+ os.remove(self.verifyPath)
+
+ @staticmethod
+ async def initialize(websocket, path):
+ try:
+ webshell = WebSocketServer(websocket, path)
+
+ consumer_task = asyncio.ensure_future(
+ webshell.consumer_handler())
+ producer_task = asyncio.ensure_future(
+ webshell.producer_handler())
+ done, pending = await asyncio.wait(
+ [consumer_task, producer_task],
+ return_when=asyncio.FIRST_COMPLETED,
+ )
+ for task in pending:
+ task.cancel()
+ except:
+ print(webshell.verifyPath)
+ os.remove(webshell.verifyPath)
+
+
+def main():
+ pidfile = '/usr/local/CyberCP/WebTerminal/pid'
+ machineIP = open('/etc/cyberpanel/machineIP', 'r').read()
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
+ context.load_cert_chain('/usr/local/lscp/conf/cert.pem', '/usr/local/lscp/conf/key.pem')
+ start_server = websockets.serve(WebSocketServer.initialize, machineIP, 5678, ssl=context)
+ asyncio.get_event_loop().run_until_complete(start_server)
+ asyncio.get_event_loop().run_forever()
+
+ writeToFile = open(pidfile, 'r')
+ writeToFile.write(str(os.getpid()))
+ writeToFile.close()
+
+
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/WebTerminal/__init__.py b/WebTerminal/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/WebTerminal/admin.py b/WebTerminal/admin.py
new file mode 100644
index 000000000..13be29d96
--- /dev/null
+++ b/WebTerminal/admin.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib import admin
+
+# Register your models here.
diff --git a/WebTerminal/apps.py b/WebTerminal/apps.py
new file mode 100644
index 000000000..b53c3b4c6
--- /dev/null
+++ b/WebTerminal/apps.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import AppConfig
+
+
+class WebterminalConfig(AppConfig):
+ name = 'WebTerminal'
diff --git a/WebTerminal/cpssh.service b/WebTerminal/cpssh.service
new file mode 100644
index 000000000..5f99bca49
--- /dev/null
+++ b/WebTerminal/cpssh.service
@@ -0,0 +1,12 @@
+[Unit]
+Description = CyberPanel SSH Websocket Daemon
+
+[Service]
+Type=forking
+ExecStart = /usr/local/CyberPanel/p3/bin/python3 /usr/local/CyberCP/WebTerminal/servCTRL.py start
+ExecStop = /usr/local/CyberPanel/p3/bin/python3 /usr/local/CyberCP/WebTerminal/servCTRL.py stop
+Restart = /usr/local/CyberPanel/p3/bin/python3 /usr/local/CyberCP/WebTerminal/servCTRL.py restart
+Restart=on-abnormal
+
+[Install]
+WantedBy=default.target
\ No newline at end of file
diff --git a/WebTerminal/migrations/__init__.py b/WebTerminal/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/WebTerminal/models.py b/WebTerminal/models.py
new file mode 100644
index 000000000..1dfab7604
--- /dev/null
+++ b/WebTerminal/models.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models
+
+# Create your models here.
diff --git a/WebTerminal/requirments.txt b/WebTerminal/requirments.txt
new file mode 100644
index 000000000..d4ec218e5
--- /dev/null
+++ b/WebTerminal/requirments.txt
@@ -0,0 +1,8 @@
+bcrypt==3.1.7
+cffi==1.13.1
+cryptography==2.8
+paramiko==2.6.0
+pycparser==2.19
+PyNaCl==1.3.0
+six==1.12.0
+websockets==8.0.2
\ No newline at end of file
diff --git a/WebTerminal/servCTRL.py b/WebTerminal/servCTRL.py
new file mode 100644
index 000000000..5c65be9ec
--- /dev/null
+++ b/WebTerminal/servCTRL.py
@@ -0,0 +1,51 @@
+import subprocess
+import shlex
+import argparse
+import os
+
+
+
+class servCTRL:
+ pidfile = '/usr/local/CyberCP/WebTerminal/pid'
+
+ def prepareArguments(self):
+
+ parser = argparse.ArgumentParser(description='CyberPanel Policy Control Parser!')
+ parser.add_argument('function', help='Specific a operation to perform!')
+
+ return parser.parse_args()
+
+ def start(self):
+
+ if os.path.exists(servCTRL.pidfile):
+ self.stop()
+
+ command = '/usr/local/CyberPanel/p3/bin/python3 /usr/local/CyberCP/WebTerminal/CPWebSocket.py'
+ subprocess.Popen(shlex.split(command))
+
+ def stop(self):
+ try:
+ path = servCTRL.pidfile
+ command = 'kill -9 %s' % (open(path, 'r').read())
+ subprocess.Popen(shlex.split(command))
+ except:
+ pass
+
+
+def main():
+
+ policy = servCTRL()
+ args = policy.prepareArguments()
+
+ ## Website functions
+
+ if args.function == "start":
+ policy.start()
+ elif args.function == "stop":
+ policy.stop()
+ elif args.function == "restart":
+ policy.stop()
+ policy.start()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/WebTerminal/static/WebTerminal/main.js b/WebTerminal/static/WebTerminal/main.js
new file mode 100755
index 000000000..6b6ea8a2b
--- /dev/null
+++ b/WebTerminal/static/WebTerminal/main.js
@@ -0,0 +1,94 @@
+var charWidth = 6.2;
+var charHeight = 15.2;
+
+/**
+ * for full screen
+ * @returns {{w: number, h: number}}
+ */
+function getTerminalSize() {
+ var width = window.innerWidth;
+ var height = window.innerHeight;
+ return {
+ w: Math.floor(width / charWidth),
+ h: Math.floor(height / charHeight)
+ };
+}
+
+
+
+function openTerminal(options) {
+ if (!$.isEmptyObject($('.terminal')[0])) {
+ alert("Please refresh this page.");
+ return
+ }
+
+ var client = new WSSHClient();
+ var term = new Terminal({cols: 120, rows: 30, screenKeys: true, useStyle: true});
+ term.on('data', function (data) {
+ client.sendClientData(data);
+ });
+ term.open();
+ $('.terminal').detach().appendTo('#term');
+ $("#term").show();
+ term.write('Connecting...' + '\r\n');
+
+ client.connect({
+ onError: function (error) {
+ term.write('Error connecting to backend.\r\n');
+ //term.destroy();
+ },
+ onConnect: function () {
+ client.sendInitData(options);
+ term.write('connection established..\r\n');
+ },
+ onClose: function (e) {
+ term.write("\r\nconnection closed.")
+ //term.destroy();
+ },
+ onData: function (data) {
+ term.write(data);
+ }
+ })
+
+}
+
+function store(options) {
+ window.localStorage.host = options.host;
+ window.localStorage.port = options.port;
+ window.localStorage.username = options.username;
+ window.localStorage.ispwd = options.ispwd;
+ window.localStorage.secret = options.secret
+}
+
+function check() {
+ return validResult["host"] && validResult["port"] && validResult["username"];
+}
+
+function connect() {
+ var remember = $("#remember").is(":checked");
+ var options = {
+ host: $("#host").val(),
+ port: $("#port").val(),
+ username: $("#username").val(),
+ ispwd: $("input[name=ispwd]:checked").val(),
+ secret: $("#secret").val(),
+ verifyPath: $("#verifyPath").text()
+ }
+ console.debug(options);
+ if (remember) {
+ store(options)
+ }
+ // if (check()) {
+ // openTerminal(options)
+ // } else {
+ // for (var key in validResult) {
+ // if (!validResult[key]) {
+ // alert(errorMsg[key]);
+ // break;
+ // }
+ // }
+ // }
+ openTerminal(options)
+}
+
+connect();
\ No newline at end of file
diff --git a/WebTerminal/static/WebTerminal/term.js b/WebTerminal/static/WebTerminal/term.js
new file mode 100755
index 000000000..68bc07343
--- /dev/null
+++ b/WebTerminal/static/WebTerminal/term.js
@@ -0,0 +1,5977 @@
+/**
+ * term.js - an xterm emulator
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * https://github.com/chjj/term.js
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * Originally forked from (with the author's permission):
+ * Fabrice Bellard's javascript vt100 for jslinux:
+ * http://bellard.org/jslinux/
+ * Copyright (c) 2011 Fabrice Bellard
+ * The original design remains. The terminal itself
+ * has been extended to include xterm CSI codes, among
+ * other features.
+ */
+
+;(function() {
+
+/**
+ * Terminal Emulation References:
+ * http://vt100.net/
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * http://invisible-island.net/vttest/
+ * http://www.inwap.com/pdp10/ansicode.txt
+ * http://linux.die.net/man/4/console_codes
+ * http://linux.die.net/man/7/urxvt
+ */
+
+'use strict';
+
+/**
+ * Shared
+ */
+
+var window = this
+ , document = this.document;
+
+/**
+ * EventEmitter
+ */
+
+function EventEmitter() {
+ this._events = this._events || {};
+}
+
+EventEmitter.prototype.addListener = function(type, listener) {
+ this._events[type] = this._events[type] || [];
+ this._events[type].push(listener);
+};
+
+EventEmitter.prototype.on = EventEmitter.prototype.addListener;
+
+EventEmitter.prototype.removeListener = function(type, listener) {
+ if (!this._events[type]) return;
+
+ var obj = this._events[type]
+ , i = obj.length;
+
+ while (i--) {
+ if (obj[i] === listener || obj[i].listener === listener) {
+ obj.splice(i, 1);
+ return;
+ }
+ }
+};
+
+EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
+
+EventEmitter.prototype.removeAllListeners = function(type) {
+ if (this._events[type]) delete this._events[type];
+};
+
+EventEmitter.prototype.once = function(type, listener) {
+ function on() {
+ var args = Array.prototype.slice.call(arguments);
+ this.removeListener(type, on);
+ return listener.apply(this, args);
+ }
+ on.listener = listener;
+ return this.on(type, on);
+};
+
+EventEmitter.prototype.emit = function(type) {
+ if (!this._events[type]) return;
+
+ var args = Array.prototype.slice.call(arguments, 1)
+ , obj = this._events[type]
+ , l = obj.length
+ , i = 0;
+
+ for (; i < l; i++) {
+ obj[i].apply(this, args);
+ }
+};
+
+EventEmitter.prototype.listeners = function(type) {
+ return this._events[type] = this._events[type] || [];
+};
+
+/**
+ * Stream
+ */
+
+function Stream() {
+ EventEmitter.call(this);
+}
+
+inherits(Stream, EventEmitter);
+
+Stream.prototype.pipe = function(dest, options) {
+ var src = this
+ , ondata
+ , onerror
+ , onend;
+
+ function unbind() {
+ src.removeListener('data', ondata);
+ src.removeListener('error', onerror);
+ src.removeListener('end', onend);
+ dest.removeListener('error', onerror);
+ dest.removeListener('close', unbind);
+ }
+
+ src.on('data', ondata = function(data) {
+ dest.write(data);
+ });
+
+ src.on('error', onerror = function(err) {
+ unbind();
+ if (!this.listeners('error').length) {
+ throw err;
+ }
+ });
+
+ src.on('end', onend = function() {
+ dest.end();
+ unbind();
+ });
+
+ dest.on('error', onerror);
+ dest.on('close', unbind);
+
+ dest.emit('pipe', src);
+
+ return dest;
+};
+
+/**
+ * States
+ */
+
+var normal = 0
+ , escaped = 1
+ , csi = 2
+ , osc = 3
+ , charset = 4
+ , dcs = 5
+ , ignore = 6
+ , UDK = { type: 'udk' };
+
+/**
+ * Terminal
+ */
+
+function Terminal(options) {
+ var self = this;
+
+ if (!(this instanceof Terminal)) {
+ return new Terminal(arguments[0], arguments[1], arguments[2]);
+ }
+
+ Stream.call(this);
+
+ if (typeof options === 'number') {
+ options = {
+ cols: arguments[0],
+ rows: arguments[1],
+ handler: arguments[2]
+ };
+ }
+
+ options = options || {};
+
+ each(keys(Terminal.defaults), function(key) {
+ if (options[key] == null) {
+ options[key] = Terminal.options[key];
+ // Legacy:
+ if (Terminal[key] !== Terminal.defaults[key]) {
+ options[key] = Terminal[key];
+ }
+ }
+ self[key] = options[key];
+ });
+
+ if (options.colors.length === 8) {
+ options.colors = options.colors.concat(Terminal._colors.slice(8));
+ } else if (options.colors.length === 16) {
+ options.colors = options.colors.concat(Terminal._colors.slice(16));
+ } else if (options.colors.length === 10) {
+ options.colors = options.colors.slice(0, -2).concat(
+ Terminal._colors.slice(8, -2), options.colors.slice(-2));
+ } else if (options.colors.length === 18) {
+ options.colors = options.colors.slice(0, -2).concat(
+ Terminal._colors.slice(16, -2), options.colors.slice(-2));
+ }
+ this.colors = options.colors;
+
+ this.options = options;
+
+ // this.context = options.context || window;
+ // this.document = options.document || document;
+ this.parent = options.body || options.parent
+ || (document ? document.getElementsByTagName('body')[0] : null);
+
+ this.cols = options.cols || options.geometry[0];
+ this.rows = options.rows || options.geometry[1];
+
+ // Act as though we are a node TTY stream:
+ this.setRawMode;
+ this.isTTY = true;
+ this.isRaw = true;
+ this.columns = this.cols;
+ this.rows = this.rows;
+
+ if (options.handler) {
+ this.on('data', options.handler);
+ }
+
+ this.ybase = 0;
+ this.ydisp = 0;
+ this.x = 0;
+ this.y = 0;
+ this.cursorState = 0;
+ this.cursorHidden = false;
+ this.convertEol;
+ this.state = 0;
+ this.queue = '';
+ this.scrollTop = 0;
+ this.scrollBottom = this.rows - 1;
+
+ // modes
+ this.applicationKeypad = false;
+ this.applicationCursor = false;
+ this.originMode = false;
+ this.insertMode = false;
+ this.wraparoundMode = false;
+ this.normal = null;
+
+ // select modes
+ this.prefixMode = false;
+ this.selectMode = false;
+ this.visualMode = false;
+ this.searchMode = false;
+ this.searchDown;
+ this.entry = '';
+ this.entryPrefix = 'Search: ';
+ this._real;
+ this._selected;
+ this._textarea;
+
+ // charset
+ this.charset = null;
+ this.gcharset = null;
+ this.glevel = 0;
+ this.charsets = [null];
+
+ // mouse properties
+ this.decLocator;
+ this.x10Mouse;
+ this.vt200Mouse;
+ this.vt300Mouse;
+ this.normalMouse;
+ this.mouseEvents;
+ this.sendFocus;
+ this.utfMouse;
+ this.sgrMouse;
+ this.urxvtMouse;
+
+ // misc
+ this.element;
+ this.children;
+ this.refreshStart;
+ this.refreshEnd;
+ this.savedX;
+ this.savedY;
+ this.savedCols;
+
+ // stream
+ this.readable = true;
+ this.writable = true;
+
+ this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
+ this.curAttr = this.defAttr;
+
+ this.params = [];
+ this.currentParam = 0;
+ this.prefix = '';
+ this.postfix = '';
+
+ this.lines = [];
+ var i = this.rows;
+ while (i--) {
+ this.lines.push(this.blankLine());
+ }
+
+ this.tabs;
+ this.setupStops();
+}
+
+inherits(Terminal, Stream);
+
+/**
+ * Colors
+ */
+
+// Colors 0-15
+Terminal.tangoColors = [
+ // dark:
+ '#2e3436',
+ '#cc0000',
+ '#4e9a06',
+ '#c4a000',
+ '#3465a4',
+ '#75507b',
+ '#06989a',
+ '#d3d7cf',
+ // bright:
+ '#555753',
+ '#ef2929',
+ '#8ae234',
+ '#fce94f',
+ '#729fcf',
+ '#ad7fa8',
+ '#34e2e2',
+ '#eeeeec'
+];
+
+Terminal.xtermColors = [
+ // dark:
+ '#000000', // black
+ '#cd0000', // red3
+ '#00cd00', // green3
+ '#cdcd00', // yellow3
+ '#0000ee', // blue2
+ '#cd00cd', // magenta3
+ '#00cdcd', // cyan3
+ '#e5e5e5', // gray90
+ // bright:
+ '#7f7f7f', // gray50
+ '#ff0000', // red
+ '#00ff00', // green
+ '#ffff00', // yellow
+ '#5c5cff', // rgb:5c/5c/ff
+ '#ff00ff', // magenta
+ '#00ffff', // cyan
+ '#ffffff' // white
+];
+
+// Colors 0-15 + 16-255
+// Much thanks to TooTallNate for writing this.
+Terminal.colors = (function() {
+ var colors = Terminal.tangoColors.slice()
+ , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]
+ , i;
+
+ // 16-231
+ i = 0;
+ for (; i < 216; i++) {
+ out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);
+ }
+
+ // 232-255 (grey)
+ i = 0;
+ for (; i < 24; i++) {
+ r = 8 + i * 10;
+ out(r, r, r);
+ }
+
+ function out(r, g, b) {
+ colors.push('#' + hex(r) + hex(g) + hex(b));
+ }
+
+ function hex(c) {
+ c = c.toString(16);
+ return c.length < 2 ? '0' + c : c;
+ }
+
+ return colors;
+})();
+
+// Default BG/FG
+Terminal.colors[256] = '#000000';
+Terminal.colors[257] = '#f0f0f0';
+
+Terminal._colors = Terminal.colors.slice();
+
+Terminal.vcolors = (function() {
+ var out = []
+ , colors = Terminal.colors
+ , i = 0
+ , color;
+
+ for (; i < 256; i++) {
+ color = parseInt(colors[i].substring(1), 16);
+ out.push([
+ (color >> 16) & 0xff,
+ (color >> 8) & 0xff,
+ color & 0xff
+ ]);
+ }
+
+ return out;
+})();
+
+/**
+ * Options
+ */
+
+Terminal.defaults = {
+ colors: Terminal.colors,
+ convertEol: false,
+ termName: 'xterm',
+ geometry: [80, 24],
+ cursorBlink: true,
+ visualBell: false,
+ popOnBell: false,
+ scrollback: 1000,
+ screenKeys: false,
+ debug: false,
+ useStyle: false
+ // programFeatures: false,
+ // focusKeys: false,
+};
+
+Terminal.options = {};
+
+each(keys(Terminal.defaults), function(key) {
+ Terminal[key] = Terminal.defaults[key];
+ Terminal.options[key] = Terminal.defaults[key];
+});
+
+/**
+ * Focused Terminal
+ */
+
+Terminal.focus = null;
+
+Terminal.prototype.focus = function() {
+ if (Terminal.focus === this) return;
+
+ if (Terminal.focus) {
+ Terminal.focus.blur();
+ }
+
+ if (this.sendFocus) this.send('\x1b[I');
+ this.showCursor();
+
+ // try {
+ // this.element.focus();
+ // } catch (e) {
+ // ;
+ // }
+
+ // this.emit('focus');
+
+ Terminal.focus = this;
+};
+
+Terminal.prototype.blur = function() {
+ if (Terminal.focus !== this) return;
+
+ this.cursorState = 0;
+ this.refresh(this.y, this.y);
+ if (this.sendFocus) this.send('\x1b[O');
+
+ // try {
+ // this.element.blur();
+ // } catch (e) {
+ // ;
+ // }
+
+ // this.emit('blur');
+
+ Terminal.focus = null;
+};
+
+/**
+ * Initialize global behavior
+ */
+
+Terminal.prototype.initGlobal = function() {
+ var document = this.document;
+
+ Terminal._boundDocs = Terminal._boundDocs || [];
+ if (~indexOf(Terminal._boundDocs, document)) {
+ return;
+ }
+ Terminal._boundDocs.push(document);
+
+ Terminal.bindPaste(document);
+
+ Terminal.bindKeys(document);
+
+ Terminal.bindCopy(document);
+
+ if (this.isMobile) {
+ this.fixMobile(document);
+ }
+
+ if (this.useStyle) {
+ Terminal.insertStyle(document, this.colors[256], this.colors[257]);
+ }
+};
+
+/**
+ * Bind to paste event
+ */
+
+Terminal.bindPaste = function(document) {
+ // This seems to work well for ctrl-V and middle-click,
+ // even without the contentEditable workaround.
+ var window = document.defaultView;
+ on(window, 'paste', function(ev) {
+ var term = Terminal.focus;
+ if (!term) return;
+ if (ev.clipboardData) {
+ term.send(ev.clipboardData.getData('text/plain'));
+ } else if (term.context.clipboardData) {
+ term.send(term.context.clipboardData.getData('Text'));
+ }
+ // Not necessary. Do it anyway for good measure.
+ term.element.contentEditable = 'inherit';
+ return cancel(ev);
+ });
+};
+
+/**
+ * Global Events for key handling
+ */
+
+Terminal.bindKeys = function(document) {
+ // We should only need to check `target === body` below,
+ // but we can check everything for good measure.
+ on(document, 'keydown', function(ev) {
+ if (!Terminal.focus) return;
+ var target = ev.target || ev.srcElement;
+ if (!target) return;
+ if (target === Terminal.focus.element
+ || target === Terminal.focus.context
+ || target === Terminal.focus.document
+ || target === Terminal.focus.body
+ || target === Terminal._textarea
+ || target === Terminal.focus.parent) {
+ return Terminal.focus.keyDown(ev);
+ }
+ }, true);
+
+ on(document, 'keypress', function(ev) {
+ if (!Terminal.focus) return;
+ var target = ev.target || ev.srcElement;
+ if (!target) return;
+ if (target === Terminal.focus.element
+ || target === Terminal.focus.context
+ || target === Terminal.focus.document
+ || target === Terminal.focus.body
+ || target === Terminal._textarea
+ || target === Terminal.focus.parent) {
+ return Terminal.focus.keyPress(ev);
+ }
+ }, true);
+
+ // If we click somewhere other than a
+ // terminal, unfocus the terminal.
+ on(document, 'mousedown', function(ev) {
+ if (!Terminal.focus) return;
+
+ var el = ev.target || ev.srcElement;
+ if (!el) return;
+
+ do {
+ if (el === Terminal.focus.element) return;
+ } while (el = el.parentNode);
+
+ Terminal.focus.blur();
+ });
+};
+
+/**
+ * Copy Selection w/ Ctrl-C (Select Mode)
+ */
+
+Terminal.bindCopy = function(document) {
+ var window = document.defaultView;
+
+ // if (!('onbeforecopy' in document)) {
+ // // Copies to *only* the clipboard.
+ // on(window, 'copy', function fn(ev) {
+ // var term = Terminal.focus;
+ // if (!term) return;
+ // if (!term._selected) return;
+ // var text = term.grabText(
+ // term._selected.x1, term._selected.x2,
+ // term._selected.y1, term._selected.y2);
+ // term.emit('copy', text);
+ // ev.clipboardData.setData('text/plain', text);
+ // });
+ // return;
+ // }
+
+ // Copies to primary selection *and* clipboard.
+ // NOTE: This may work better on capture phase,
+ // or using the `beforecopy` event.
+ on(window, 'copy', function(ev) {
+ var term = Terminal.focus;
+ if (!term) return;
+ if (!term._selected) return;
+ var textarea = term.getCopyTextarea();
+ var text = term.grabText(
+ term._selected.x1, term._selected.x2,
+ term._selected.y1, term._selected.y2);
+ term.emit('copy', text);
+ textarea.focus();
+ textarea.textContent = text;
+ textarea.value = text;
+ textarea.setSelectionRange(0, text.length);
+ setTimeout(function() {
+ term.element.focus();
+ term.focus();
+ }, 1);
+ });
+};
+
+/**
+ * Fix Mobile
+ */
+
+Terminal.prototype.fixMobile = function(document) {
+ var self = this;
+
+ var textarea = document.createElement('textarea');
+ textarea.style.position = 'absolute';
+ textarea.style.left = '-32000px';
+ textarea.style.top = '-32000px';
+ textarea.style.width = '0px';
+ textarea.style.height = '0px';
+ textarea.style.opacity = '0';
+ textarea.style.backgroundColor = 'transparent';
+ textarea.style.borderStyle = 'none';
+ textarea.style.outlineStyle = 'none';
+ textarea.autocapitalize = 'none';
+ textarea.autocorrect = 'off';
+
+ document.getElementsByTagName('body')[0].appendChild(textarea);
+
+ Terminal._textarea = textarea;
+
+ setTimeout(function() {
+ textarea.focus();
+ }, 1000);
+
+ if (this.isAndroid) {
+ on(textarea, 'change', function() {
+ var value = textarea.textContent || textarea.value;
+ textarea.value = '';
+ textarea.textContent = '';
+ self.send(value + '\r');
+ });
+ }
+};
+
+/**
+ * Insert a default style
+ */
+
+Terminal.insertStyle = function(document, bg, fg) {
+ var style = document.getElementById('term-style');
+ if (style) return;
+
+ var head = document.getElementsByTagName('head')[0];
+ if (!head) return;
+
+ var style = document.createElement('style');
+ style.id = 'term-style';
+
+ // textContent doesn't work well with IE for