diff --git a/.circleci/config.yml b/.circleci/config.yml index 7be2716..d43f4c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: machine: - docker_layer_caching: true + docker_layer_caching: false steps: - checkout - run: @@ -25,6 +25,5 @@ jobs: - run: command: | if [[ "$CIRCLE_BRANCH" == "release" ]]; then - make release-in-docker + make release-in-docker release-packagecloud-in-docker fi - make release-packagecloud-in-docker || true diff --git a/.gitignore b/.gitignore index 9417d10..efee0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ validation/* .env* *Procfile + +procfile-util diff --git a/Makefile b/Makefile index f3b71e9..da93d6f 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ MAINTAINER_NAME = Jose Diaz-Gonzalez REPOSITORY = go-procfile-util HARDWARE = $(shell uname -m) SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') -BASE_VERSION ?= 0.7.0 +BASE_VERSION ?= 0.8.0 IMAGE_NAME ?= $(MAINTAINER)/$(REPOSITORY) PACKAGECLOUD_REPOSITORY ?= dokku/dokku-betafish @@ -42,7 +42,7 @@ targets = $(addsuffix -in-docker, $(LIST)) @echo "PACKAGECLOUD_TOKEN=$(PACKAGECLOUD_TOKEN)" >> .env.docker @echo "VERSION=$(VERSION)" >> .env.docker -build: +build: prebuild @$(MAKE) build/darwin/$(NAME) @$(MAKE) build/linux/$(NAME) @$(MAKE) build/deb/$(NAME)_$(VERSION)_amd64.deb @@ -143,17 +143,9 @@ release-packagecloud: @$(MAKE) release-packagecloud-rpm release-packagecloud-deb: build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/trusty build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/utopic build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/vivid build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/wily build/deb/$(NAME)_$(VERSION)_amd64.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/xenial build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/yakkety build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/zesty build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/artful build/deb/$(NAME)_$(VERSION)_amd64.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/bionic build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/wheezy build/deb/$(NAME)_$(VERSION)_amd64.deb - package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/jessie build/deb/$(NAME)_$(VERSION)_amd64.deb + package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/focal build/deb/$(NAME)_$(VERSION)_amd64.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/stretch build/deb/$(NAME)_$(VERSION)_amd64.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/buster build/deb/$(NAME)_$(VERSION)_amd64.deb @@ -170,3 +162,7 @@ validate: ls -lah build/deb build/rpm validation sha1sum build/deb/$(NAME)_$(VERSION)_amd64.deb sha1sum build/rpm/$(NAME)-$(VERSION)-1.x86_64.rpm + +prebuild: + go get -u github.com/go-bindata/go-bindata/... + go-bindata templates/... diff --git a/README.md b/README.md index bae8a21..6f1eb69 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,30 @@ procfile-util expand --allow-getenv --env-file .env procfile-util expand --allow-getenv --env-file .env --default-port 3000 ``` +### export + +> export the application to another process management format + +Due to argument parsing limitations, the `--location` flag is currently required. + +In addition, not all formats support all arguments, and not all arguments have examples below. + +```shell +# export systemd init files to the `tmp` directory +# support formats include: [launchd, runit, systemd, systemd-user, sysv, upstart] +# the default format is: systemd +procfile-util export --format systemd --location tmp + +# override the app name +procfile-util export --location tmp --app node-js-app + +# set the group and user used to launch processes +procfile-util export --location tmp --group root --user root + +# set a working directory path for the process +procfile-util export --location tmp --working-directory /root +``` + ### list > list all process types in a procfile diff --git a/bindata.go b/bindata.go new file mode 100644 index 0000000..328249c --- /dev/null +++ b/bindata.go @@ -0,0 +1,475 @@ +// Code generated for package main by go-bindata DO NOT EDIT. (@generated) +// sources: +// templates/launchd/launchd.plist.tmpl +// templates/runit/log/run.tmpl +// templates/runit/run.tmpl +// templates/systemd/default/control.target.tmpl +// templates/systemd/default/program.service.tmpl +// templates/systemd-user/default/program.service.tmpl +// templates/sysv/default/init.sh.tmpl +// templates/upstart/default/control.conf.tmpl +// templates/upstart/default/process-type.conf.tmpl +// templates/upstart/default/program.conf.tmpl +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +// Name return file name +func (fi bindataFileInfo) Name() string { + return fi.name +} + +// Size return file size +func (fi bindataFileInfo) Size() int64 { + return fi.size +} + +// Mode return file mode +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} + +// Mode return file modify time +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} + +// IsDir return file whether a directory +func (fi bindataFileInfo) IsDir() bool { + return fi.mode&os.ModeDir != 0 +} + +// Sys return file is sys mode +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _templatesLaunchdLaunchdPlistTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x54\x5f\x8f\x9a\x40\x10\x7f\xf7\x53\x4c\x89\x8f\x85\xd5\xb7\xe6\x82\x5c\xec\x69\x93\x4b\x8d\x92\x53\xdb\xf4\xc9\xec\xc1\x1c\xb7\x11\x76\xe9\xec\xa2\x25\x84\xef\xde\x80\x70\xe2\x29\x97\x3e\xf4\x6d\x32\xf9\xfd\x9b\x61\x16\xf7\xfe\x4f\x12\xc3\x01\x49\x0b\x25\x27\xd6\xd8\x19\x59\x80\x32\x50\xa1\x90\xd1\xc4\xda\x6e\xbe\xd9\x5f\xac\x7b\x6f\xe0\x7e\x9a\xad\x1e\x36\xbf\xfc\x39\xa4\xb1\xd0\x06\xfc\xed\xd7\xc5\xe3\x03\x58\x36\x63\xd3\x34\x8d\x91\xb1\xd9\x66\x06\xfe\xe2\x71\xbd\x81\xb1\x33\x62\x6c\xbe\xb4\xc0\x7a\x35\x26\xbd\x63\xec\x78\x3c\x3a\xbc\x42\x39\x81\x4a\x2a\xa0\x66\x3e\xa9\x14\xc9\xe4\x0b\xa1\x8d\x3d\x76\x46\x4e\x68\x42\xcb\x1b\xb8\x27\xf5\x8b\x38\xde\xc0\x0d\x45\x60\xbc\x01\x00\x80\xbb\xc7\xdc\x5b\xf0\x67\x8c\x5d\x56\x95\xa7\xa6\x36\x24\x64\xe4\x15\x05\x54\x3e\x50\x96\x76\x55\xa6\xa4\x02\xd4\x7a\x67\xf2\x14\xdb\x9e\xcc\x12\x28\x4b\x97\x35\x8c\xb3\xe6\x5c\x1e\x04\x29\x99\xa0\x34\x3f\x38\x09\xfe\x1c\xa3\xee\x5a\x9c\x23\xbc\x51\xfc\xd5\xd3\xa6\x03\x79\x9f\x24\x55\x64\xae\xbc\xce\xe4\xf5\x47\x54\x7d\x45\x2c\x0a\x1b\x88\xcb\x08\x61\xb8\xc7\xfc\x33\x0c\x0f\x3c\xce\x10\xee\x26\xe0\xa0\x3c\x40\x59\x5e\xca\x17\x45\x8d\xab\x65\xfa\x6c\x1a\x89\x8e\x53\x51\x00\xca\xb0\x15\x73\xd9\xbb\xbd\xfb\xa4\x22\xe2\xc9\x94\xa2\xac\xda\xd3\xc5\x7e\x38\x11\xcf\xaf\xa2\x06\x2a\x49\xb8\x0c\xeb\x98\x4d\xbd\xab\xbf\x70\x37\xef\xd9\x5c\xbc\x00\xfe\x3e\xb3\xac\x61\xb5\x62\x0b\xca\xb2\x4a\xdb\x2e\xb4\x0a\x19\x6b\x6c\xba\x2d\xf6\xd4\xaf\x8b\xde\x71\x3a\x19\xeb\x79\xbe\x23\xa6\xd3\x58\x1c\xb0\x3b\x88\xa1\x0c\x59\x07\xf4\x94\xc9\xa9\x59\x28\x1e\x7e\x04\x5a\x1b\x2e\x43\x4e\xe1\x2a\x33\x3e\x37\xaf\x3d\xb7\x19\xab\x08\xca\x92\xfd\xf3\x99\xda\xda\x84\x2a\x33\x15\xef\xc6\xc5\xb6\x9e\x73\x22\x45\xff\xd9\x15\x89\x7a\x5c\xb7\x1a\x69\xc9\x13\xec\x31\xcb\x34\xd2\xed\x07\xf6\x53\xd1\x5e\xc8\x68\x26\x08\x03\xa3\x28\xef\x11\x38\x9e\x60\xbb\xb0\xc5\xdd\x7c\x09\xe2\x05\x1c\x29\x02\x7c\xfb\xb6\x95\xd6\x52\x04\x4d\x2c\x57\x48\x83\x11\x52\xad\xd8\xe0\x5c\xd6\x69\x36\x67\xd1\x5e\xb8\xcb\xea\xff\x8e\x37\xf8\x1b\x00\x00\xff\xff\xff\xd0\xa2\x61\x0e\x05\x00\x00") + +func templatesLaunchdLaunchdPlistTmplBytes() ([]byte, error) { + return bindataRead( + _templatesLaunchdLaunchdPlistTmpl, + "templates/launchd/launchd.plist.tmpl", + ) +} + +func templatesLaunchdLaunchdPlistTmpl() (*asset, error) { + bytes, err := templatesLaunchdLaunchdPlistTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/launchd/launchd.plist.tmpl", size: 1294, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesRunitLogRunTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\x8c\xcf\x0a\x82\x40\x10\x87\xef\xf3\x14\xbf\x2c\xbc\x6d\x46\x10\x9d\x3a\x7b\x11\x7c\x84\xa8\x75\x50\x49\xdd\xc5\x59\xfb\x83\xce\xbb\xc7\x42\x1e\xba\x7d\xcc\x37\xbf\x6f\xbb\xc9\xee\xed\x90\x49\x43\xc2\x01\x86\x89\x8a\x32\xbf\xcc\x33\xf6\x9d\xab\xa1\x9a\x45\xbc\x79\x0f\x55\x13\xd1\x8f\xce\xb2\xc8\x35\x7c\x3c\xaf\xb7\x61\xea\xa1\x4a\x14\x58\x02\x4c\x85\x64\x57\x94\x79\x82\x65\x41\xff\xa8\xda\x11\xc6\xc3\xf4\x38\x9e\x4f\x87\x55\xa5\x29\x6c\xe3\x5e\x03\xe2\x7c\x12\x1e\xa1\xfa\x73\xc4\x6f\xb6\xb0\x8d\x8f\xad\xe9\xef\x41\x9e\x9d\xab\xd7\x3c\x7d\x03\x00\x00\xff\xff\x6a\x88\x64\xed\xba\x00\x00\x00") + +func templatesRunitLogRunTmplBytes() ([]byte, error) { + return bindataRead( + _templatesRunitLogRunTmpl, + "templates/runit/log/run.tmpl", + ) +} + +func templatesRunitLogRunTmpl() (*asset, error) { + bytes, err := templatesRunitLogRunTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/runit/log/run.tmpl", size: 186, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesRunitRunTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x34\x8d\x4d\x0a\x02\x31\x0c\x46\xf7\x73\x8a\x88\xe0\xae\x16\x3d\x80\x57\x19\x6a\x1a\x9c\xa2\x4d\x4a\x7f\xd4\x61\xc8\xdd\xa5\x95\xd9\xbd\x17\xf2\xf8\x8e\x07\x7b\x0f\x6c\xcb\x32\xa1\x87\x6d\x83\xf3\x47\xf2\x33\xf0\x63\xf6\x21\x13\x56\xc9\x2b\xa8\x4e\xf4\x25\x84\xeb\xed\x74\xf9\x13\x2e\xa9\x54\x30\x6d\x04\xad\x50\x06\x55\x30\x34\xf4\x25\xe8\x6a\x10\x06\x55\xdb\xdd\xa5\x04\xaa\xa6\x63\xca\x82\x54\xca\x5c\xd7\x44\xfb\x8d\x5b\xec\x9f\xc4\xef\x51\xa3\xc4\xe8\xd8\xf7\xcd\x5f\x00\x00\x00\xff\xff\xb9\xe8\x41\x7e\x9a\x00\x00\x00") + +func templatesRunitRunTmplBytes() ([]byte, error) { + return bindataRead( + _templatesRunitRunTmpl, + "templates/runit/run.tmpl", + ) +} + +func templatesRunitRunTmpl() (*asset, error) { + bytes, err := templatesRunitRunTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/runit/run.tmpl", size: 154, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesSystemdDefaultControlTargetTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8a\x0e\xcd\xcb\x2c\x89\xe5\x0a\x4f\xcc\x2b\x29\xb6\xad\xae\x56\x28\x4a\xcc\x4b\x4f\x55\x50\x29\x4b\xcc\x29\x4d\x55\xb0\xb2\x55\xd0\x2b\x28\xca\x4f\x4e\x2d\x2e\x4e\x2d\x56\xa8\xad\xad\xae\x86\xc9\xd4\xd6\x2a\x54\x57\x2b\xa4\xe6\xa5\x28\xd4\xd6\x72\x71\x45\x7b\xe6\x15\x97\x24\xe6\xe4\x40\xcc\x49\x4d\x71\xaa\xb4\xcd\x2d\xcd\x29\xc9\xd4\x2d\x2d\x4e\x2d\xd2\x2b\x49\x2c\x4a\x4f\x2d\xe1\x02\x04\x00\x00\xff\xff\x4f\xc2\xa4\x0c\x6a\x00\x00\x00") + +func templatesSystemdDefaultControlTargetTmplBytes() ([]byte, error) { + return bindataRead( + _templatesSystemdDefaultControlTargetTmpl, + "templates/systemd/default/control.target.tmpl", + ) +} + +func templatesSystemdDefaultControlTargetTmpl() (*asset, error) { + bytes, err := templatesSystemdDefaultControlTargetTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/systemd/default/control.target.tmpl", size: 106, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesSystemdDefaultProgramServiceTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x92\x41\x6b\x1b\x31\x10\x85\xef\xfa\x15\xc2\xa4\xe4\x52\x7b\x29\xf4\x54\xd8\x5b\x9c\x62\x9a\xc6\x21\x1b\x93\x43\x08\x46\x91\x66\x37\x22\xda\xd1\x32\x9a\x75\xbc\x2c\xfa\xef\x45\x72\xd7\x2e\x6e\x7b\xc9\x4d\x7a\xf3\xcd\x93\xde\x30\x4f\x1b\xb4\xfc\x2c\xc6\x71\x2e\x6d\x2d\x17\x06\x82\x26\xdb\xb1\xf5\x28\x63\x14\x57\xa7\x6b\x39\x8e\xe7\xe5\x71\x94\x80\x26\x71\x77\x8a\x78\x5d\x67\x44\x75\x9d\x8c\x71\xc1\x8a\x1a\x60\x51\xb1\xef\x1e\x5f\x01\x37\x88\x00\x06\x4c\x39\x40\x10\xe2\xa9\x02\xda\x59\x0d\xcf\x62\x13\x80\x72\x5b\x1f\x80\x92\xd3\x77\xf2\x7d\x97\x95\x26\x9d\x92\xf4\xe8\xe9\xcd\x62\x73\x65\x09\x34\x7b\x1a\x72\xf5\xfd\x20\x6e\xcd\xa4\x26\x72\x89\x3b\x4b\x1e\x5b\x40\x2e\xef\xd6\xf7\x0f\x99\xec\x3c\xf1\x5f\xc5\xea\x50\x0a\x67\x85\x6b\xeb\xa0\x9c\x17\xc0\xba\x30\x50\xab\xde\x71\x31\x65\xfa\x3f\x19\x86\xa0\x3d\xd6\xb6\x29\x4e\xf9\xf3\x40\x49\x61\x03\xf2\xe2\x0d\x86\xcf\xf2\x62\xa7\x5c\x0f\xf2\x5b\x29\x17\x80\xbb\xf3\xff\xcc\xc6\x31\x73\x32\xc6\xf4\xb1\xdf\x70\x8c\x33\x71\x9a\xf1\x72\x0f\xba\x62\x45\x5c\x16\x2f\x16\x8b\x17\x15\x5e\xe5\xdc\x69\x79\x09\x7b\xd0\x72\xae\xe4\xec\x18\x69\x26\xd3\x51\xfb\xb6\x55\xb9\xf7\x52\xdc\x43\xc8\xad\xca\xbd\xab\x21\x4c\xd7\x0a\x74\xf9\xe5\x6b\x10\x15\x2b\x34\x8a\xcc\x0a\xbb\x9e\x4b\xec\x9d\x3b\x4a\xeb\x9e\x93\x96\xfc\x9c\x6f\x64\x8c\x7f\x84\x9c\xe7\x07\xc9\x6b\x08\x61\xcb\x43\x07\x93\x86\x7d\x9b\x8e\x81\x8d\xef\x39\xf5\x1d\xed\x96\x44\x9e\x3e\xec\x06\x44\x07\xb7\x21\x38\xdf\xac\x0c\x20\xdb\xda\x02\x95\x9f\x50\xfc\xb0\xce\xfd\xf4\x06\xca\xd6\xee\xc1\x88\x07\xdb\x82\xef\x39\xed\x5f\x4a\x99\x7c\xf8\x20\xa5\x59\x1e\xf7\x1d\xad\x4e\xef\x88\x5b\xab\x21\x43\x93\x70\x9a\xfb\x84\x3a\xdb\x5a\xde\xfa\x0e\x70\x5b\x5b\x07\x79\x75\x6e\x92\x76\xbb\xbe\x5e\xdd\x2c\x0f\xa1\xfe\xc1\x9c\x9c\x7e\x05\x00\x00\xff\xff\xdd\xbc\x49\x0c\x6c\x03\x00\x00") + +func templatesSystemdDefaultProgramServiceTmplBytes() ([]byte, error) { + return bindataRead( + _templatesSystemdDefaultProgramServiceTmpl, + "templates/systemd/default/program.service.tmpl", + ) +} + +func templatesSystemdDefaultProgramServiceTmpl() (*asset, error) { + bytes, err := templatesSystemdDefaultProgramServiceTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/systemd/default/program.service.tmpl", size: 876, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesSystemdUserDefaultProgramServiceTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x90\x31\x6b\xf3\x30\x10\x86\x77\xfd\x0a\x2f\xdf\x18\x9b\x0f\xba\x6a\x29\x49\x21\x53\x4a\x9c\x92\x21\x84\x20\xa4\x8b\x2b\x2a\x9d\xc4\xe9\x9c\x54\x18\xff\xf7\x22\x9b\x24\x34\x63\x37\xdd\xf3\xde\xf3\x72\xf6\xe1\x03\x2d\x1f\xc5\x30\x2c\x2a\x7b\xae\x6a\x03\x49\x93\x8d\x6c\x03\x56\xe3\x28\x96\x8f\x51\x0e\xc3\x73\x3c\x0c\x15\xa0\x29\x7b\x95\x38\xb4\x40\x17\xab\xe1\x28\xf6\x81\xbe\x2c\x76\x4b\x4b\xa0\x39\x50\x9e\xc4\xeb\x0c\x4f\xe6\x46\x8b\xb5\xcb\x11\x64\xb2\x3e\x3a\x10\x2b\xbc\x58\x0a\xe8\x01\x59\xbe\x6f\xb6\xbb\xc9\x8a\x81\xb8\x2c\xfe\x0a\xdb\x39\x4a\x4f\xc1\x9b\x75\x20\x17\x25\xfa\x0c\x1e\xaa\x71\x6c\x6a\x1d\xf0\x6c\xbb\x26\xe5\xc4\xe0\x4d\xd3\x27\xa0\xe6\xee\xd6\xf0\x70\xc5\xea\x1b\x74\xcb\x8a\x78\xea\xd6\xc1\x7b\x35\x7f\xd8\x16\xd2\x84\x95\xbb\xaa\x9c\x6e\x63\x0b\x5a\xfe\x7f\x49\xa2\x65\x85\x46\x91\x59\x63\xec\x59\x62\xef\xdc\x1d\x6d\x7a\x2e\xac\xd4\xb9\xd0\x95\x73\xca\x53\xc5\x58\x8d\xe3\x74\x65\xa4\xa0\x21\xa5\x13\xe7\x08\x37\x86\xbd\x2f\xcf\xc4\x26\xf4\x5c\xbc\x7b\xdd\x8a\x28\xd0\x9f\xdb\x80\x68\x6e\xcb\xc9\x85\x6e\x6d\x00\xd9\x9e\x2d\x90\xfc\x87\x42\x1c\xd6\x98\x58\x39\x77\x14\x7b\x85\x0c\xe6\x35\x4b\xdf\x3b\xb6\x8b\xf2\xbf\x6a\x56\xd4\x01\x8b\x9f\x00\x00\x00\xff\xff\xad\xa5\x8f\x08\x27\x02\x00\x00") + +func templatesSystemdUserDefaultProgramServiceTmplBytes() ([]byte, error) { + return bindataRead( + _templatesSystemdUserDefaultProgramServiceTmpl, + "templates/systemd-user/default/program.service.tmpl", + ) +} + +func templatesSystemdUserDefaultProgramServiceTmpl() (*asset, error) { + bytes, err := templatesSystemdUserDefaultProgramServiceTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/systemd-user/default/program.service.tmpl", size: 551, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesSysvDefaultInitShTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xa4\x57\x6d\x73\xdb\xb8\x11\xfe\xce\x5f\xb1\xa1\x34\x76\x9c\xea\xc5\x76\x7a\xed\x8c\x33\xce\x35\x2f\xbe\x54\x73\x89\xed\xb1\x74\x73\x9d\xb9\xdc\x28\x10\xb1\x14\x51\x91\x00\x03\x80\x96\x75\x36\xff\x7b\x67\x01\x92\x22\x6d\x29\xcd\xb5\xf9\xe0\x88\xc0\xee\xb3\x8b\x7d\x79\xb0\xe8\x3d\x1b\x2f\x84\x1c\x9b\x24\xe8\xc1\x44\x0a\x0b\x26\xd2\x22\xb7\x10\x2b\x0d\xf7\xf7\x30\x62\x79\x0e\x65\x39\xa4\x9f\xb9\x56\x11\x1a\x33\xb7\x9b\x1c\xeb\x35\x59\x64\x50\x96\x41\x0f\x3e\x31\x21\x2d\x13\x12\x39\x2c\x36\xa4\x29\x62\x18\xb1\xc2\x26\x4a\x43\x59\x3a\xa4\xd6\x07\x4a\xee\xd5\x3e\xa0\x44\xcd\xac\xd7\xca\x53\x64\x06\x75\x21\x47\xe4\x4c\x96\xa7\x98\xa1\x74\x7b\xcc\x20\x07\x25\xe1\xe3\xf4\x2d\xbc\x53\x1a\xe1\xe5\xe8\xe4\x2c\xe8\x01\xc0\x0b\x98\x62\x64\x85\x92\xe6\x0c\x4e\x8f\x47\xa7\x03\xfa\xfb\x32\xe8\x05\xbd\x5e\x0f\xde\x5e\x7c\x98\x5c\xc2\xe4\x72\x32\x83\xc9\xe5\x4f\x57\x41\x0f\xae\xb5\xba\x15\x1c\xcd\x19\x34\xff\xfe\xcc\x29\x6f\xf0\x6b\x21\x34\xf2\xe1\xd4\x32\x6d\x1d\x48\x5f\x63\xa6\x2c\xce\x63\x03\x7d\xb3\x31\xa9\x5a\x76\xe5\x54\xee\x6d\xed\x94\x7b\x8f\x31\x2b\x52\xdb\x82\x83\x53\x78\x09\x7f\x85\x1f\x3a\x9b\x35\x06\x1c\xc3\x09\xfc\x2d\xe8\xc1\x34\x51\xda\x0e\xdf\xa3\x4f\x96\x50\xf2\xac\x0e\xb9\x92\x38\x4f\x85\xc4\x39\xdf\x6e\x56\x09\xd8\xbf\xd5\xa4\xa3\x83\xb8\x0d\x4f\x57\xc1\x85\xf6\xe2\xf2\x7d\x2b\xb0\xc1\xf5\x9b\xd9\x3f\xcf\xc7\x66\x21\xe4\xd9\xb8\x30\xba\xfa\xd5\x7c\x2e\x84\x0c\xf0\x2e\x57\xda\x02\x49\x06\x81\x64\x19\x9e\x7f\x7f\xe4\x73\xad\x96\x9a\x65\xe7\x95\x18\xfd\x2e\xcb\x80\xe9\xa5\xf1\x20\x7a\x69\xe6\x68\x22\x96\xa3\x3b\x48\x2e\x78\x2c\x52\x3c\x0f\xc7\xb7\x4c\x8f\x75\x21\xc7\x7d\x32\x38\xca\x05\x0f\x83\xc2\xa0\x3e\x0f\x49\x8d\x7e\x41\x59\x86\xc1\x52\xab\x22\xf7\x6b\xee\xa7\x5b\x8c\x12\xad\x94\xf5\xab\x6b\xa5\x57\x42\x2e\xe7\x5c\x68\x8c\xac\xd2\x9b\x4a\x82\x0b\xfd\x2d\x01\x29\x22\xf4\xfb\xf4\x8b\x56\xee\xef\x87\x2e\x4d\xa9\xc8\x84\x9d\x47\x4a\x23\x2f\x32\xb2\x17\x74\x57\xbc\xd6\x13\xa9\x70\x9b\xac\x47\x48\x79\x61\x45\x86\x2d\x20\xbf\xd0\xc1\x69\x64\xf6\xc2\x70\x66\xd9\x16\x83\xbe\xda\x00\xd5\xee\x5e\x6d\x8a\xf9\xdc\x88\x3f\x5a\x6e\x34\x4b\x6d\x9c\xb6\xdc\x5e\xb0\x54\x45\x2b\xe4\xf3\x0c\x33\x1f\xcd\x60\xc7\x72\x1b\xf4\xb1\xfc\x5e\x60\x95\xa3\x74\x2e\x98\x2d\xea\x76\xad\x0d\xd9\x91\xdc\x8b\x47\x55\x34\xaf\x6a\xb7\x8d\xd9\x5d\x6f\xe3\x3e\xd1\xd8\x8b\x9d\x27\x1b\x23\x22\x96\x3e\x09\xc3\xa3\x8d\x36\xfa\x53\x9d\xbd\xf0\xc6\xb2\x68\xf5\x28\x63\xdb\xb5\x36\x68\x47\xb2\x85\x47\x44\x1d\x83\x4d\x84\x01\x61\xc0\xa0\x05\xab\xe0\x64\x00\x36\x41\x09\x6b\xfa\xf3\xc5\x58\x95\x7f\xa1\xdd\x88\xa5\x29\xf2\x01\xd9\xb7\x09\x42\x15\x00\x48\x98\x09\x7a\x20\x95\x05\xbc\x13\xc4\xf6\x6b\x61\x13\x21\x81\x81\x46\x66\x94\x64\x8b\x14\x81\x0a\x77\x00\xd3\xc9\x87\x9f\x27\x1f\x3f\xc2\x5a\xa4\x29\x2c\x10\x0c\x4a\x0b\x12\xef\x2c\x5d\x18\xb3\x04\x81\x7b\xca\x84\x05\x26\xec\x56\x28\x4d\x66\xad\x02\x23\xb2\x3c\xdd\x40\xaa\x96\xc0\x20\x43\x63\xd8\x12\x21\xac\x88\x04\xc8\x41\x88\x99\x48\x91\xbf\x02\x63\x09\x5b\x17\x52\x0a\xb9\x0c\x03\x32\x37\xbf\xba\x9c\x4f\x67\x57\xd7\xf3\xd9\xe4\xd3\xc5\xd5\x2f\xb3\xf3\x63\x3a\xf6\xaf\x74\xb8\x54\x31\x2e\xe4\xb2\xb1\xcb\x24\x07\xb3\x31\x91\x92\xb1\x58\x82\x2b\x9d\x01\xac\x11\x0a\x83\xf0\x85\xa2\x33\x64\x5f\xc8\xa1\x8c\xad\x30\xe8\x01\x4b\x53\xb8\x65\x5a\xd0\x11\x0d\xb0\xc2\xaa\x8c\x59\xca\x5d\xba\x01\x21\xad\x02\x94\xb7\x42\x2b\x49\xd7\xe0\x56\x70\x14\x78\xa4\xe0\x37\x18\x6a\x18\xa3\x8d\xc6\x95\xfd\x71\xcd\xa8\x50\x96\xf0\x3b\x1c\x1c\xc0\x68\xef\x7e\x4b\xbb\xf1\x78\xbc\x65\xe4\x8e\xfa\x2e\x01\xe7\xc4\x5f\x58\x43\xeb\x57\x37\x33\x4f\xcf\xf4\x55\x96\xcd\xfa\xd4\xaf\xba\xc6\x20\x9b\x7f\x40\xd8\x27\x36\x0c\xbd\x05\x47\x91\xc7\x41\x60\x35\x8b\xf0\xf9\x11\xdc\x07\x40\x79\x5a\xa2\x86\xa1\x85\xd0\x99\x17\x52\xd8\x11\x1f\x7f\xf7\x75\x11\x42\xd8\xff\x47\x18\x94\x41\x80\x99\xb0\x15\xa8\x33\xe0\x37\x00\x30\x4a\x54\x23\x64\xe8\xf2\xad\xa4\xee\xef\xc7\x2f\x60\x02\x5c\xb9\x82\xa4\xb4\x1d\x9a\xe2\x10\x12\xd4\x48\x79\xd3\x85\x04\x66\x80\x01\x17\x71\x8c\x9a\xd2\xe2\x2e\x91\x05\x46\x8c\x84\xdb\x75\x4d\x8a\x81\xbf\x43\x8d\x65\x1b\x43\x8a\x6e\x9f\x91\xde\x00\x48\x83\x6a\x47\x15\x1a\xaa\x0b\x8b\x4c\x44\xca\xcd\x51\x5e\x54\x70\x50\xb1\x77\x81\xfc\xb1\x09\x56\x88\x75\xed\xae\x91\x0a\x05\x25\x47\x5e\xf9\x37\x82\x8f\x45\xb4\x12\xe9\xc6\x35\x21\x1c\xfa\x6b\xec\xb0\xd1\x50\x12\xae\xa6\xff\x1a\xc0\x4f\x1a\xf1\xed\xf4\xfd\xc0\xd5\xec\x47\x21\x8b\xbb\x0a\x9a\x8a\xd2\x14\xb9\xcb\x9e\x59\x0b\x1b\x25\xe4\x25\x1d\xd3\x38\x59\x61\x41\xc8\x5b\xb5\x42\x03\x78\x87\xd1\x2d\x82\xc8\x32\xe4\x82\x59\x4c\x37\xc0\x62\x8b\x1a\xbc\x51\x21\x97\x23\x78\x31\xa6\xc4\x03\xf4\xe0\x42\x9a\x42\xfb\x18\x51\x27\x6e\xaf\x4a\xcf\x1c\x45\x0e\x91\xd2\xb4\x96\x6e\x46\x01\x10\x4d\xfc\x06\xcf\x60\xc8\xc1\xf3\x90\x5a\x82\xcb\xed\xef\xaf\x3c\xbb\x38\x77\xb3\x15\x17\xba\x2b\xe0\xd6\xa3\x44\xad\x25\x84\x7d\x72\x3b\x3c\x0b\xfb\xee\x5a\x0f\x77\x0a\x66\x8a\xc3\xdf\x7f\xf8\xe1\xc9\x5e\x2c\x02\x57\x10\x9e\x30\x73\x8d\xae\x4e\xa8\x8c\x2b\xdf\xc2\xfe\xf5\xcd\xc5\x74\xf6\xe6\x66\x16\xc2\xb3\x73\x08\xa5\xa2\x9a\xf6\xde\x39\x6c\xc7\x8d\x8d\x22\x31\x8c\x19\x00\x5b\xb8\xc0\xd2\xd2\x28\xf0\xa9\xac\x04\x1e\x1e\x40\xa3\x2d\xb4\x84\xfe\x8f\xde\xbe\x9b\xbe\x6a\xa6\x25\xbc\xa9\x0b\x13\x93\x9b\x36\x33\xb0\x14\x8c\x2d\xe2\x18\x16\x18\x2b\x8d\x09\x93\xdc\x6b\x92\xdf\x45\xc5\xdf\x09\xa6\x69\x35\x07\x3e\x5d\x6a\x9b\xb8\x29\x64\x5d\xc6\x54\x2f\xcf\x5a\x31\xa0\x5e\x85\xb2\x74\xff\x0d\x65\xdd\xc7\x5b\x7d\xa8\xf2\x0e\xc3\xa1\x2b\x97\x1c\xa3\x1d\x29\xe8\x7b\xa1\x10\x4c\x02\xc3\x08\x7c\x1a\xfe\x07\x6f\x5d\xf6\x38\x7c\x26\x40\x2e\xf4\x67\x0f\x44\x25\x49\x6b\x95\xfb\x9f\x43\xe8\xd3\x74\x18\x00\x84\xf0\xfa\x35\xd4\x39\x2e\xcb\xef\xa7\x93\xa1\xb1\x5c\x15\xd6\xe9\x9d\xfe\x1f\x18\xa8\xb5\xd3\x3b\xf0\x81\xae\x9f\x3e\x75\xa7\x3b\x02\x88\xb5\xca\x1c\xdb\x8c\xa8\x76\x5c\x77\x1b\x8b\x8c\x43\xc6\xb8\x97\x8c\x95\x5e\x21\xaf\x59\xc6\x21\x2d\x6b\x24\xe1\x28\x42\x63\x73\x41\x32\x70\xac\x17\x29\xc9\x85\x9b\xde\x17\x68\xd7\x88\xb2\x63\x73\xad\x05\xb5\xaa\x83\xa2\x06\x67\x0d\x85\xe5\xca\x18\xb1\xa0\xae\x36\x34\xdd\xba\x47\xa1\xb1\xcc\x16\x66\x54\x93\x68\xff\x19\xbc\x86\x7e\x05\x45\x07\x23\xca\xa5\xd2\x60\x19\xfa\x2a\x47\x4e\x99\xa9\x2a\xfb\xd8\x13\xae\xca\x2b\xbe\xed\xc1\x4c\x6f\x80\x41\x8c\x6b\x77\xc7\xbb\xeb\x7a\x45\xde\xcf\x2e\x6e\x3e\xb5\x0b\xd1\x77\x9d\xb7\xde\x6e\xb2\x5c\xf0\xf3\xfe\xf3\x88\x91\xd1\xca\x8d\xf0\xc8\xed\x54\x8c\xff\xb3\x48\x53\x72\xde\xbb\xf4\x9c\x38\x95\x04\x8f\xdc\x9c\x41\x23\x05\x59\xf2\xc5\xe3\x0c\x0f\x9d\x65\x12\xa9\x9a\xf8\x57\x26\xfc\x7b\x58\xb8\xe9\x86\xa6\x14\xdf\xb9\x6e\x0d\x84\x84\x93\xfa\xd5\x06\xaf\x80\xab\x8a\x47\x2b\xf3\xa4\xbd\xd3\xbc\x55\xc0\x05\x8e\x46\xa3\x70\x7b\x4b\xd0\xd9\x1e\x1e\x60\xa1\x91\xad\xea\xd5\x14\x31\x87\x13\xf7\xc5\x95\xf4\x17\xc0\xce\x48\x34\xb4\xb4\x6b\x68\x09\x61\x88\x5f\xe1\xa4\x4b\x50\x2d\x37\x67\x22\x43\x55\x58\x9a\xb9\xa2\x04\xf9\x08\xfe\x6b\xd8\xc8\xca\x08\x60\x46\xb3\x5f\xc6\x36\xa0\xd1\xd0\x08\x24\x24\xb8\x77\x42\xaa\x8c\x69\x8e\x56\x87\xd6\x4d\x6f\x4d\x68\x5d\xbf\xb6\xca\x85\x64\xaa\xf9\xaf\x31\x50\x23\x60\x6a\x70\xa7\xd2\xfe\xe9\xad\xd1\x75\x4c\xda\x81\x78\x0c\x90\x23\xaf\xa4\x9d\x6c\x2c\xaa\xb9\xc0\x16\xa6\x2a\x54\x17\xda\x61\xdc\x2a\xb2\x6e\x28\xf7\x97\xa1\x88\x21\x37\x30\xcc\xdd\xb9\xe1\x35\x8c\x39\xde\x8e\x65\x91\xa6\x70\xda\xfe\xe8\xa4\xa5\xd7\xb4\xe0\x62\xe3\xa7\x6b\xd2\x15\xa6\x39\x5a\x23\x37\xb1\x2e\xf8\x34\x1d\x2c\xb0\x9e\x25\x06\xb0\x28\x88\x0a\x98\x3d\x34\xb0\x4e\x98\x85\x8d\x2a\x60\x89\xd6\xc7\xf6\xdf\x85\xb1\x75\xf7\x9b\x2d\xd4\xec\xea\xfd\xd5\x73\x23\x8c\xc1\xf4\xe8\x0c\xde\x25\x18\xad\xfc\xac\x4e\xe6\x2b\x77\x0c\x62\xe6\x7a\x74\xe1\xd9\xc8\x50\x04\xab\xc1\x46\x49\x84\x35\x36\x70\x78\x97\x63\x64\x47\x30\xb1\x87\x9c\xc4\xdd\xb5\x61\x95\x1b\xaa\x62\x7a\xad\x39\x9e\xf3\xae\xfa\xef\x82\x5e\x43\xc4\x6f\x83\xea\x39\x80\xd1\xa0\x81\x33\x8a\xfa\x8f\x06\x67\x43\x3f\xbe\x16\xc2\x22\xb0\xf5\x6a\xcd\x34\xaf\x61\xdd\xd8\xe4\x5e\x1a\x06\xeb\x73\x35\xc4\xf3\xa8\x04\xaa\xf5\x53\x1f\x6b\x37\x1a\x09\x03\x9c\xb8\x96\x3c\xa2\x78\x3b\x76\xc4\x3b\x61\xac\xd9\xd6\x46\x03\x51\x01\xbc\xec\x02\x90\xe3\x55\x92\x9a\x42\x8a\x95\x8e\x70\xde\x62\xbd\x9d\xed\x4b\xfb\x41\x8b\x07\x0e\x0e\x3a\x4d\xb3\xa3\xb6\x3c\xfa\xae\x09\xa5\xfe\xdd\x4c\xb5\xdd\xed\xa0\x36\x72\xde\xff\x31\xa8\x8b\xbb\x5f\xd9\x1d\x2e\x2d\x1c\x77\x8b\xdb\xb7\xcb\x75\x8d\x10\xa9\x2c\xa3\x8b\xc2\x77\x9d\xaf\xa9\x48\x71\xac\x21\xdc\xdd\x45\x15\xb7\x16\x26\x71\xef\xaf\x95\xc8\x2b\x2e\xef\x42\x0c\xdc\x93\xb1\x1e\xa3\xce\xa5\xa2\x14\x6e\xa8\x8a\x5b\xf3\xcd\x28\xac\xa7\xa1\x7a\x3c\xf2\x66\x02\x7a\xce\x36\xb3\x0b\xe5\x1c\xc2\xfe\x49\x08\x82\x9c\x76\x31\x1f\x3a\x63\x0f\xf5\x5f\x95\x3f\xd4\xcb\x2a\x7f\xa8\x5c\xe9\xdc\x15\x6f\xac\xc5\x2c\x77\x7c\x7d\xd8\x3f\x39\xa4\x61\xf9\xfb\x5f\x1d\x0e\xe8\xd5\xab\x00\x0d\x8b\xbe\xe5\x8f\xb7\xd8\x3a\xf4\x76\x70\x09\xfb\xc7\xa1\xbf\x35\x6b\x30\x80\x96\x4e\x75\x6e\x37\xf9\x28\x8e\xe7\x6e\x4e\xac\xd3\xe7\x32\x40\x24\x7f\x5c\x8f\xc8\x3b\xc8\x4e\x18\x60\xa9\x46\xc6\x37\xdb\x37\x6e\x25\x75\x27\xac\x07\x79\xdc\x29\x5b\x7f\x6a\xa9\x1f\x83\x16\xad\x56\x4e\xaa\xfc\xc8\x73\xb1\xfb\xde\xc6\xf9\x08\xb6\xe5\xdf\x1c\xc8\x16\xe6\x4f\x9e\x08\xbe\x75\xa4\xce\x51\xf6\xd0\x7c\xb7\x37\xc3\xf6\x09\x1e\x1d\xdd\x39\xd9\xa9\x8e\xc7\x2d\x56\x96\xdf\xf7\x00\xe8\x0e\xf7\xdd\xd8\x75\x07\x59\x17\x9d\x83\x83\xc7\xb9\x7f\xe1\xed\xfb\xe7\xea\x2f\x86\x2d\xf1\x0c\xfa\xd3\x77\x37\x93\xeb\xd9\xe5\x9b\x4f\x17\x70\xef\x4b\xbb\x5b\xec\xad\x32\x6f\x6f\xaa\xfc\xc1\x47\xbb\xae\xfc\x32\x84\xd7\x07\xa7\xdb\x08\xbc\x0c\x5a\xe5\x5b\x3b\xfb\x9f\x00\x00\x00\xff\xff\x7f\x40\x9c\x08\x20\x18\x00\x00") + +func templatesSysvDefaultInitShTmplBytes() ([]byte, error) { + return bindataRead( + _templatesSysvDefaultInitShTmpl, + "templates/sysv/default/init.sh.tmpl", + ) +} + +func templatesSysvDefaultInitShTmpl() (*asset, error) { + bytes, err := templatesSysvDefaultInitShTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/sysv/default/init.sh.tmpl", size: 6176, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesUpstartDefaultControlConfTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\x49\x2d\x4e\x2e\xca\x2c\x28\xc9\xcc\xcf\x53\xa8\xae\x56\xd0\x4b\x2c\x28\x50\xa8\xad\xe5\xe2\x2a\x2e\x49\x2c\x2a\x51\xc8\xcf\x53\xd0\x00\xb3\x52\x53\x14\xf2\x52\x4b\xca\xf3\x8b\xb2\x33\xf3\xd2\x35\xb9\x8a\x4b\xf2\x0b\x40\x92\x45\xa5\x79\x39\xa9\x65\xa9\x39\x0a\xd1\x06\x66\xb1\x5c\x80\x00\x00\x00\xff\xff\x88\x35\x19\x59\x4c\x00\x00\x00") + +func templatesUpstartDefaultControlConfTmplBytes() ([]byte, error) { + return bindataRead( + _templatesUpstartDefaultControlConfTmpl, + "templates/upstart/default/control.conf.tmpl", + ) +} + +func templatesUpstartDefaultControlConfTmpl() (*asset, error) { + bytes, err := templatesUpstartDefaultControlConfTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/upstart/default/control.conf.tmpl", size: 76, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesUpstartDefaultProcessTypeConfTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcb\x31\x0a\x42\x41\x0c\x84\xe1\x3e\xa7\x98\x52\x0b\xc5\xca\xcb\x88\xc8\x63\x0d\xb2\xb0\x24\x21\x89\x82\x2c\xb9\xbb\xac\x95\x76\x3f\x7c\x33\x77\x8e\xe6\xdd\xb2\xab\x60\x4e\x1c\x37\x33\x54\x1d\x56\x9a\x6b\xe3\x88\x5b\xbe\x8d\x51\x45\x14\xb9\x79\x42\x05\xdf\xe8\xf2\xf8\x79\x50\xa4\xda\xb2\x9d\x3f\x65\xf0\x8b\x07\x2e\xa7\xf3\x15\xea\x58\x62\xff\xeb\x3d\x7d\x02\x00\x00\xff\xff\xc1\x84\x51\xbe\x78\x00\x00\x00") + +func templatesUpstartDefaultProcessTypeConfTmplBytes() ([]byte, error) { + return bindataRead( + _templatesUpstartDefaultProcessTypeConfTmpl, + "templates/upstart/default/process-type.conf.tmpl", + ) +} + +func templatesUpstartDefaultProcessTypeConfTmpl() (*asset, error) { + bytes, err := templatesUpstartDefaultProcessTypeConfTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/upstart/default/process-type.conf.tmpl", size: 120, mode: os.FileMode(420), modTime: time.Unix(1584472497, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _templatesUpstartDefaultProgramConfTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x52\xc1\x4e\x1b\x31\x10\xbd\xfb\x2b\xde\x01\x01\x51\xb5\xd9\xb6\xb7\x1e\xc8\x89\x0b\xa7\x46\x6d\x6f\x14\xc1\xca\x9e\x6c\xac\x12\x8f\x35\x33\x5b\x1a\x81\xff\xbd\xf2\x6e\x28\xa0\x06\x35\x52\xb9\x79\xdf\xbc\xf7\xf6\x3d\x7b\x02\xa9\x97\x98\x2d\x72\xc2\xfd\x3d\xe6\x5d\xce\x28\xa5\xa9\xc7\x2c\xec\x49\xf5\xda\xb6\x99\x1e\xb1\x34\x6c\x50\x8a\x73\x6a\x9d\x18\x38\xe1\x74\x3c\xc5\xd4\xff\x43\x0d\x16\xec\xa1\xce\x9c\x1a\xe7\x9d\x11\xe7\x7c\xa8\xd1\x5f\xd4\x99\x73\x4a\x36\xc4\x30\x62\x83\x92\xd4\x9c\x4a\xd6\xef\xa0\x5e\x78\xa8\x44\xe7\xd7\x21\x0a\x46\xec\x8e\xe5\x47\x4c\xfd\x75\x88\x42\xde\x58\xb6\x75\x2e\xa4\xb9\xbb\x4b\xce\x4d\x17\xe3\x00\xfa\x95\x59\x0c\xcb\xcf\x5f\xbe\x9d\x8d\x89\xea\x57\x29\xcf\x26\x5f\x27\x5c\x27\xf4\x12\x8d\xa0\x25\xf3\x6d\xa0\x55\x37\xdc\x5a\xfb\x98\xb3\xe6\xbf\xc2\xf1\x31\xe6\xaf\xce\x5f\xe8\x75\xab\x9e\xd3\x2a\xf6\xed\x53\xd3\x17\x06\xfb\x08\x0e\xf0\xe1\xf5\x7e\x35\x35\xf9\x71\xee\x79\xb3\xe9\x52\xa8\xa6\xdf\x1d\x00\x2c\x16\x23\x7e\xcb\x3d\x4a\x69\x0f\xde\x87\x46\x2d\xf0\x60\xa3\x6e\x32\xfa\xf8\x1f\x4e\x24\x52\x75\x8e\x52\xc0\xee\x0d\x5c\x66\xb5\x66\x5a\xba\x3f\xcf\xb2\xbc\x38\x3f\xbb\x51\xeb\x6c\xd0\xc3\x57\x17\x0f\xa0\x5e\x28\xa3\xe1\x88\x93\xd3\xcb\xf7\xcd\xa7\xab\x77\xb3\xa3\x13\x3c\x60\x4d\x5d\x40\x93\x3e\xdc\xd4\x2b\xf2\x6b\xc6\xd1\xf2\xe2\x1c\x0b\xb4\x3f\x3b\x69\x65\x48\xcf\x5a\x1c\x5e\x68\x9e\x63\xd8\xd7\x84\xf3\x53\x11\xd9\xa0\x59\xbd\xed\x6f\x7e\x07\x00\x00\xff\xff\x75\xdd\xd3\xde\xd7\x03\x00\x00") + +func templatesUpstartDefaultProgramConfTmplBytes() ([]byte, error) { + return bindataRead( + _templatesUpstartDefaultProgramConfTmpl, + "templates/upstart/default/program.conf.tmpl", + ) +} + +func templatesUpstartDefaultProgramConfTmpl() (*asset, error) { + bytes, err := templatesUpstartDefaultProgramConfTmplBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/upstart/default/program.conf.tmpl", size: 983, mode: os.FileMode(420), modTime: time.Unix(1584491772, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "templates/launchd/launchd.plist.tmpl": templatesLaunchdLaunchdPlistTmpl, + "templates/runit/log/run.tmpl": templatesRunitLogRunTmpl, + "templates/runit/run.tmpl": templatesRunitRunTmpl, + "templates/systemd/default/control.target.tmpl": templatesSystemdDefaultControlTargetTmpl, + "templates/systemd/default/program.service.tmpl": templatesSystemdDefaultProgramServiceTmpl, + "templates/systemd-user/default/program.service.tmpl": templatesSystemdUserDefaultProgramServiceTmpl, + "templates/sysv/default/init.sh.tmpl": templatesSysvDefaultInitShTmpl, + "templates/upstart/default/control.conf.tmpl": templatesUpstartDefaultControlConfTmpl, + "templates/upstart/default/process-type.conf.tmpl": templatesUpstartDefaultProcessTypeConfTmpl, + "templates/upstart/default/program.conf.tmpl": templatesUpstartDefaultProgramConfTmpl, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "templates": &bintree{nil, map[string]*bintree{ + "launchd": &bintree{nil, map[string]*bintree{ + "launchd.plist.tmpl": &bintree{templatesLaunchdLaunchdPlistTmpl, map[string]*bintree{}}, + }}, + "runit": &bintree{nil, map[string]*bintree{ + "log": &bintree{nil, map[string]*bintree{ + "run.tmpl": &bintree{templatesRunitLogRunTmpl, map[string]*bintree{}}, + }}, + "run.tmpl": &bintree{templatesRunitRunTmpl, map[string]*bintree{}}, + }}, + "systemd": &bintree{nil, map[string]*bintree{ + "default": &bintree{nil, map[string]*bintree{ + "control.target.tmpl": &bintree{templatesSystemdDefaultControlTargetTmpl, map[string]*bintree{}}, + "program.service.tmpl": &bintree{templatesSystemdDefaultProgramServiceTmpl, map[string]*bintree{}}, + }}, + }}, + "systemd-user": &bintree{nil, map[string]*bintree{ + "default": &bintree{nil, map[string]*bintree{ + "program.service.tmpl": &bintree{templatesSystemdUserDefaultProgramServiceTmpl, map[string]*bintree{}}, + }}, + }}, + "sysv": &bintree{nil, map[string]*bintree{ + "default": &bintree{nil, map[string]*bintree{ + "init.sh.tmpl": &bintree{templatesSysvDefaultInitShTmpl, map[string]*bintree{}}, + }}, + }}, + "upstart": &bintree{nil, map[string]*bintree{ + "default": &bintree{nil, map[string]*bintree{ + "control.conf.tmpl": &bintree{templatesUpstartDefaultControlConfTmpl, map[string]*bintree{}}, + "process-type.conf.tmpl": &bintree{templatesUpstartDefaultProcessTypeConfTmpl, map[string]*bintree{}}, + "program.conf.tmpl": &bintree{templatesUpstartDefaultProgramConfTmpl, map[string]*bintree{}}, + }}, + }}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/export.go b/export.go new file mode 100644 index 0000000..4538a4b --- /dev/null +++ b/export.go @@ -0,0 +1,348 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "text/template" +) + +func exportLaunchd(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + l, err := loadTemplate("launchd", "templates/launchd/launchd.plist.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/Library/LaunchDaemons/"); os.IsNotExist(err) { + os.MkdirAll(location+"/Library/LaunchDaemons/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(l, fmt.Sprintf("%s/Library/LaunchDaemons/%s-%s.plist", location, app, processName), config) { + return false + } + + num += 1 + } + } + + return true +} + +func exportRunit(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + r, err := loadTemplate("run", "templates/runit/run.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + l, err := loadTemplate("log", "templates/runit/log/run.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/service"); os.IsNotExist(err) { + os.MkdirAll(location+"/service", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processDirectory := fmt.Sprintf("%s-%s-%d", app, entry.Name, num) + folderPath := location + "/service/" + processDirectory + processName := fmt.Sprintf("%s-%d", entry.Name, num) + + fmt.Println("creating:", folderPath) + os.MkdirAll(folderPath, os.ModePerm) + + fmt.Println("creating:", folderPath+"/env") + os.MkdirAll(folderPath+"/env", os.ModePerm) + + fmt.Println("creating:", folderPath+"/log") + os.MkdirAll(folderPath+"/log", os.ModePerm) + + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + + if !writeOutput(r, fmt.Sprintf("%s/run", folderPath), config) { + return false + } + + env, ok := config["env"].(map[string]string) + if !ok { + fmt.Fprintf(os.Stderr, "invalid env map\n") + return false + } + + env["PORT"] = strconv.Itoa(port) + env["PS"] = app + "-" + processName + + for key, value := range env { + fmt.Println("writing:", folderPath+"/env/"+key) + f, err := os.Create(folderPath + "/env/" + key) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) + return false + } + defer f.Close() + + if _, err = f.WriteString(value); err != nil { + fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) + return false + } + + if err = f.Sync(); err != nil { + fmt.Fprintf(os.Stderr, "error syncing output: %s\n", err) + return false + } + } + + if !writeOutput(l, fmt.Sprintf("%s/log/run", folderPath), config) { + return false + } + + num += 1 + } + } + + return true +} + +func exportSystemd(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + t, err := loadTemplate("target", "templates/systemd/default/control.target.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + s, err := loadTemplate("service", "templates/systemd/default/program.service.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/systemd/system/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/systemd/system/", os.ModePerm) + } + + processes := []string{} + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + fileName := fmt.Sprintf("%s.%d", entry.Name, num) + processes = append(processes, fmt.Sprintf(app+"-%s.service", fileName)) + + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(s, fmt.Sprintf("%s/etc/systemd/system/%s-%s.service", location, app, fileName), config) { + return false + } + + num += 1 + } + } + + config := vars + config["processes"] = processes + if writeOutput(t, fmt.Sprintf("%s/etc/systemd/system/%s.target", location, app), config) { + fmt.Println("You will want to run 'systemctl --system daemon-reload' to activate the service on the target host") + return true + } + + return true +} + +func exportSystemdUser(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + s, err := loadTemplate("service", "templates/systemd-user/default/program.service.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + path := vars["home"].(string) + "/.config/systemd/user/" + if _, err := os.Stat(location + path); os.IsNotExist(err) { + os.MkdirAll(location+path, os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(s, fmt.Sprintf("%s%s%s-%s.service", location, path, app, processName), config) { + return false + } + + num += 1 + } + } + + fmt.Println("You will want to run 'systemctl --user daemon-reload' to activate the service on the target host") + return true +} + +func exportSysv(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + l, err := loadTemplate("launchd", "templates/sysv/default/init.sh.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/init.d/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/init.d/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(l, fmt.Sprintf("%s/etc/init.d/%s-%s", location, app, processName), config) { + return false + } + + num += 1 + } + } + + return true +} +func exportUpstart(app string, entries []procfileEntry, formations map[string]formationEntry, location string, defaultPort int, vars map[string]interface{}) bool { + p, err := loadTemplate("program", "templates/upstart/default/program.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + c, err := loadTemplate("app", "templates/upstart/default/control.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + t, err := loadTemplate("process-type", "templates/upstart/default/process-type.conf.tmpl") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if _, err := os.Stat(location + "/etc/init/"); os.IsNotExist(err) { + os.MkdirAll(location+"/etc/init/", os.ModePerm) + } + + for i, entry := range entries { + num := 1 + count := processCount(entry, formations) + + if count > 0 { + config := vars + config["process_type"] = entry.Name + if !writeOutput(t, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, entry.Name), config) { + return false + } + } + + for num <= count { + processName := fmt.Sprintf("%s-%d", entry.Name, num) + fileName := fmt.Sprintf("%s-%d", entry.Name, num) + port := portFor(i, num, defaultPort) + config := templateVars(app, entry, processName, num, port, vars) + if !writeOutput(p, fmt.Sprintf("%s/etc/init/%s-%s.conf", location, app, fileName), config) { + return false + } + + num += 1 + } + } + + config := vars + return writeOutput(c, fmt.Sprintf("%s/etc/init/%s.conf", location, app), config) +} +func processCount(entry procfileEntry, formations map[string]formationEntry) int { + count := 0 + if f, ok := formations["all"]; ok { + count = f.Count + } + if f, ok := formations[entry.Name]; ok { + count = f.Count + } + return count +} + +func portFor(processIndex int, instance int, base int) int { + return 5000 + (processIndex * 100) + (instance - 1) +} + +func templateVars(app string, entry procfileEntry, processName string, num int, port int, vars map[string]interface{}) map[string]interface{} { + config := vars + config["args"] = entry.args() + config["args_escaped"] = entry.argsEscaped() + config["command"] = entry.Command + config["command_list"] = entry.commandList() + config["num"] = num + config["port"] = port + config["process_name"] = processName + config["process_type"] = entry.Name + config["program"] = entry.program() + config["ps"] = app + "-" + entry.Name + "." + strconv.Itoa(num) + if config["description"] == "" { + config["description"] = fmt.Sprintf("%s.%s process for %s", entry.Name, strconv.Itoa(num), app) + } + + return config +} + +func writeOutput(t *template.Template, outputPath string, variables map[string]interface{}) bool { + fmt.Println("writing:", outputPath) + f, err := os.Create(outputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error creating file: %s\n", err) + return false + } + defer f.Close() + + if err = t.Execute(f, variables); err != nil { + fmt.Fprintf(os.Stderr, "error writing output: %s\n", err) + return false + } + + if err := os.Chmod(outputPath, 0755); err != nil { + fmt.Fprintf(os.Stderr, "error setting mode: %s\n", err) + return false + } + + return true +} + +func loadTemplate(name string, filename string) (*template.Template, error) { + asset, err := Asset(filename) + if err != nil { + return nil, err + } + + t, err := template.New(name).Parse(string(asset)) + if err != nil { + return nil, fmt.Errorf("error parsing template: %s", err) + } + + return t, nil +} diff --git a/go.mod b/go.mod index c4672ee..4a7af8e 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,7 @@ go 1.12 require ( github.com/akamensky/argparse v0.0.0-20180405044306-95911c018170 github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 + github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/joho/godotenv v1.2.0 + gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) diff --git a/go.sum b/go.sum index 60acdd4..071f9c4 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,9 @@ github.com/akamensky/argparse v0.0.0-20180405044306-95911c018170 h1:JuYOgmyY+ckp github.com/akamensky/argparse v0.0.0-20180405044306-95911c018170/go.mod h1:pdh+2piXurh466J9tqIqq39/9GO2Y8nZt6Cxzu18T9A= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= +github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc= github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= diff --git a/main.go b/main.go index bb2cc25..b79a163 100644 --- a/main.go +++ b/main.go @@ -5,13 +5,16 @@ import ( "fmt" "io/ioutil" "os" + "os/user" "regexp" "sort" + "strconv" "strings" "github.com/akamensky/argparse" "github.com/andrew-d/go-termutil" "github.com/joho/godotenv" + "gopkg.in/alessio/shellescape.v1" ) type procfileEntry struct { @@ -19,6 +22,29 @@ type procfileEntry struct { Command string } +type formationEntry struct { + Name string + Count int +} + +type exportFunc func(string, []procfileEntry, map[string]formationEntry, string, int, map[string]interface{}) bool + +func (p *procfileEntry) commandList() []string { + return strings.Fields(p.Command) +} + +func (p *procfileEntry) program() string { + return strings.Fields(p.Command)[0] +} + +func (p *procfileEntry) args() string { + return strings.Join(strings.Fields(p.Command)[1:], " ") +} + +func (p *procfileEntry) argsEscaped() string { + return shellescape.Quote(p.args()) +} + const portEnvVar = "PORT" // Version contains the procfile-util version @@ -169,13 +195,13 @@ func parseProcfile(path string, delimiter string) ([]procfileEntry, error) { return entries, nil } -func expandEnv(e procfileEntry, envPath string, allowEnv bool, defaultPort string) (string, error) { +func expandEnv(e procfileEntry, envPath string, allowEnv bool, defaultPort int) (string, error) { baseExpandFunc := func(key string) string { if key == "PS" { return os.Getenv("PS") } if key == portEnvVar { - return defaultPort + return string(defaultPort) } return "" } @@ -256,7 +282,7 @@ func existsCommand(entries []procfileEntry, processType string) bool { return false } -func expandCommand(entries []procfileEntry, envPath string, allowGetenv bool, processType string, defaultPort string, delimiter string) bool { +func expandCommand(entries []procfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int, delimiter string) bool { hasErrors := false var expandedEntries []procfileEntry for _, entry := range entries { @@ -283,6 +309,156 @@ func expandCommand(entries []procfileEntry, envPath string, allowGetenv bool, pr return true } +func exportCommand(entries []procfileEntry, app string, description string, envPath string, format string, formation string, group string, home string, limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string, location string, logPath string, nice string, prestart string, workingDirectoryPath string, runPath string, timeout int, processUser string, defaultPort int) bool { + if format == "" { + fmt.Fprintf(os.Stderr, "no format specified\n") + return false + } + if location == "" { + fmt.Fprintf(os.Stderr, "no output location specified\n") + return false + } + + formats := map[string]exportFunc{ + "launchd": exportLaunchd, + "runit": exportRunit, + "systemd": exportSystemd, + "systemd-user": exportSystemdUser, + "sysv": exportSysv, + "upstart": exportUpstart, + } + + if _, ok := formats[format]; !ok { + fmt.Fprintf(os.Stderr, "invalid format type: %s\n", format) + return false + } + + formations, err := parseFormation(formation) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if processUser == "" { + processUser = app + } + + if group == "" { + group = app + } + + u, err := user.Current() + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + return false + } + + if home == "" { + home = "/home/" + u.Username + } + + env := make(map[string]string) + if envPath != "" { + b, err := ioutil.ReadFile(envPath) + if err != nil { + fmt.Fprintf(os.Stderr, "error reading env file: %s\n", err) + return false + } + + content := string(b) + env, err = godotenv.Unmarshal(content) + if err != nil { + fmt.Fprintf(os.Stderr, "error parsing env file: %s\n", err) + return false + } + } + + vars := make(map[string]interface{}) + vars["app"] = app + vars["description"] = description + vars["env"] = env + vars["group"] = group + vars["home"] = home + vars["log"] = logPath + vars["location"] = location + vars["limit_coredump"] = limitCoredump + vars["limit_cputime"] = limitCputime + vars["limit_data"] = limitData + vars["limit_file_size"] = limitFileSize + vars["limit_locked_memory"] = limitLockedMemory + vars["limit_open_files"] = limitOpenFiles + vars["limit_user_processes"] = limitUserProcesses + vars["limit_physical_memory"] = limitPhysicalMemory + vars["limit_stack_size"] = limitStackSize + vars["nice"] = nice + vars["prestart"] = prestart + vars["working_directory"] = workingDirectoryPath + vars["timeout"] = strconv.Itoa(timeout) + vars["ulimit_shell"] = ulimitShell(limitCoredump, limitCputime, limitData, limitFileSize, limitLockedMemory, limitOpenFiles, limitUserProcesses, limitPhysicalMemory, limitStackSize) + vars["user"] = processUser + + if fn, ok := formats[format]; ok { + return fn(app, entries, formations, location, defaultPort, vars) + } + + return false +} + +func ulimitShell(limitCoredump string, limitCputime string, limitData string, limitFileSize string, limitLockedMemory string, limitOpenFiles string, limitUserProcesses string, limitPhysicalMemory string, limitStackSize string) string { + s := []string{} + if limitCoredump != "" { + s = append(s, "ulimit -c ${limit_coredump}") + } + if limitCputime != "" { + s = append(s, "ulimit -t ${limit_cputime}") + } + if limitData != "" { + s = append(s, "ulimit -d ${limit_data}") + } + if limitFileSize != "" { + s = append(s, "ulimit -f ${limit_file_size}") + } + if limitLockedMemory != "" { + s = append(s, "ulimit -l ${limit_locked_memory}") + } + if limitOpenFiles != "" { + s = append(s, "ulimit -n ${limit_open_files}") + } + if limitUserProcesses != "" { + s = append(s, "ulimit -u ${limit_user_processes}") + } + if limitPhysicalMemory != "" { + s = append(s, "ulimit -m ${limit_physical_memory}") + } + if limitStackSize != "" { + s = append(s, "ulimit -s ${limit_stack_size}") + } + + return strings.Join(s, "\n") +} + +func parseFormation(formation string) (map[string]formationEntry, error) { + entries := make(map[string]formationEntry) + for _, formation := range strings.Split(formation, ",") { + parts := strings.Split(formation, "=") + if len(parts) != 2 { + return entries, fmt.Errorf("invalid formation: %s", formation) + } + + i, err := strconv.Atoi(parts[1]) + if err != nil { + return entries, fmt.Errorf("invalid formation: %s", err) + } + + entries[parts[0]] = formationEntry{ + Name: parts[0], + Count: i, + } + } + + return entries, nil +} + func deleteCommand(entries []procfileEntry, processType string, writePath string, stdout bool, delimiter string, path string) bool { var validEntries []procfileEntry for _, entry := range entries { @@ -315,7 +491,7 @@ func setCommand(entries []procfileEntry, processType string, command string, wri return outputProcfile(path, writePath, delimiter, stdout, validEntries) } -func showCommand(entries []procfileEntry, envPath string, allowGetenv bool, processType string, defaultPort string) bool { +func showCommand(entries []procfileEntry, envPath string, allowGetenv bool, processType string, defaultPort int) bool { var foundEntry procfileEntry for _, entry := range entries { if processType == entry.Name { @@ -344,7 +520,7 @@ func main() { loglevelFlag := parser.Selector("l", "loglevel", []string{"info", "debug"}, &argparse.Options{Default: "info", Help: "loglevel to use"}) procfileFlag := parser.String("P", "procfile", &argparse.Options{Default: "Procfile", Help: "path to a procfile"}) delimiterFlag := parser.String("D", "delimiter", &argparse.Options{Default: ":", Help: "delimiter in use within procfile"}) - defaultPortFlag := parser.String("d", "default-port", &argparse.Options{Default: "5000", Help: "default port to use"}) + defaultPortFlag := parser.Int("d", "default-port", &argparse.Options{Default: 5000, Help: "default port to use"}) versionFlag := parser.Flag("v", "version", &argparse.Options{Help: "show version"}) checkCmd := parser.NewCommand("check", "check that the specified procfile is valid") @@ -362,6 +538,32 @@ func main() { envPathExpandFlag := expandCmd.String("e", "env-file", &argparse.Options{Help: "path to a dotenv file"}) processTypeExpandFlag := expandCmd.String("p", "process-type", &argparse.Options{Help: "name of process to expand"}) + exportCmd := parser.NewCommand("export", "export the application to another process management format") + appExportFlag := exportCmd.String("", "app", &argparse.Options{Default: "app", Help: "name of app"}) + descriptionExportFlag := exportCmd.String("", "description", &argparse.Options{Help: "process description"}) + envPathExportFlag := exportCmd.String("e", "env-file", &argparse.Options{Help: "path to a dotenv file"}) + formatExportFlag := exportCmd.String("", "format", &argparse.Options{Default: "systemd", Help: "format to export"}) + formationExportFlag := exportCmd.String("", "formation", &argparse.Options{Default: "all=1", Help: "specify what processes will run and how many"}) + groupExportFlag := exportCmd.String("", "group", &argparse.Options{Help: "group to run the command as"}) + homeExportFlag := exportCmd.String("", "home", &argparse.Options{Help: "home directory for program"}) + limitCoredumpExportFlag := exportCmd.String("", "limit-coredump", &argparse.Options{Help: "Largest size (in blocks) of a core file that can be created. (setrlimit RLIMIT_CORE)"}) + limitCputimeExportFlag := exportCmd.String("", "limit-cputime", &argparse.Options{Help: "Maximum amount of cpu time (in seconds) a program may use. (setrlimit RLIMIT_CPU)"}) + limitDataExportFlag := exportCmd.String("", "limit-data", &argparse.Options{Help: "Maximum data segment size (setrlimit RLIMIT_DATA)"}) + limitFileSizeExportFlag := exportCmd.String("", "limit-file-size", &argparse.Options{Help: "Maximum size (in blocks) of a file receiving writes (setrlimit RLIMIT_FSIZE)"}) + limitLockedMemoryExportFlag := exportCmd.String("", "limit-locked-memory", &argparse.Options{Help: "Maximum amount of memory (in bytes) lockable with mlock(2) (setrlimit RLIMIT_MEMLOCK)"}) + limitOpenFilesExportFlag := exportCmd.String("", "limit-open-files", &argparse.Options{Help: "maximum number of open files, sockets, etc. (setrlimit RLIMIT_NOFILE)"}) + limitUserProcessesExportFlag := exportCmd.String("", "limit-user-processes", &argparse.Options{Help: "Maximum number of running processes (or threads!) for this user id. Not recommended because this setting applies to the user, not the process group. (setrlimit RLIMIT_NPROC)"}) + limitPhysicalMemoryExportFlag := exportCmd.String("", "limit-physical-memory", &argparse.Options{Help: "Maximum resident set size (in bytes); the amount of physical memory used by a process. (setrlimit RLIMIT_RSS)"}) + limitStackSizeExportFlag := exportCmd.String("", "limit-stack-size", &argparse.Options{Help: "Maximum size (in bytes) of a stack segment (setrlimit RLIMIT_STACK)"}) + locationExportFlag := exportCmd.String("", "location", &argparse.Options{Help: "location to output to"}) + logPathExportFlag := exportCmd.String("", "log-path", &argparse.Options{Default: "/var/log", Help: "log directory"}) + niceExportFlag := exportCmd.String("", "nice", &argparse.Options{Help: "nice level to add to this program before running"}) + prestartExportFlag := exportCmd.String("", "prestart", &argparse.Options{Help: "A command to execute before starting and restarting. A failure of this command will cause the start/restart to abort. This is useful for health checks, config tests, or similar operations."}) + workingDirectoryPathExportFlag := exportCmd.String("", "working-directory-path", &argparse.Options{Default: "/", Help: "working directory path for app"}) + runExportFlag := exportCmd.String("", "run", &argparse.Options{Help: "run pid file directory, defaults to /var/run/"}) + timeoutExportFlag := exportCmd.Int("", "timeout", &argparse.Options{Default: 5, Help: "amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL"}) + userExportFlag := exportCmd.String("", "user", &argparse.Options{Help: "user to run the command as"}) + listCmd := parser.NewCommand("list", "list all process types in a procfile") setCmd := parser.NewCommand("set", "set the command for a process type in a file") @@ -375,8 +577,7 @@ func main() { envPathShowFlag := showCmd.String("e", "env-file", &argparse.Options{Help: "path to a dotenv file"}) processTypeShowFlag := showCmd.String("p", "process-type", &argparse.Options{Help: "name of process to show", Required: true}) - err := parser.Parse(os.Args) - if err != nil { + if err := parser.Parse(os.Args); err != nil { fmt.Fprintf(os.Stderr, "%s\n", parser.Usage(err)) os.Exit(1) return @@ -406,6 +607,8 @@ func main() { success = existsCommand(entries, *processTypeExistsFlag) } else if expandCmd.Happened() { success = expandCommand(entries, *envPathExpandFlag, *allowGetenvExpandFlag, *processTypeExpandFlag, *defaultPortFlag, *delimiterFlag) + } else if exportCmd.Happened() { + success = exportCommand(entries, *appExportFlag, *descriptionExportFlag, *envPathExportFlag, *formatExportFlag, *formationExportFlag, *groupExportFlag, *homeExportFlag, *limitCoredumpExportFlag, *limitCputimeExportFlag, *limitDataExportFlag, *limitFileSizeExportFlag, *limitLockedMemoryExportFlag, *limitOpenFilesExportFlag, *limitUserProcessesExportFlag, *limitPhysicalMemoryExportFlag, *limitStackSizeExportFlag, *locationExportFlag, *logPathExportFlag, *niceExportFlag, *prestartExportFlag, *workingDirectoryPathExportFlag, *runExportFlag, *timeoutExportFlag, *userExportFlag, *defaultPortFlag) } else if listCmd.Happened() { success = listCommand(entries) } else if setCmd.Happened() { diff --git a/templates/launchd/launchd.plist.tmpl b/templates/launchd/launchd.plist.tmpl new file mode 100644 index 0000000..03c6000 --- /dev/null +++ b/templates/launchd/launchd.plist.tmpl @@ -0,0 +1,37 @@ + + + + + Label + {{ .app }}-{{ .process_type }}-{{ .num }} + EnvironmentVariables + + PORT + {{ .port }} + PS + {{ .ps }} + {{- range $key, $value := .env }} + {{ $key }} + {{ $value }}{{ end }} + + ProgramArguments + + {{- range $command := .command_list }} + {{ if eq $command "$PORT" }}{{ $.port }}{{ else }}{{ $command }}{{ end }}{{ end }} + + KeepAlive + + RunAtLoad + + StandardOutPath + {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stdout.log + StandardErrorPath + {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stderr.log + UserName + {{ .user }} + WorkingDirectory + {{ .working_directory }} + {{- if .nice }} + Nice{{ .nice }}{{ end }} + + diff --git a/templates/runit/log/run.tmpl b/templates/runit/log/run.tmpl new file mode 100644 index 0000000..0e90e4e --- /dev/null +++ b/templates/runit/log/run.tmpl @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +LOG={{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }} + +test -d "$LOG" || mkdir -p -m 2750 "$LOG" && chown {{ .user }} "$LOG" +exec chpst -u {{ .user }} svlogd "$LOG" diff --git a/templates/runit/run.tmpl b/templates/runit/run.tmpl new file mode 100644 index 0000000..03dc9cc --- /dev/null +++ b/templates/runit/run.tmpl @@ -0,0 +1,4 @@ +#!/bin/sh +cd {{ .working_directory }} +exec 2>&1 +exec chpst -u {{ .user }} -e {{ .location }}/{{ .app }}-{{ .process_type }}-{{ .num }}/env {{ .command }} diff --git a/templates/systemd-user/default/program.service.tmpl b/templates/systemd-user/default/program.service.tmpl new file mode 100644 index 0000000..34f01e4 --- /dev/null +++ b/templates/systemd-user/default/program.service.tmpl @@ -0,0 +1,20 @@ +[Unit] +{{- if .description }} +Description={{ .description }}{{ end }} + +[Service] +WorkingDirectory={{ .working_directory }} +Type=simple +Environment=PORT={{ .port }} +Environment=PS={{ .ps }} +EnvironmentFile=-{{ .home }}/.config/systemd/user/{{ .ps }}.environment +ExecStart={{ .command }} +Restart=always +RestartSec=14s +StandardInput=null +StandardOutput={{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stdout.log +StandardError={{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stderr.log +SyslogIdentifier=%n + +[Install] +WantedBy=multi-user.target diff --git a/templates/systemd/default/control.target.tmpl b/templates/systemd/default/control.target.tmpl new file mode 100644 index 0000000..ba4755f --- /dev/null +++ b/templates/systemd/default/control.target.tmpl @@ -0,0 +1,5 @@ +[Unit] +Wants={{ range $value := .processes }}{{ $value }} {{ end }} + +[Install] +WantedBy=multi-user.target diff --git a/templates/systemd/default/program.service.tmpl b/templates/systemd/default/program.service.tmpl new file mode 100644 index 0000000..7218e82 --- /dev/null +++ b/templates/systemd/default/program.service.tmpl @@ -0,0 +1,33 @@ +[Unit] +{{- if .description }} +Description={{ .description }}{{ end }} +PartOf={{ .app }}.target +StopWhenUnneeded=yes + +[Service] +User={{ .user }} +Group={{ .group }} +WorkingDirectory={{ .working_directory }} +Environment=PORT={{ .port }} +Environment=PS={{ .ps }} +EnvironmentFile=-/etc/default/{{ .app }} +EnvironmentFile=-/etc/sysconfig/{{ .app }} +{{- range $key, $value := .env }} +Environment="{{ $key }}={{ $value }}" +{{ end }} +ExecStart=/bin/bash -lc 'exec -a "{{ .ps }}" {{ .command }}' +Restart=always +RestartSec=14s +StandardInput=null +StandardOutput={{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stdout.log +StandardError={{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stderr.log +SyslogIdentifier=%n +KillMode=mixed +TimeoutStopSec={{ .timeout }} + +{{- if .nice }} +Nice={{ .nice }} +{{ end }} +{{- if .limit_open_files }} +LimitNOFILE={{ .limit_open_files }} +{{ end }} diff --git a/templates/sysv/default/init.sh.tmpl b/templates/sysv/default/init.sh.tmpl new file mode 100644 index 0000000..21b0c7f --- /dev/null +++ b/templates/sysv/default/init.sh.tmpl @@ -0,0 +1,224 @@ +#!/bin/sh +# Init script for {{ .app }}-{{ .process_type }}-{{ .num }} +# Maintained by {{ if .author }}{{ .author }}{{ end }} +# Generated by pleaserun. +# Implemented based on LSB Core 3.1: +# * Sections: 20.2, 20.3 +# +### BEGIN INIT INFO +# Provides: {{ .app }}-{{ .process_type }}-{{ .num }} +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: {{ if .one_line_description }}{{ .one_line_description }}{{ end }} +# Description: {{ .description }} +### END INIT INFO + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +export PATH + +name={{ .app }}-{{ .process_type }}-{{ .num }} +program={{ .program}} +args={{ .args_escaped }} +pidfile="/var/run/$name.pid" +user="{{ .user }}" +group="{{ .group }}" +chroot="{{ .working_directory }}" +chdir="{{ .working_directory }}" +nice="{{ .nice}}" +{{- if .limit_coredump }} +limit_coredump="{{ .limit_coredump }}"{{ end }} +{{- if .limit_cputime }} +limit_cputime="{{ .limit_cputime }}"{{ end }} +{{- if .limit_data }} +limit_data="{{ .limit_data }}"{{ end }} +{{- if .limit_file_size }} +limit_file_size="{{ .limit_file_size }}"{{ end }} +{{- if .limit_locked_memory }} +limit_locked_memory="{{ .limit_locked_memory }}"{{ end }} +{{- if .limit_open_files }} +limit_open_files="{{ .limit_open_files }}"{{ end }} +{{- if .limit_user_processes }} +limit_user_processes="{{ .limit_user_processes }}"{{ end }} +{{- if .limit_physical_memory }} +limit_physical_memory="{{ .limit_physical_memory }}"{{ end }} +{{- if .limit_stack_size }} +limit_stack_size="{{ .limit_stack_size }}"{{ end }} + +# If this is set to 1, then when `stop` is called, if the process has +# not exited within a reasonable time, SIGKILL will be sent next. +# The default behavior is to simply log a message "program stop failed; still running" +KILL_ON_STOP_TIMEOUT=0 + +# When loading default and sysconfig files, we use `set -a` to make +# all variables automatically into environment variables. +set -a +[ -r /etc/default/{{ .app }} ] && . /etc/default/{{ .app }} +[ -r /etc/sysconfig/{{ .app }} ] && . /etc/sysconfig/{{ .app }} +set +a +export PORT={{ .port }} +export PS={{ .ps }} + +[ -z "$nice" ] && nice=0 + +trace() { + logger -t "/etc/init.d/{{ .app }}-{{ .process_type }}-{{ .num }}" "$@" +} + +emit() { + trace "$@" + echo "$@" +} + +start() { + {{/* I do not use 'su' here to run as a different user because the process 'su' + stays as the parent, causing our pidfile to contain the pid of 'su' not the + program we intended to run. Luckily, the 'chroot' program on OSX, FreeBSD, and Linux + all support switching users and it invokes execve immediately after chrooting. */}} + + # Ensure the log directory is setup correctly. + if [ ! -d "{{ .log }}" ]; then + mkdir "{{ .log }}" + chown "$user":"$group" "{{ .log }}" + chmod 755 "{{ .log }}" + fi + + {{- if .prestart }} + if [ "$PRESTART" != "no" ] ; then + # If prestart fails, abort start. + prestart || return $? + fi + {{ end }} + + # Setup any environmental stuff beforehand + {{ if .ulimit_shell }}{{ .ulimit_shell }}{{ end }} + + # Run the program! + {{- if .nice }}nice -n "$nice"{{ end }} + chroot --userspec "$user":"$group" "$chroot" sh -c " + {{ if .ulimit_shell }}{{ .ulimit_shell }}{{ end }} + cd \"$chdir\" + exec \"$program\" $args + " >> {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stdout.log 2>> {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stderr.log & + + # Generate the pidfile from here. If we instead made the forked process + # generate it there will be a race condition between the pidfile writing + # and a process possibly asking for status. + echo $! > $pidfile + + emit "$name started" + return 0 +} + +stop() { + # Try a few times to kill TERM the program + if status ; then + pid=$(cat "$pidfile") + trace "Killing $name (pid $pid) with SIGTERM" + kill -TERM $pid + # Wait for it to exit. + for i in 1 2 3 4 5 ; do + trace "Waiting $name (pid $pid) to die..." + status || break + sleep 1 + done + if status ; then + if [ "$KILL_ON_STOP_TIMEOUT" -eq 1 ] ; then + trace "Timeout reached. Killing $name (pid $pid) with SIGKILL. This may result in data loss." + kill -KILL $pid + emit "$name killed with SIGKILL." + else + emit "$name stop failed; still running." + fi + else + emit "$name stopped." + fi + fi +} + +status() { + if [ -f "$pidfile" ] ; then + pid=$(cat "$pidfile") + if ps -p $pid > /dev/null 2> /dev/null ; then + # process by this pid is running. + # It may not be our pid, but that's what you get with just pidfiles. + # TODO(sissel): Check if this process seems to be the same as the one we + # expect. It'd be nice to use flock here, but flock uses fork, not exec, + # so it makes it quite awkward to use in this case. + return 0 + else + return 2 # program is dead but pid file exists + fi + else + return 3 # program is not running + fi +} + +force_stop() { + if status ; then + stop + status && kill -KILL $(cat "$pidfile") + fi +} + +{{- if .prestart }} +prestart() { + {{ .prestart }} + + status=$? + + if [ $status -gt 0 ] ; then + emit "Prestart command failed with code $status. If you wish to skip the prestart command, set PRESTART=no in your environment." + fi + return $status +} +{{ end }} + +case "$1" in + force-start|start|stop|force-stop|restart) + trace "Attempting '$1' on {{ .app }}-{{ .process_type }}-{{ .num }}" + ;; +esac + +case "$1" in + force-start) + PRESTART=no + exec "$0" start + ;; + start) + status + code=$? + if [ $code -eq 0 ]; then + emit "$name is already running" + exit $code + else + start + exit $? + fi + ;; + stop) stop ;; + force-stop) force_stop ;; + status) + status + code=$? + if [ $code -eq 0 ] ; then + emit "$name is running" + else + emit "$name is not running" + fi + exit $code + ;; + restart) + {{- if .prestart}}if [ "$PRESTART" != "no" ] ; then + prestart || exit $? + fi{{ end }} + stop && start + ;; + *) + echo "Usage: $SCRIPTNAME {start|force-start|stop|force-start|force-stop|status|restart}" >&2 + exit 3 + ;; +esac + +exit $? diff --git a/templates/upstart/default/control.conf.tmpl b/templates/upstart/default/control.conf.tmpl new file mode 100644 index 0000000..0ddf36c --- /dev/null +++ b/templates/upstart/default/control.conf.tmpl @@ -0,0 +1,4 @@ +description {{ .app }} + +start on (started networking) +stop on runlevel [06] diff --git a/templates/upstart/default/process-type.conf.tmpl b/templates/upstart/default/process-type.conf.tmpl new file mode 100644 index 0000000..5e14cf1 --- /dev/null +++ b/templates/upstart/default/process-type.conf.tmpl @@ -0,0 +1,4 @@ +description {{ .app }}-{{ .process_type }} + +start on starting {{ .app }} +stop on (runlevel [06] or stopping {{ .app }}) diff --git a/templates/upstart/default/program.conf.tmpl b/templates/upstart/default/program.conf.tmpl new file mode 100644 index 0000000..b0e7b8d --- /dev/null +++ b/templates/upstart/default/program.conf.tmpl @@ -0,0 +1,29 @@ +description {{ .app }}-{{ .process_type }}-{{ .num }} + +start on (starting {{ .app }}-{{ .process_type }} or starting {{ .app }}) +stop on (stopping {{ .app }}-{{ .process_type }} or stopping {{ .app }}) + +setuid {{ .user }} +setgid {{ .group }} +chdir {{ .working_directory }} +respawn + +script + export PORT={{ .port }} + export PS={{ .ps }} + [ -r /etc/default/{{ .app }} ] && . /etc/default/{{ .app }} + [ -r /etc/sysconfig/{{ .app }} ] && . /etc/sysconfig/{{ .app }} + cd {{ .working_directory }} + exec {{ .command }} \ + >> {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stdout.log \ + 2>> {{ .log }}/{{ .app }}-{{ .process_type }}-{{ .num }}-stderr.log +end script + +post-start script + PID=`status {{ .app }}-{{ .process_type }}-{{ .num }} | egrep -oi '([0-9]+)$' | head -n1` + echo $PID > /var/run/{{ .app }}/{{ .app }}-{{ .process_type }}-{{ .num }}.pid +end script + +post-stop script + rm -f /var/run/{{ .app }}/{{ .app }}-{{ .process_type }}-{{ .num }}.pid +end script