@@ -2,23 +2,30 @@ const core = require('@actions/core');
22const github = require ( '@actions/github' ) ;
33const { NodeSSH } = require ( 'node-ssh' ) ;
44const axios = require ( 'axios' ) ;
5+ const dotenv = require ( 'dotenv' ) ;
56
67const ssh = new NodeSSH ( ) ;
78
9+ // Load environment variables from .env file in development mode
10+ if ( process . env . NODE_ENV !== 'production' ) {
11+ dotenv . config ( ) ;
12+ }
13+
814async function deploy ( ) {
915 try {
1016 const inputs = getInputs ( ) ;
17+ validateInputs ( inputs ) ;
1118 logInputs ( inputs ) ;
1219
1320 await checkSponsorship ( inputs . githubRepoOwner ) ;
1421
15- await connectToServer ( inputs ) ;
22+ await sshOperations . connect ( inputs ) ;
1623
1724 await prepareDeployment ( inputs ) ;
1825
1926 await activateRelease ( inputs ) ;
2027
21- console . log ( "Deployment completed successfully!" ) ;
28+ log ( "Deployment completed successfully!" ) ;
2229 } catch ( error ) {
2330 console . error ( `Error: ${ error . message } ` ) ;
2431 core . setFailed ( error . message ) ;
@@ -29,72 +36,121 @@ async function deploy() {
2936
3037function getInputs ( ) {
3138 return {
32- host : core . getInput ( 'host' ) ,
33- username : core . getInput ( 'username' ) ,
34- port : core . getInput ( 'port' ) ,
35- password : core . getInput ( 'password' ) ,
36- sshKey : core . getInput ( 'ssh_key' ) , // SSH key input
37- target : core . getInput ( 'target' ) ,
38- sha : core . getInput ( 'sha' ) ,
39- githubToken : core . getInput ( 'github_token' ) ,
40- envFile : core . getInput ( 'env_file' ) ,
41- commandScriptBeforeCheckFolders : core . getInput ( 'command_script_before_check_folders' ) ,
42- commandScriptAfterCheckFolders : core . getInput ( 'command_script_after_check_folders' ) ,
43- commandScriptBeforeDownload : core . getInput ( 'command_script_before_download' ) ,
44- commandScriptAfterDownload : core . getInput ( 'command_script_after_download' ) ,
45- commandScriptBeforeActivate : core . getInput ( 'command_script_before_activate' ) ,
46- commandScriptAfterActivate : core . getInput ( 'command_script_after_activate' ) ,
47- githubRepoOwner : github . context . payload . repository . owner . login ,
48- githubRepo : github . context . payload . repository . name
39+ host : process . env . HOST || core . getInput ( 'host' ) ,
40+ username : process . env . REMOTE_USERNAME || core . getInput ( 'username' ) ,
41+ port : process . env . PORT || core . getInput ( 'port' ) ,
42+ password : process . env . PASSWORD || core . getInput ( 'password' ) ,
43+ sshKey : ( process . env . SSH_KEY || core . getInput ( 'ssh_key' ) ) . replace ( / \\ n / g, '\n' ) , // Replace escaped newlines with actual newlines
44+ passphrase : process . env . SSH_PASSPHRASE || core . getInput ( 'ssh_passphrase' ) , // Add passphrase
45+ target : process . env . TARGET || core . getInput ( 'target' ) ,
46+ sha : process . env . SHA || core . getInput ( 'sha' ) ,
47+ deploy_branch : process . env . GITHUB_DEPLOY_BRANCH || core . getInput ( 'deploy_branch' ) ,
48+ githubToken : process . env . GITHUB_TOKEN || core . getInput ( 'github_token' ) ,
49+ envFile : process . env . ENV_FILE || core . getInput ( 'env_file' ) ,
50+ commandScriptBeforeCheckFolders : process . env . COMMAND_SCRIPT_BEFORE_CHECK_FOLDERS || core . getInput ( 'command_script_before_check_folders' ) ,
51+ commandScriptAfterCheckFolders : process . env . COMMAND_SCRIPT_AFTER_CHECK_FOLDERS || core . getInput ( 'command_script_after_check_folders' ) ,
52+ commandScriptBeforeDownload : process . env . COMMAND_SCRIPT_BEFORE_DOWNLOAD || core . getInput ( 'command_script_before_download' ) ,
53+ commandScriptAfterDownload : process . env . COMMAND_SCRIPT_AFTER_DOWNLOAD || core . getInput ( 'command_script_after_download' ) ,
54+ commandScriptBeforeActivate : process . env . COMMAND_SCRIPT_BEFORE_ACTIVATE || core . getInput ( 'command_script_before_activate' ) ,
55+ commandScriptAfterActivate : process . env . COMMAND_SCRIPT_AFTER_ACTIVATE || core . getInput ( 'command_script_after_activate' ) ,
56+ githubRepoOwner : process . env . GITHUB_REPO_OWNER || github . context . payload . repository . owner . login ,
57+ githubRepo : process . env . GITHUB_REPO || github . context . payload . repository . name
4958 } ;
5059}
5160
61+ function validateInputs ( inputs ) {
62+ if ( ! inputs . host ) throw new Error ( "Host is required." ) ;
63+ if ( ! inputs . username ) throw new Error ( "Username is required." ) ;
64+ if ( inputs . port && ( isNaN ( inputs . port ) || inputs . port < 1 || inputs . port > 65535 ) ) {
65+ throw new Error ( "Port must be a valid number between 1 and 65535." ) ;
66+ }
67+ if ( ! inputs . target ) throw new Error ( "Target directory is required." ) ;
68+ if ( ! inputs . sha ) throw new Error ( "SHA is required." ) ;
69+ if ( ! inputs . githubToken ) throw new Error ( "GitHub token is required." ) ;
70+ }
71+
72+ function log ( message ) {
73+ const timestamp = new Date ( ) . toISOString ( ) ;
74+ console . log ( `[${ timestamp } ] ${ message } ` ) ;
75+ }
76+
5277function logInputs ( inputs ) {
53- console . log ( `Host: ${ inputs . host } ` ) ;
54- console . log ( `Target: ${ inputs . target } ` ) ;
55- console . log ( `SHA: ${ inputs . sha } ` ) ;
56- console . log ( `GitHub Repo Owner: ${ inputs . githubRepoOwner } ` ) ;
78+ log ( `Host: ${ inputs . host } ` ) ;
79+ log ( `Target: ${ inputs . target } ` ) ;
80+ log ( `SHA: ${ inputs . sha } ` ) ;
81+ log ( `GitHub Repo Owner: ${ inputs . githubRepoOwner } ` ) ;
82+ // Omitting sensitive information like GitHub token and SSH key
5783}
5884
5985async function checkSponsorship ( githubRepoOwner ) {
6086 try {
6187 const response = await axios . post ( 'https://deployer.flowsahl.com/api/check-github-sponsorship' , {
6288 github_username : githubRepoOwner
6389 } ) ;
64- console . log ( 'Thanks for sponsoring us :)' ) ;
90+ log ( 'Thanks for sponsoring us :)' ) ;
6591 } catch ( error ) {
6692 handleSponsorshipError ( error ) ;
6793 }
6894}
6995
7096function handleSponsorshipError ( error ) {
71- if ( error . response && error . response . status === 403 ) {
72- throw new Error ( "You are not a sponsor, Please consider sponsoring us to use this action, https://github.com/sponsors/FlowSahl , Start sponsoring us and try again [1$ or more]" ) ;
73- } else if ( error . response && error . response . status === 500 ) {
74- console . error ( "An error occurred while checking sponsorship, but the deployment will continue." ) ;
97+ if ( error . response ) {
98+ if ( error . response . status === 403 ) {
99+ throw new Error ( "You are not a sponsor. Please consider sponsoring us to use this action: https://github.com/sponsors/FlowSahl. Start sponsoring us and try again [1$ or more]." ) ;
100+ } else if ( error . response . status === 500 ) {
101+ log ( "An internal server error occurred while checking sponsorship, but the deployment will continue." ) ;
102+ } else {
103+ log ( `Sponsorship check failed with status ${ error . response . status } : ${ error . response . data } ` ) ;
104+ throw new Error ( "Sponsorship check failed. Please try again later." ) ;
105+ }
75106 } else {
76- throw error ;
107+ log ( "An unknown error occurred during the sponsorship check." ) ;
108+ // throw error;
77109 }
78110}
79111
80- async function connectToServer ( { host, username, port, password, sshKey } ) {
81- console . log ( "Connecting to the server..." ) ;
82- const connectionOptions = {
83- host,
84- username,
85- port : port ? parseInt ( port ) : undefined ,
86- } ;
87-
88- if ( sshKey ) {
89- connectionOptions . privateKey = sshKey ;
90- } else if ( password ) {
91- connectionOptions . password = password ;
92- } else {
93- throw new Error ( "Either an SSH key or a password must be provided for the SSH connection." ) ;
112+ const sshOperations = {
113+ async connect ( { host, username, port, password, sshKey, passphrase } ) {
114+ log ( "Connecting to the server..." ) ;
115+ const connectionOptions = {
116+ host,
117+ username,
118+ port : port ? parseInt ( port ) : undefined ,
119+ privateKey : sshKey ,
120+ passphrase : passphrase
121+ } ;
122+
123+ try {
124+ await ssh . connect ( connectionOptions ) ;
125+ log ( "Successfully connected using SSH key." ) ;
126+ } catch ( keyError ) {
127+ if ( password ) {
128+ log ( "SSH key authentication failed, attempting password authentication..." ) ;
129+ try {
130+ await ssh . connect ( { host, username, port : port ? parseInt ( port ) : undefined , password } ) ;
131+ log ( "Successfully connected using password." ) ;
132+ } catch ( passwordError ) {
133+ log ( `Failed to connect with password: ${ passwordError . message } ` ) ;
134+ throw passwordError ;
135+ }
136+ } else {
137+ log ( `Failed to connect with SSH key: ${ keyError . message } ` ) ;
138+ throw keyError ;
139+ }
140+ }
141+ } ,
142+
143+ async execute ( command ) {
144+ try {
145+ const result = await ssh . execCommand ( command ) ;
146+ if ( result . stdout ) log ( result . stdout ) ;
147+ if ( result . stderr ) console . error ( result . stderr ) ;
148+ if ( result . code !== 0 ) throw new Error ( `Command failed: ${ command } - ${ result . stderr } ` ) ;
149+ } catch ( error ) {
150+ throw new Error ( `Failed to execute command: ${ command } - ${ error . message } ` ) ;
151+ }
94152 }
95-
96- await ssh . connect ( connectionOptions ) ;
97- }
153+ } ;
98154
99155async function prepareDeployment ( inputs ) {
100156 const paths = getPaths ( inputs . target , inputs . sha ) ;
@@ -123,13 +179,13 @@ function getPaths(target, sha) {
123179
124180async function runOptionalScript ( script , description ) {
125181 if ( script !== 'false' ) {
126- console . log ( `Running script ${ description } : ${ script } ` ) ;
127- await executeCommand ( script ) ;
182+ log ( `Running script ${ description } : ${ script } ` ) ;
183+ await sshOperations . execute ( script ) ;
128184 }
129185}
130186
131187async function checkAndPrepareFolders ( paths ) {
132- console . log ( "Checking the folders..." ) ;
188+ log ( "Checking the folders..." ) ;
133189 const folders = [
134190 `${ paths . target } /releases` ,
135191 `${ paths . target } /storage` ,
@@ -141,63 +197,50 @@ async function checkAndPrepareFolders(paths) {
141197 `${ paths . target } /storage/framework/sessions` ,
142198 `${ paths . target } /storage/framework/views`
143199 ] ;
144- await executeCommand ( `mkdir -p ${ folders . join ( ' ' ) } ` ) ;
145- await executeCommand ( `rm -rf ${ paths . target } /_temp_${ paths . sha } ` ) ;
146- await executeCommand ( `rm -rf ${ paths . target } /releases/${ paths . sha } ` ) ;
147- await executeCommand ( `rm -rf ${ paths . target } /${ paths . sha } .zip` ) ;
200+ await sshOperations . execute ( `mkdir -p ${ folders . join ( ' ' ) } ` ) ;
201+ await sshOperations . execute ( `rm -rf ${ paths . target } /_temp_${ paths . sha } ` ) ;
202+ await sshOperations . execute ( `rm -rf ${ paths . target } /releases/${ paths . sha } ` ) ;
203+ await sshOperations . execute ( `rm -rf ${ paths . target } /${ paths . sha } .zip` ) ;
148204}
149205
150206async function cloneAndPrepareRepository ( inputs , paths ) {
151- if ( inputs . commandScriptBeforeDownload !== 'false' ) {
152- await runOptionalScript ( inputs . commandScriptBeforeDownload , "before clone" ) ;
153- }
207+ await runOptionalScript ( inputs . commandScriptBeforeDownload , "before clone" ) ;
154208
155- const repoUrl = `https:// ${ inputs . githubRepoOwner } : ${ inputs . githubToken } @github.com/ ${ inputs . githubRepoOwner } /${ inputs . githubRepo } .git` ;
156- console . log ( `Cloning Repo: ${ repoUrl } ` ) ;
209+ const repoUrl = `git @github.com: ${ inputs . githubRepoOwner } /${ inputs . githubRepo } .git` ;
210+ log ( `Cloning Repo: ${ repoUrl } ` ) ;
157211
158- await executeCommand ( `
212+ await sshOperations . execute ( `
159213 cd ${ inputs . target } &&
214+ rm -rf ${ paths . releasePath } &&
160215 git clone ${ repoUrl } ${ paths . releasePath } &&
161216 cd ${ paths . releasePath } &&
162- git checkout ${ inputs . sha }
217+ git checkout ${ inputs . deploy_branch }
163218 ` ) ;
164219}
165220
166221async function syncEnvironmentFile ( envFile , paths ) {
167222 if ( envFile ) {
168- console . log ( "Syncing .env file" ) ;
169- await executeCommand ( `echo '${ envFile } ' > ${ paths . target } /.env` ) ;
170- await executeCommand ( `ln -sfn ${ paths . target } /.env ${ paths . releasePath } /.env` ) ;
223+ log ( "Syncing .env file" ) ;
224+ await sshOperations . execute ( `echo '${ envFile } ' > ${ paths . target } /.env` ) ;
225+ await sshOperations . execute ( `ln -sfn ${ paths . target } /.env ${ paths . releasePath } /.env` ) ;
171226 }
172227}
173228
174229async function linkStorage ( paths ) {
175- console . log ( "Linking the current release with storage" ) ;
176- await executeCommand ( `ln -sfn ${ paths . target } /storage ${ paths . releasePath } /storage` ) ;
230+ log ( "Linking the current release with storage" ) ;
231+ await sshOperations . execute ( `ln -sfn ${ paths . target } /storage ${ paths . releasePath } /storage` ) ;
177232}
178233
179234async function activateRelease ( inputs ) {
180235 const paths = getPaths ( inputs . target , inputs . sha ) ;
181236
182- if ( inputs . commandScriptBeforeActivate !== 'false' ) {
183- await runOptionalScript ( inputs . commandScriptBeforeActivate , "before activate" ) ;
184- }
237+ await runOptionalScript ( inputs . commandScriptBeforeActivate , "before activate" ) ;
185238
186- console . log ( "Activating the release" ) ;
187- await executeCommand ( `ln -sfn ${ paths . releasePath } ${ paths . activeReleasePath } && ls -1dt ${ inputs . target } /releases/*/ | tail -n +4 | xargs rm -rf` ) ;
239+ log ( "Activating the release" ) ;
240+ await sshOperations . execute ( `ln -sfn ${ paths . releasePath } ${ paths . activeReleasePath } && ls -1dt ${ inputs . target } /releases/*/ | tail -n +4 | xargs rm -rf` ) ;
188241
189- if ( inputs . commandScriptAfterActivate !== 'false' ) {
190- await runOptionalScript ( inputs . commandScriptAfterActivate , "after activate" ) ;
191- }
192- }
193-
194- async function executeCommand ( command ) {
195- const result = await ssh . execCommand ( command ) ;
196- if ( result . stdout ) console . log ( result . stdout ) ;
197- if ( result . stderr ) console . error ( result . stderr ) ;
198- if ( result . code !== 0 ) throw new Error ( `Command failed: ${ command } - ${ result . stderr } ` ) ;
242+ await runOptionalScript ( inputs . commandScriptAfterActivate , "after activate" ) ;
199243}
200-
201244module . exports = { deploy } ;
202245
203246// Automatically run the deploy function when the script is executed
0 commit comments