1+ const internalNginx = require ( '../../internal/nginx' ) ;
2+ const logger = require ( '../../logger' ) . ddns ;
3+ const internalAccessList = require ( '../../internal/access-list' ) ;
4+ const ddnsResolver = require ( './ddns_resolver' ) ;
5+
6+ const ddnsUpdater = {
7+ /**
8+ * Starts a timer to periodically check for ddns updates
9+ */
10+ initTimer : ( ) => {
11+ ddnsUpdater . _initialize ( ) ;
12+ ddnsUpdater . _interval = setInterval ( ddnsUpdater . _checkForDDNSUpdates , ddnsUpdater . _updateIntervalMs ) ;
13+ logger . info ( `DDNS Update Timer initialized (interval: ${ Math . floor ( ddnsUpdater . _updateIntervalMs / 1000 ) } s)` ) ;
14+ // Trigger a run so that initial cache is populated and hosts can be updated - delay by 10s to give server time to boot up
15+ setTimeout ( ddnsUpdater . _checkForDDNSUpdates , 10 * 1000 ) ;
16+ } ,
17+
18+ /** Private **/
19+ // Properties
20+ _initialized : false ,
21+ _updateIntervalMs : 60 * 60 * 1000 , // 1 hr default (overriden with $DDNS_UPDATE_INTERVAL env var)
22+ _interval : null , // reference to created interval id
23+ _processingDDNSUpdate : false ,
24+
25+ // Methods
26+
27+ _initialize : ( ) => {
28+ if ( ddnsUpdater . _initialized ) {
29+ return ;
30+ }
31+ // Init the resolver
32+ // Read and set custom update interval from env if needed
33+ if ( typeof process . env . DDNS_UPDATE_INTERVAL !== 'undefined' ) {
34+ const interval = Number ( process . env . DDNS_UPDATE_INTERVAL . toLowerCase ( ) ) ;
35+ if ( ! isNaN ( interval ) ) {
36+ // Interval value from env is in seconds. Set min to 60s.
37+ ddnsUpdater . _updateIntervalMs = Math . max ( interval * 1000 , 60 * 1000 ) ;
38+ } else {
39+ logger . warn ( `[DDNS] invalid value for update interval: '${ process . env . DDNS_UPDATE_INTERVAL } '` ) ;
40+ }
41+ }
42+ ddnsUpdater . _initialized = true ;
43+ } ,
44+
45+ /**
46+ * Triggered by a timer, will check for and update ddns hosts in access list clients
47+ */
48+ _checkForDDNSUpdates : ( ) => {
49+ logger . info ( 'Checking for DDNS updates...' ) ;
50+ if ( ! ddnsUpdater . _processingDDNSUpdate ) {
51+ ddnsUpdater . _processingDDNSUpdate = true ;
52+
53+ const updatedAddresses = new Map ( ) ;
54+
55+ // Get all ddns hostnames in use
56+ return ddnsUpdater . _getAccessLists ( )
57+ . then ( ( rows ) => {
58+ // Build map of used addresses that require resolution
59+ const usedAddresses = new Map ( ) ;
60+ for ( const row of rows ) {
61+ if ( ! row . proxy_host_count ) {
62+ // Ignore rows (access lists) that are not associated to any hosts
63+ continue ;
64+ }
65+ for ( const client of row . clients ) {
66+ if ( ! ddnsResolver . requiresResolution ( client . address ) ) {
67+ continue ;
68+ }
69+ if ( ! usedAddresses . has ( client . address ) ) {
70+ usedAddresses . set ( client . address , [ row ] ) ;
71+ } else {
72+ usedAddresses . get ( client . address ) . push ( row ) ;
73+ }
74+ }
75+ }
76+ logger . info ( `Found ${ usedAddresses . size } address(es) in use.` ) ;
77+ // Remove unused addresses
78+ const addressesToRemove = [ ] ;
79+ for ( const address of ddnsResolver . _cache . keys ( ) ) {
80+ if ( ! usedAddresses . has ( address ) ) {
81+ addressesToRemove . push ( address ) ;
82+ }
83+ }
84+ addressesToRemove . forEach ( ( address ) => { ddnsResolver . _cache . delete ( address ) ; } ) ;
85+
86+ const promises = [ ] ;
87+
88+ for ( const [ address , rows ] of usedAddresses ) {
89+ let oldIP = '' ;
90+ if ( ddnsResolver . _cache . has ( address ) ) {
91+ oldIP = ddnsResolver . _cache . get ( address ) [ 0 ] ;
92+ }
93+ const p = ddnsResolver . resolveAddress ( address , true )
94+ . then ( ( resolvedIP ) => {
95+ if ( resolvedIP !== address && resolvedIP !== oldIP ) {
96+ // Mark this as an updated address
97+ updatedAddresses . set ( address , rows ) ;
98+ }
99+ } ) ;
100+ promises . push ( p ) ;
101+ }
102+
103+ if ( promises . length ) {
104+ return Promise . all ( promises ) ;
105+ }
106+ return Promise . resolve ( ) ;
107+ } )
108+ . then ( ( ) => {
109+ logger . info ( `${ updatedAddresses . size } DDNS IP(s) updated.` ) ;
110+ const updatedRows = new Map ( ) ;
111+ const proxy_hosts = [ ] ;
112+ for ( const rows of updatedAddresses . values ( ) ) {
113+ for ( const row of rows ) {
114+ if ( ! updatedRows . has ( row . id ) ) {
115+ updatedRows . set ( row . id , 1 ) ;
116+ for ( const host of row . proxy_hosts ) {
117+ if ( host . enabled ) {
118+ proxy_hosts . push ( host ) ;
119+ }
120+ }
121+ }
122+ }
123+ }
124+ if ( proxy_hosts . length ) {
125+ logger . info ( `Updating ${ proxy_hosts . length } proxy host(s) affected by DDNS changes` ) ;
126+ return internalNginx . bulkGenerateConfigs ( 'proxy_host' , proxy_hosts )
127+ . then ( internalNginx . reload ) ;
128+ }
129+ return Promise . resolve ( ) ;
130+ } )
131+ . then ( ( ) => {
132+ logger . info ( 'Finished checking for DDNS updates' ) ;
133+ ddnsUpdater . _processingDDNSUpdate = false ;
134+ } ) ;
135+ } else {
136+ logger . info ( 'Skipping since previous DDNS update check is in progress' ) ;
137+ }
138+ } ,
139+
140+ _getAccessLists : ( ) => {
141+ const fakeAccess = {
142+ can : ( /*role*/ ) => {
143+ return Promise . resolve ( {
144+ permission_visibility : 'all'
145+ } ) ;
146+ }
147+ } ;
148+
149+ return internalAccessList . getAll ( fakeAccess )
150+ . then ( ( rows ) => {
151+ const promises = [ ] ;
152+ for ( const row of rows ) {
153+ const p = internalAccessList . get ( fakeAccess , {
154+ id : row . id ,
155+ expand : [ 'owner' , 'items' , 'clients' , 'proxy_hosts.[certificate,access_list.[clients,items]]' ]
156+ } , true /* <- skip masking */ ) ;
157+ promises . push ( p ) ;
158+ }
159+ if ( promises . length ) {
160+ return Promise . all ( promises ) ;
161+ }
162+ return Promise . resolve ( [ ] ) ;
163+ } ) ;
164+ }
165+ } ;
166+
167+ module . exports = ddnsUpdater ;
0 commit comments