grim/hgkeeper

Use Go 1.22 and update dependencies
default tip
2 months ago, aklitzing
f33f223bc8fe
Use Go 1.22 and update dependencies

Reviewed at https://reviews.imfreedom.org/r/2949/
package setup
import (
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"text/template"
"golang.org/x/crypto/ssh"
"keep.imfreedom.org/grim/hgkeeper/globals"
"keep.imfreedom.org/grim/hgkeeper/hg"
)
type Command struct {
AdminUsername string `kong:"flag,name='admin-username',env='HGK_ADMIN_USERNAME',help='the name of the initial admin user',required='true'"`
AdminSSHPubkey string `kong:"flag,name='admin-pubkey',env='HGK_ADMIN_PUBKEY',help='the path to the ssh pubkey to use for the admin',required='true',type='existingfile'"`
}
//go:embed resources/*
var resources embed.FS
var (
hgUsername = "hgkeeper"
commitMessage = "initial revision"
)
func runCmd(cmd *hg.Command) error {
output, err := cmd.Cmd().CombinedOutput()
if len(output) > 0 {
fmt.Printf("%s\n", output)
}
return err
}
func (c *Command) createAdminRepo(reposPath, adminRepo string) error {
adminPath := filepath.Join(reposPath, adminRepo)
// create the admin repo
if err := runCmd(hg.Init(adminPath)); err != nil {
return err
}
filenames := []string{}
err := fs.WalkDir(resources, "resources", func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
if entry.IsDir() {
return nil
}
// Remove the resources prefix.
stripped := strings.TrimPrefix(path, "resources")
// Figure out the absolute path of the target file.
absname := filepath.Join(adminPath, stripped)
// We can't start a name with . so we have to manually replace it.
absname = strings.Replace(absname, "/dothg", "/.hg", 1)
// We need to know the directory name so we can create it.
dirname := filepath.Dir(absname)
// if we don't have the directory create it
if _, err := os.Stat(dirname); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(dirname, 0755); err != nil {
return err
}
} else {
return err
}
}
// If this is a template file, execute it
if filepath.Ext(absname) == ".template" {
absname = strings.TrimSuffix(absname, ".template")
data, err := resources.ReadFile(path)
if err != nil {
return err
}
tplate, err := template.New(path).Parse(string(data))
if err != nil {
return err
}
file, err := os.Create(absname)
if err != nil {
return err
}
defer file.Close()
tplateData := struct {
AdminRepo string
AdminUsername string
}{
AdminRepo: "/" + adminRepo,
AdminUsername: c.AdminUsername,
}
if err := tplate.Execute(file, tplateData); err != nil {
fmt.Printf("error: %v\n", err)
return err
}
} else {
// If it's not a template just write it out
data, err := resources.ReadFile(path)
if err != nil {
return err
}
if err := os.WriteFile(absname, data, 0644); err != nil {
return err
}
}
// Figure out which files need to be committed to the repository.
rel, err := filepath.Rel(adminPath, absname)
if err != nil {
return err
}
if !strings.HasPrefix(rel, fmt.Sprintf(".hg%c", filepath.Separator)) {
filenames = append(filenames, rel)
}
return nil
})
if err != nil {
return err
}
// create our keys directory
keysDir := filepath.Join(adminPath, "keys")
if err := os.MkdirAll(keysDir, 0755); err != nil {
return err
}
// now copy the admin key into the keys directory
pubkey, err := os.ReadFile(c.AdminSSHPubkey)
if err != nil {
return err
}
adminPubkey := filepath.Join(keysDir, c.AdminUsername)
relAdminPubkey, err := filepath.Rel(adminPath, adminPubkey)
if err != nil {
return err
}
filenames = append(filenames, relAdminPubkey)
if err := os.WriteFile(adminPubkey, pubkey, 0644); err != nil {
return err
}
// add our files
if err := runCmd(hg.Add(adminPath, filenames...)); err != nil {
return err
}
// commit our changes
if err := runCmd(hg.Commit(adminPath, hgUsername, commitMessage)); err != nil {
return err
}
return nil
}
func isPubkey(filename string) error {
bytes, err := os.ReadFile(filename)
if err != nil {
return err
}
// ssh.ParsePublicKey wants the wire format which includes a size
// parameter. However, since every entry of an authorized_key file is
// just a straight public key, we can use ParseAuthorizedKey to check if
// the file is a public key.
if _, _, _, _, err := ssh.ParseAuthorizedKey(bytes); err != nil {
return err
}
return nil
}
func (c *Command) Run(g *globals.Globals) error {
// make sure the adminPubkey is an SSH pubkey as it is too easy to
// accidentally give the path to the private key rather than the public
// key, and we do no want the private key in the repository.
if err := isPubkey(c.AdminSSHPubkey); err != nil {
return fmt.Errorf("%s is not a public key file", c.AdminSSHPubkey)
}
// create the admin repo
if err := c.createAdminRepo(g.ReposPath, g.AdminRepo); err != nil {
// do clean up
return err
}
return nil
}