├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cell_installer.go ├── core_installer.go ├── frontend_installer.go ├── go.mod ├── go.sum ├── installer.go └── module_updater.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea 15 | .fleet 16 | log 17 | pkg 18 | cert 19 | bin 20 | installer 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.2.2] - 2023-11-19 4 | 5 | ### 变更 6 | 7 | - 优化命令行执行失败时的输出信息 8 | - 更新go.mod的go版本为1.17,更新依赖包版本 9 | 10 | ### Changed 11 | 12 | - Optimize error output when shell command executed fail 13 | - Upgrade go.mod with go 1.17 and newer dependent packages 14 | 15 | ## [1.2.1] - 2021-02-19 16 | 17 | ### Added 18 | 19 | - Print nano version when start 20 | 21 | ## [1.2.0] - 2020-04-29 22 | 23 | ### Added 24 | 25 | - Add forcibly update modules without check 26 | 27 | ### Changed 28 | 29 | - Check firewalld status before installation 30 | 31 | ## [1.1.0] - 2020-01-02 32 | 33 | ### Changed 34 | 35 | - Change directory of frontend portal files from 'resource' to 'web_root' 36 | 37 | ## [1.0.0] - 2019-6-25 38 | 39 | ### Added 40 | 41 | - Add 'Update' option to update all installed modules 42 | 43 | ### Changed 44 | 45 | - Change namespace of the reference library to "github.com/project-nano" 46 | 47 | ## [0.1.9] - 2019-5-29 48 | 49 | ### Added 50 | 51 | - Add 'all' option to install all modules 52 | 53 | ### Fixed 54 | 55 | - Read interface fail due to script code in ifcfg 56 | 57 | ## [0.1.8] - 2018-12-1 58 | 59 | ### Added 60 | 61 | - Enable DHCP port for cell 62 | 63 | ### Changed 64 | 65 | - Disable NetworkManager before link bridge to prevent ssh disconnection 66 | 67 | - Migrate bridge configure from interface 68 | 69 | ## [0.1.7] - 2018-11-3 70 | 71 | ### Added 72 | 73 | - Check default route 74 | 75 | ## [0.1.6] - 2018-10-2 76 | 77 | ### Changed 78 | 79 | - Install nfs-client/semanage for cell 80 | 81 | - Ask if continue when installing fail 82 | 83 | ## [0.1.5] - 2018-8-17 84 | 85 | ### Added 86 | 87 | - Open magic port TCP 25469 for cell guest initiator service 88 | 89 | - Install genisoimage for building cloud-init image 90 | 91 | ## [0.1.4] - 2018-8-7 92 | 93 | ### Modified 94 | 95 | - Change /dev/kvm owner to chosen user 96 | 97 | ## [0.1.3] - 2018-8-6 98 | 99 | ### Added 100 | 101 | - modify user/group in /etc/libvirt/qemu.conf before start service 102 | 103 | ## [0.1.2] - 2018-07-25 104 | 105 | ### Modified 106 | 107 | - Output version 108 | 109 | - Install EPEL before yum installing 110 | 111 | 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 project-nano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nano Installer 2 | 3 | [版本历史/ChangeLog](CHANGELOG.md) 4 | 5 | [English Version](#introduce) 6 | 7 | 8 | ## 说明 9 | 10 | Installer是用于部署Nano集群的辅助程序,自动化完成依赖安装、环境配置等工作。 11 | 12 | Installer执行过程中会修改并重启宿主机网络,所以**不应当通过SSH服务远程进行调用**,而是应当在本地console或者通过iDRAC等专用远程协议执行。 13 | 14 | Installer需要携带相关的部署包才能正常执行,不能单独工作,详见[Releases项目](https://github.com/project-nano/releases) 15 | 16 | ### 编译 17 | 18 | 编译要求: 19 | 1. CentOS 7 20 | 2. Golang 1.17或以上版本 21 | 22 | 执行以下指令编译 23 | ``` 24 | $git clone https://github.com/project-nano/installer.git 25 | $go build 26 | ``` 27 | 28 | ### 使用 29 | 30 | 运行要求 31 | - CentOS 7 32 | 33 | 执行以下指令启动安装 34 | ``` 35 | $./installer 36 | ``` 37 | 38 | #### 目录结构 39 | 40 | ``` 41 | installer - 部署程序 42 | bin\ - 集群各模块程序 43 | bin\frontend_files\web_root - Web管理门户的页面文件 44 | cert\ - 集群根证书 45 | rpms\ - 本地安装依赖的RPM包 46 | rpms\cell - 本地安装时,cell模块需要的包 47 | ``` 48 | 49 | ## Introduce 50 | 51 | Installer is a helper program used to deploy Nano clusters, which automates the installation of dependencies and configuration of the environment. 52 | 53 | During the execution of the Installer, it modifies and restarts the network of the host machine, so it **should not be called remotely via SSH**, but should be executed in the local console or via remote protocols such as iDRAC. 54 | 55 | ### Compile 56 | 57 | Requirements 58 | 1. CentOS 7 59 | 2. Golang 1.17 or above 60 | 61 | Execute in shell 62 | ``` 63 | $git clone https://github.com/project-nano/installer.git 64 | $go build 65 | ``` 66 | 67 | ### Usage 68 | 69 | Requirements 70 | - CentOS 7 71 | 72 | Execute in shell 73 | ``` 74 | $./installer 75 | ``` 76 | 77 | #### Directory Structure 78 | 79 | ``` 80 | Installer - this program 81 | bin - binary for each modules 82 | bin\frontend_files\web_root - page files for web portal 83 | cert\ - root certificates for cluster 84 | rpms\ - RPM packages for local installation 85 | rpms\cell - Packages required by the cell module during local installation 86 | ``` -------------------------------------------------------------------------------- /cell_installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/pkg/errors" 8 | "github.com/project-nano/framework" 9 | "github.com/vishvananda/netlink" 10 | "io/ioutil" 11 | "net" 12 | "os" 13 | "os/exec" 14 | "os/user" 15 | "path/filepath" 16 | "strings" 17 | ) 18 | 19 | const ( 20 | MonitorPortBegin = 5901 21 | MonitorPortEnd = 6000 22 | InitiatorMagicPort = 25469 23 | DHCPServerPort = 67 24 | ) 25 | 26 | func CellInstaller(session *SessionInfo) (ranges []PortRange, err error){ 27 | const ( 28 | ModulePathName = "cell" 29 | ConfigPathName = "config" 30 | ModuleExecuteName = "cell" 31 | ) 32 | fmt.Println("installing cell module...") 33 | var workingPath = filepath.Join(session.ProjectPath, ModulePathName) 34 | if err = ensurePath(workingPath, "module", session.UID, session.GID);err != nil{ 35 | return 36 | } 37 | var sourceFile = filepath.Join(session.BinaryPath, ModuleExecuteName) 38 | if _, err = os.Stat(sourceFile); os.IsNotExist(err){ 39 | return 40 | } 41 | var targetFile = filepath.Join(workingPath, ModuleExecuteName) 42 | if err = copyFile(sourceFile, targetFile);err != nil{ 43 | return 44 | } 45 | if err = enableExecuteAccess(session, targetFile);err != nil{ 46 | fmt.Printf("enable execute access fail: %s\n", err.Error()) 47 | return 48 | } 49 | fmt.Printf("binary '%s' copied\n", targetFile) 50 | var configPath = filepath.Join(workingPath, ConfigPathName) 51 | if err = ensurePath(configPath, "config", session.UID, session.GID);err != nil{ 52 | return 53 | } 54 | if err = enableLibvirtService(session);err != nil{ 55 | return 56 | } 57 | if err = writeCellDomainConfig(session, configPath);err != nil{ 58 | return 59 | } 60 | if err = installPolkitAccess(session); err != nil{ 61 | return 62 | } 63 | 64 | ranges = []PortRange{ 65 | {MonitorPortBegin, MonitorPortEnd, "tcp"}, 66 | {InitiatorMagicPort, InitiatorMagicPort, "tcp"}, 67 | {DHCPServerPort, DHCPServerPort, "udp"}, 68 | } 69 | fmt.Println("cell module installed") 70 | return ranges, nil 71 | } 72 | 73 | func installCellDependencyPackages() (err error){ 74 | const ( 75 | PackagePath = "rpms" 76 | CellPath = "cell" 77 | ) 78 | var packagePath = filepath.Join(PackagePath, CellPath) 79 | if _, err = os.Stat(packagePath); os.IsNotExist(err){ 80 | err = fmt.Errorf("can not find dependency package path %s", packagePath) 81 | return 82 | } 83 | fmt.Println("installing cell dependency packages...") 84 | var cmd = exec.Command("rpm", "-i", "--force", fmt.Sprintf("%s/*", packagePath)) 85 | if err = executeWithOutput(cmd);err != nil{ 86 | fmt.Printf("install pacakge fail: %s\n", err.Error()) 87 | fmt.Println("try installing from online reciprocity...") 88 | { 89 | //install EPEL first 90 | epel := exec.Command("yum", "install", "-y", "epel-release") 91 | if err = epel.Run(); nil != err{ 92 | fmt.Printf("install EPEL fail: %s\n", err.Error()) 93 | return 94 | } 95 | } 96 | cmd = exec.Command("yum", "install", "-y", "qemu-system-x86", "bridge-utils","libvirt", 97 | "seabios", "genisoimage", "nfs-utils", "policycoreutils-python") 98 | if err = executeWithOutput(cmd);err != nil { 99 | fmt.Printf("install online reciprocity fail: %s\n", err.Error()) 100 | return 101 | } 102 | } 103 | fmt.Println("dependency packages installed") 104 | return nil 105 | } 106 | 107 | func enableLibvirtService(session *SessionInfo) (err error){ 108 | if err = configureLibvirtGroup(session);err != nil{ 109 | return 110 | } 111 | { 112 | var cmd = exec.Command("systemctl", "enable", "libvirtd") 113 | if err = executeWithOutput(cmd);err != nil{ 114 | fmt.Printf("enable libvirt fail: %s\n", err.Error()) 115 | return 116 | }else{ 117 | fmt.Println("libvirt enabled") 118 | } 119 | } 120 | { 121 | var cmd = exec.Command("systemctl", "start", "libvirtd") 122 | if err = executeWithOutput(cmd);err != nil{ 123 | fmt.Printf("start libvirt fail: %s\n", err.Error()) 124 | return 125 | }else{ 126 | fmt.Println("libvirt started") 127 | } 128 | } 129 | 130 | return 131 | } 132 | func writeCellDomainConfig(session *SessionInfo, configPath string) (err error){ 133 | const ( 134 | DomainConfigFileName = "domain.cfg" 135 | ) 136 | type DomainConfig struct { 137 | Domain string `json:"domain"` 138 | GroupAddress string `json:"group_address"` 139 | GroupPort int `json:"group_port"` 140 | } 141 | 142 | var configFile = filepath.Join(configPath, DomainConfigFileName) 143 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 144 | var config = DomainConfig{Domain:session.Domain, GroupAddress:session.GroupAddress, GroupPort:session.GroupPort} 145 | //write 146 | var data []byte 147 | data, err = json.MarshalIndent(config, "", " ") 148 | if err != nil { 149 | return 150 | } 151 | if err = ioutil.WriteFile(configFile, data, DefaultFilePerm); err != nil { 152 | return 153 | } 154 | fmt.Printf("domain configure '%s' generated\n", configFile) 155 | } 156 | return nil 157 | } 158 | 159 | func installPolkitAccess(session *SessionInfo) (err error){ 160 | const ( 161 | FileName = "/etc/polkit-1/localauthority/50-local.d/50-org.libvirt-group-access.pkla" 162 | ) 163 | if _, err = os.Stat(FileName);os.IsNotExist(err){ 164 | //need install 165 | var file *os.File 166 | file, err = os.Create(FileName) 167 | if err != nil{ 168 | return err 169 | } 170 | fmt.Fprintln(file, "[libvirt group Management Access]") 171 | fmt.Fprintln(file, "Identity=unix-group:libvirt") 172 | fmt.Fprintln(file, "Action=org.libvirt.unix.manage") 173 | fmt.Fprintln(file, "ResultAny=yes") 174 | fmt.Fprintln(file, "ResultInactive=yes") 175 | fmt.Fprintln(file, "ResultActive=yes") 176 | file.Close() 177 | fmt.Println("polkit access installed") 178 | 179 | }else{ 180 | fmt.Println("polkit access alreay installed") 181 | } 182 | return nil 183 | } 184 | 185 | func configureLibvirtGroup(session *SessionInfo) (err error) { 186 | const ( 187 | GroupName = "libvirt" 188 | ) 189 | if err = enableQEMUAuthority(session.User, session.UserGroup); err != nil{ 190 | return err 191 | } 192 | if _, err = user.LookupGroup(GroupName);err != nil{ 193 | var cmd = exec.Command("groupadd","libvirt") 194 | if err = executeWithOutput(cmd);err != nil{ 195 | fmt.Printf("create group fail: %s\n", err.Error()) 196 | return 197 | }else{ 198 | fmt.Printf("new group %s created", GroupName) 199 | } 200 | }else{ 201 | fmt.Printf("group %s already exists\n", GroupName) 202 | } 203 | libvirtGroup, err := user.LookupGroup(GroupName) 204 | if err != nil{ 205 | fmt.Printf("get group %s fail: %s", GroupName, err.Error()) 206 | return 207 | } 208 | currentUser, err := user.Lookup(session.User) 209 | if err != nil{ 210 | fmt.Printf("get current user fail: %s", err.Error()) 211 | return 212 | } 213 | groups, err := currentUser.GroupIds() 214 | if err != nil{ 215 | fmt.Printf("get groups for user %s fail: %s", session.User, err.Error()) 216 | return 217 | } 218 | 219 | for _, groupID := range groups{ 220 | if groupID == libvirtGroup.Gid{ 221 | fmt.Printf("user %s already in group %s", session.User, GroupName) 222 | return nil 223 | } 224 | } 225 | //need add 226 | var cmd = exec.Command("usermod","-a", "-G", GroupName, session.User) 227 | if err = executeWithOutput(cmd);err != nil{ 228 | fmt.Printf("add %s to group %s fail: %s\n", session.User, GroupName, err.Error()) 229 | return 230 | }else{ 231 | fmt.Printf("user %s added to group %s\n", session.User, GroupName) 232 | } 233 | return nil 234 | } 235 | 236 | func enableQEMUAuthority(user, group string) (err error){ 237 | const ( 238 | ConfigPath = "/etc/libvirt/qemu.conf" 239 | DefaultUser = "#user = \"root\"" 240 | DefaultGroup = "#group = \"root\"" 241 | KVMDevice = "/dev/kvm" 242 | ) 243 | data, err := ioutil.ReadFile(ConfigPath) 244 | if err != nil{ 245 | return err 246 | } 247 | var userString = fmt.Sprintf("user = \"%s\"", user) 248 | var groupString = fmt.Sprintf("group = \"%s\"", group) 249 | var content = strings.Replace(string(data), DefaultUser, userString, 1) 250 | content = strings.Replace(content, DefaultGroup, groupString, 1) 251 | if err = ioutil.WriteFile(ConfigPath, []byte(content), DefaultFilePerm);err != nil{ 252 | return 253 | } 254 | fmt.Printf("user %s / group %s updated in %s\n", user, group, ConfigPath) 255 | { 256 | if _, err = os.Stat(KVMDevice); os.IsNotExist(err){ 257 | err = errors.New("No KVM module available, check Intel VT-x/AMD-v in BIOS to enable virtualization before installing Nano") 258 | return 259 | } 260 | var cmd = exec.Command("chown", fmt.Sprintf("%s:%s", user, group), KVMDevice) 261 | if err = executeWithOutput(cmd); err != nil{ 262 | return 263 | } 264 | fmt.Printf("%s owner changed\n", KVMDevice) 265 | } 266 | return nil 267 | } 268 | 269 | 270 | func configureNetworkForCell() (err error) { 271 | if hasDefaultBridge(){ 272 | fmt.Printf("bridge %s already exists\n", DefaultBridgeName) 273 | return nil 274 | } 275 | ename, err := framework.SelectEthernetInterface("interface to bridge", true) 276 | if err != nil{ 277 | return 278 | } 279 | fmt.Printf("try link interface '%s' to bridge '%s', input 'yes' to confirm:", ename, DefaultBridgeName) 280 | var input string 281 | _, err = fmt.Scanln(&input) 282 | if err != nil{ 283 | return 284 | } 285 | if "yes" != input{ 286 | return errors.New("user interrupted") 287 | } 288 | { 289 | //disable & stop network manager 290 | var cmd = exec.Command("systemctl", "stop", "NetworkManager") 291 | if err = executeWithOutput(cmd);err != nil{ 292 | fmt.Printf("warning: stop networkmanager fail: %s", err.Error()) 293 | }else{ 294 | fmt.Println("network manager stopped") 295 | } 296 | cmd = exec.Command("systemctl", "disable", "NetworkManager") 297 | if err = executeWithOutput(cmd);err != nil{ 298 | fmt.Printf("warning: disable networkmanager fail: %s", err.Error()) 299 | }else{ 300 | fmt.Println("network manager disabled") 301 | } 302 | } 303 | 304 | if err = linkBridge(ename, DefaultBridgeName);err != nil{ 305 | return 306 | } 307 | 308 | { 309 | //restart network 310 | var cmd = exec.Command("systemctl", "stop", "network") 311 | if err = executeWithOutput(cmd);err != nil{ 312 | fmt.Printf("warning: stop network service fail: %s", err.Error()) 313 | }else{ 314 | fmt.Println("network service stopped") 315 | } 316 | cmd = exec.Command("systemctl", "start", "network") 317 | if err = executeWithOutput(cmd);err != nil{ 318 | fmt.Printf("warning: start network service fail: %s", err.Error()) 319 | return 320 | }else{ 321 | fmt.Println("network service restarted") 322 | } 323 | } 324 | return 325 | } 326 | 327 | func hasDefaultBridge() bool{ 328 | list, err := net.Interfaces() 329 | if err != nil{ 330 | fmt.Printf("fetch interface fail: %s", err.Error()) 331 | return false 332 | } 333 | for _, i := range list{ 334 | if DefaultBridgeName == i.Name{ 335 | return true 336 | } 337 | } 338 | return false 339 | } 340 | 341 | func linkBridge(interfaceName, bridgeName string) (err error){ 342 | const ( 343 | ScriptsPath = "/etc/sysconfig/network-scripts" 344 | ScriptPrefix = "ifcfg" 345 | ) 346 | var interfaceScript = filepath.Join(ScriptsPath, fmt.Sprintf("%s-%s", ScriptPrefix, interfaceName)) 347 | var bridgeScript = filepath.Join(ScriptsPath, fmt.Sprintf("%s-%s", ScriptPrefix, bridgeName)) 348 | interfaceConfig, err := readInterfaceConfig(interfaceScript) 349 | if err != nil{ 350 | return 351 | } 352 | bridgeConfig, err := generateBridgeConfig(bridgeName) 353 | if err != nil{ 354 | return 355 | } 356 | err = migrateInterfaceConfig(bridgeName, &interfaceConfig, &bridgeConfig) 357 | if err != nil{ 358 | return 359 | } 360 | err = writeInterfaceConfig(interfaceConfig, interfaceScript) 361 | if err != nil{ 362 | return 363 | } 364 | fmt.Printf("interface script %s updated\n", interfaceScript) 365 | err = writeInterfaceConfig(bridgeConfig, bridgeScript) 366 | if err != nil{ 367 | return 368 | } 369 | fmt.Printf("bridge script %s generated\n", bridgeScript) 370 | link, err := netlink.LinkByName(interfaceName) 371 | if err != nil{ 372 | return 373 | } 374 | if err = netlink.LinkSetDown(link);err != nil{ 375 | fmt.Printf("warning:set down link fail: %s\n", err.Error()) 376 | } 377 | var bridgeAttrs = netlink.NewLinkAttrs() 378 | bridgeAttrs.Name = bridgeName 379 | var bridge = &netlink.Bridge{LinkAttrs: bridgeAttrs} 380 | if err = netlink.LinkAdd(bridge);err != nil{ 381 | return 382 | } 383 | fmt.Printf("new bridge %s created\n", bridgeName) 384 | if err = netlink.LinkSetMaster(link, bridge);err != nil{ 385 | return 386 | } 387 | fmt.Printf("link %s added to bridge %s\n", interfaceName, bridgeName) 388 | if err = netlink.LinkSetUp(bridge); err != nil{ 389 | return 390 | } 391 | fmt.Printf("bridge %s up\n", bridgeName) 392 | if err = netlink.LinkSetUp(link); err != nil{ 393 | return 394 | } 395 | fmt.Printf("link %s up\n", interfaceName) 396 | return nil 397 | } 398 | 399 | type InterfaceConfig struct { 400 | Params map[string]string 401 | } 402 | 403 | func generateBridgeConfig(bridgeName string)(config InterfaceConfig, err error){ 404 | config.Params = map[string]string{ 405 | "NM_CONTROLLED": "no", 406 | "DELAY": "0", 407 | "TYPE": "Bridge", 408 | "ONBOOT": "yes", 409 | "ZONE": "public", 410 | } 411 | config.Params["NAME"] = bridgeName 412 | config.Params["DEVICE"] = bridgeName 413 | return config, nil 414 | } 415 | func readInterfaceConfig(filepath string) (config InterfaceConfig, err error){ 416 | const ( 417 | ValidDataCount = 2 418 | DataName = 0 419 | DataValue = 1 420 | ) 421 | file, err := os.Open(filepath) 422 | if err != nil{ 423 | return 424 | } 425 | config.Params = map[string]string{} 426 | var scanner = bufio.NewScanner(file) 427 | var lineIndex = 0 428 | for scanner.Scan(){ 429 | var line = scanner.Text() 430 | var data = strings.Split(line, "=") 431 | lineIndex++ 432 | if ValidDataCount != len(data){ 433 | fmt.Printf("ignore line %d of '%s': %s\n", lineIndex, filepath, line) 434 | continue 435 | } 436 | config.Params[data[DataName]] = data[DataValue] 437 | } 438 | fmt.Printf("%d params loaded from '%s'\n", len(config.Params), filepath) 439 | return config, nil 440 | } 441 | 442 | func writeInterfaceConfig(config InterfaceConfig, filepath string) (err error){ 443 | file, err := os.Create(filepath) 444 | if err != nil{ 445 | return err 446 | } 447 | for name, value := range config.Params{ 448 | fmt.Fprintf(file, "%s=%s\n", name, value) 449 | } 450 | return file.Close() 451 | } 452 | 453 | func migrateInterfaceConfig(bridgeName string, ifcfg, brcfg *InterfaceConfig) (err error){ 454 | const ( 455 | NMControl = "NM_CONTROLLED" 456 | BRIDGE = "BRIDGE" 457 | ONBOOT = "ONBOOT" 458 | ) 459 | var migrateList = []string{ 460 | "BOOTPROTO", "PREFIX", "IPADDR", "GATEWAY", "NETMASK", "DNS1", "DNS2", "DOMAIN", 461 | "DEFROUTE", "PEERDNS", "PEERROUTES", "IPV4_FAILURE_FATAL", "IPV6_FAILURE_FATAL", "PROXY_METHOD", 462 | "IPV6ADDR", "IPV6_DEFAULTGW", "IPV6_AUTOCONF", "IPV6_DEFROUTE", "IPV6INIT", "IPV6_ADDR_GEN_MODE", 463 | } 464 | 465 | for _, name := range migrateList{ 466 | if value, exists := ifcfg.Params[name]; exists{ 467 | brcfg.Params[name] = value 468 | delete(ifcfg.Params, name) 469 | } 470 | } 471 | ifcfg.Params[NMControl] = "no" 472 | ifcfg.Params[BRIDGE] = bridgeName 473 | ifcfg.Params[ONBOOT] = "yes" 474 | return nil 475 | } 476 | 477 | -------------------------------------------------------------------------------- /core_installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "os" 6 | "fmt" 7 | "github.com/project-nano/framework" 8 | "encoding/json" 9 | "io/ioutil" 10 | "crypto/tls" 11 | "crypto/x509" 12 | "math/big" 13 | "crypto/rsa" 14 | "encoding/pem" 15 | "crypto/x509/pkix" 16 | "time" 17 | "net" 18 | "crypto/rand" 19 | ) 20 | 21 | const ( 22 | ImagePortBegin = 5801 23 | ImagePortEnd = 5849 24 | APIPortBegin = 5850 25 | APIPortEnd = 5869 26 | ) 27 | func CoreInstaller(session *SessionInfo) (ranges []PortRange, err error){ 28 | const ( 29 | ModulePathName = "core" 30 | ConfigPathName = "config" 31 | CertPathName = "cert" 32 | ModuleExecuteName = "core" 33 | ) 34 | fmt.Println("installing core module...") 35 | var workingPath = filepath.Join(session.ProjectPath, ModulePathName) 36 | if err = ensurePath(workingPath, "module", session.UID, session.GID);err != nil{ 37 | return 38 | } 39 | var sourceFile = filepath.Join(session.BinaryPath, ModuleExecuteName) 40 | if _, err = os.Stat(sourceFile); os.IsNotExist(err){ 41 | return 42 | } 43 | var targetFile = filepath.Join(workingPath, ModuleExecuteName) 44 | if err = copyFile(sourceFile, targetFile);err != nil{ 45 | return 46 | } 47 | if err = enableExecuteAccess(session, targetFile);err != nil{ 48 | fmt.Printf("enable execute access fail: %s\n", err.Error()) 49 | return 50 | } 51 | fmt.Printf("binary '%s' copied\n", targetFile) 52 | var configPath = filepath.Join(workingPath, ConfigPathName) 53 | if err = ensurePath(configPath, "config", session.UID, session.GID);err != nil{ 54 | return 55 | } 56 | if err = writeCoreDomainConfig(session, configPath);err != nil{ 57 | return 58 | } 59 | if err = writeCoreAPIConfig(session, configPath);err != nil{ 60 | return 61 | } 62 | var certPath = filepath.Join(workingPath, CertPathName) 63 | if err = writeCoreImageConfig(session, configPath, certPath);err != nil{ 64 | return 65 | } 66 | ranges = []PortRange{{ImagePortBegin, ImagePortEnd, "tcp"}, {APIPortBegin, APIPortEnd, "tcp"}} 67 | fmt.Println("core module installed") 68 | return ranges, nil 69 | } 70 | 71 | func writeCoreDomainConfig(session *SessionInfo, configPath string) (err error){ 72 | const ( 73 | DomainConfigFileName = "domain.cfg" 74 | ) 75 | type DomainConfig struct { 76 | Domain string `json:"domain"` 77 | GroupAddress string `json:"group_address"` 78 | GroupPort int `json:"group_port"` 79 | ListenAddress string `json:"listen_address"` 80 | } 81 | var configFile = filepath.Join(configPath, DomainConfigFileName) 82 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 83 | 84 | var config = DomainConfig{Domain:session.Domain, GroupAddress:session.GroupAddress, GroupPort:session.GroupPort} 85 | if config.ListenAddress, err = framework.ChooseIPV4Address("Listen Address"); err != nil{ 86 | return 87 | } 88 | session.LocalAddress = config.ListenAddress 89 | session.APIAddress = config.ListenAddress 90 | //write 91 | var data []byte 92 | data, err = json.MarshalIndent(config, "", " ") 93 | if err != nil { 94 | return 95 | } 96 | if err = ioutil.WriteFile(configFile, data, DefaultFilePerm); err != nil { 97 | return 98 | } 99 | fmt.Printf("domain configure '%s' generated\n", configFile) 100 | } 101 | return nil 102 | } 103 | 104 | func writeCoreAPIConfig(session *SessionInfo, configPath string) (err error){ 105 | const ( 106 | APIConfigFilename = "api.cfg" 107 | DefaultAPIServePort = 5850 108 | ) 109 | type APIConfig struct { 110 | Port int `json:"port"` 111 | } 112 | var configFile = filepath.Join(configPath, APIConfigFilename) 113 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 114 | var config = APIConfig{} 115 | if config.Port, err = framework.InputNetworkPort(fmt.Sprintf("API Serve Port (%d ~ %d)", APIPortBegin, APIPortEnd), DefaultAPIServePort);err !=nil{ 116 | return 117 | } 118 | session.APIPort = config.Port 119 | //write 120 | var data []byte 121 | data, err = json.MarshalIndent(config, "", " ") 122 | if err != nil { 123 | return 124 | } 125 | if err = ioutil.WriteFile(configFile, data, DefaultFilePerm); err != nil { 126 | return 127 | } 128 | fmt.Printf("api configure '%s' generated\n", configFile) 129 | } 130 | return nil 131 | } 132 | 133 | func writeCoreImageConfig(session *SessionInfo, configPath, certPath string) (err error){ 134 | const ( 135 | ImageConfigFilename = "image.cfg" 136 | ) 137 | type ImageServiceConfig struct { 138 | CertFile string `json:"cert_file"` 139 | KeyFile string `json:"key_file"` 140 | } 141 | 142 | var configFile = filepath.Join(configPath, ImageConfigFilename) 143 | var certFileName = fmt.Sprintf("%s_image.crt.pem", ProjectName) 144 | var keyFileName = fmt.Sprintf("%s_image.key.pem", ProjectName) 145 | 146 | var generatedCertFile = filepath.Join(certPath, certFileName) 147 | var generatedKeyFile = filepath.Join(certPath, keyFileName) 148 | 149 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 150 | if _, err = os.Stat(generatedCertFile); os.IsNotExist(err){ 151 | //generate new cert 152 | if err = ensurePath(certPath, "image server cert", session.UID, session.GID);err != nil{ 153 | return 154 | } 155 | if err = signImageCertificate(session.CACertPath, session.CAKeyPath, session.LocalAddress, generatedCertFile, generatedKeyFile);err != nil{ 156 | return 157 | } 158 | } 159 | 160 | var config = ImageServiceConfig{generatedCertFile, generatedKeyFile} 161 | //write 162 | var data []byte 163 | data, err = json.MarshalIndent(config, "", " ") 164 | if err != nil { 165 | return 166 | } 167 | if err = ioutil.WriteFile(configFile, data, DefaultFilePerm); err != nil { 168 | return 169 | } 170 | fmt.Printf("image server configure '%s' generated\n", configFile) 171 | } 172 | return 173 | } 174 | 175 | func signImageCertificate(caCert, caKey, localAddress, certPath, keyPath string) (err error){ 176 | const ( 177 | RSAKeyBits = 2048 178 | DefaultDurationYears = 99 179 | ) 180 | rootPair, err := tls.LoadX509KeyPair(caCert, caKey) 181 | if err != nil{ 182 | return 183 | } 184 | rootCA, err := x509.ParseCertificate(rootPair.Certificate[0]) 185 | if err != nil{ 186 | return err 187 | } 188 | var serialNumber = big.NewInt(1700) 189 | var imageCert = x509.Certificate{ 190 | SerialNumber: serialNumber, 191 | Subject: pkix.Name{ 192 | CommonName: fmt.Sprintf("%s ImageServer", ProjectName), 193 | Organization: []string{ProjectName}, 194 | }, 195 | NotBefore: time.Now(), 196 | NotAfter: time.Now().AddDate(DefaultDurationYears, 0, 0), 197 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 198 | KeyUsage: x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment|x509.KeyUsageDataEncipherment, 199 | IPAddresses: []net.IP{net.ParseIP(localAddress)}, 200 | } 201 | var imagePrivate *rsa.PrivateKey 202 | imagePrivate, err = rsa.GenerateKey(rand.Reader, RSAKeyBits) 203 | if err != nil { 204 | return 205 | } 206 | fmt.Printf("private key with %d bits generated\n", RSAKeyBits) 207 | var imagePublic = imagePrivate.PublicKey 208 | var certContent []byte 209 | certContent, err = x509.CreateCertificate(rand.Reader, &imageCert, rootCA, &imagePublic, rootPair.PrivateKey) 210 | if err != nil { 211 | return 212 | } 213 | // Public key 214 | var certFile *os.File 215 | certFile, err = os.Create(certPath) 216 | if err != nil { 217 | return 218 | } 219 | if err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certContent}); err != nil { 220 | return 221 | } 222 | if err = certFile.Close(); err != nil { 223 | return 224 | } 225 | fmt.Printf("cert file '%s' generated\n", certPath) 226 | 227 | // Private key 228 | var keyFile *os.File 229 | keyFile, err = os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultFilePerm) 230 | if err != nil { 231 | os.Remove(certPath) 232 | return 233 | } 234 | if err = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(imagePrivate)}); err != nil { 235 | os.Remove(certPath) 236 | return 237 | } 238 | if err = keyFile.Close(); err != nil { 239 | os.Remove(certPath) 240 | return 241 | } 242 | fmt.Printf("key file '%s' generated\n", keyPath) 243 | return nil 244 | } 245 | 246 | -------------------------------------------------------------------------------- /frontend_installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "os" 6 | "fmt" 7 | "github.com/project-nano/framework" 8 | "encoding/json" 9 | "io/ioutil" 10 | ) 11 | 12 | const ( 13 | PortalPortBegin = 5870 14 | PortalPortEnd = 5899 15 | FrontEndFilesPath = "frontend_files" 16 | FrontEndWebPath = "web_root" 17 | ) 18 | 19 | func FrontendInstaller(session *SessionInfo) (ranges []PortRange, err error){ 20 | const ( 21 | ModulePathName = "frontend" 22 | ConfigPathName = "config" 23 | ModuleExecuteName = "frontend" 24 | ) 25 | fmt.Println("installing frontend module...") 26 | var workingPath = filepath.Join(session.ProjectPath, ModulePathName) 27 | if err = ensurePath(workingPath, "module", session.UID, session.GID);err != nil{ 28 | return 29 | } 30 | var sourceFile = filepath.Join(session.BinaryPath, ModuleExecuteName) 31 | if _, err = os.Stat(sourceFile); os.IsNotExist(err){ 32 | return 33 | } 34 | var targetFile = filepath.Join(workingPath, ModuleExecuteName) 35 | if err = copyFile(sourceFile, targetFile);err != nil{ 36 | return 37 | } 38 | if err = enableExecuteAccess(session, targetFile);err != nil{ 39 | fmt.Printf("enable execute access fail: %s\n", err.Error()) 40 | return 41 | } 42 | fmt.Printf("binary '%s' copied\n", targetFile) 43 | if err = copyResources(session, workingPath); err != nil{ 44 | return 45 | } 46 | var configPath = filepath.Join(workingPath, ConfigPathName) 47 | if err = ensurePath(configPath, "config", session.UID, session.GID);err != nil{ 48 | return 49 | } 50 | if err = writeFrontEndConfig(session, configPath);err != nil{ 51 | return 52 | } 53 | ranges = []PortRange{{PortalPortBegin, PortalPortEnd, "tcp"}} 54 | fmt.Println("frontend module installed") 55 | return ranges, nil 56 | } 57 | 58 | func copyResources(session *SessionInfo, workingPath string) (err error){ 59 | 60 | var sourcePath = filepath.Join(session.BinaryPath, FrontEndFilesPath, FrontEndWebPath) 61 | if _, err = os.Stat(sourcePath);os.IsNotExist(err){ 62 | return 63 | } 64 | var targetPath = filepath.Join(workingPath, FrontEndWebPath) 65 | return copyDir(sourcePath, targetPath) 66 | } 67 | 68 | func writeFrontEndConfig(session *SessionInfo, configPath string) (err error){ 69 | const ( 70 | ConfigFileName = "frontend.cfg" 71 | DefaultBackEndPort = 5850 72 | DefaultFrontEndPort = 5870 73 | ) 74 | type FrontEndConfig struct { 75 | ListenAddress string `json:"address"` 76 | ListenPort int `json:"port"` 77 | ServiceHost string `json:"service_host"` 78 | ServicePort int `json:"service_port"` 79 | } 80 | 81 | var configFile = filepath.Join(configPath, ConfigFileName) 82 | if _, err = os.Stat(configFile); os.IsNotExist(err) { 83 | fmt.Println("No configures available, following instructions to generate a new one.") 84 | 85 | var config = FrontEndConfig{} 86 | if session.LocalAddress != ""{ 87 | config.ListenAddress = session.LocalAddress 88 | fmt.Printf("using %s as portal listen address\n", session.LocalAddress) 89 | }else{ 90 | config.ListenAddress, err = framework.ChooseIPV4Address("Portal listen address") 91 | if err != nil{ 92 | return 93 | } 94 | session.LocalAddress = config.ListenAddress 95 | } 96 | if config.ListenPort, err = framework.InputNetworkPort(fmt.Sprintf("Portal listen port (%d ~ %d)", PortalPortBegin, PortalPortEnd), 97 | DefaultFrontEndPort); err !=nil{ 98 | return 99 | } 100 | if session.APIAddress != ""{ 101 | //same host 102 | config.ServiceHost = session.APIAddress 103 | fmt.Printf("using %s as api address\n", session.APIAddress) 104 | }else{ 105 | if config.ServiceHost, err = framework.InputIPAddress("Backend API Host Address", config.ListenAddress); err !=nil{ 106 | return 107 | } 108 | session.APIAddress = config.ServiceHost 109 | } 110 | 111 | if 0 != session.APIPort{ 112 | config.ServicePort = session.APIPort 113 | fmt.Printf("using %d as backend api port\n", session.APIPort) 114 | }else{ 115 | if config.ServicePort, err = framework.InputNetworkPort("Backend API port", DefaultBackEndPort); err != nil{ 116 | return 117 | } 118 | session.APIPort = config.ServicePort 119 | } 120 | 121 | //write 122 | data, err := json.MarshalIndent(config, "", " ") 123 | if err != nil { 124 | return err 125 | } 126 | if err = ioutil.WriteFile(configFile, data, DefaultFilePerm); err != nil { 127 | return err 128 | } 129 | fmt.Printf("default configure '%s' generated\n", configFile) 130 | } 131 | return 132 | } 133 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/project-nano/installer 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/project-nano/framework v1.0.9 8 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f 9 | github.com/vishvananda/netlink v1.1.0 10 | ) 11 | 12 | require ( 13 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect 14 | github.com/klauspost/cpuid/v2 v2.2.6 // indirect 15 | github.com/klauspost/reedsolomon v1.11.8 // indirect 16 | github.com/sevlyar/go-daemon v0.1.6 // indirect 17 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 18 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 19 | github.com/tjfoc/gmsm v1.4.1 // indirect 20 | github.com/vishvananda/netns v0.0.4 // indirect 21 | github.com/xtaci/kcp-go v5.4.20+incompatible // indirect 22 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect 23 | golang.org/x/crypto v0.15.0 // indirect 24 | golang.org/x/net v0.18.0 // indirect 25 | golang.org/x/sys v0.14.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 7 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 15 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 16 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 17 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 18 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 19 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 20 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 21 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 22 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 24 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= 25 | github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= 26 | github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 27 | github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 28 | github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 29 | github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= 30 | github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 31 | github.com/klauspost/reedsolomon v1.10.0/go.mod h1:qHMIzMkuZUWqIh8mS/GruPdo3u0qwX2jk/LH440ON7Y= 32 | github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= 33 | github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= 34 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 35 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 36 | github.com/project-nano/framework v1.0.9 h1:n0WBKuEQu2o8i70vlmM75dxannWTPZ03ahfCLGAORLE= 37 | github.com/project-nano/framework v1.0.9/go.mod h1:wIJW6aMQqLGxwjGeXnlDn/+FJbDxIhiqMD4XArUNtUQ= 38 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f h1:NorTDWkZl22V1v/2t0gG01ylSACOmjDmrGpN3R/Pbgg= 39 | github.com/project-nano/sonar v0.0.0-20190628085230-df7942628d6f/go.mod h1:VYPy/Adnn0NLwbDfa/7vv12vWbftUqTHZzwt83Q5QAo= 40 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 41 | github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= 42 | github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= 43 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= 44 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= 45 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= 46 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= 47 | github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= 48 | github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= 49 | github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= 50 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 51 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 52 | github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= 53 | github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 54 | github.com/xtaci/kcp-go v4.3.4+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 55 | github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= 56 | github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= 57 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= 58 | github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= 59 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 60 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 61 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 62 | golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 63 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 64 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 65 | golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= 66 | golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= 67 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 68 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 69 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 70 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 71 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 72 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 73 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 74 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 75 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 76 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 77 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 78 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 79 | golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 80 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 81 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 82 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 83 | golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 84 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 85 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 86 | golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= 87 | golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 88 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 89 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 93 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 96 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 111 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 112 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 113 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 114 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 115 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 116 | golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= 117 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 118 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 119 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 120 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 121 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 122 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 123 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 124 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 125 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 126 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 127 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 128 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 129 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 130 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 131 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 132 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 133 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 135 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 136 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 137 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 138 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 139 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 140 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 141 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 142 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 143 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 144 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 145 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 146 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 147 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 148 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 149 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 150 | -------------------------------------------------------------------------------- /installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "errors" 11 | "fmt" 12 | "github.com/project-nano/framework" 13 | "github.com/project-nano/sonar" 14 | "github.com/vishvananda/netlink" 15 | "io" 16 | "io/ioutil" 17 | "math/big" 18 | "os" 19 | "os/exec" 20 | "os/user" 21 | "path" 22 | "path/filepath" 23 | "strconv" 24 | "strings" 25 | "time" 26 | ) 27 | 28 | type SessionInfo struct { 29 | Local bool 30 | Host string 31 | User string 32 | Password string 33 | ProjectPath string 34 | BinaryPath string 35 | CACertPath string 36 | CAKeyPath string 37 | Domain string 38 | GroupAddress string 39 | GroupPort int 40 | LocalAddress string 41 | APIAddress string 42 | APIPort int 43 | UserGroup string 44 | UID int 45 | GID int 46 | } 47 | 48 | type PortRange struct { 49 | Begin int 50 | End int 51 | Protocol string 52 | } 53 | 54 | //todo: add dependency params 55 | type ModuleInstaller func(*SessionInfo) ([]PortRange, error) 56 | 57 | const ( 58 | ModuleCore = iota 59 | ModuleFrontEnd 60 | ModuleCell 61 | ModuleAll 62 | ModuleUpdate 63 | ModuleForciblyUpdate 64 | ModuleExit 65 | ) 66 | 67 | const ( 68 | ProjectName = "nano" 69 | BinaryPathName = "bin" 70 | DefaultPathPerm = 0740 71 | DefaultFilePerm = 0640 72 | DefaultBridgeName = "br0" 73 | CurrentVersion = "1.2.2" 74 | NanoVersion = "1.4.0" 75 | ) 76 | 77 | func main() { 78 | var optionNames = map[int]string{ 79 | ModuleCore: "Core", 80 | ModuleFrontEnd: "FrontEnd", 81 | ModuleCell: "Cell", 82 | ModuleAll: "All", 83 | ModuleUpdate: "Update", 84 | ModuleForciblyUpdate: "Forcibly Update", 85 | ModuleExit: "Exit", 86 | } 87 | 88 | var optionFunctions = map[int]ModuleInstaller{ 89 | ModuleCore: CoreInstaller, 90 | ModuleFrontEnd: FrontendInstaller, 91 | ModuleCell: CellInstaller, 92 | } 93 | fmt.Printf("Installer v%s started\n\nReady to install Project-Nano v%s ...\n", CurrentVersion, NanoVersion) 94 | var selected = map[int]bool{} 95 | for { 96 | for index := ModuleCore; index <= ModuleExit; index++ { 97 | name, _ := optionNames[index] 98 | fmt.Printf("%d : %s\n", index, name) 99 | } 100 | fmt.Println("Input index to select module to install, multi-modules split by ',' (like 2,3):") 101 | var input string 102 | fmt.Scanln(&input) 103 | if "" == input { 104 | continue 105 | } 106 | //clear 107 | selected = map[int]bool{} 108 | for _, value := range strings.Split(input, ",") { 109 | index, err := strconv.Atoi(value) 110 | if err != nil { 111 | fmt.Printf("Invalid input: %s\n", value) 112 | continue 113 | } 114 | selected[index] = true 115 | } 116 | var exists bool 117 | if _, exists = selected[ModuleExit]; exists { 118 | return 119 | } 120 | if _, exists = selected[ModuleAll]; exists { 121 | selected = map[int]bool{ModuleCore: true, ModuleFrontEnd:true, ModuleCell:true} 122 | } 123 | if _, exists = selected[ModuleUpdate]; exists{ 124 | UpdateAllModules(false) 125 | return 126 | }else if _, exists = selected[ModuleForciblyUpdate]; exists{ 127 | UpdateAllModules(true) 128 | return 129 | } 130 | break 131 | } 132 | var err error 133 | if err = checkDefaultRoute(); err != nil{ 134 | fmt.Printf("check default route fail: %s\n", err.Error()) 135 | return 136 | } 137 | if err = checkFirewalld(); err != nil{ 138 | fmt.Printf("check firewalld fail: %s\n", err.Error()) 139 | return 140 | } 141 | var session = SessionInfo{Local: true} 142 | session.BinaryPath = BinaryPathName 143 | var username string 144 | if username, err = framework.InputString("Service Owner Name", "root"); err != nil{ 145 | return 146 | } 147 | if err = setUserInfo(&session, username);err != nil{ 148 | fmt.Printf("set user info fail: %s\n", err.Error()) 149 | return 150 | } 151 | if err = installBasicComponents(&session); err != nil { 152 | fmt.Printf("install basic components fail: %s\n", err.Error()) 153 | return 154 | } 155 | updateAllAccess(session) 156 | fmt.Printf("%d modules will install...\n", len(selected)) 157 | 158 | var allRange []PortRange 159 | //default ranges 160 | { 161 | const ( 162 | GroupPortBegin = 5500 163 | GroupPortEnd = 5599 164 | ModulePortBegin = 5600 165 | ModulePortEnd = 5800 166 | ) 167 | allRange = append(allRange, PortRange{GroupPortBegin, GroupPortEnd, "udp"}) 168 | allRange = append(allRange, PortRange{ModulePortBegin, ModulePortEnd, "udp"}) 169 | } 170 | if _, exists := selected[ModuleCell];exists{ 171 | if err = installCellDependencyPackages();err != nil{ 172 | fmt.Printf("install cell dependency package fail: %s\n", err.Error()) 173 | answer, err := framework.InputString("Do you want to continue? (y/N)", "no") 174 | answer = strings.ToLower(answer) 175 | if err != nil || ("y" != answer && "yes" != answer){ 176 | fmt.Println("installing interupted by user") 177 | return 178 | } 179 | } 180 | if err = configureNetworkForCell();err != nil{ 181 | fmt.Printf("configure default network bridge fail: %s\n", err.Error()) 182 | return 183 | } 184 | } 185 | 186 | for index := ModuleCore; index < ModuleExit; index++ { 187 | if _, exists := selected[index]; exists { 188 | //selected 189 | ranges, err := optionFunctions[index](&session) 190 | if err != nil { 191 | fmt.Printf("install module %s fail: %s\n", optionNames[index], err.Error()) 192 | return 193 | } 194 | for _, ports := range ranges { 195 | allRange = append(allRange, ports) 196 | } 197 | 198 | } 199 | } 200 | updateAllAccess(session) 201 | if 0 != len(allRange) { 202 | if err = enabledPortRanges(session, allRange); err != nil { 203 | fmt.Printf("enabled port ranges fail: %s\n", err.Error()) 204 | } 205 | } 206 | if err = enableIPForward(); err != nil{ 207 | fmt.Printf("enable ip forward fail: %s", err.Error()) 208 | return 209 | } 210 | fmt.Println("all modules installed") 211 | } 212 | 213 | func checkDefaultRoute() (err error){ 214 | routes, err := netlink.RouteList(nil, netlink.FAMILY_ALL) 215 | if err != nil{ 216 | return 217 | } 218 | if 0 == len(routes){ 219 | err = errors.New("no route available") 220 | return 221 | } 222 | var defaultRouteAvailable = false 223 | for _, route := range routes{ 224 | if route.Dst == nil{ 225 | defaultRouteAvailable = true 226 | } 227 | } 228 | if !defaultRouteAvailable{ 229 | err = errors.New("no default route available") 230 | return 231 | } 232 | fmt.Printf("default route ready\n") 233 | return nil 234 | } 235 | 236 | func checkFirewalld() (err error) { 237 | var ready = false 238 | //inactive (dead) 239 | var cmd = exec.Command("systemctl", "status", "firewalld") 240 | var output []byte 241 | if output, err = cmd.CombinedOutput(); err != nil{ 242 | fmt.Println("warning: firewalld service maybe stopped") 243 | }else { 244 | var content = string(output) 245 | if -1 != strings.Index(content, "; disabled;"){ 246 | //disabled 247 | fmt.Println("warning: firewalld service disabled") 248 | }else if -1 != strings.Index(content, "dead") || -1 != strings.Index(content, "inactive"){ 249 | fmt.Println("warning: firewalld service is stopped") 250 | }else{ 251 | ready = true 252 | } 253 | } 254 | if !ready { 255 | fmt.Println("Nano requires a running firewalld service to work properly.\nenter to exit installation, or input 'yes' to continue") 256 | var input string 257 | _, err = fmt.Scanln(&input) 258 | if "yes" != input{ 259 | err = errors.New("quit installation") 260 | return 261 | } 262 | fmt.Println("warning: choose to continue with risk, your installation may not work") 263 | return nil 264 | }else{ 265 | fmt.Println("firewalld service ready") 266 | return nil 267 | } 268 | //disabled 269 | 270 | } 271 | 272 | func enableIPForward() (err error){ 273 | const ( 274 | CheckPath = "/proc/sys/net/ipv4/ip_forward" 275 | ConfigFile = "/usr/lib/sysctl.d/50-default.conf" 276 | EnableLine = "net.ipv4.ip_forward = 1" 277 | ) 278 | { 279 | var file *os.File 280 | if file, err = os.Open(CheckPath);err != nil{ 281 | return 282 | } 283 | var scanner = bufio.NewScanner(file) 284 | if !scanner.Scan(){ 285 | err = errors.New("no content available") 286 | return 287 | } 288 | var data = scanner.Text() 289 | var current int 290 | current, err = strconv.Atoi(data) 291 | if err != nil{ 292 | return 293 | } 294 | if current == 1{ 295 | fmt.Println("ip_forward already enabled") 296 | return nil 297 | }else{ 298 | fmt.Println("try enable ip_forward") 299 | } 300 | } 301 | { 302 | //write config 303 | var file *os.File 304 | file, err = os.OpenFile(ConfigFile, os.O_WRONLY|os.O_APPEND, 0644) 305 | if err != nil{ 306 | return 307 | } 308 | fmt.Fprintln(file, EnableLine) 309 | file.Close() 310 | fmt.Printf("ip_forward enabled in config %s ", ConfigFile) 311 | } 312 | { 313 | var cmd = exec.Command("/sbin/sysctl", "-w", "net.ipv4.ip_forward=1") 314 | if err = executeWithOutput(cmd);err != nil{ 315 | fmt.Printf("enable ip_forward fail: %s", err.Error()) 316 | return 317 | }else{ 318 | fmt.Println("ip_forward enabled") 319 | } 320 | } 321 | return 322 | } 323 | 324 | func setUserInfo(session *SessionInfo, userName string) (err error) { 325 | u, err := user.Lookup(userName) 326 | if err != nil{ 327 | err = fmt.Errorf("invalid user %s", userName) 328 | return 329 | } 330 | var group *user.Group 331 | //same name first 332 | group, err = user.LookupGroupId(u.Gid) 333 | if err != nil{ 334 | err = fmt.Errorf("invalid gid %s", u.Gid) 335 | return 336 | } 337 | session.User = userName 338 | session.UserGroup = group.Name 339 | if session.UID, err = strconv.Atoi(u.Uid);err != nil{ 340 | err = fmt.Errorf("invalid uid %s", u.Uid) 341 | return 342 | } 343 | if session.GID, err = strconv.Atoi(group.Gid);err != nil{ 344 | err = fmt.Errorf("invalid gid %s", group.Gid) 345 | return 346 | } 347 | fmt.Printf("set user %s (uid: %d), group %s (gid: %d)\n", 348 | session.User, session.UID, 349 | session.UserGroup, session.GID) 350 | return nil 351 | } 352 | 353 | func updateAllAccess(session SessionInfo){ 354 | var cmd = exec.Command("chown", "-R", fmt.Sprintf("%s:%s", session.User, session.UserGroup), 355 | session.ProjectPath) 356 | if err := executeWithOutput(cmd);err != nil{ 357 | fmt.Printf("update access fail: %s\n", err.Error()) 358 | }else{ 359 | fmt.Println("all access modified") 360 | } 361 | } 362 | 363 | func installBasicComponents(session *SessionInfo) (err error) { 364 | const ( 365 | RootPath = "/opt" 366 | ) 367 | 368 | var projectPath = filepath.Join(RootPath, ProjectName) 369 | if err = ensurePath(projectPath, "project", session.UID, session.GID);err != nil{ 370 | return 371 | } 372 | session.ProjectPath = projectPath 373 | if err = inputDomainConfigure(session); err != nil{ 374 | return 375 | } 376 | if err = installRootCA(session); err != nil { 377 | return 378 | } 379 | return 380 | } 381 | 382 | func inputDomainConfigure(session *SessionInfo) (err error){ 383 | if session.Domain, err = framework.InputString("Group Domain Name", sonar.DefaultDomain); err != nil{ 384 | return 385 | } 386 | if session.GroupAddress, err = framework.InputMultiCastAddress("Group MultiCast Address", sonar.DefaultMulticastAddress); err != nil{ 387 | return 388 | } 389 | if session.GroupPort, err = framework.InputNetworkPort("Group MultiCast Port", sonar.DefaultMulticastPort);err !=nil{ 390 | return 391 | } 392 | return nil 393 | } 394 | 395 | func installRootCA(session *SessionInfo) (err error) { 396 | const ( 397 | CertPathName = "cert" 398 | TrustedPath = "/etc/pki/ca-trust/source/anchors" 399 | DefaultDurationYears = 99 400 | RSAKeyBits = 2048 401 | ) 402 | var serialNumber = big.NewInt(1699) 403 | 404 | var certFileName = fmt.Sprintf("%s_ca.crt.pem", ProjectName) 405 | var keyFileName = fmt.Sprintf("%s_ca.key.pem", ProjectName) 406 | if err = ensurePath(CertPathName, "cert", session.UID, session.GID); err != nil{ 407 | return 408 | } 409 | var generatedCertFile = filepath.Join(CertPathName, certFileName) 410 | var generatedKeyFile = filepath.Join(CertPathName, keyFileName) 411 | if _, err = os.Stat(generatedCertFile); os.IsNotExist(err) { 412 | //generate cert file 413 | var certificate = x509.Certificate{ 414 | SerialNumber: serialNumber, 415 | Subject: pkix.Name{ 416 | CommonName: fmt.Sprintf("%s Root CA", ProjectName), 417 | Organization: []string{ProjectName}, 418 | }, 419 | NotBefore: time.Now(), 420 | NotAfter: time.Now().AddDate(DefaultDurationYears, 0, 0), 421 | IsCA: true, 422 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, 423 | KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 424 | BasicConstraintsValid: true, 425 | } 426 | var privateKey *rsa.PrivateKey 427 | privateKey, err = rsa.GenerateKey(rand.Reader, RSAKeyBits) 428 | if err != nil { 429 | return 430 | } 431 | fmt.Printf("private key with %d bits generated\n", RSAKeyBits) 432 | var publicKey = privateKey.PublicKey 433 | var certContent []byte 434 | certContent, err = x509.CreateCertificate(rand.Reader, &certificate, &certificate, &publicKey, privateKey) 435 | if err != nil { 436 | return 437 | } 438 | // Public key 439 | var certFile *os.File 440 | certFile, err = os.Create(generatedCertFile) 441 | if err != nil { 442 | return 443 | } 444 | if err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certContent}); err != nil { 445 | return 446 | } 447 | if err = certFile.Close(); err != nil { 448 | return 449 | } 450 | fmt.Printf("cert file '%s' generated\n", generatedCertFile) 451 | 452 | // Private key 453 | var keyFile *os.File 454 | keyFile, err = os.OpenFile(generatedKeyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultFilePerm) 455 | if err != nil { 456 | os.Remove(generatedCertFile) 457 | return 458 | } 459 | if err = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}); err != nil { 460 | os.Remove(generatedCertFile) 461 | return 462 | } 463 | if err = keyFile.Close(); err != nil { 464 | os.Remove(generatedCertFile) 465 | return 466 | } 467 | fmt.Printf("key file '%s' generated\n", generatedKeyFile) 468 | if err = updateAccess(session, generatedCertFile);err != nil{ 469 | return 470 | } 471 | if err = updateAccess(session, generatedKeyFile);err != nil{ 472 | return 473 | } 474 | } else { 475 | fmt.Printf("cert '%s', key '%s' already generated\n", generatedCertFile, generatedKeyFile) 476 | } 477 | 478 | //install path 479 | var installedPath = filepath.Join(session.ProjectPath, CertPathName) 480 | if err = ensurePath(installedPath, "cert install", session.UID, session.GID); err != nil{ 481 | return 482 | } 483 | var installedCertFile = filepath.Join(installedPath, certFileName) 484 | var installedKeyFile = filepath.Join(installedPath, keyFileName) 485 | if _, err = os.Stat(installedCertFile); os.IsNotExist(err) { 486 | if err = copyFile(generatedCertFile, installedCertFile); err != nil { 487 | return 488 | } else { 489 | fmt.Printf("'%s' copied to '%s'\n", generatedCertFile, installedCertFile) 490 | } 491 | updateAccess(session, installedCertFile) 492 | } else { 493 | fmt.Printf("cert file '%s' already installed\n", installedCertFile) 494 | } 495 | if _, err = os.Stat(installedKeyFile); os.IsNotExist(err) { 496 | if err = copyFile(generatedKeyFile, installedKeyFile); err != nil { 497 | return 498 | } else { 499 | fmt.Printf("'%s' copied to '%s'\n", generatedKeyFile, installedKeyFile) 500 | } 501 | updateAccess(session, installedKeyFile) 502 | } else { 503 | fmt.Printf("key file '%s' already installed\n", installedKeyFile) 504 | } 505 | var trustedCertFile = filepath.Join(TrustedPath, certFileName) 506 | if _, err = os.Stat(trustedCertFile); os.IsNotExist(err) { 507 | if err = copyFile(installedCertFile, trustedCertFile); err != nil { 508 | return 509 | } else { 510 | fmt.Printf("'%s' copied to '%s'\n", installedCertFile, trustedCertFile) 511 | } 512 | updateAccess(session, trustedCertFile) 513 | var cmd = exec.Command("update-ca-trust") 514 | if err = executeWithOutput(cmd); err != nil { 515 | return 516 | } else { 517 | fmt.Printf("'%s' updated\n", trustedCertFile) 518 | } 519 | } else { 520 | fmt.Printf("'%s' already installed\n", trustedCertFile) 521 | } 522 | session.CACertPath = installedCertFile 523 | session.CAKeyPath = installedKeyFile 524 | return 525 | } 526 | 527 | func enabledPortRanges(session SessionInfo, ranges []PortRange) (err error) { 528 | //#firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -m pkttype --pkt-type multicast -j ACCEPT 529 | //#firewall-cmd --zone=public --add-port=5599-5800/udp --permanent 530 | //#firewall-cmd --zone=public --add-port=5801-6000/tcp --permanent 531 | //#firewall-cmd --zone=public --add-port=15900-16000/tcp --permanent 532 | //#firewall-cmd --reload 533 | 534 | //enable multicast 535 | var cmd = exec.Command("firewall-cmd", "--permanent","--direct","--add-rule","ipv4","filter","INPUT","0","-m","pkttype","--pkt-type","multicast","-j","ACCEPT") 536 | if err = executeWithOutput(cmd);err != nil{ 537 | fmt.Printf("enable multicast warning: %s", err.Error()) 538 | } 539 | for _, config := range ranges{ 540 | if config.Begin != config.End{ 541 | cmd = exec.Command("firewall-cmd","--zone=public", "--permanent", fmt.Sprintf("--add-port=%d-%d/%s", config.Begin, config.End, config.Protocol)) 542 | }else{ 543 | cmd = exec.Command("firewall-cmd","--zone=public", "--permanent", fmt.Sprintf("--add-port=%d/%s", config.Begin, config.Protocol)) 544 | } 545 | if err = executeWithOutput(cmd);err != nil{ 546 | fmt.Printf("add ports warning: %s", err.Error()) 547 | } 548 | } 549 | cmd = exec.Command("firewall-cmd","--reload") 550 | return executeWithOutput(cmd) 551 | } 552 | 553 | func ensurePath(path, name string, uid, gid int) (err error) { 554 | if _, err = os.Stat(path);os.IsNotExist(err){ 555 | if err = os.MkdirAll(path, DefaultPathPerm);err != nil{ 556 | return 557 | }else if err = os.Chown(path, uid, gid);err != nil{ 558 | return 559 | }else{ 560 | fmt.Printf("%s path '%s' created\n", name, path) 561 | } 562 | } 563 | return nil 564 | } 565 | 566 | func updateAccess(session *SessionInfo, path string) (err error){ 567 | return os.Chown(path, session.UID, session.GID) 568 | } 569 | 570 | func enableExecuteAccess(session *SessionInfo, path string) (err error){ 571 | if err = os.Chown(path, session.UID, session.GID); err != nil{ 572 | return err 573 | } 574 | const ( 575 | ExecutePerm = 0740 576 | ) 577 | err = os.Chmod(path, ExecutePerm) 578 | return 579 | } 580 | 581 | func copyFile(src, dst string) error { 582 | var err error 583 | var srcfd *os.File 584 | var dstfd *os.File 585 | var srcinfo os.FileInfo 586 | 587 | if srcfd, err = os.Open(src); err != nil { 588 | return err 589 | } 590 | defer srcfd.Close() 591 | 592 | if dstfd, err = os.Create(dst); err != nil { 593 | return err 594 | } 595 | defer dstfd.Close() 596 | 597 | if _, err = io.Copy(dstfd, srcfd); err != nil { 598 | return err 599 | } 600 | if srcinfo, err = os.Stat(src); err != nil { 601 | return err 602 | } 603 | return os.Chmod(dst, srcinfo.Mode()) 604 | } 605 | 606 | func copyDir(src string, dst string) error { 607 | var err error 608 | var fds []os.FileInfo 609 | var srcinfo os.FileInfo 610 | 611 | if srcinfo, err = os.Stat(src); err != nil { 612 | return err 613 | } 614 | 615 | if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil { 616 | return err 617 | } 618 | 619 | if fds, err = ioutil.ReadDir(src); err != nil { 620 | return err 621 | } 622 | for _, fd := range fds { 623 | srcfp := path.Join(src, fd.Name()) 624 | dstfp := path.Join(dst, fd.Name()) 625 | 626 | if fd.IsDir() { 627 | if err = copyDir(srcfp, dstfp); err != nil { 628 | fmt.Println(err) 629 | } 630 | } else { 631 | if err = copyFile(srcfp, dstfp); err != nil { 632 | fmt.Println(err) 633 | } 634 | } 635 | } 636 | return nil 637 | } 638 | 639 | func executeWithOutput(cmd *exec.Cmd) (err error){ 640 | var output []byte 641 | if output, err = cmd.CombinedOutput(); err != nil{ 642 | err = errors.New(string(output)) 643 | } 644 | return 645 | } 646 | -------------------------------------------------------------------------------- /module_updater.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/project-nano/framework" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "path" 9 | "os/exec" 10 | "bytes" 11 | "strings" 12 | "github.com/pkg/errors" 13 | "crypto/sha1" 14 | "io" 15 | "time" 16 | ) 17 | type ResourcePath struct { 18 | Source string 19 | Target string 20 | } 21 | 22 | type ModuleBinary struct { 23 | Module string 24 | Binary string 25 | Resources []ResourcePath 26 | } 27 | 28 | func UpdateAllModules(forcibly bool) { 29 | var modules = map[string]ModuleBinary{ 30 | "core": ModuleBinary{"core", "core", nil}, 31 | "cell": ModuleBinary{"cell", "cell", nil}, 32 | "frontend": {"frontend", "frontend", []ResourcePath{{path.Join("bin", FrontEndFilesPath, FrontEndWebPath), FrontEndWebPath}}}, 33 | } 34 | 35 | var moduleOrder = []string{"core", "cell", "frontend"} 36 | 37 | const ( 38 | DefaultProjectPath = "/opt/nano" 39 | ) 40 | var projectPath string 41 | var err error 42 | projectPath, err = framework.InputString("Project Installed Path", DefaultProjectPath) 43 | if err != nil{ 44 | fmt.Printf("get installed path fail: %s\n", err.Error()) 45 | return 46 | } 47 | 48 | if _, err = os.Stat(projectPath); os.IsNotExist(err){ 49 | fmt.Printf("project path '%s' not exists\n", projectPath) 50 | return 51 | } 52 | 53 | var binaries []ModuleBinary 54 | 55 | for _, moduleName := range moduleOrder{ 56 | if _, err = os.Stat(filepath.Join(projectPath, moduleName)); os.IsNotExist(err){ 57 | continue 58 | }else if binary, exists := modules[moduleName]; exists{ 59 | binaries = append(binaries, binary) 60 | }else{ 61 | fmt.Printf("invalid module '%s' in path '%s'\n", moduleName, projectPath) 62 | return 63 | } 64 | } 65 | 66 | if 0 == len(binaries){ 67 | fmt.Println("no module binary available") 68 | return 69 | } 70 | for _, binary := range binaries { 71 | err = updateModule(projectPath, binary, forcibly) 72 | if err != nil{ 73 | fmt.Printf("update module '%s' fail: %s\n", binary.Module, err.Error()) 74 | return 75 | } 76 | } 77 | fmt.Printf("%d module(s) updated success\n", len(binaries)) 78 | } 79 | 80 | func updateModule(projectPath string, binary ModuleBinary, forcibly bool) (err error) { 81 | var sourceBinary = path.Join(BinaryPathName, binary.Binary) 82 | var binaryName = path.Join(projectPath, binary.Module, binary.Binary) 83 | if !forcibly { 84 | isIdentical, err := isIdentical(sourceBinary, binaryName) 85 | if err != nil{ 86 | return err 87 | } 88 | if isIdentical{ 89 | fmt.Printf("module %s already updated\n", binary.Module) 90 | return nil 91 | } 92 | } 93 | 94 | isRunning, err := isModuleRunning(binaryName) 95 | if err != nil{ 96 | return err 97 | } 98 | if isRunning{ 99 | //stop first 100 | if err = stopModule(binaryName); err != nil{ 101 | err = fmt.Errorf("stop binary '%s' fail: %s\n", binaryName, err.Error()) 102 | return 103 | } 104 | fmt.Printf("module %s stopped\n", binary.Module) 105 | const ( 106 | StopGap = time.Millisecond * 300 107 | ) 108 | time.Sleep(StopGap) 109 | } 110 | 111 | if err = copyFile(sourceBinary, binaryName); err != nil{ 112 | err = fmt.Errorf("overwrite binary '%s' fail: %s\n", binaryName, err.Error()) 113 | return 114 | } 115 | fmt.Printf("overwrite %s success\n", binaryName) 116 | if 0 != len(binary.Resources){ 117 | for _, resource := range binary.Resources{ 118 | var targetPath = path.Join(projectPath, binary.Module, resource.Target) 119 | if err = copyDir(resource.Source, targetPath); err != nil{ 120 | err = fmt.Errorf("overwrite to resource path '%s' fail: %s\n", targetPath, err.Error()) 121 | return 122 | } 123 | fmt.Printf("resoure %s overwritten to '%s'\n", resource.Source, targetPath) 124 | } 125 | } 126 | 127 | if isRunning{ 128 | //start again 129 | if err = startModule(binaryName); err != nil{ 130 | err = fmt.Errorf("restart binary '%s' fail: %s\n", binaryName, err.Error()) 131 | return 132 | } 133 | fmt.Printf("module %s restarted\n", binary.Module) 134 | } 135 | fmt.Printf("module %s update success\n", binary.Module) 136 | return nil 137 | } 138 | 139 | func isIdentical(source, target string) (identical bool, err error){ 140 | var files = []string{target, source} 141 | var hashResult [][]byte 142 | for _, filename := range files{ 143 | var fileStream *os.File 144 | fileStream, err = os.Open(filename) 145 | if err != nil{ 146 | return 147 | } 148 | defer fileStream.Close() 149 | var hashLoader = sha1.New() 150 | if _, err = io.Copy(hashLoader, fileStream); err != nil{ 151 | return 152 | } 153 | var fileHash = hashLoader.Sum(nil) 154 | hashResult = append(hashResult, fileHash) 155 | } 156 | const ( 157 | TargetHashOffset = iota 158 | SourceHashOffset 159 | ValidHashCount = 2 160 | ) 161 | if ValidHashCount != len(hashResult){ 162 | err = fmt.Errorf("invalid hash count %d", len(hashResult)) 163 | return false, err 164 | } 165 | 166 | return bytes.Equal(hashResult[TargetHashOffset], hashResult[SourceHashOffset]), nil 167 | } 168 | 169 | func isModuleRunning(binaryPath string) (running bool, err error) { 170 | var cmd = exec.Command(binaryPath, "status") 171 | var output bytes.Buffer 172 | cmd.Stdout = &output 173 | if err = cmd.Run(); err != nil{ 174 | return 175 | } 176 | const ( 177 | Keyword = "running" 178 | ) 179 | return strings.Contains(output.String(), Keyword), nil 180 | } 181 | 182 | func startModule(binaryPath string) (err error){ 183 | var cmd = exec.Command(binaryPath, "start") 184 | var output bytes.Buffer 185 | cmd.Stdout = &output 186 | if err = cmd.Run(); err != nil{ 187 | return 188 | } 189 | const ( 190 | Keyword = "fail" 191 | ) 192 | var content = output.String() 193 | if strings.Contains(content, Keyword){ 194 | //fail 195 | return errors.New(content) 196 | } 197 | return nil 198 | } 199 | 200 | func stopModule(binaryPath string) (err error){ 201 | var cmd = exec.Command(binaryPath, "stop") 202 | var output bytes.Buffer 203 | cmd.Stdout = &output 204 | if err = cmd.Run(); err != nil{ 205 | return 206 | } 207 | const ( 208 | Keyword = "fail" 209 | ) 210 | var content = output.String() 211 | if strings.Contains(content, Keyword){ 212 | //fail 213 | return errors.New(content) 214 | } 215 | return nil 216 | } 217 | --------------------------------------------------------------------------------