初版功能完成
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled

This commit is contained in:
qsc
2026-08-29 13:12:17 +08:00
commit 142e5dc7d6
217 changed files with 21313 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package serverwg
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// LoadOrCreatePrivateKey reads a Server key or atomically creates a 0600 file.
func LoadOrCreatePrivateKey(path string) (wgtypes.Key, error) {
if raw, err := os.ReadFile(path); err == nil {
key, err := wgtypes.ParseKey(strings.TrimSpace(string(raw)))
if err != nil {
return wgtypes.Key{}, fmt.Errorf("parse Server private key %q: %w", path, err)
}
if err := os.Chmod(path, 0o600); err != nil {
return wgtypes.Key{}, fmt.Errorf("restrict Server private key permissions: %w", err)
}
return key, nil
} else if !errors.Is(err, os.ErrNotExist) {
return wgtypes.Key{}, fmt.Errorf("read Server private key %q: %w", path, err)
}
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return wgtypes.Key{}, fmt.Errorf("create Server key directory: %w", err)
}
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
return wgtypes.Key{}, fmt.Errorf("generate Server WireGuard private key: %w", err)
}
directory := filepath.Dir(path)
file, err := os.CreateTemp(directory, ".server-wg-*.tmp")
if err != nil {
return wgtypes.Key{}, fmt.Errorf("create temporary Server private key: %w", err)
}
temporaryPath := file.Name()
defer os.Remove(temporaryPath)
if err := file.Chmod(0o600); err != nil {
file.Close()
return wgtypes.Key{}, fmt.Errorf("restrict temporary Server private key: %w", err)
}
writeErr := func() error {
if _, err := file.WriteString(key.String() + "\n"); err != nil {
return err
}
return file.Sync()
}()
closeErr := file.Close()
if writeErr != nil {
return wgtypes.Key{}, fmt.Errorf("write Server private key: %w", writeErr)
}
if closeErr != nil {
return wgtypes.Key{}, fmt.Errorf("close Server private key: %w", closeErr)
}
// A hard link publishes the fully flushed inode without overwriting a key
// concurrently created by another Server process. Temporary and final files
// are guaranteed to be on the same data-directory filesystem.
if err := os.Link(temporaryPath, path); err != nil {
if errors.Is(err, os.ErrExist) {
return LoadOrCreatePrivateKey(path)
}
return wgtypes.Key{}, fmt.Errorf("publish Server private key %q: %w", path, err)
}
return key, nil
}