You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
5.8 KiB
Go
262 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"strconv"
|
|
"syscall"
|
|
)
|
|
|
|
const usage = `workspace - Use a containerized workspace
|
|
|
|
COMMANDS
|
|
|
|
dump-init-files: Dump emacs/bash init files to home directory, overwriting existing files.
|
|
List of files: ~/.bash_aliases, ~/.emacs, ~/.custom.el
|
|
`
|
|
|
|
//go:embed emacs/init.el
|
|
var initLisp string
|
|
|
|
//go:embed emacs/custom.el
|
|
var customLisp string
|
|
|
|
//go:embed bash/bash_aliases
|
|
var bashAliases string
|
|
|
|
func EntryPoint(command []string) error {
|
|
if len(command) == 0 {
|
|
command = []string{"/bin/bash", "-i"}
|
|
}
|
|
|
|
uidString := os.Getenv("WORKSPACE_USER")
|
|
uid, err := strconv.Atoi(uidString)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Failed to parse UID from WORKSPACE_USER env var with contents \"%s\". Error: %w",
|
|
os.Getenv("WORKSPACE_USER"),
|
|
err,
|
|
)
|
|
}
|
|
|
|
dockerGid, err := strconv.Atoi(os.Getenv("WORKSPACE_DOCKER_GID"))
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Failed to parse GID from WORKSPACE_DOCKER_GID env var with contents \"%s\". Error: %w",
|
|
os.Getenv("WORKSPACE_DOCKER_GID"),
|
|
err,
|
|
)
|
|
}
|
|
|
|
err = syscall.Setgroups([]int{dockerGid, sudoGid})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = syscall.Setgid(uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = syscall.Setuid(uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path, err := exec.LookPath(command[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
SetEnvVars()
|
|
|
|
return syscall.Exec(path, command, os.Environ())
|
|
}
|
|
|
|
func Run(detach bool, command []string) error {
|
|
workDir, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
curUser, err := user.Current()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
home := os.Getenv("HOME")
|
|
dockerGroup, err := user.LookupGroup("docker")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dockerCommand := []string{
|
|
"/bin/docker", "run", "--network=host",
|
|
"--workdir=" + workDir,
|
|
"-e", "DISPLAY=" + os.Getenv("DISPLAY"),
|
|
"-e", "WORKSPACE_USER=" + curUser.Uid,
|
|
"-e", "WORKSPACE_USERNAME=" + curUser.Name,
|
|
"-e", "WORKSPACE_DOCKER_GID=" + dockerGroup.Gid,
|
|
"-e", "HOME=" + home,
|
|
"-h", os.Getenv("HOSTNAME"),
|
|
"-v", "/var/run/docker.sock:/var/run/docker.sock",
|
|
"-v", "/etc/subuid:/etc/subuid:ro",
|
|
"-v", "/etc/subgid:/etc/subgid:ro",
|
|
"-v", "/etc/hosts:/etc/hosts:ro",
|
|
"-v", "/etc/passwd:/etc/passwd:ro",
|
|
"-v", "/etc/resolv.conf:/etc/resolv.conf:ro",
|
|
"-v", "/etc/sudoers:/etc/sudoers:ro",
|
|
"-v", "/etc/group:/etc/group:ro",
|
|
"-v", home + ":" + home,
|
|
"-e", "SSH_AGENT_LAUNCHER=" + os.Getenv("SSH_AGENT_LAUNCHER"),
|
|
"-e", "SSH_AUTH_SOCK=" + os.Getenv("SSH_AUTH_SOCK"),
|
|
"-e", "PULSE_SERVER=unix:/run/user/" + curUser.Uid + "/pulse/native",
|
|
"-e", "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/" + curUser.Uid + "/bus",
|
|
"-e", "TERM=" + os.Getenv("TERM"),
|
|
"-v", "/tmp/.X11-unix:/tmp/.X11-unix",
|
|
"-v", "/run/user/" + curUser.Uid + ":/run/user/" + curUser.Uid,
|
|
"-v", "/dev/snd",
|
|
}
|
|
|
|
if detach {
|
|
dockerCommand = append(dockerCommand, "--detach")
|
|
}
|
|
|
|
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) != 0 {
|
|
dockerCommand = append(dockerCommand, "-ti")
|
|
}
|
|
|
|
fileInfo, err := os.Lstat(home)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Failed to determine whether home directory is a symbolic link: %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
destination, err := os.Readlink(home)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"Failed to determine whether home directory is a symbolic link: %w",
|
|
err,
|
|
)
|
|
}
|
|
|
|
dockerCommand = append(dockerCommand, "-v", destination+":"+destination)
|
|
}
|
|
|
|
dockerCommand = append(dockerCommand, "hugotty/workspace:latest")
|
|
dockerCommand = append(dockerCommand, command...)
|
|
|
|
return syscall.Exec(dockerCommand[0], dockerCommand, os.Environ())
|
|
}
|
|
|
|
func SetEnvVars() {
|
|
home := os.Getenv("HOME")
|
|
os.Setenv("PATH", home+"/bin:"+os.Getenv("PATH"))
|
|
os.Setenv("PATH", home+"/dotfiles/bin:"+os.Getenv("PATH"))
|
|
|
|
// PHP
|
|
os.Setenv("PATH", home+"/.config/composer/vendor/bin:"+os.Getenv("PATH"))
|
|
|
|
// Node
|
|
os.Setenv("PATH", home+"/.npm_packages/bin:"+os.Getenv("PATH"))
|
|
|
|
// Perl
|
|
os.Setenv("PATH", home+"/perl5/perlbrew/bin:"+os.Getenv("PATH"))
|
|
|
|
// Rust
|
|
os.Setenv("PATH", home+"/.cargo/bin:"+os.Getenv("PATH"))
|
|
|
|
// Locally installed python packages
|
|
os.Setenv("PATH", home+"/.local/bin:"+os.Getenv("PATH"))
|
|
|
|
// Home for virtualenvs
|
|
os.Setenv("WORKON_HOME", home+"/.local/share/virtualenvs")
|
|
|
|
// GO Stuff
|
|
os.Setenv("GOPATH", home+"/go")
|
|
os.Setenv("PATH", home+"/go/bin:"+os.Getenv("PATH"))
|
|
os.Setenv("GOPRIVATE", "git.snorba.art")
|
|
}
|
|
|
|
func DumpStringToFile(data string, path string) error {
|
|
file, err := os.Create(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
writer := bufio.NewWriter(file)
|
|
_, err = writer.WriteString(data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return writer.Flush()
|
|
}
|
|
|
|
func main() {
|
|
var cmd string
|
|
if len(os.Args) > 1 {
|
|
cmd = os.Args[1]
|
|
}
|
|
|
|
var detach bool
|
|
run := flag.NewFlagSet("run", flag.ExitOnError)
|
|
run.BoolVar(
|
|
&detach,
|
|
"detach",
|
|
false,
|
|
"Whether or not to detach from the container after running the command",
|
|
)
|
|
|
|
switch cmd {
|
|
case "entrypoint":
|
|
err := EntryPoint(os.Args[2:])
|
|
if err != nil {
|
|
fmt.Println("error executing entrypoint command", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
case "run":
|
|
err := run.Parse(os.Args[2:])
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
err = Run(detach, run.Args())
|
|
if err != nil {
|
|
fmt.Println("error running command in container", err)
|
|
os.Exit(1)
|
|
}
|
|
case "dump-init-files":
|
|
home := os.Getenv("HOME")
|
|
|
|
files := map[string]string{
|
|
home + "/.emacs": initLisp,
|
|
home + "/.custom.el": customLisp,
|
|
home + "/.bash_aliases": bashAliases,
|
|
}
|
|
|
|
for file, contents := range files {
|
|
fmt.Fprintln(os.Stderr, "Dumping to "+file)
|
|
err := DumpStringToFile(contents, file)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
default:
|
|
fmt.Println(usage)
|
|
fmt.Println("run COMMAND: Start workspace container and run COMMAND within it")
|
|
run.Usage()
|
|
}
|
|
}
|