aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/app/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/app/main.go')
-rw-r--r--cmd/app/main.go137
1 files changed, 119 insertions, 18 deletions
diff --git a/cmd/app/main.go b/cmd/app/main.go
index 00bceb57..9b51d601 100644
--- a/cmd/app/main.go
+++ b/cmd/app/main.go
@@ -1,24 +1,25 @@
-// The app program is a proof-of-concept frontend for cmd/hakurei.
-//
-// This program is not covered by the compatibility promise. The command line
-// interface and configuration syntax may change at any time.
package main
import (
"context"
"errors"
+ "fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"path/filepath"
+ "slices"
+ "strconv"
+ "strings"
"syscall"
"hakurei.app/check"
"hakurei.app/command"
"hakurei.app/fhs"
"hakurei.app/hst"
+ "hakurei.app/internal/outcome"
"hakurei.app/message"
)
@@ -36,7 +37,7 @@ func main() {
flagBase string
flagInsecure bool
- base, template, initial *check.Absolute
+ b *base
)
c := command.New(os.Stderr, log.Printf, "app", func([]string) (err error) {
msg.SwapVerbose(flagVerbose)
@@ -44,14 +45,15 @@ func main() {
if flagBase == "" {
flagBase = "state"
}
+
+ var a *check.Absolute
if flagBase, err = filepath.Abs(flagBase); err != nil {
return
- } else if base, err = check.NewAbs(flagBase); err != nil {
+ } else if a, err = check.NewAbs(flagBase); err != nil {
return
}
+ b = (*base)(a)
- template = base.Append("template")
- initial = base.Append("initial")
return
}).Flag(
&flagVerbose,
@@ -76,7 +78,7 @@ func main() {
"enter", "Enter mutable state template",
func(args []string) error {
if len(args) != 1 {
- return list(template, true)
+ return list(b.append("template"), true)
}
config := hst.Config{
@@ -86,9 +88,9 @@ func main() {
Filesystem: []hst.FilesystemConfigJSON{
{FilesystemConfig: &hst.FSOverlay{
Target: fhs.AbsRoot,
- Lower: []*check.Absolute{initial},
- Upper: template.Append(args[0]),
- Work: base.Append("work", args[0]),
+ Lower: []*check.Absolute{b.initial()},
+ Upper: b.template(args[0]),
+ Work: b.work(args[0]),
}},
{FilesystemConfig: &hst.FSEphemeral{
Target: fhs.AbsTmp,
@@ -122,7 +124,7 @@ func main() {
config.Container.Home = a
}
- remove, err := acquireTemplate(base, args[0])
+ remove, err := acquireTemplate(b, args[0])
if err != nil {
return err
}
@@ -148,19 +150,19 @@ func main() {
"run", "Start the named application",
func(args []string) error {
if len(args) < 1 {
- return list(base.Append("app"), false)
+ return list(b.append("app"), false)
}
var config *hst.Config
var r io.Reader
- f, err := os.Open(base.Append("app", args[0]).String())
+ f, err := os.Open(b.append("app", args[0]).String())
if err != nil {
return err
}
r = f
var common *os.File
- if common, err = os.Open(base.Append("common").String()); err != nil {
+ if common, err = os.Open(b.append("common").String()); err != nil {
if !errors.Is(err, os.ErrNotExist) {
_ = f.Close()
return err
@@ -170,7 +172,7 @@ func main() {
}
var name string
- config, err = parse(args[0], base, r, &name)
+ config, err = parse(args[0], b, r, &name)
if closeErr := f.Close(); err == nil {
err = closeErr
}
@@ -187,7 +189,7 @@ func main() {
config.Container.Args[2] = flagCommand
}
- if err = enterTemplate(base, name); err != nil {
+ if err = enterTemplate(b, name); err != nil {
return err
}
return run(ctx, msg, flagInsecure, config, args[1:]...)
@@ -200,6 +202,105 @@ func main() {
)
}
+ c.NewCommand(
+ "id", "Show user/group id of the specified appid",
+ func(args []string) error {
+ var appid int
+ switch len(args) {
+ case 0:
+ log.Println("appid not specified, assuming reserved user")
+ break
+
+ case 1:
+ var err error
+ appid, err = strconv.Atoi(args[0])
+ if err != nil {
+ return os.ErrInvalid
+ }
+ break
+
+ default:
+ return errors.New("id requires 1 argument")
+ }
+
+ fmt.Println(hst.ToUser(outcome.Info().User, appid))
+ return nil
+ },
+ )
+
+ c.NewCommand(
+ "next", "Find next unused identity",
+ func([]string) error {
+ var names []string
+ if dents, err := os.ReadDir(b.append("app").String()); err != nil {
+ return err
+ } else {
+ names = make([]string, 0, len(dents))
+ for _, dent := range dents {
+ name := dent.Name()
+ if dent.IsDir() || (len(name) > 0 && name[0] == '.') {
+ continue
+ }
+ names = append(names, name)
+ }
+ }
+
+ apps := make([]*hst.Config, len(names))
+ for i, name := range names {
+ r, err := os.Open(b.append("app", name).String())
+ if err != nil {
+ return err
+ }
+
+ apps[i], err = parse(name, b, r, nil)
+ if closeErr := r.Close(); err == nil {
+ err = closeErr
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ p := make(map[int][]*hst.Config)
+ for _, config := range apps {
+ p[config.Identity] = append(p[config.Identity], config)
+ }
+
+ identities := make([]int, 0, len(p))
+ for identity, a := range p {
+ identities = append(identities, identity)
+ if msg.IsVerbose() && len(a) != 1 {
+ ids := make([]string, len(a))
+ for i, config := range a {
+ ids[i] = config.ID
+ }
+ slices.Sort(ids)
+ msg.Verbosef(
+ "%s shares identity %d",
+ strings.Join(ids, ", "), identity,
+ )
+ }
+ }
+
+ if len(identities) == 0 {
+ // 0 is the reserved identity
+ fmt.Println(1)
+ return nil
+ }
+
+ slices.Sort(identities)
+ next := identities[0] - 1
+ for _, identity := range identities {
+ if identity != next+1 {
+ break
+ }
+ next = identity
+ }
+ fmt.Println(next + 1)
+ return nil
+ },
+ )
+
c.MustParse(os.Args[1:], func(err error) {
if e, ok := errors.AsType[*exec.ExitError](err); ok && e != nil {
os.Exit(e.ExitCode())