@@ -6,11 +6,19 @@ import { Connection } from "./types.js";
66
77/**
88 * Cloudflared launchd identifier.
9+ * @platform macOS
910 */
1011export const identifier = "com.cloudflare.cloudflared" ;
1112
13+ /**
14+ * Cloudflared service name.
15+ * @platform linux
16+ */
17+ export const service_name = "cloudflared.service" ;
18+
1219/**
1320 * Path of service related files.
21+ * @platform macOS
1422 */
1523export const MACOS_SERVICE_PATH = {
1624 PLIST : is_root ( )
@@ -24,10 +32,21 @@ export const MACOS_SERVICE_PATH = {
2432 : `${ os . homedir ( ) } /Library/Logs/${ identifier } .err.log` ,
2533} ;
2634
35+ /**
36+ * Path of service related files.
37+ * @platform linux
38+ */
39+ export const LINUX_SERVICE_PATH = {
40+ SYSTEMD : `/etc/systemd/system/${ service_name } ` ,
41+ SERVICE : "/etc/init.d/cloudflared" ,
42+ SERVICE_OUT : "/var/log/cloudflared.log" ,
43+ SERVICE_ERR : "/var/log/cloudflared.err" ,
44+ } ;
45+
2746/**
2847 * Cloudflared Service API.
2948 */
30- export const service = { install, uninstall, exists, log, err, current, clean } ;
49+ export const service = { install, uninstall, exists, log, err, current, clean, journal } ;
3150
3251/**
3352 * Throw when service is already installed.
@@ -50,9 +69,10 @@ export class NotInstalledError extends Error {
5069/**
5170 * Install Cloudflared service.
5271 * @param token Tunnel service token.
72+ * @platform macOS, linux
5373 */
5474export function install ( token ?: string ) : void {
55- if ( process . platform !== "darwin" ) {
75+ if ( ! [ "darwin" , "linux" ] . includes ( process . platform ) ) {
5676 throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
5777 }
5878
@@ -71,9 +91,10 @@ export function install(token?: string): void {
7191
7292/**
7393 * Uninstall Cloudflared service.
94+ * @platform macOS, linux
7495 */
7596export function uninstall ( ) : void {
76- if ( process . platform !== "darwin" ) {
97+ if ( ! [ "darwin" , "linux" ] . includes ( process . platform ) ) {
7798 throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
7899 }
79100
@@ -83,45 +104,76 @@ export function uninstall(): void {
83104
84105 spawnSync ( bin , [ "service" , "uninstall" ] ) ;
85106
86- fs . rmSync ( MACOS_SERVICE_PATH . OUT ) ;
87- fs . rmSync ( MACOS_SERVICE_PATH . ERR ) ;
107+ if ( process . platform === "darwin" ) {
108+ fs . rmSync ( MACOS_SERVICE_PATH . OUT ) ;
109+ fs . rmSync ( MACOS_SERVICE_PATH . ERR ) ;
110+ } else if ( process . platform === "linux" && ! is_systemd ( ) ) {
111+ fs . rmSync ( LINUX_SERVICE_PATH . SERVICE_OUT ) ;
112+ fs . rmSync ( LINUX_SERVICE_PATH . SERVICE_ERR ) ;
113+ }
88114}
89115
90116/**
91117 * Get stdout log of cloudflared service. (Usually empty)
92118 * @returns stdout log of cloudflared service.
119+ * @platform macOS, linux (sysv)
93120 */
94121export function log ( ) : string {
95- if ( process . platform !== "darwin" ) {
96- throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
97- }
98-
99122 if ( ! exists ( ) ) {
100123 throw new NotInstalledError ( ) ;
101124 }
102125
103- return fs . readFileSync ( MACOS_SERVICE_PATH . OUT , "utf8" ) ;
126+ if ( process . platform === "darwin" ) {
127+ return fs . readFileSync ( MACOS_SERVICE_PATH . OUT , "utf8" ) ;
128+ }
129+
130+ if ( process . platform === "linux" && ! is_systemd ( ) ) {
131+ return fs . readFileSync ( LINUX_SERVICE_PATH . SERVICE_OUT , "utf8" ) ;
132+ }
133+
134+ throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
104135}
105136
106137/**
107138 * Get stderr log of cloudflared service. (cloudflared print all things here)
108139 * @returns stderr log of cloudflared service.
140+ * @platform macOS, linux (sysv)
109141 */
110142export function err ( ) : string {
111- if ( process . platform !== "darwin" ) {
112- throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
113- }
114-
115143 if ( ! exists ( ) ) {
116144 throw new NotInstalledError ( ) ;
117145 }
118146
119- return fs . readFileSync ( MACOS_SERVICE_PATH . ERR , "utf8" ) ;
147+ if ( process . platform === "darwin" ) {
148+ return fs . readFileSync ( MACOS_SERVICE_PATH . ERR , "utf8" ) ;
149+ }
150+
151+ if ( process . platform === "linux" && ! is_systemd ( ) ) {
152+ return fs . readFileSync ( LINUX_SERVICE_PATH . SERVICE_ERR , "utf8" ) ;
153+ }
154+
155+ throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
156+ }
157+
158+ /**
159+ * Get cloudflared service journal from journalctl.
160+ * @param n The number of entries to return.
161+ * @returns cloudflared service journal.
162+ * @platform linux (systemd)
163+ */
164+ export function journal ( n = 300 ) : string {
165+ if ( process . platform === "linux" && is_systemd ( ) ) {
166+ const args = [ "-u" , service_name , "-o" , "cat" , "-n" , n . toString ( ) ] ;
167+ return spawnSync ( "journalctl" , args ) . stdout . toString ( ) ;
168+ }
169+
170+ throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
120171}
121172
122173/**
123174 * Get informations of current running cloudflared service.
124175 * @returns informations of current running cloudflared service.
176+ * @platform macOS, linux
125177 */
126178export function current ( ) : {
127179 /** Tunnel ID */
@@ -134,61 +186,72 @@ export function current(): {
134186 metrics : string ;
135187 /** Tunnel Configuration */
136188 config : {
137- ingress : { service : string ; hostname ?: string } [ ] ;
189+ ingress ? : { service : string ; hostname ?: string } [ ] ;
138190 [ key : string ] : unknown ;
139191 } ;
140192} {
141- if ( process . platform !== "darwin" ) {
193+ if ( ! [ "darwin" , "linux" ] . includes ( process . platform ) ) {
142194 throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
143195 }
144196
145197 if ( ! exists ( ) ) {
146198 throw new NotInstalledError ( ) ;
147199 }
148200
149- const error = err ( ) ;
201+ const log = is_systemd ( ) ? journal ( ) : err ( ) ;
150202
151203 const regex = {
152- tunnelID : / t u n n e l I D = ( [ 0 - 9 a - z - ] + ) / g ,
153- connectorID : / C o n n e c t o r I D : ( [ 0 - 9 a - z - ] + ) / g ,
154- connections :
155- / C o n n e c t i o n ( [ a - z 0 - 9 - ] + ) r e g i s t e r e d c o n n I n d e x = ( \d ) i p = ( [ 0 - 9 . ] + ) l o c a t i o n = ( [ A - Z ] + ) / g ,
156- metrics : / m e t r i c s s e r v e r o n ( [ 0 - 9 . : ] + \/ m e t r i c s ) / g ,
157- config : / c o n f i g = " ( .+ [ ^ \\ ] ) " / g ,
204+ tunnelID : / t u n n e l I D = ( [ 0 - 9 a - z - ] + ) / ,
205+ connectorID : / C o n n e c t o r I D : ( [ 0 - 9 a - z - ] + ) / ,
206+ connect : / C o n n e c t i o n ( [ a - z 0 - 9 - ] + ) r e g i s t e r e d c o n n I n d e x = ( \d ) i p = ( [ 0 - 9 . ] + ) l o c a t i o n = ( [ A - Z ] + ) / ,
207+ disconnect : / U n r e g i s t e r e d t u n n e l c o n n e c t i o n c o n n I n d e x = ( \d ) / ,
208+ metrics : / m e t r i c s s e r v e r o n ( [ 0 - 9 . : ] + \/ m e t r i c s ) / ,
209+ config : / c o n f i g = " ( .+ [ ^ \\ ] ) " / ,
158210 } ;
159211
160- const match = {
161- tunnelID : regex . tunnelID . exec ( error ) ,
162- connectorID : regex . connectorID . exec ( error ) ,
163- connections : error . matchAll ( regex . connections ) ,
164- metrics : regex . metrics . exec ( error ) ,
165- config : regex . config . exec ( error ) ,
166- } ;
212+ let tunnelID = "" ;
213+ let connectorID = "" ;
214+ const connections : Connection [ ] = [ ] ;
215+ let metrics = "" ;
216+ let config : {
217+ ingress ?: { service : string ; hostname ?: string } [ ] ;
218+ [ key : string ] : unknown ;
219+ } = { } ;
167220
168- const config = ( ( ) => {
221+ for ( const line of log . split ( "\n" ) ) {
169222 try {
170- return JSON . parse ( match . config ?. [ 1 ] . replace ( / \\ / g, "" ) ?? "{}" ) ;
171- } catch ( e ) {
172- return { } ;
223+ if ( line . match ( regex . tunnelID ) ) {
224+ tunnelID = line . match ( regex . tunnelID ) ?. [ 1 ] ?? "" ;
225+ } else if ( line . match ( regex . connectorID ) ) {
226+ connectorID = line . match ( regex . connectorID ) ?. [ 1 ] ?? "" ;
227+ } else if ( line . match ( regex . connect ) ) {
228+ const [ , id , idx , ip , location ] = line . match ( regex . connect ) ?? [ ] ;
229+ if ( id && idx && ip && location ) {
230+ connections [ parseInt ( idx ) ] = { id, ip, location } ;
231+ }
232+ } else if ( line . match ( regex . disconnect ) ) {
233+ const [ , idx ] = line . match ( regex . disconnect ) ?? [ ] ;
234+ if ( parseInt ( idx ) in connections ) {
235+ connections [ parseInt ( idx ) ] = { id : "" , ip : "" , location : "" } ;
236+ }
237+ } else if ( line . match ( regex . metrics ) ) {
238+ metrics = line . match ( regex . metrics ) ?. [ 1 ] ?? "" ;
239+ } else if ( line . match ( regex . config ) ) {
240+ config = JSON . parse ( line . match ( regex . config ) ?. [ 1 ] . replace ( / \\ / g, "" ) ?? "{}" ) ;
241+ }
242+ } catch ( err ) {
243+ if ( process . env . VERBOSE ) {
244+ console . error ( "log parsing failed" , err ) ;
245+ }
173246 }
174- } ) ( ) ;
175-
176- return {
177- tunnelID : match . tunnelID ?. [ 1 ] ?? "" ,
178- connectorID : match . connectorID ?. [ 1 ] ?? "" ,
179- connections :
180- [ ...match . connections ] . map ( ( [ , id , , ip , location ] ) => ( {
181- id,
182- ip,
183- location,
184- } ) ) ?? [ ] ,
185- metrics : match . metrics ?. [ 1 ] ?? "" ,
186- config,
187- } ;
247+ }
248+
249+ return { tunnelID, connectorID, connections, metrics, config } ;
188250}
189251
190252/**
191253 * Clean up service log files.
254+ * @platform macOS
192255 */
193256export function clean ( ) : void {
194257 if ( process . platform !== "darwin" ) {
@@ -206,13 +269,24 @@ export function clean(): void {
206269/**
207270 * Check if cloudflared service is installed.
208271 * @returns true if service is installed, false otherwise.
272+ * @platform macOS, linux
209273 */
210274export function exists ( ) : boolean {
211- return is_root ( )
212- ? fs . existsSync ( MACOS_SERVICE_PATH . PLIST )
213- : fs . existsSync ( MACOS_SERVICE_PATH . PLIST ) ;
275+ if ( process . platform === "darwin" ) {
276+ return fs . existsSync ( MACOS_SERVICE_PATH . PLIST ) ;
277+ } else if ( process . platform === "linux" ) {
278+ return is_systemd ( )
279+ ? fs . existsSync ( LINUX_SERVICE_PATH . SYSTEMD )
280+ : fs . existsSync ( LINUX_SERVICE_PATH . SERVICE ) ;
281+ }
282+
283+ throw new Error ( `Not Implemented on platform ${ process . platform } ` ) ;
214284}
215285
216286function is_root ( ) : boolean {
217287 return process . getuid ?.( ) === 0 ;
218288}
289+
290+ function is_systemd ( ) : boolean {
291+ return process . platform === "linux" && fs . existsSync ( "/run/systemd/system" ) ;
292+ }
0 commit comments