blob: 1d41ba60e433e1ab2cd4f2503cd28a88170e391f [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sysinit
// TODO(cnicolaou): will need to figure out a simple of way of handling the
// different init systems supported by various versions of linux. One simple
// option is to just include them in the name when installing - e.g.
// simplevns-upstart.
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"syscall"
)
// action is a var so we can override it for testing.
var action = func(command, action, service string) error {
cmd := exec.Command(command, action, service)
if os.Geteuid() == 0 && os.Getuid() > 0 {
// Set uid to root (e.g. when running from a suid binary),
// otherwise initctl doesn't work.
sysProcAttr := new(syscall.SysProcAttr)
sysProcAttr.Credential = new(syscall.Credential)
sysProcAttr.Credential.Gid = uint32(0)
sysProcAttr.Credential.Uid = uint32(0)
cmd.SysProcAttr = sysProcAttr
}
// Clear env. In particular, initctl doesn't like USER being set to
// something other than root.
cmd.Env = []string{}
output, err := cmd.CombinedOutput()
fmt.Fprintf(os.Stderr, "%s output: for %s %s: %s\n", command, action, service, output)
return err
}
var (
upstartDir = "/etc/init"
upstartBin = "/sbin/initctl"
systemdDir = "/lib/systemd/system" // This works for both rpi and edison (/usr/lib does not)
systemdTmpFileDir = "/usr/lib/tmpfiles.d"
dockerDir = "/home/veyron/init"
)
// InitSystem attempts to determine what kind of init system is in use on
// the platform that it is run on. It recognises upstart and systemd by
// testing for the presence of the initctl and systemctl commands. upstart
// is tested for first and hence is preferred in the unlikely case that both
// are installed. Docker containers do not support upstart and systemd and
// for them we have our own init system that uses the daemon command to
// start/stop/respawn jobs.
func InitSystem() string {
// NOTE(spetrovic): This check is kind of a hack. Ideally, we would
// detect a docker system by looking at the "container=lxc" environment
// variable. However, we run sysinit during image creation, at which
// point we're on a native system and this variable isn't set.
if fi, err := os.Stat("/home/veyron/init"); err == nil && fi.Mode().IsDir() {
return "docker"
}
if fi, err := os.Stat(upstartBin); err == nil {
if (fi.Mode() & 0100) != 0 {
return "upstart"
}
}
if findSystemdSystemCtl() != "" {
return "systemd"
}
return ""
}
// New returns the appropriate implementation of InstallSystemInit for the
// underlying system.
func New(system string, sd *ServiceDescription) InstallSystemInit {
switch system {
case "docker":
return (*DockerService)(sd)
case "upstart":
return (*UpstartService)(sd)
case "systemd":
return (*SystemdService)(sd)
default:
return nil
}
}
///////////////////////////////////////////////////////////////////////////////
// Upstart support
// See http://upstart.ubuntu.com/cookbook/ for info on upstart
// UpstartService is the implementation of InstallSystemInit interfacing with
// the Upstart system.
type UpstartService ServiceDescription
var upstartTemplate = `# This file was auto-generated by the Vanadium SysInit tool.
# Date: {{.Date}}
#
# {{.Service}} - {{.Description}}
#
# Upstart config for Ubuntu-GCE
description "{{.Description}}"
start on runlevel [2345]
stop on runlevel [!2345]
{{if .Environment}}
# Environment variables
{{range $var, $value := .Environment}}
env {{$var}}={{$value}}{{end}}
{{end}}
respawn
respawn limit 10 5
umask 022
pre-start script
test -x {{.Binary}} || { stop; exit 0; }
mkdir -p -m0755 /var/log/veyron
chown -R {{.User}} /var/log/veyron
end script
script
set -e
echo '{{.Service}} starting'
exec sudo -u {{.User}} {{range $cmd := .Command}} {{$cmd}}{{end}}
end script
`
// Implements the InstallSystemInit method.
func (u *UpstartService) Install() error {
file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
return (*ServiceDescription)(u).writeTemplate(upstartTemplate, file)
}
// Print implements the InstallSystemInit method.
func (u *UpstartService) Print() error {
return (*ServiceDescription)(u).writeTemplate(upstartTemplate, "")
}
// Implements the InstallSystemInit method.
func (u *UpstartService) Uninstall() error {
// For now, ignore any errors returned by Stop, since Stop complains
// when there is no instance to stop.
// TODO(caprita): Only call Stop if there are running instances.
u.Stop()
file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
return os.Remove(file)
}
// Start implements the InstallSystemInit method.
func (u *UpstartService) Start() error {
return action(upstartBin, "start", u.Service)
}
// Stop implements the InstallSystemInit method.
func (u *UpstartService) Stop() error {
return action(upstartBin, "stop", u.Service)
}
///////////////////////////////////////////////////////////////////////////////
// Systemd support
// SystemdService is the implementation of InstallSystemInit interfacing with
// the Systemd system.
type SystemdService ServiceDescription
const systemdTemplate = `# This file was auto-generated by the Vanadium SysInit tool.
# Date: {{.Date}}
#
# {{.Service}} - {{.Description}}
#
[Unit]
Description={{.Description}}
After=openntpd.service
[Service]
User={{.User}}{{if .Environment}}{{println}}Environment={{range $var, $value := .Environment}}"{{$var}}={{$value}}" {{end}}{{end}}
ExecStart={{range $cmd := .Command}}{{$cmd}} {{end}}
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
RestartSecs=10
StandardOutput=syslog
[Install]
WantedBy=multi-user.target
`
// Install implements the InstallSystemInit method.
func (s *SystemdService) Install() error {
file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
if err := (*ServiceDescription)(s).writeTemplate(systemdTemplate, file); err != nil {
return fmt.Errorf("failed to write template (uid= %d, euid= %d): %v", os.Getuid(), os.Geteuid(), err)
}
file = fmt.Sprintf("%s/veyron.conf", systemdTmpFileDir)
f, err := os.Create(file)
if err != nil {
return err
}
f.WriteString("d /var/log/veyron 0755 veyron veyron\n")
f.Close()
err = action("systemd-tmpfiles", "--create", file)
if err != nil {
return err
}
// First call disable to get rid of any symlink lingering around from a previous install
// We don't care about the return status on the disable action.
action(findSystemdSystemCtl(), "disable", s.Service)
return action(findSystemdSystemCtl(), "enable", s.Service)
}
// Print implements the InstallSystemInit method.
func (s *SystemdService) Print() error {
return (*ServiceDescription)(s).writeTemplate(systemdTemplate, "")
}
// Uninstall implements the InstallSystemInit method.
func (s *SystemdService) Uninstall() error {
if err := s.Stop(); err != nil {
return err
}
if err := action(findSystemdSystemCtl(), "disable", s.Service); err != nil {
return err
}
file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
return os.Remove(file)
}
// Start implements the InstallSystemInit method.
func (s *SystemdService) Start() error {
return action(findSystemdSystemCtl(), "start", s.Service)
}
// Stop implements the InstallSystemInit method.
func (s *SystemdService) Stop() error {
return action(findSystemdSystemCtl(), "stop", s.Service)
}
// This is a variable so it can be overridden for testing.
var findSystemdSystemCtl = func() string {
// Systems using systemd may have systemctl in one of several possible places. This finds it.
paths := []string{"/sbin", "/bin", "/usr/bin", "/usr/sbin"}
for _, path := range paths {
testpath := filepath.Join(path, "systemctl")
if fi, err := os.Stat(testpath); err == nil && (fi.Mode()&0100) != 0 {
return testpath
}
}
return ""
}
///////////////////////////////////////////////////////////////////////////////
// Docker support
// DockerService is the implementation of InstallSystemInit interfacing with
// Docker.
type DockerService ServiceDescription
const dockerTemplate = `#!/bin/bash
# This file was auto-generated by the Vanadium SysInit tool.
# Date: {{.Date}}
#
# {{.Service}} - {{.Description}}
#
set -e
{{if .Environment}}
# Environment variables
{{range $var, $value := .Environment}}export {{$var}}={{$value}}{{end}}
{{end}}
echo '{{.Service}} setup.'
test -x {{.Binary}} || { stop; exit 0; }
mkdir -p -m0755 /var/log/veyron
chown -R {{.User}} /var/log/veyron
echo '{{.Service}} starting'
exec daemon -n {{.Service}} -r -A 2 -L 10 -M 5 -X '{{range $cmd := .Command}} {{$cmd}}{{end}}' &
`
// Install implements the InstallSystemInit method.
func (s *DockerService) Install() error {
file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
if err := (*ServiceDescription)(s).writeTemplate(dockerTemplate, file); err != nil {
return err
}
os.Chmod(file, 0755)
return nil
}
// Print implements the InstallSystemInit method.
func (s *DockerService) Print() error {
return (*ServiceDescription)(s).writeTemplate(dockerTemplate, "")
}
// Uninstall implements the InstallSystemInit method.
func (s *DockerService) Uninstall() error {
if err := s.Stop(); err != nil {
return err
}
file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
return os.Remove(file)
}
// Start implements the InstallSystemInit method.
func (s *DockerService) Start() error {
return action(fmt.Sprintf("%s/%s.sh", dockerDir, s.Service), "", s.Service)
}
// Stop implements the InstallSystemInit method.
func (s *DockerService) Stop() error {
return action("daemon", fmt.Sprintf("-n %s --stop", s.Service), s.Service)
}