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