2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2020-08-15 22:54:32 -07:00
initialize ,
uninitialize ,
getConfig ,
getLogs ,
2015-07-20 00:09:47 -07:00
2020-08-15 22:54:32 -07:00
reboot ,
isRebootRequired ,
2015-10-27 16:00:31 -07:00
2020-08-15 22:54:32 -07:00
onActivated ,
2018-01-29 15:47:26 -08:00
2020-08-15 23:17:29 -07:00
setupDnsAndCert ,
2020-08-15 22:54:32 -07:00
prepareDashboardDomain ,
setDashboardDomain ,
updateDashboardDomain ,
renewCerts ,
2021-02-24 18:42:39 -08:00
syncDnsRecords ,
2018-11-11 09:29:11 -08:00
2020-08-15 22:54:32 -07:00
runSystemChecks
2015-09-28 23:10:09 -07:00
} ;
2015-07-20 00:09:47 -07:00
2021-01-21 11:31:35 -08:00
const apps = require ( './apps.js' ) ,
2020-02-14 12:20:15 +01:00
appstore = require ( './appstore.js' ) ,
2019-03-18 19:13:44 -07:00
assert = require ( 'assert' ) ,
2015-07-20 00:09:47 -07:00
async = require ( 'async' ) ,
2019-08-03 13:59:11 -07:00
auditSource = require ( './auditsource.js' ) ,
2019-02-28 16:46:30 -08:00
backups = require ( './backups.js' ) ,
2019-10-22 14:06:19 -07:00
BoxError = require ( './boxerror.js' ) ,
2020-10-18 10:15:36 -07:00
branding = require ( './branding.js' ) ,
2018-12-14 09:57:28 -08:00
constants = require ( './constants.js' ) ,
2017-01-09 11:00:09 -08:00
cron = require ( './cron.js' ) ,
2015-07-20 00:09:47 -07:00
debug = require ( 'debug' ) ( 'box:cloudron' ) ,
2018-11-11 09:29:11 -08:00
domains = require ( './domains.js' ) ,
2019-02-04 20:24:28 -08:00
eventlog = require ( './eventlog.js' ) ,
2018-11-25 17:02:01 +01:00
fs = require ( 'fs' ) ,
2019-02-04 14:49:51 -08:00
mail = require ( './mail.js' ) ,
2019-02-06 15:47:56 +01:00
notifications = require ( './notifications.js' ) ,
2015-07-20 00:09:47 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2017-01-07 23:33:20 -08:00
platform = require ( './platform.js' ) ,
2018-01-30 12:23:27 -08:00
reverseProxy = require ( './reverseproxy.js' ) ,
2019-08-03 13:59:11 -07:00
safe = require ( 'safetydance' ) ,
2021-01-21 11:31:35 -08:00
services = require ( './services.js' ) ,
2015-07-20 00:09:47 -07:00
settings = require ( './settings.js' ) ,
shell = require ( './shell.js' ) ,
2017-04-18 15:15:35 -07:00
spawn = require ( 'child_process' ) . spawn ,
split = require ( 'split' ) ,
2020-08-15 23:17:29 -07:00
sysinfo = require ( './sysinfo.js' ) ,
2018-12-10 20:20:53 -08:00
tasks = require ( './tasks.js' ) ,
2019-10-22 14:06:19 -07:00
users = require ( './users.js' ) ;
2015-07-20 00:09:47 -07:00
2018-07-31 11:35:23 -07:00
var REBOOT _CMD = path . join ( _ _dirname , 'scripts/reboot.sh' ) ;
2015-07-20 00:09:47 -07:00
2019-10-22 14:06:19 -07:00
const NOOP _CALLBACK = function ( error ) { if ( error ) debug ( error ) ; } ;
2015-07-20 00:09:47 -07:00
function initialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-11-10 18:21:15 -08:00
runStartupTasks ( ) ;
2019-05-08 15:24:37 -07:00
2019-08-03 13:59:11 -07:00
notifyUpdate ( callback ) ;
2015-07-20 00:09:47 -07:00
}
function uninitialize ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2017-01-09 09:24:32 -08:00
async . series ( [
2018-09-26 12:44:38 -07:00
cron . stopJobs ,
2020-08-06 22:04:46 -07:00
platform . stopAllTasks
2017-01-09 09:24:32 -08:00
] , callback ) ;
2015-07-20 00:09:47 -07:00
}
2021-02-24 14:56:09 -08:00
function onActivated ( options , callback ) {
2021-02-24 15:03:49 -08:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-11-10 18:21:15 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-11-22 21:31:30 -08:00
// Starting the platform after a user is available means:
// 1. mail bounces can now be sent to the cloudron owner
// 2. the restore code path can run without sudo (since mail/ is non-root)
2018-11-10 18:21:15 -08:00
async . series ( [
2021-02-24 14:56:09 -08:00
platform . start . bind ( null , options ) ,
2020-07-28 17:09:28 +02:00
cron . startJobs ,
2020-10-06 18:13:12 -07:00
function checkBackupConfiguration ( done ) {
2020-07-28 17:09:28 +02:00
backups . checkConfiguration ( function ( error , message ) {
2020-10-06 18:13:12 -07:00
if ( error ) return done ( error ) ;
notifications . alert ( notifications . ALERT _BACKUP _CONFIG , 'Backup configuration is unsafe' , message , done ) ;
2020-07-28 17:09:28 +02:00
} ) ;
2020-10-06 18:13:12 -07:00
} ,
// disable responding to api calls via IP to not leak domain info. this is carefully placed as the last item, so it buys
// the UI some time to query the dashboard domain in the restore code path
( done ) => setTimeout ( ( ) => reverseProxy . writeDefaultConfig ( { activated : true } , done ) , 30000 )
2018-11-10 18:21:15 -08:00
] , callback ) ;
}
2019-08-03 13:59:11 -07:00
function notifyUpdate ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
const version = safe . fs . readFileSync ( paths . VERSION _FILE , 'utf8' ) ;
if ( version === constants . VERSION ) return callback ( ) ;
2019-10-14 09:30:20 -07:00
eventlog . add ( eventlog . ACTION _UPDATE _FINISH , auditSource . CRON , { errorMessage : '' , oldVersion : version || 'dev' , newVersion : constants . VERSION } , function ( error ) {
2019-10-22 14:06:19 -07:00
if ( error ) return callback ( error ) ;
2019-08-04 08:59:46 -07:00
2019-09-05 11:29:46 -07:00
tasks . setCompletedByType ( tasks . TASK _UPDATE , { error : null } , function ( error ) {
2019-10-22 20:12:44 -07:00
if ( error && error . reason !== BoxError . NOT _FOUND ) return callback ( error ) ; // when hotfixing, task may not exist
2019-08-03 13:59:11 -07:00
2019-08-04 08:59:46 -07:00
safe . fs . writeFileSync ( paths . VERSION _FILE , constants . VERSION , 'utf8' ) ;
2019-08-03 13:59:11 -07:00
2019-08-04 08:59:46 -07:00
callback ( ) ;
} ) ;
2019-08-03 13:59:11 -07:00
} ) ;
}
2018-11-10 18:21:15 -08:00
// each of these tasks can fail. we will add some routes to fix/re-run them
function runStartupTasks ( ) {
2020-09-02 17:32:31 -07:00
const tasks = [
// stop all the systemd tasks
platform . stopAllTasks ,
2020-08-06 22:04:46 -07:00
2020-09-02 17:32:31 -07:00
// this configures collectd to collect backup storage metrics if filesystem is used. This is also triggerd when the settings change with the rest api
function ( callback ) {
settings . getBackupConfig ( function ( error , backupConfig ) {
if ( error ) return callback ( error ) ;
2020-03-06 17:32:29 -08:00
2020-09-02 17:32:31 -07:00
backups . configureCollectd ( backupConfig , callback ) ;
} ) ;
} ,
2018-11-11 08:19:24 -08:00
2020-09-02 17:32:31 -07:00
// always generate webadmin config since we have no versioning mechanism for the ejs
function ( callback ) {
if ( ! settings . adminDomain ( ) ) return callback ( ) ;
2020-09-01 15:35:34 -07:00
2020-09-23 15:45:04 -07:00
reverseProxy . writeDashboardConfig ( settings . adminDomain ( ) , callback ) ;
2020-09-02 17:32:31 -07:00
} ,
// check activation state and start the platform
function ( callback ) {
users . isActivated ( function ( error , activated ) {
if ( error ) return callback ( error ) ;
// configure nginx to be reachable by IP when not activated. for the moment, the IP based redirect exists even after domain is setup
// just in case user forgot or some network error happenned in the middle (then browser refresh takes you to activation page)
// we remove the config as a simple security measure to not expose IP <-> domain
if ( ! activated ) {
debug ( 'runStartupTasks: not activated. generating IP based redirection config' ) ;
2020-09-23 22:13:02 -07:00
return reverseProxy . writeDefaultConfig ( { activated : false } , callback ) ;
2020-09-02 17:32:31 -07:00
}
2021-02-24 14:56:09 -08:00
onActivated ( { } , callback ) ;
2020-09-02 17:32:31 -07:00
} ) ;
2020-08-13 14:00:55 -07:00
}
2020-09-02 17:32:31 -07:00
] ;
2017-11-22 21:31:30 -08:00
2020-09-02 17:32:31 -07:00
// we used to run tasks in parallel but simultaneous nginx reloads was causing issues
async . series ( async . reflectAll ( tasks ) , function ( error , results ) {
results . forEach ( ( result , idx ) => {
if ( result . error ) debug ( ` Startup task at index ${ idx } failed: ${ result . error . message } ` ) ;
} ) ;
2017-11-22 21:31:30 -08:00
} ) ;
}
2015-07-20 00:09:47 -07:00
function getConfig ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2018-08-03 18:25:43 -07:00
settings . getAll ( function ( error , allSettings ) {
2019-10-22 14:06:19 -07:00
if ( error ) return callback ( error ) ;
2018-01-02 13:05:30 -08:00
2018-06-28 17:40:57 -07:00
// be picky about what we send out here since this is sent for 'normal' users as well
2018-06-28 17:18:15 -07:00
callback ( null , {
2019-07-26 10:49:29 -07:00
apiServerOrigin : settings . apiServerOrigin ( ) ,
webServerOrigin : settings . webServerOrigin ( ) ,
adminDomain : settings . adminDomain ( ) ,
adminFqdn : settings . adminFqdn ( ) ,
mailFqdn : settings . mailFqdn ( ) ,
2019-07-25 14:40:52 -07:00
version : constants . VERSION ,
2019-07-26 10:49:29 -07:00
isDemo : settings . isDemo ( ) ,
2019-05-07 09:34:23 -07:00
cloudronName : allSettings [ settings . CLOUDRON _NAME _KEY ] ,
2020-10-18 10:15:36 -07:00
footer : branding . renderFooter ( allSettings [ settings . FOOTER _KEY ] || constants . FOOTER ) ,
2020-07-09 17:18:06 -07:00
features : appstore . getFeatures ( ) ,
2020-07-10 10:00:03 -07:00
profileLocked : allSettings [ settings . DIRECTORY _CONFIG _KEY ] . lockUserProfiles ,
mandatory2FA : allSettings [ settings . DIRECTORY _CONFIG _KEY ] . mandatory2FA
2015-07-20 00:09:47 -07:00
} ) ;
} ) ;
}
function reboot ( callback ) {
2020-03-06 00:39:37 -08:00
notifications . alert ( notifications . ALERT _REBOOT , 'Reboot Required' , '' , function ( error ) {
2020-08-02 11:43:18 -07:00
if ( error ) debug ( 'reboot: failed to clear reboot notification.' , error ) ;
2020-03-06 00:39:37 -08:00
shell . sudo ( 'reboot' , [ REBOOT _CMD ] , { } , callback ) ;
} ) ;
2015-07-20 00:09:47 -07:00
}
2018-11-25 17:02:01 +01:00
function isRebootRequired ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
// https://serverfault.com/questions/92932/how-does-ubuntu-keep-track-of-the-system-restart-required-flag-in-motd
callback ( null , fs . existsSync ( '/var/run/reboot-required' ) ) ;
}
2019-02-06 15:47:56 +01:00
// called from cron.js
2019-12-12 20:41:03 -08:00
function runSystemChecks ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
2019-02-06 15:47:56 +01:00
async . parallel ( [
2019-02-19 09:19:56 -08:00
checkMailStatus ,
checkRebootRequired
2019-12-12 20:41:03 -08:00
] , callback ) ;
2019-02-06 15:47:56 +01:00
}
2019-02-06 14:58:45 -08:00
function checkMailStatus ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'checking mail status' ) ;
2019-02-28 16:46:30 -08:00
mail . checkConfiguration ( function ( error , message ) {
2019-02-06 14:58:45 -08:00
if ( error ) return callback ( error ) ;
2019-03-07 14:49:38 -08:00
notifications . alert ( notifications . ALERT _MAIL _STATUS , 'Email is not configured properly' , message , callback ) ;
2019-02-06 14:58:45 -08:00
} ) ;
}
2019-02-19 09:19:56 -08:00
function checkRebootRequired ( callback ) {
assert . strictEqual ( typeof callback , 'function' ) ;
debug ( 'checking if reboot required' ) ;
isRebootRequired ( function ( error , rebootRequired ) {
if ( error ) return callback ( error ) ;
2020-03-05 21:01:15 -08:00
notifications . alert ( notifications . ALERT _REBOOT , 'Reboot Required' , rebootRequired ? 'To finish ubuntu security updates, a reboot is necessary.' : '' , callback ) ;
2019-02-19 09:19:56 -08:00
} ) ;
}
2018-06-11 20:09:38 +02:00
function getLogs ( unit , options , callback ) {
assert . strictEqual ( typeof unit , 'string' ) ;
2017-04-18 20:32:57 -07:00
assert ( options && typeof options === 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-01-08 12:10:53 -08:00
assert . strictEqual ( typeof options . lines , 'number' ) ;
assert . strictEqual ( typeof options . format , 'string' ) ;
assert . strictEqual ( typeof options . follow , 'boolean' ) ;
2017-04-18 15:15:35 -07:00
2019-01-08 12:10:53 -08:00
var lines = options . lines === - 1 ? '+1' : options . lines ,
format = options . format || 'json' ,
follow = options . follow ;
2017-04-18 15:15:35 -07:00
2018-06-11 20:09:38 +02:00
debug ( 'Getting logs for %s as %s' , unit , format ) ;
2018-11-16 13:05:11 +01:00
let args = [ '--lines=' + lines ] ;
if ( follow ) args . push ( '--follow' ) ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
// need to handle box.log without subdir
if ( unit === 'box' ) args . push ( path . join ( paths . LOG _DIR , 'box.log' ) ) ;
2019-03-01 15:45:44 -08:00
else if ( unit . startsWith ( 'crash-' ) ) args . push ( path . join ( paths . CRASH _LOG _DIR , unit . slice ( 6 ) + '.log' ) ) ;
2019-10-22 14:06:19 -07:00
else return callback ( new BoxError ( BoxError . BAD _FIELD , 'No such unit' , { field : 'unit' } ) ) ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
var cp = spawn ( '/usr/bin/tail' , args ) ;
2017-04-18 20:32:57 -07:00
2018-11-16 13:05:11 +01:00
var transformStream = split ( function mapper ( line ) {
if ( format !== 'json' ) return line + '\n' ;
2017-04-18 15:15:35 -07:00
2018-11-16 13:05:11 +01:00
var data = line . split ( ' ' ) ; // logs are <ISOtimestamp> <msg>
var timestamp = ( new Date ( data [ 0 ] ) ) . getTime ( ) ;
if ( isNaN ( timestamp ) ) timestamp = 0 ;
return JSON . stringify ( {
realtimeTimestamp : timestamp * 1000 ,
message : line . slice ( data [ 0 ] . length + 1 ) ,
source : unit
} ) + '\n' ;
} ) ;
2017-04-18 15:15:35 -07:00
transformStream . close = cp . kill . bind ( cp , 'SIGKILL' ) ; // closing stream kills the child process
cp . stdout . pipe ( transformStream ) ;
return callback ( null , transformStream ) ;
}
2018-11-11 09:29:11 -08:00
2018-12-15 15:27:16 -08:00
function prepareDashboardDomain ( domain , auditSource , callback ) {
2018-12-14 09:57:28 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-15 15:27:16 -08:00
debug ( ` prepareDashboardDomain: ${ domain } ` ) ;
2018-12-14 09:57:28 -08:00
2020-02-19 10:45:55 -08:00
if ( settings . isDemo ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Not allowed in demo mode' ) ) ;
2019-03-18 19:13:44 -07:00
domains . get ( domain , function ( error , domainObject ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2019-03-18 19:13:44 -07:00
const fqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
apps . getAll ( function ( error , result ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-03-18 19:13:44 -07:00
const conflict = result . filter ( app => app . fqdn === fqdn ) ;
2019-10-22 14:06:19 -07:00
if ( conflict . length ) return callback ( new BoxError ( BoxError . BAD _STATE , 'Dashboard location conflicts with an existing app' ) ) ;
2019-03-18 19:13:44 -07:00
2020-08-24 12:57:48 -07:00
tasks . add ( tasks . TASK _SETUP _DNS _AND _CERT , [ constants . ADMIN _LOCATION , domain , auditSource ] , function ( error , taskId ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-27 22:39:59 -07:00
tasks . startTask ( taskId , { } , NOOP _CALLBACK ) ;
callback ( null , taskId ) ;
} ) ;
2019-03-18 19:13:44 -07:00
} ) ;
} ) ;
2018-12-14 09:57:28 -08:00
}
2019-02-26 19:43:18 -08:00
// call this only pre activation since it won't start mail server
2019-02-04 20:24:28 -08:00
function setDashboardDomain ( domain , auditSource , callback ) {
2018-12-07 16:15:21 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
2019-02-04 20:24:28 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2018-12-07 16:15:21 -08:00
assert . strictEqual ( typeof callback , 'function' ) ;
2018-12-08 18:18:45 -08:00
debug ( ` setDashboardDomain: ${ domain } ` ) ;
2018-12-07 16:15:21 -08:00
2018-12-14 09:57:28 -08:00
domains . get ( domain , function ( error , domainObject ) {
2019-10-23 10:02:04 -07:00
if ( error ) return callback ( error ) ;
2018-12-07 16:15:21 -08:00
2020-09-23 15:45:04 -07:00
reverseProxy . writeDashboardConfig ( domain , function ( error ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2018-12-14 09:57:28 -08:00
2019-01-16 21:36:48 -08:00
const fqdn = domains . fqdn ( constants . ADMIN _LOCATION , domainObject ) ;
2018-12-07 16:15:21 -08:00
2020-08-15 19:24:32 -07:00
settings . setAdminLocation ( domain , fqdn , function ( error ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-01-16 21:36:48 -08:00
2019-02-04 20:24:28 -08:00
eventlog . add ( eventlog . ACTION _DASHBOARD _DOMAIN _UPDATE , auditSource , { domain : domain , fqdn : fqdn } ) ;
2019-01-16 21:36:48 -08:00
callback ( null ) ;
} ) ;
2018-12-07 16:15:21 -08:00
} ) ;
} ) ;
}
2018-12-10 20:20:53 -08:00
2019-02-26 19:43:18 -08:00
// call this only post activation because it will restart mail server
2020-08-15 22:53:05 -07:00
function updateDashboardDomain ( domain , auditSource , callback ) {
2019-02-26 19:43:18 -08:00
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2020-08-15 22:53:05 -07:00
debug ( ` updateDashboardDomain: ${ domain } ` ) ;
2019-02-26 19:43:18 -08:00
2020-02-19 10:45:55 -08:00
if ( settings . isDemo ( ) ) return callback ( new BoxError ( BoxError . CONFLICT , 'Not allowed in demo mode' ) ) ;
2019-02-26 19:43:18 -08:00
setDashboardDomain ( domain , auditSource , function ( error ) {
if ( error ) return callback ( error ) ;
2021-01-21 11:31:35 -08:00
services . rebuildService ( 'turn' , NOOP _CALLBACK ) ; // to update the realm variable
2019-02-26 19:43:18 -08:00
callback ( null ) ;
} ) ;
}
2018-12-10 20:20:53 -08:00
function renewCerts ( options , auditSource , callback ) {
2018-12-11 12:00:47 +01:00
assert . strictEqual ( typeof options , 'object' ) ;
2018-12-10 20:20:53 -08:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-08-27 22:39:59 -07:00
tasks . add ( tasks . TASK _RENEW _CERTS , [ options , auditSource ] , function ( error , taskId ) {
2019-10-24 18:32:33 -07:00
if ( error ) return callback ( error ) ;
2019-08-27 22:39:59 -07:00
tasks . startTask ( taskId , { } , NOOP _CALLBACK ) ;
callback ( null , taskId ) ;
} ) ;
2018-12-10 20:20:53 -08:00
}
2020-08-15 23:17:29 -07:00
function setupDnsAndCert ( subdomain , domain , auditSource , progressCallback , callback ) {
assert . strictEqual ( typeof subdomain , 'string' ) ;
assert . strictEqual ( typeof domain , 'string' ) ;
assert . strictEqual ( typeof auditSource , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
domains . get ( domain , function ( error , domainObject ) {
if ( error ) return callback ( error ) ;
const adminFqdn = domains . fqdn ( subdomain , domainObject ) ;
sysinfo . getServerIp ( function ( error , ip ) {
if ( error ) return callback ( error ) ;
async . series ( [
2020-09-09 21:49:44 -07:00
( done ) => { progressCallback ( { message : ` Updating DNS of ${ adminFqdn } ` } ) ; done ( ) ; } ,
2020-08-15 23:17:29 -07:00
domains . upsertDnsRecords . bind ( null , subdomain , domain , 'A' , [ ip ] ) ,
2020-09-09 21:49:44 -07:00
( done ) => { progressCallback ( { message : ` Waiting for DNS of ${ adminFqdn } ` } ) ; done ( ) ; } ,
2020-08-15 23:17:29 -07:00
domains . waitForDnsRecord . bind ( null , subdomain , domain , 'A' , ip , { interval : 30000 , times : 50000 } ) ,
2020-09-09 21:49:44 -07:00
( done ) => { progressCallback ( { message : ` Getting certificate of ${ adminFqdn } ` } ) ; done ( ) ; } ,
2020-08-15 23:17:29 -07:00
reverseProxy . ensureCertificate . bind ( null , domains . fqdn ( subdomain , domainObject ) , domain , auditSource )
] , function ( error ) {
if ( error ) return callback ( error ) ;
callback ( null ) ;
} ) ;
} ) ;
} ) ;
}
2021-02-24 18:42:39 -08:00
function syncDnsRecords ( options , callback ) {
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
tasks . add ( tasks . TASK _SYNC _DNS _RECORDS , [ options ] , function ( error , taskId ) {
if ( error ) return callback ( error ) ;
tasks . startTask ( taskId , { } , NOOP _CALLBACK ) ;
callback ( null , taskId ) ;
} ) ;
}