mirror of
https://github.com/Websoft9/websoft9.git
synced 2024-11-22 07:30:24 +00:00
250 lines
No EOL
7.4 KiB
Go
250 lines
No EOL
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math/big"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
portainerURL = "http://localhost:9000/api"
|
|
maxRetries = 5
|
|
retryDelay = 5 * time.Second
|
|
charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@$()_"
|
|
credentialFilePath = "/data/credential"
|
|
initFlagFilePath = "/data/init.flag"
|
|
)
|
|
|
|
type Credentials struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
func main() {
|
|
// 检查初始化标志文件是否存在
|
|
if _, err := os.Stat(initFlagFilePath); err == nil {
|
|
log.Println("Initialization has already been completed by another instance.")
|
|
startPortainer()
|
|
return
|
|
}
|
|
|
|
// 启动 Portainer
|
|
// cmd := exec.Command("/portainer")
|
|
cmd := exec.Command("/portainer", os.Args[1:]...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Fatalf("Failed to start Portainer: %v", err)
|
|
}
|
|
|
|
// 等待 Portainer 启动
|
|
waitForPortainer()
|
|
|
|
// 初始化 Portainer
|
|
adminUsername := "admin"
|
|
adminPassword := generateRandomPassword(12)
|
|
|
|
if err := initializePortainerUser(adminUsername, adminPassword); err != nil {
|
|
log.Fatalf("Failed to initialize Portainer user: %v", err)
|
|
} else {
|
|
if err := writeCredentialsToFile(adminPassword); err != nil {
|
|
log.Fatalf("Failed to write credentials to file: %v", err)
|
|
} else {
|
|
if err := initializeLocalEndpoint(adminUsername, adminPassword); err != nil {
|
|
log.Fatalf("Failed to initialize local endpoint: %v", err)
|
|
} else {
|
|
fmt.Println("Portainer initialization completed successfully.")
|
|
// 创建初始化标志文件
|
|
if err := ioutil.WriteFile(initFlagFilePath, []byte("initialized"), 0644); err != nil {
|
|
log.Fatalf("Failed to create initialization flag file: %v", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 等待 Portainer 进程结束
|
|
if err := cmd.Wait(); err != nil {
|
|
log.Fatalf("Portainer process exited with error: %v", err)
|
|
}
|
|
}
|
|
|
|
func startPortainer() {
|
|
cmd := exec.Command("/portainer")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
log.Fatalf("Failed to start Portainer: %v", err)
|
|
}
|
|
|
|
// 等待 Portainer 进程结束
|
|
if err := cmd.Wait(); err != nil {
|
|
log.Fatalf("Portainer process exited with error: %v", err)
|
|
}
|
|
}
|
|
|
|
func waitForPortainer() {
|
|
timeout := time.Duration(60) * time.Second
|
|
start := time.Now()
|
|
|
|
for {
|
|
resp, err := http.Get(portainerURL + "/system/status")
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
|
fmt.Println("Portainer is up!")
|
|
break
|
|
}
|
|
|
|
if time.Since(start) > timeout {
|
|
fmt.Println("Timeout waiting for Portainer")
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Println("Waiting for Portainer...")
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
}
|
|
|
|
func generateRandomPassword(length int) string {
|
|
password := make([]byte, length)
|
|
for i := range password {
|
|
char, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
password[i] = charset[char.Int64()]
|
|
}
|
|
return string(password)
|
|
}
|
|
|
|
func initializePortainerUser(username, password string) error {
|
|
requestBody := Credentials{Username: username, Password: password}
|
|
jsonBody, err := json.Marshal(requestBody)
|
|
if err != nil {
|
|
return fmt.Errorf("error marshaling request body: %w", err)
|
|
}
|
|
|
|
resp, err := retryRequest("POST", portainerURL+"/users/admin/init", "application/json", bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("error making request to Portainer API: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusConflict {
|
|
return nil
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
|
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func initializeLocalEndpoint(username, password string) error {
|
|
authBody := Credentials{Username: username, Password: password}
|
|
jsonBody, err := json.Marshal(authBody)
|
|
if err != nil {
|
|
return fmt.Errorf("error marshaling auth body: %w", err)
|
|
}
|
|
|
|
resp, err := retryRequest("POST", portainerURL+"/auth", "application/json", bytes.NewBuffer(jsonBody))
|
|
if err != nil {
|
|
return fmt.Errorf("error authenticating with Portainer API: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
|
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
|
|
}
|
|
|
|
var authResult map[string]string
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading authentication response: %w", err)
|
|
}
|
|
|
|
err = json.Unmarshal(body, &authResult)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing authentication response: %w", err)
|
|
}
|
|
|
|
jwtToken := authResult["jwt"]
|
|
|
|
endpointBody := url.Values{}
|
|
endpointBody.Set("Name", "local")
|
|
endpointBody.Set("EndpointCreationType", "1")
|
|
|
|
req, err := http.NewRequest("POST", portainerURL+"/endpoints", strings.NewReader(endpointBody.Encode()))
|
|
if err != nil {
|
|
return fmt.Errorf("error creating endpoint request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Set("Authorization", "Bearer "+jwtToken)
|
|
|
|
client := &http.Client{}
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating endpoint in Portainer API: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusConflict {
|
|
body, _ := ioutil.ReadAll(resp.Body)
|
|
return fmt.Errorf("unexpected response status: %d, body: %s", resp.StatusCode, body)
|
|
}
|
|
|
|
if resp.StatusCode == http.StatusConflict {
|
|
fmt.Println("Endpoint already exists, but this is considered a success.")
|
|
} else {
|
|
fmt.Println("Endpoint created successfully.")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeCredentialsToFile(password string) error {
|
|
err := ioutil.WriteFile(credentialFilePath, []byte(password), 0755)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing password to file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func retryRequest(method, url, contentType string, body *bytes.Buffer) (*http.Response, error) {
|
|
client := &http.Client{}
|
|
for i := 0; i < maxRetries; i++ {
|
|
var req *http.Request
|
|
var err error
|
|
|
|
if body != nil {
|
|
req, err = http.NewRequest(method, url, bytes.NewBuffer(body.Bytes()))
|
|
} else {
|
|
req, err = http.NewRequest(method, url, nil)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", contentType)
|
|
|
|
resp, err := client.Do(req)
|
|
if err == nil {
|
|
return resp, nil
|
|
}
|
|
|
|
log.Printf("Request failed: %v. Retrying in %v...", err, retryDelay)
|
|
time.Sleep(retryDelay)
|
|
}
|
|
return nil, fmt.Errorf("max retries reached")
|
|
} |