install.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. package install
  2. import (
  3. "ehang.io/nps/lib/common"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "github.com/c4milo/unpackit"
  8. "io"
  9. "io/ioutil"
  10. "log"
  11. "net/http"
  12. "os"
  13. "path/filepath"
  14. "runtime"
  15. "strings"
  16. )
  17. // Keep it in sync with the template from service_sysv_linux.go file
  18. // Use "ps | grep -v grep | grep $(get_pid)" because "ps PID" may not work on OpenWrt
  19. const SysvScript = `#!/bin/sh
  20. # For RedHat and cousins:
  21. # chkconfig: - 99 01
  22. # description: {{.Description}}
  23. # processname: {{.Path}}
  24. ### BEGIN INIT INFO
  25. # Provides: {{.Path}}
  26. # Required-Start:
  27. # Required-Stop:
  28. # Default-Start: 2 3 4 5
  29. # Default-Stop: 0 1 6
  30. # Short-Description: {{.DisplayName}}
  31. # Description: {{.Description}}
  32. ### END INIT INFO
  33. cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
  34. name=$(basename $(readlink -f $0))
  35. pid_file="/var/run/$name.pid"
  36. stdout_log="/var/log/$name.log"
  37. stderr_log="/var/log/$name.err"
  38. [ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
  39. get_pid() {
  40. cat "$pid_file"
  41. }
  42. is_running() {
  43. [ -f "$pid_file" ] && ps | grep -v grep | grep $(get_pid) > /dev/null 2>&1
  44. }
  45. case "$1" in
  46. start)
  47. if is_running; then
  48. echo "Already started"
  49. else
  50. echo "Starting $name"
  51. {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
  52. $cmd >> "$stdout_log" 2>> "$stderr_log" &
  53. echo $! > "$pid_file"
  54. if ! is_running; then
  55. echo "Unable to start, see $stdout_log and $stderr_log"
  56. exit 1
  57. fi
  58. fi
  59. ;;
  60. stop)
  61. if is_running; then
  62. echo -n "Stopping $name.."
  63. kill $(get_pid)
  64. for i in $(seq 1 10)
  65. do
  66. if ! is_running; then
  67. break
  68. fi
  69. echo -n "."
  70. sleep 1
  71. done
  72. echo
  73. if is_running; then
  74. echo "Not stopped; may still be shutting down or shutdown may have failed"
  75. exit 1
  76. else
  77. echo "Stopped"
  78. if [ -f "$pid_file" ]; then
  79. rm "$pid_file"
  80. fi
  81. fi
  82. else
  83. echo "Not running"
  84. fi
  85. ;;
  86. restart)
  87. $0 stop
  88. if is_running; then
  89. echo "Unable to stop, will not attempt to start"
  90. exit 1
  91. fi
  92. $0 start
  93. ;;
  94. status)
  95. if is_running; then
  96. echo "Running"
  97. else
  98. echo "Stopped"
  99. exit 1
  100. fi
  101. ;;
  102. *)
  103. echo "Usage: $0 {start|stop|restart|status}"
  104. exit 1
  105. ;;
  106. esac
  107. exit 0
  108. `
  109. const SystemdScript = `[Unit]
  110. Description={{.Description}}
  111. ConditionFileIsExecutable={{.Path|cmdEscape}}
  112. {{range $i, $dep := .Dependencies}}
  113. {{$dep}} {{end}}
  114. [Service]
  115. LimitNOFILE=65536
  116. StartLimitInterval=5
  117. StartLimitBurst=10
  118. ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
  119. {{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
  120. {{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
  121. {{if .UserName}}User={{.UserName}}{{end}}
  122. {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
  123. {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
  124. {{if and .LogOutput .HasOutputFileSupport -}}
  125. StandardOutput=file:/var/log/{{.Name}}.out
  126. StandardError=file:/var/log/{{.Name}}.err
  127. {{- end}}
  128. Restart=always
  129. RestartSec=120
  130. [Install]
  131. WantedBy=multi-user.target
  132. `
  133. func UpdateNps() {
  134. destPath := downloadLatest("server")
  135. //复制文件到对应目录
  136. copyStaticFile(destPath, "nps")
  137. fmt.Println("Update completed, please restart")
  138. }
  139. func UpdateNpc() {
  140. destPath := downloadLatest("client")
  141. //复制文件到对应目录
  142. copyStaticFile(destPath, "npc")
  143. fmt.Println("Update completed, please restart")
  144. }
  145. type release struct {
  146. TagName string `json:"tag_name"`
  147. }
  148. func downloadLatest(bin string) string {
  149. // get version
  150. data, err := http.Get("https://api.github.com/repos/ehang-io/nps/releases/latest")
  151. if err != nil {
  152. log.Fatal(err.Error())
  153. }
  154. b, err := ioutil.ReadAll(data.Body)
  155. if err != nil {
  156. log.Fatal(err)
  157. }
  158. rl := new(release)
  159. json.Unmarshal(b, &rl)
  160. version := rl.TagName
  161. fmt.Println("the latest version is", version)
  162. filename := runtime.GOOS + "_" + runtime.GOARCH + "_" + bin + ".tar.gz"
  163. // download latest package
  164. downloadUrl := fmt.Sprintf("https://ehang.io/nps/releases/download/%s/%s", version, filename)
  165. fmt.Println("download package from ", downloadUrl)
  166. resp, err := http.Get(downloadUrl)
  167. if err != nil {
  168. log.Fatal(err.Error())
  169. }
  170. destPath, err := unpackit.Unpack(resp.Body, "")
  171. if err != nil {
  172. log.Fatal(err)
  173. }
  174. if bin == "server" {
  175. destPath = strings.Replace(destPath, "/web", "", -1)
  176. destPath = strings.Replace(destPath, `\web`, "", -1)
  177. destPath = strings.Replace(destPath, "/views", "", -1)
  178. destPath = strings.Replace(destPath, `\views`, "", -1)
  179. } else {
  180. destPath = strings.Replace(destPath, `\conf`, "", -1)
  181. destPath = strings.Replace(destPath, "/conf", "", -1)
  182. }
  183. return destPath
  184. }
  185. func copyStaticFile(srcPath, bin string) string {
  186. path := common.GetInstallPath()
  187. if bin == "nps" {
  188. //复制文件到对应目录
  189. if err := CopyDir(filepath.Join(srcPath, "web", "views"), filepath.Join(path, "web", "views")); err != nil {
  190. log.Fatalln(err)
  191. }
  192. chMod(filepath.Join(path, "web", "views"), 0766)
  193. if err := CopyDir(filepath.Join(srcPath, "web", "static"), filepath.Join(path, "web", "static")); err != nil {
  194. log.Fatalln(err)
  195. }
  196. chMod(filepath.Join(path, "web", "static"), 0766)
  197. }
  198. binPath, _ := filepath.Abs(os.Args[0])
  199. if !common.IsWindows() {
  200. if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin); err != nil {
  201. if _, err := copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin); err != nil {
  202. log.Fatalln(err)
  203. } else {
  204. copyFile(filepath.Join(srcPath, bin), "/usr/local/bin/"+bin+"-update")
  205. chMod("/usr/local/bin/"+bin+"-update", 0755)
  206. binPath = "/usr/local/bin/" + bin
  207. }
  208. } else {
  209. copyFile(filepath.Join(srcPath, bin), "/usr/bin/"+bin+"-update")
  210. chMod("/usr/bin/"+bin+"-update", 0755)
  211. binPath = "/usr/bin/" + bin
  212. }
  213. } else {
  214. copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+"-update.exe"))
  215. copyFile(filepath.Join(srcPath, bin+".exe"), filepath.Join(common.GetAppPath(), bin+".exe"))
  216. }
  217. chMod(binPath, 0755)
  218. return binPath
  219. }
  220. func InstallNpc() {
  221. path := common.GetInstallPath()
  222. if !common.FileExists(path) {
  223. err := os.Mkdir(path, 0755)
  224. if err != nil {
  225. log.Fatal(err)
  226. }
  227. }
  228. copyStaticFile(common.GetAppPath(), "npc")
  229. }
  230. func InstallNps() string {
  231. path := common.GetInstallPath()
  232. if common.FileExists(path) {
  233. MkidrDirAll(path, "web/static", "web/views")
  234. } else {
  235. MkidrDirAll(path, "conf", "web/static", "web/views")
  236. // not copy config if the config file is exist
  237. if err := CopyDir(filepath.Join(common.GetAppPath(), "conf"), filepath.Join(path, "conf")); err != nil {
  238. log.Fatalln(err)
  239. }
  240. chMod(filepath.Join(path, "conf"), 0766)
  241. }
  242. binPath := copyStaticFile(common.GetAppPath(), "nps")
  243. log.Println("install ok!")
  244. log.Println("Static files and configuration files in the current directory will be useless")
  245. log.Println("The new configuration file is located in", path, "you can edit them")
  246. if !common.IsWindows() {
  247. log.Println(`You can start with:
  248. nps start|stop|restart|uninstall|update or nps-update update
  249. anywhere!`)
  250. } else {
  251. log.Println(`You can copy executable files to any directory and start working with:
  252. nps.exe start|stop|restart|uninstall|update or nps-update.exe update
  253. now!`)
  254. }
  255. chMod(common.GetLogPath(), 0777)
  256. return binPath
  257. }
  258. func MkidrDirAll(path string, v ...string) {
  259. for _, item := range v {
  260. if err := os.MkdirAll(filepath.Join(path, item), 0755); err != nil {
  261. log.Fatalf("Failed to create directory %s error:%s", path, err.Error())
  262. }
  263. }
  264. }
  265. func CopyDir(srcPath string, destPath string) error {
  266. //检测目录正确性
  267. if srcInfo, err := os.Stat(srcPath); err != nil {
  268. fmt.Println(err.Error())
  269. return err
  270. } else {
  271. if !srcInfo.IsDir() {
  272. e := errors.New("SrcPath is not the right directory!")
  273. return e
  274. }
  275. }
  276. if destInfo, err := os.Stat(destPath); err != nil {
  277. return err
  278. } else {
  279. if !destInfo.IsDir() {
  280. e := errors.New("DestInfo is not the right directory!")
  281. return e
  282. }
  283. }
  284. err := filepath.Walk(srcPath, func(path string, f os.FileInfo, err error) error {
  285. if f == nil {
  286. return err
  287. }
  288. if !f.IsDir() {
  289. destNewPath := strings.Replace(path, srcPath, destPath, -1)
  290. log.Println("copy file ::" + path + " to " + destNewPath)
  291. copyFile(path, destNewPath)
  292. if !common.IsWindows() {
  293. chMod(destNewPath, 0766)
  294. }
  295. }
  296. return nil
  297. })
  298. return err
  299. }
  300. //生成目录并拷贝文件
  301. func copyFile(src, dest string) (w int64, err error) {
  302. srcFile, err := os.Open(src)
  303. if err != nil {
  304. return
  305. }
  306. defer srcFile.Close()
  307. //分割path目录
  308. destSplitPathDirs := strings.Split(dest, string(filepath.Separator))
  309. //检测时候存在目录
  310. destSplitPath := ""
  311. for index, dir := range destSplitPathDirs {
  312. if index < len(destSplitPathDirs)-1 {
  313. destSplitPath = destSplitPath + dir + string(filepath.Separator)
  314. b, _ := pathExists(destSplitPath)
  315. if b == false {
  316. log.Println("mkdir:" + destSplitPath)
  317. //创建目录
  318. err := os.Mkdir(destSplitPath, os.ModePerm)
  319. if err != nil {
  320. log.Fatalln(err)
  321. }
  322. }
  323. }
  324. }
  325. dstFile, err := os.Create(dest)
  326. if err != nil {
  327. return
  328. }
  329. defer dstFile.Close()
  330. return io.Copy(dstFile, srcFile)
  331. }
  332. //检测文件夹路径时候存在
  333. func pathExists(path string) (bool, error) {
  334. _, err := os.Stat(path)
  335. if err == nil {
  336. return true, nil
  337. }
  338. if os.IsNotExist(err) {
  339. return false, nil
  340. }
  341. return false, err
  342. }
  343. func chMod(name string, mode os.FileMode) {
  344. if !common.IsWindows() {
  345. os.Chmod(name, mode)
  346. }
  347. }