-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
333 lines (290 loc) · 10.6 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
)
const NginxSitesAvailablePath = "/etc/nginx/sites-available"
const ApacheSitesAvailablePath = "/etc/apache2/sites-available"
const HttpdSitesAvailablePath = "/etc/httpd/conf.d"
var (
domain string
projectRoot string
gitEndpoint string
whichWebServer string
)
func main() {
fmt.Println("[Server host automation tool by @metallurgical(https://github.com/metallurgical)]")
if isRoot, _ := isRoot(); isRoot == false {
fmt.Println("You're not running this application as root. Abort!")
os.Exit(1)
}
askForInput()
if projectRoot != "" && gitEndpoint != "" {
cloneGitRepo()
}
switch whichWebServer {
case "2":
createNginxVhost()
break
case "1":
createApacheVhost()
break
}
}
func askForInput() {
fmt.Print("1) What is the domain name (without http/https) ? : ")
fmt.Scanln(&domain)
fmt.Print("2) Where to clone the git repo (Full path to base folder of project) ? : ")
fmt.Scanln(&projectRoot)
fmt.Print("3) Full URL of git endpoint (will be automatically cloned into folder set in no 2) ? : ")
fmt.Scanln(&gitEndpoint)
fmt.Print("4) Which web server? (Apache - 1, Nginx - 2, key in 1 or 2) ? : ")
fmt.Scanln(&whichWebServer)
// @keepOnHold Not really need this by now can get it directly from `php -v` command
// fmt.Print("5) PHP version currently use? (Put only first two major number, eg: 7.4, 8.0, 7.2, 5.6) ? : ")
// fmt.Scanln(&phpVersion)
}
func cloneGitRepo() {
fmt.Println(">>>> Cloning git repository into " + projectRoot)
if isExist, _ := exists(projectRoot); isExist == true {
fmt.Println(">>>> Project directory path already exist. Skip")
} else {
cmdCloneRepo := exec.Command("git", "clone", gitEndpoint, projectRoot)
cmdCloneRepo.Run()
fmt.Println(">>>> Done clone git repository")
}
// Change ownership of storage folder
fmt.Println(">>>> Change owner storage folder of " + projectRoot + "/storage as www-data/apache user")
webServerUser,_ := getWebServerUser();
cmdChangeUser := exec.Command("chown", webServerUser + ":" + webServerUser, "-R", projectRoot+"/storage")
cmdChangeUser.Run()
cmdPermission := exec.Command("chmod", "-R", "755", projectRoot)
cmdPermission.Run()
fmt.Println(">>>> Done change ownership of storage folder")
// Copy .env.example file
fmt.Println(">>>> Copy .env.example's content into .env file")
if isExist, _ := exists(projectRoot + "/.env"); isExist == true {
fmt.Println(">>>> Env file already exists. Skip")
} else {
cmdCopyEnv := exec.Command("cp", projectRoot+"/.env.example", projectRoot+"/.env")
cmdCopyEnv.Run()
fmt.Println(">>>> Done copied")
}
// Change directory easier to run any command related to project
os.Chdir(projectRoot)
// Run composer install
fmt.Println(">>>> Running composer install, this might take a while.")
if isExist, _ := exists(projectRoot + "/vendor"); isExist == true {
fmt.Println(">>>> Vendor folder already exist. Skip.")
} else {
cmdComposer := exec.Command("composer", "install")
cmdComposer.Run()
fmt.Println(">>>> Done install composer dependencies")
}
// Run php generate key
fmt.Println(">>>> Generate new APP_KEY")
cmdPhpKey := exec.Command("php", "artisan", "key:generate")
cmdPhpKey.Run()
fmt.Println(">>>> Done generating APP_KEY")
}
// createNginxVhost will create a minimal server block for apache
// to run the desire domain for laravel project.
func createNginxVhost() {
fmt.Println(">>>> Check if " + NginxSitesAvailablePath + " folder is exist..")
if _, err := os.Stat(NginxSitesAvailablePath); err != nil {
fmt.Println(">>>> Folder " + NginxSitesAvailablePath + " does not exist!. Abort")
revertGitChanges();
os.Exit(1)
return
}
fmt.Println(">>>> Create nginx server block for domain: " + domain + ".")
// Get/download the file from source
cmdWget := exec.Command("wget", "https://raw.githubusercontent.com/metallurgical/server-host-automation/master/default-nginx-host.conf", "-P", "/tmp")
cmdWget.Run()
// Move the file from source into nginx sites-available folder
var vhostFileName = domain + ".conf"
var domainPath = NginxSitesAvailablePath + "/" + vhostFileName
if isExist, _ := exists(domainPath); isExist == true {
fmt.Println(">>>> Server block already exist. Skip")
} else {
cmdCp := exec.Command("mv", "/tmp/default-nginx-host.conf", domainPath)
cmdCp.Run()
// Replace document root full path into new project directory path
replaceContent(domainPath, "[documentRoot]", projectRoot+"/public")
// Replace matching server name with new the exact domain name
replaceContent(domainPath, "[serverName]", domain)
// Get the full path of php-fpm socket. This will return the output
// something similar to this eg: "listen = /run/php/php7.4-fpm.sock"
// cmdGetFpmPath, err := exec.Command("bash", "-c", "cat /etc/php/"+phpVersion+"/fpm/pool.d/www.conf | grep 'listen ='").Output()
version, _ := getPhpVersion()
cmdGetFpmPath, err := exec.Command("bash", "-c", "cat /etc/php/"+version+"/fpm/pool.d/www.conf | grep 'listen ='").Output()
if err != nil {
return
}
// Replace php fpm socket path
replaceContent(domainPath, "[phpFpmSocket]", "unix:/var"+strings.TrimSpace(string(cmdGetFpmPath)[9:]))
fmt.Println(">>>> Done creating server block")
// Once successfully created into sites-available, create symlink to that file
fmt.Println(">>>> Create symlink server block for domain: " + domain)
cmdLn := exec.Command("ln", "-s", domainPath, "/etc/nginx/sites-enabled/")
cmdLn.Run()
// Restart nginx web server once done
fmt.Println(">>>> Reloading web server to take effect of new changes")
cmdRestartWebServer := exec.Command("service", "nginx", "reload")
cmdRestartWebServer.Run()
fmt.Println(">>>> Project are available to browse with new domain: " + domain)
}
}
// createApacheVhost will create a minimal server block for apache
// to run the desire domain for laravel project.
func createApacheVhost() {
fmt.Println(">>>> Check if " + ApacheSitesAvailablePath + " folder is exist..")
var actualApacheSitePath string;
var isHttpd = false
// Check for apache2 root folder
if isExist, _ := exists(ApacheSitesAvailablePath); isExist == true {
actualApacheSitePath = ApacheSitesAvailablePath;
isHttpd = false;
} else {
// Check for httpd folder instead of apache2
actualApacheSitePath = HttpdSitesAvailablePath;
if isExist, _ := exists(actualApacheSitePath); isExist != true {
fmt.Println(">>>> Folder " + actualApacheSitePath + " does not exist!. Abort")
revertGitChanges();
os.Exit(1)
return
} else {
isHttpd = true;
}
}
fmt.Println(">>>> Create apache server block for domain: " + domain + ".")
// Get/download the file from source
cmdWget := exec.Command("wget", "https://raw.githubusercontent.com/metallurgical/server-host-automation/master/default-apache-host.conf", "-P", "/tmp")
cmdWget.Run()
// Move the file from source into apache2 sites-available folder
var vhostFileName = domain + ".conf"
var domainPath = actualApacheSitePath + "/" + vhostFileName
if isExist, _ := exists(domainPath); isExist == true {
fmt.Println(">>>> Server block already exist. Skip")
} else {
cmdCp := exec.Command("mv", "/tmp/default-apache-host.conf", domainPath)
cmdCp.Run()
// Replace document root full path into new project directory path
replaceContent(domainPath, "[documentRoot]", projectRoot+"/public")
// Replace matching server name with new the exact domain name
replaceContent(domainPath, "[serverName]", domain)
fmt.Println(">>>> Done creating server block")
// Once successfully created into sites-available, create symlink to that file (only for Ubuntu)
if isHttpd == false {
fmt.Println(">>>> Create symlink server block for domain: " + domain)
cmdLn := exec.Command("ln", "-s", domainPath, "/etc/apache2/sites-enabled/")
cmdLn.Run()
// Enable newly created site
enSite := exec.Command("a2ensite", vhostFileName)
enSite.Run()
}
// Restart apache web server once done
fmt.Println(">>>> Reloading web server to take effect of new changes")
var process string;
if isHttpd == false {
process = "apache2"
} else {
process = "httpd"
}
cmdRestartWebServer := exec.Command("service", process, "reload")
cmdRestartWebServer.Run()
fmt.Println(">>>> Project are available to browse with new domain: " + domain)
}
}
// replaceContent search for string and replace with the new one.
func replaceContent(source string, search string, replace string) {
input, err := ioutil.ReadFile(source)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
output := bytes.Replace(input, []byte(search), []byte(replace), -1)
if err = ioutil.WriteFile(source, output, 0666); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func executeCommand(command string) {
cmdArgs := strings.Fields(command)
cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
stdout, _ := cmd.StdoutPipe()
cmd.Start()
oneByte := make([]byte, 100)
num := 1
for {
if _, err := stdout.Read(oneByte); err != nil {
fmt.Printf(err.Error())
break
}
r := bufio.NewReader(stdout)
line, _, _ := r.ReadLine()
fmt.Println(string(line))
num = num + 1
if num > 3 {
os.Exit(0)
}
}
cmd.Wait()
}
// exists returns whether the given file or directory exists
// credit to https://stackoverflow.com/a/10510783
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
// getPhpVersion retrieve automatically php version and extract only
// first 2 major version
func getPhpVersion() (string, error) {
cmdPhpVersion, err := exec.Command("php", "-v").Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(cmdPhpVersion)[4:7]), nil
}
// getWebServerUser retrieve web server's user that running as
// not using `ps aux` since its returning a lot of text.
func getWebServerUser() (string, error) {
var webServer = "nginx"
if whichWebServer != "2" {
webServer = "httpd|apache2|apache"
}
cmdUser, err := exec.Command("bash", "-c", "ps -ef | egrep '("+webServer+")' | grep -v `whoami` | grep -v root | head -n1 | awk '{print $1}'").Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(cmdUser)), nil
}
// revertGitChanges delete cloned git folder
func revertGitChanges() {
if isExist, _ := exists(projectRoot); isExist == true {
cmdDeleteProjectFolder := exec.Command("rm", "-rf", projectRoot)
cmdDeleteProjectFolder.Run();
}
}
// isRoot check whether running command as root user or else.
func isRoot() (bool, error) {
cmdWhoami, err := exec.Command("whoami").Output()
if err != nil {
return false, err
}
if strings.TrimSpace(string(cmdWhoami)) == "root" {
return true, nil
}
return false, nil
}