299 lines
6.2 KiB
Go
299 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/codeclysm/extract/v3"
|
|
"io"
|
|
"io/fs"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
func installMap(conn net.Conn, instance string, objmap map[string]interface{}) error {
|
|
package_path, has := objmap["path"]
|
|
if !has {
|
|
return errors.New("a path is required")
|
|
}
|
|
|
|
return install(conn, instance, package_path.(string))
|
|
}
|
|
|
|
func install(conn net.Conn, instance string, package_path string) error {
|
|
|
|
unpack_path, err := filepath.Abs(filepath.Join("/tmp", "boundaries"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if exists, _ := path_exists(unpack_path); exists {
|
|
err = os.RemoveAll(unpack_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = os.MkdirAll(unpack_path, 0775)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
package_path, err = filepath.Abs(package_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if is_dir, _ := is_directory(package_path); is_dir {
|
|
err = copy_directory(unpack_path, package_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
archive, err := os.Open(package_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = extract.Archive(context.TODO(), archive, unpack_path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
dir, _, err := find_infofile(unpack_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
info_path := filepath.Join(dir, "boundaries.json")
|
|
|
|
infofile_content, err := os.ReadFile(info_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var info map[string]interface{}
|
|
err = json.Unmarshal(infofile_content, &info)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
package_name, has := info["name"]
|
|
if !has {
|
|
return errors.New("infofile does not contain name field")
|
|
}
|
|
|
|
package_path, err = filepath.Abs(filepath.Join(instance, "apps", package_name.(string)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if exists, _ := path_exists(package_path); exists {
|
|
err := remove(conn, instance, package_name.(string), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = os.MkdirAll(package_path, 0775)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = copy_directory(package_path, dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.MkdirAll(filepath.Join(instance, "var", package_name.(string)), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
commands, has := info["command"]
|
|
if !has {
|
|
return errors.New("infofile does not contain command field")
|
|
}
|
|
|
|
targets := make(map[string]string)
|
|
|
|
for target, command := range commands.(map[string]interface{}) {
|
|
if target == "install" {
|
|
return_code, err := run_command(conn, command.(string), package_path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if return_code != 0 {
|
|
return errors.New("install script failed")
|
|
}
|
|
}
|
|
targets[target] = command.(string)
|
|
}
|
|
|
|
if _, has := targets["run"]; !has {
|
|
return errors.New("run target not found")
|
|
}
|
|
|
|
bin_files, has := info["bin"]
|
|
if has {
|
|
install_wrapped_bin, has := info["wrap_bin"]
|
|
if !has {
|
|
install_wrapped_bin = true
|
|
}
|
|
switch bin_files.(type) {
|
|
case string:
|
|
if install_wrapped_bin.(bool) {
|
|
err = install_bin(instance, bin_files.(string), "run", package_name.(string))
|
|
} else {
|
|
err = install_unwrapped_bin(instance, bin_files.(string), filepath.Join(package_path, targets["run"]))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case map[string]interface{}:
|
|
for target, path := range bin_files.(map[string]interface{}) {
|
|
if install_wrapped_bin.(bool) {
|
|
err = install_bin(instance, path.(string), target, package_name.(string))
|
|
} else {
|
|
err = install_unwrapped_bin(instance, path.(string), filepath.Join(package_path, targets[target]))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
default:
|
|
return errors.New("unknown type for bin")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func install_bin(instance string, path string, target string, program string) error {
|
|
bin_path, err := filepath.Abs(filepath.Join(instance, "exec", "bin", path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists, err := path_exists(bin_path); exists && err == nil {
|
|
err = os.Remove(bin_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
bnd_run_path, err := filepath.Abs(filepath.Join(instance, "exec", "bin", "bnd-run"))
|
|
|
|
contents := "#!/usr/bin/env bash\nexec " + bnd_run_path + " -i " + instance + " -t " + target + " -w $PWD " + program + " $@\n"
|
|
|
|
err = os.WriteFile(bin_path, []byte(contents), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func install_unwrapped_bin(instance string, path string, source string) error {
|
|
bin_path, err := filepath.Abs(filepath.Join(instance, "exec", "bin", path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists, err := path_exists(bin_path); exists && err == nil {
|
|
err = os.Remove(bin_path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
os.Symlink(source, bin_path)
|
|
|
|
return nil
|
|
}
|
|
|
|
func find_infofile(source string) (string, string, error) {
|
|
|
|
var found_file string = ""
|
|
var found_dir string = ""
|
|
|
|
fs.WalkDir(os.DirFS(source), ".", func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dir, file := filepath.Split(filepath.Join(source, path))
|
|
if file == "boundaries.json" {
|
|
found_file = file
|
|
found_dir = dir
|
|
}
|
|
return nil
|
|
})
|
|
if found_dir != "" && found_file != "" {
|
|
return found_dir, found_file, nil
|
|
} else {
|
|
return "", "", errors.New("no infofile found")
|
|
}
|
|
}
|
|
|
|
// Stolen from https://stackoverflow.com/a/72246196/19680146
|
|
func copy_directory(dst, src string) error {
|
|
|
|
return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// copy to this path
|
|
outpath := filepath.Join(dst, strings.TrimPrefix(path, src))
|
|
|
|
if info.IsDir() {
|
|
os.MkdirAll(outpath, info.Mode())
|
|
return nil // means recursive
|
|
}
|
|
|
|
// handle irregular files
|
|
if !info.Mode().IsRegular() {
|
|
switch info.Mode().Type() & os.ModeType {
|
|
case os.ModeSymlink:
|
|
link, err := os.Readlink(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.Symlink(link, outpath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// copy contents of regular file efficiently
|
|
|
|
// open input
|
|
in, _ := os.Open(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
|
|
// create output
|
|
fh, err := os.Create(outpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fh.Close()
|
|
|
|
// make it the same
|
|
fh.Chmod(info.Mode())
|
|
|
|
// copy content
|
|
_, err = io.Copy(fh, in)
|
|
return err
|
|
})
|
|
}
|
|
|
|
func is_directory(path string) (bool, error) {
|
|
fileInfo, err := os.Stat(path)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return fileInfo.IsDir(), err
|
|
}
|