summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHéctor Orón Martínez <zumbi@debian.org>2018-08-10 18:07:56 +0200
committerHéctor Orón Martínez <zumbi@debian.org>2018-08-10 18:07:56 +0200
commita3819b2c8c823955bb053d5a34da27c94aef4d47 (patch)
tree430633212532264c15f75ca47e02f3e4740c7c3c
parent8ab62bc45ed897f007493d02084538503ade6f37 (diff)
New upstream version 1.0.0+git20180808.5b74d5d
-rw-r--r--README.md109
-rw-r--r--action.go27
-rw-r--r--actions/debootstrap_action.go31
-rw-r--r--actions/image_partition_action.go55
-rw-r--r--actions/ostree_commit_action.go12
-rw-r--r--actions/pack_action.go2
-rw-r--r--actions/run_action.go10
-rw-r--r--cmd/debos/debos.go68
-rw-r--r--commands.go8
-rw-r--r--doc/examples/example.yaml34
-rw-r--r--doc/examples/overlays/sudo/etc/sudoers.d/user1
-rwxr-xr-xdoc/examples/setup-user.sh10
-rwxr-xr-xdoc/man/create_manpage.sh28
-rw-r--r--doc/man/debos.1132
-rw-r--r--filesystem.go13
15 files changed, 481 insertions, 59 deletions
diff --git a/README.md b/README.md
index 3defeeb..87e374a 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,102 @@
-debos
-=====
+# debos - Debian OS images builder
-Debian OS builder. debos is a tool to make creation of various debian based os
-"images" simpler. While most other tools focus on specific use-case, debos is
-more meant as a toolchain to make comon actions trivial while providing enough
-rope to do whatever tweaking that might be required behind the scene.
+## Sypnosis
-debos expects a yaml file as input, syntax description can be found at:
- https://godoc.org/github.com/go-debos/debos/actions
+ debos [options] <recipe file in YAML>
+ debos [--help]
+
+Application Options:
+
+ --artifactdir=
+ -t, --template-var= Template variables
+ --debug-shell Fall into interactive shell on error
+ -s, --shell= Redefine interactive shell binary (default: bash)
+ --scratchsize= Size of disk backed scratch space
+
+
+## Description
+
+debos is a tool to make the creation of various Debian-based OS images
+simpler. While most other tools focus on specific use-cases, debos is
+more meant as a tool-chain to make common actions trivial while providing
+enough rope to do whatever tweaking that might be required behind the scene.
+
+debos expects a YAML file as input and will run the actions listed in the
+file sequentially. These actions should be self-contained and independent
+of each other.
+
+Some of the actions provided by debos to customize and produce images are:
+
+* apt: install packages and their dependencies with 'apt'
+* debootstrap: construct the target rootfs with debootstrap
+* download: download a single file from the internet
+* filesystem-deploy: deploy a root filesystem to an image previously created
+* image-partition: create an image file, make partitions and format them
+* ostree-commit: create an OSTree commit from rootfs
+* ostree-deploy: deploy an OSTree branch to the image
+* overlay: do a recursive copy of directories or files to the target filesystem
+* pack: create a tarball with the target filesystem
+* raw: directly write a file to the output image at a given offset
+* run: allows to run a command or script in the filesystem or in the host
+* unpack: unpack files from archive in the filesystem
+
+A full syntax description of all the debos actions can be found at:
+https://godoc.org/github.com/go-debos/debos/actions
+
+## Installation (under Debian)
+
+ sudo apt install golang
+ sudo apt install libglib2.0-dev libostree-dev
+ export GOPATH=/opt/src/gocode # or whatever suites your needs
+ go get -u github.com/go-debos/debos/cmd/debos
+ /opt/src/gocode/bin/debos --help
+
+## Simple example
+
+The following example will create a arm64 image, install several
+packages in it, change the file /etc/hostname to "debian" and finally
+make a tarball.
+
+ {{- $image := or .image "debian.tgz" -}}
+
+ architecture: arm64
+
+ actions:
+ - action: debootstrap
+ suite: "buster"
+ components:
+ - main
+ - non-free
+ mirror: https://deb.debian.org/debian
+ variant: minbase
+
+ - action: apt
+ packages: [ sudo, openssh-server, adduser, systemd-sysv, firmware-linux ]
+
+ - action: run
+ chroot: true
+ command: echo debian > /etc/hostname
+
+ - action: pack
+ file: {{ $image }}
+ compression: gz
+
+To run it, create a file named `example.yaml` and run:
+
+ debos example.yaml
+
+The final tarball will be named "debian.tgz" if you would like to modify
+this name, you can provided a different name for the variable image like
+this:
+
+ debos -t image:"debian-arm64.tgz" example.yaml
+
+## Other examples
+
+This example builds a customized image for a Raspberry Pi 3.
+https://github.com/go-debos/debos-recipes
+
+
+## See also
+fakemachine at https://github.com/go-debos/fakemachine
-and examples are to be found at:
- https://github.com/go-debos/debos-recipes
diff --git a/action.go b/action.go
index f7c4062..b15aabb 100644
--- a/action.go
+++ b/action.go
@@ -6,6 +6,14 @@ import (
"log"
)
+type DebosState int
+
+// Represent the current state of Debos
+const (
+ Success DebosState = iota
+ Failed
+)
+
// Mapping from partition name as configured in the image-partition action to
// device path for usage by other actions
type Partition struct {
@@ -27,6 +35,7 @@ type DebosContext struct {
Architecture string
DebugShell string
Origins map[string]string
+ State DebosState
}
type Action interface {
@@ -35,8 +44,13 @@ type Action interface {
PreMachine(context *DebosContext, m *fakemachine.Machine, args *[]string) error
PreNoMachine(context *DebosContext) error
Run(context *DebosContext) error
- Cleanup(context DebosContext) error
- PostMachine(context DebosContext) error
+ // Cleanup() method gets called only if the Run for an action
+ // was started and in the same machine (host or fake) as Run has run
+ Cleanup(context *DebosContext) error
+ PostMachine(context *DebosContext) error
+ // PostMachineCleanup() gets called for all actions if Pre*Machine() method
+ // has run for Action. This method is always executed on the host with user's permissions.
+ PostMachineCleanup(context *DebosContext) error
String() string
}
@@ -55,10 +69,11 @@ func (b *BaseAction) PreMachine(context *DebosContext,
args *[]string) error {
return nil
}
-func (b *BaseAction) PreNoMachine(context *DebosContext) error { return nil }
-func (b *BaseAction) Run(context *DebosContext) error { return nil }
-func (b *BaseAction) Cleanup(context DebosContext) error { return nil }
-func (b *BaseAction) PostMachine(context DebosContext) error { return nil }
+func (b *BaseAction) PreNoMachine(context *DebosContext) error { return nil }
+func (b *BaseAction) Run(context *DebosContext) error { return nil }
+func (b *BaseAction) Cleanup(context *DebosContext) error { return nil }
+func (b *BaseAction) PostMachine(context *DebosContext) error { return nil }
+func (b *BaseAction) PostMachineCleanup(context *DebosContext) error { return nil }
func (b *BaseAction) String() string {
if b.Description == "" {
return b.Action
diff --git a/actions/debootstrap_action.go b/actions/debootstrap_action.go
index 4f30ee6..bfbf3dd 100644
--- a/actions/debootstrap_action.go
+++ b/actions/debootstrap_action.go
@@ -10,6 +10,7 @@ Yaml syntax:
components: <list of components>
variant: "name"
keyring-package:
+ keyring-file:
Mandatory properties:
@@ -17,6 +18,8 @@ Mandatory properties:
Optional properties:
+- check-gpg -- verify GPG signatures on Release files, true by default
+
- mirror -- URL with Debian-compatible repository
- variant -- name of the bootstrap script variant to use
@@ -25,7 +28,9 @@ Optional properties:
Example:
components: [ main, contrib ]
-- keyring-package -- keyring for packages validation. Currently ignored.
+- keyring-package -- keyring for package validation.
+
+- keyring-file -- keyring file for repository validation.
- merged-usr -- use merged '/usr' filesystem, true by default.
*/
@@ -47,14 +52,18 @@ type DebootstrapAction struct {
Mirror string
Variant string
KeyringPackage string `yaml:"keyring-package"`
+ KeyringFile string `yaml:"keyring-file"`
Components []string
MergedUsr bool `yaml:"merged-usr"`
+ CheckGpg bool `yaml:"check-gpg"`
}
func NewDebootstrapAction() *DebootstrapAction {
d := DebootstrapAction{}
// Use filesystem with merged '/usr' by default
d.MergedUsr = true
+ // Be secure by default
+ d.CheckGpg = true
return &d
}
@@ -74,17 +83,31 @@ func (d *DebootstrapAction) RunSecondStage(context debos.DebosContext) error {
// Can't use nspawn for debootstrap as it wants to create device nodes
c.ChrootMethod = debos.CHROOT_METHOD_CHROOT
- return c.Run("Debootstrap (stage 2)", cmdline...)
+ err := c.Run("Debootstrap (stage 2)", cmdline...)
+
+ if (err != nil) {
+ log := path.Join(context.Rootdir, "debootstrap/debootstrap.log")
+ _ = debos.Command{}.Run("debootstrap.log", "cat", log)
+ }
+
+ return err
}
func (d *DebootstrapAction) Run(context *debos.DebosContext) error {
d.LogStart()
- cmdline := []string{"debootstrap", "--no-check-gpg"}
+ cmdline := []string{"debootstrap"}
if d.MergedUsr {
cmdline = append(cmdline, "--merged-usr")
}
+ if !d.CheckGpg {
+ cmdline = append(cmdline, fmt.Sprintf("--no-check-gpg"))
+ } else if d.KeyringFile != "" {
+ path := debos.CleanPathAt(d.KeyringFile, context.RecipeDir)
+ cmdline = append(cmdline, fmt.Sprintf("--keyring=%s", path))
+ }
+
if d.KeyringPackage != "" {
cmdline = append(cmdline, fmt.Sprintf("--include=%s", d.KeyringPackage))
}
@@ -115,6 +138,8 @@ func (d *DebootstrapAction) Run(context *debos.DebosContext) error {
err := debos.Command{}.Run("Debootstrap", cmdline...)
if err != nil {
+ log := path.Join(context.Rootdir, "debootstrap/debootstrap.log")
+ _ = debos.Command{}.Run("debootstrap.log", "cat", log)
return err
}
diff --git a/actions/image_partition_action.go b/actions/image_partition_action.go
index 5054e6d..cd832ac 100644
--- a/actions/image_partition_action.go
+++ b/actions/image_partition_action.go
@@ -116,6 +116,7 @@ import (
"os"
"os/exec"
"path"
+ "path/filepath"
"strings"
"syscall"
@@ -183,19 +184,23 @@ func (i *ImagePartitionAction) generateKernelRoot(context *debos.DebosContext) e
}
func (i ImagePartitionAction) getPartitionDevice(number int, context debos.DebosContext) string {
+ /* Always look up canonical device as udev might not generate the by-id
+ * symlinks while there is an flock on /dev/vda */
+ device, _ := filepath.EvalSymlinks(context.Image)
+
suffix := "p"
/* Check partition naming first: if used 'by-id'i naming convention */
- if strings.Contains(context.Image, "/disk/by-id/") {
+ if strings.Contains(device, "/disk/by-id/") {
suffix = "-part"
}
/* If the iamge device has a digit as the last character, the partition
* suffix is p<number> else it's just <number> */
- last := context.Image[len(context.Image)-1]
+ last := device[len(device)-1]
if last >= '0' && last <= '9' {
- return fmt.Sprintf("%s%s%d", context.Image, suffix, number)
+ return fmt.Sprintf("%s%s%d", device, suffix, number)
} else {
- return fmt.Sprintf("%s%d", context.Image, number)
+ return fmt.Sprintf("%s%d", device, number)
}
}
@@ -247,7 +252,7 @@ func (i ImagePartitionAction) formatPartition(p *Partition, context debos.DebosC
return nil
}
-func (i ImagePartitionAction) PreNoMachine(context *debos.DebosContext) error {
+func (i *ImagePartitionAction) PreNoMachine(context *debos.DebosContext) error {
img, err := os.OpenFile(i.ImageName, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
@@ -274,11 +279,28 @@ func (i ImagePartitionAction) PreNoMachine(context *debos.DebosContext) error {
func (i ImagePartitionAction) Run(context *debos.DebosContext) error {
i.LogStart()
+ /* Exclusively Lock image device file to prevent udev from triggering
+ * partition rescans, which cause confusion as some time asynchronously the
+ * partition device might disappear and reappear due to that! */
+ imageFD, err := os.Open(context.Image)
+ if err != nil {
+ return err
+ }
+ /* Defer will keep the fd open until the function returns, at which points
+ * the filesystems will have been mounted protecting from more udev funnyness
+ */
+ defer imageFD.Close()
+
+ err = syscall.Flock(int(imageFD.Fd()), syscall.LOCK_EX)
+ if err != nil {
+ return err
+ }
+
command := []string{"parted", "-s", context.Image, "mklabel", i.PartitionType}
if len(i.GptGap) > 0 {
command = append(command, i.GptGap)
}
- err := debos.Command{}.Run("parted", command...)
+ err = debos.Command{}.Run("parted", command...)
if err != nil {
return err
}
@@ -317,12 +339,6 @@ func (i ImagePartitionAction) Run(context *debos.DebosContext) error {
}
devicePath := i.getPartitionDevice(p.number, *context)
- // Give a chance for udevd to create proper symlinks
- err = debos.Command{}.Run("udevadm", "udevadm", "settle", "-t", "5",
- "-E", devicePath)
- if err != nil {
- return err
- }
err = i.formatPartition(p, *context)
if err != nil {
@@ -358,7 +374,7 @@ func (i ImagePartitionAction) Run(context *debos.DebosContext) error {
return nil
}
-func (i ImagePartitionAction) Cleanup(context debos.DebosContext) error {
+func (i ImagePartitionAction) Cleanup(context *debos.DebosContext) error {
for idx := len(i.Mountpoints) - 1; idx >= 0; idx-- {
m := i.Mountpoints[idx]
mntpath := path.Join(context.ImageMntDir, m.Mountpoint)
@@ -372,6 +388,19 @@ func (i ImagePartitionAction) Cleanup(context debos.DebosContext) error {
return nil
}
+func (i ImagePartitionAction) PostMachineCleanup(context *debos.DebosContext) error {
+ image := path.Join(context.Artifactdir, i.ImageName)
+ /* Remove the image in case of any action failure */
+ if context.State != debos.Success {
+ if _, err := os.Stat(image); !os.IsNotExist(err) {
+ if err = os.Remove(image); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
func (i *ImagePartitionAction) Verify(context *debos.DebosContext) error {
if len(i.GptGap) > 0 {
log.Println("WARNING: special version of parted is needed for 'gpt_gap' option")
diff --git a/actions/ostree_commit_action.go b/actions/ostree_commit_action.go
index 6d41b89..a0d8333 100644
--- a/actions/ostree_commit_action.go
+++ b/actions/ostree_commit_action.go
@@ -45,9 +45,17 @@ type OstreeCommitAction struct {
func emptyDir(dir string) {
d, _ := os.Open(dir)
defer d.Close()
- files, _ := d.Readdirnames(-1)
+
+ files, err := d.Readdirnames(-1)
+ if err != nil {
+ log.Fatal(err)
+ }
+
for _, f := range files {
- os.RemoveAll(f)
+ err := os.RemoveAll(path.Join(dir, f))
+ if err != nil {
+ log.Fatalf("Failed to remove file: %v", err)
+ }
}
}
diff --git a/actions/pack_action.go b/actions/pack_action.go
index a90cb1d..1cb1af0 100644
--- a/actions/pack_action.go
+++ b/actions/pack_action.go
@@ -10,7 +10,7 @@ Yaml syntax:
Mandatory properties:
-- file -- name of the output tarball.
+- file -- name of the output tarball, relative to the artifact directory.
- compression -- compression type to use. Only 'gz' is supported at the moment.
diff --git a/actions/run_action.go b/actions/run_action.go
index 90e4572..c6115cc 100644
--- a/actions/run_action.go
+++ b/actions/run_action.go
@@ -21,8 +21,10 @@ host's or chrooted environment -- depending on 'chroot' property.
Optional properties:
- chroot -- run script or command in target filesystem if set to true.
-In other case the command or script is executed within the build process, with
-access to the filesystem and the image. In both cases it is run with root privileges.
+Otherwise the command or script is executed within the build process, with
+access to the filesystem ($ROOTDIR), the image if any ($IMAGE), the
+recipe directory ($RECIPEDIR) and the artifact directory ($ARTIFACTDIR).
+In both cases it is run with root privileges.
- postprocess -- if set script or command is executed after all other commands and
has access to the image file.
@@ -126,9 +128,9 @@ func (run *RunAction) Run(context *debos.DebosContext) error {
return run.doRun(*context)
}
-func (run *RunAction) PostMachine(context debos.DebosContext) error {
+func (run *RunAction) PostMachine(context *debos.DebosContext) error {
if !run.PostProcess {
return nil
}
- return run.doRun(context)
+ return run.doRun(*context)
}
diff --git a/cmd/debos/debos.go b/cmd/debos/debos.go
index 0d1f7ff..f0f0ce3 100644
--- a/cmd/debos/debos.go
+++ b/cmd/debos/debos.go
@@ -14,25 +14,29 @@ import (
"github.com/jessevdk/go-flags"
)
-func checkError(context debos.DebosContext, err error, a debos.Action, stage string) int {
+func checkError(context *debos.DebosContext, err error, a debos.Action, stage string) int {
if err == nil {
return 0
}
+ context.State = debos.Failed
log.Printf("Action `%s` failed at stage %s, error: %s", a, stage, err)
- debos.DebugShell(context)
+ debos.DebugShell(*context)
return 1
}
func main() {
var context debos.DebosContext
var options struct {
- ArtifactDir string `long:"artifactdir"`
+ ArtifactDir string `long:"artifactdir" description:"Directory for packed archives and ostree repositories (default: current directory)"`
InternalImage string `long:"internal-image" hidden:"true"`
- TemplateVars map[string]string `short:"t" long:"template-var" description:"Template variables"`
+ TemplateVars map[string]string `short:"t" long:"template-var" description:"Template variables (use -t VARIABLE:VALUE syntax)"`
DebugShell bool `long:"debug-shell" description:"Fall into interactive shell on error"`
Shell string `short:"s" long:"shell" description:"Redefine interactive shell binary (default: bash)" optionsl:"" default:"/bin/bash"`
ScratchSize string `long:"scratchsize" description:"Size of disk backed scratch space"`
+ CPUs int `short:"c" long:"cpus" description:"Number of CPUs to use for build VM (default: 2)"`
+ Memory string `short:"m" long:"memory" description:"Amount of memory for build VM (default: 2048MB)"`
+ ShowBoot bool `long:"show-boot" description:"Show boot/console messages from the fake machine"`
}
var exitcode int = 0
@@ -111,9 +115,11 @@ func main() {
context.Architecture = r.Architecture
+ context.State = debos.Success
+
for _, a := range r.Actions {
err = a.Verify(&context)
- if exitcode = checkError(context, err, a, "Verify"); exitcode != 0 {
+ if exitcode = checkError(&context, err, a, "Verify"); exitcode != 0 {
return
}
}
@@ -122,6 +128,24 @@ func main() {
m := fakemachine.NewMachine()
var args []string
+ if options.Memory == "" {
+ // Set default memory size for fakemachine
+ options.Memory = "2Gb"
+ }
+ memsize, err := units.RAMInBytes(options.Memory)
+ if err != nil {
+ fmt.Printf("Couldn't parse memory size: %v\n", err)
+ exitcode = 1
+ return
+ }
+ m.SetMemory(int(memsize / 1024 / 1024))
+
+ if options.CPUs == 0 {
+ // Set default CPU count for fakemachine
+ options.CPUs = 2
+ }
+ m.SetNumCPUs(options.CPUs)
+
if options.ScratchSize != "" {
size, err := units.FromHumanSize(options.ScratchSize)
if err != nil {
@@ -132,6 +156,8 @@ func main() {
m.SetScratch(size, "")
}
+ m.SetShowBoot(options.ShowBoot)
+
m.AddVolume(context.Artifactdir)
args = append(args, "--artifactdir", context.Artifactdir)
@@ -148,8 +174,11 @@ func main() {
}
for _, a := range r.Actions {
+ // Stack PostMachineCleanup methods
+ defer a.PostMachineCleanup(&context)
+
err = a.PreMachine(&context, m, &args)
- if exitcode = checkError(context, err, a, "PreMachine"); exitcode != 0 {
+ if exitcode = checkError(&context, err, a, "PreMachine"); exitcode != 0 {
return
}
}
@@ -161,12 +190,13 @@ func main() {
}
if exitcode != 0 {
+ context.State = debos.Failed
return
}
for _, a := range r.Actions {
- err = a.PostMachine(context)
- if exitcode = checkError(context, err, a, "Postmachine"); exitcode != 0 {
+ err = a.PostMachine(&context)
+ if exitcode = checkError(&context, err, a, "Postmachine"); exitcode != 0 {
return
}
}
@@ -177,8 +207,11 @@ func main() {
if !fakemachine.InMachine() {
for _, a := range r.Actions {
+ // Stack PostMachineCleanup methods
+ defer a.PostMachineCleanup(&context)
+
err = a.PreNoMachine(&context)
- if exitcode = checkError(context, err, a, "PreNoMachine"); exitcode != 0 {
+ if exitcode = checkError(&context, err, a, "PreNoMachine"); exitcode != 0 {
return
}
}
@@ -195,22 +228,21 @@ func main() {
for _, a := range r.Actions {
err = a.Run(&context)
- if exitcode = checkError(context, err, a, "Run"); exitcode != 0 {
- return
- }
- }
- for _, a := range r.Actions {
- err = a.Cleanup(context)
- if exitcode = checkError(context, err, a, "Cleanup"); exitcode != 0 {
+ // This does not stop the call of stacked Cleanup methods for other Actions
+ // Stack Cleanup methods
+ defer a.Cleanup(&context)
+
+ // Check the state of Run method
+ if exitcode = checkError(&context, err, a, "Run"); exitcode != 0 {
return
}
}
if !fakemachine.InMachine() {
for _, a := range r.Actions {
- err = a.PostMachine(context)
- if exitcode = checkError(context, err, a, "PostMachine"); exitcode != 0 {
+ err = a.PostMachine(&context)
+ if exitcode = checkError(&context, err, a, "PostMachine"); exitcode != 0 {
return
}
}
diff --git a/commands.go b/commands.go
index 557b636..96e2de1 100644
--- a/commands.go
+++ b/commands.go
@@ -176,6 +176,14 @@ func newQemuHelper(c Command) qemuHelper {
q.qemusrc = "/usr/bin/qemu-arm-static"
case "arm64":
q.qemusrc = "/usr/bin/qemu-aarch64-static"
+ case "mips":
+ q.qemusrc = "/usr/bin/qemu-mips-static"
+ case "mipsel":
+ q.qemusrc = "/usr/bin/qemu-mipsel-static"
+ case "mips64el":
+ q.qemusrc = "/usr/bin/qemu-mips64el-static"
+ case "riscv64":
+ q.qemusrc = "/usr/bin/qemu-riscv64-static"
case "amd64", "i386":
/* Dummy, no qemu */
default:
diff --git a/doc/examples/example.yaml b/doc/examples/example.yaml
new file mode 100644
index 0000000..c0b6f55
--- /dev/null
+++ b/doc/examples/example.yaml
@@ -0,0 +1,34 @@
+{{- $architecture := or .architecture "arm64" -}}
+{{- $suite := or .suite "stretch" -}}
+{{ $image := or .image (printf "debian-%s-%s.tgz" $suite $architecture) }}
+
+architecture: {{ $architecture }}
+
+actions:
+ - action: debootstrap
+ suite: "buster"
+ components:
+ - main
+ - contrib
+ - non-free
+ mirror: https://deb.debian.org/debian
+ variant: minbase
+
+ - action: apt
+ description: Install some packages
+ packages: [ sudo, openssh-server, adduser, systemd-sysv, firmware-linux ]
+
+ - action: run
+ chroot: true
+ script: setup-user.sh
+
+ - action: overlay
+ source: overlays/sudo
+
+ - action: run
+ chroot: true
+ command: echo debian > /etc/hostname
+
+ - action: pack
+ file: {{ $image }}
+ compression: gz
diff --git a/doc/examples/overlays/sudo/etc/sudoers.d/user b/doc/examples/overlays/sudo/etc/sudoers.d/user
new file mode 100644
index 0000000..125f22b
--- /dev/null
+++ b/doc/examples/overlays/sudo/etc/sudoers.d/user
@@ -0,0 +1 @@
+%sudo ALL=(ALL) NOPASSWD: /usr/bin/su
diff --git a/doc/examples/setup-user.sh b/doc/examples/setup-user.sh
new file mode 100755
index 0000000..da155d1
--- /dev/null
+++ b/doc/examples/setup-user.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+set -e
+
+echo "I: create user"
+adduser --gecos User user
+
+echo "I: set user password"
+echo "user:user" | chpasswd
+adduser user sudo
diff --git a/doc/man/create_manpage.sh b/doc/man/create_manpage.sh
new file mode 100755
index 0000000..acac83a
--- /dev/null
+++ b/doc/man/create_manpage.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Create a manpage from the README.md
+
+# Add header
+echo '''% debos(1)
+
+# NAME
+
+ debos - Debian OS images builder
+''' > debos.md
+
+# Add README.md
+tail -n +2 ../../README.md >> debos.md
+
+# Some tweaks to the markdown
+# Uppercase titles
+sed -i 's/^\(##.*\)$/\U\1/' debos.md
+
+# Remove double #
+sed -i 's/^\##/#/' debos.md
+
+# Create the manpage
+pandoc -s -t man debos.md -o debos.1
+
+# Resulting manpage can be browsed with groff:
+#groff -man -Tascii debos.1
+
+
diff --git a/doc/man/debos.1 b/doc/man/debos.1
new file mode 100644
index 0000000..6e58a40
--- /dev/null
+++ b/doc/man/debos.1
@@ -0,0 +1,132 @@
+.\" Automatically generated by Pandoc 2.2.1
+.\"
+.TH "debos" "1" "" "" ""
+.hy
+.SH NAME
+.IP
+.nf
+\f[C]
+debos\ \-\ \ Debian\ OS\ images\ builder
+\f[]
+.fi
+.SH SYPNOSIS
+.IP
+.nf
+\f[C]
+debos\ [options]\ <recipe\ file\ in\ YAML>
+debos\ [\-\-help]
+\f[]
+.fi
+.PP
+Application Options:
+.IP
+.nf
+\f[C]
+\ \ \ \ \ \ \-\-artifactdir=
+\ \ \-t,\ \-\-template\-var=\ \ \ Template\ variables
+\ \ \ \ \ \ \-\-debug\-shell\ \ \ \ \ Fall\ into\ interactive\ shell\ on\ error
+\ \ \-s,\ \-\-shell=\ \ \ \ \ \ \ \ \ \ Redefine\ interactive\ shell\ binary\ (default:\ bash)
+\ \ \ \ \ \ \-\-scratchsize=\ \ \ \ Size\ of\ disk\ backed\ scratch\ space
+\f[]
+.fi
+.SH DESCRIPTION
+.PP
+debos is a tool to make the creation of various Debian\-based OS images
+simpler.
+While most other tools focus on specific use\-cases, debos is more meant
+as a tool\-chain to make common actions trivial while providing enough
+rope to do whatever tweaking that might be required behind the scene.
+.PP
+debos expects a YAML file as input and will run the actions listed in
+the file sequentially.
+These actions should be self\-contained and independent of each other.
+.PP
+Some of the actions provided by debos to customize and produce images
+are:
+.IP \[bu] 2
+apt: install packages and their dependencies with `apt'
+.IP \[bu] 2
+debootstrap: construct the target rootfs with debootstrap
+.IP \[bu] 2
+download: download a single file from the internet
+.IP \[bu] 2
+filesystem\-deploy: deploy a root filesystem to an image previously
+created
+.IP \[bu] 2
+image\-partition: create an image file, make partitions and format them
+.IP \[bu] 2
+ostree\-commit: create an OSTree commit from rootfs
+.IP \[bu] 2
+ostree\-deploy: deploy an OSTree branch to the image
+.IP \[bu] 2
+overlay: do a recursive copy of directories or files to the target
+filesystem
+.IP \[bu] 2
+pack: create a tarball with the target filesystem
+.IP \[bu] 2
+raw: directly write a file to the output image at a given offset
+.IP \[bu] 2
+run: allows to run a command or script in the filesystem or in the host
+.IP \[bu] 2
+unpack: unpack files from archive in the filesystem
+.PP
+A full syntax description of all the debos actions can be found at:
+https://godoc.org/github.com/go\-debos/debos/actions
+.SH SIMPLE EXAMPLE
+.PP
+The following example will create a arm64 image, install several
+packages in it, change the file /etc/hostname to \[lq]debian\[rq] and
+finally make a tarball.
+.IP
+.nf
+\f[C]
+{{\-\ $image\ :=\ or\ .image\ "debian.tgz"\ \-}}
+
+architecture:\ arm64
+
+actions:
+\ \ \-\ action:\ debootstrap
+\ \ \ \ suite:\ "buster"
+\ \ \ \ components:
+\ \ \ \ \ \ \-\ main
+\ \ \ \ \ \ \-\ non\-free
+\ \ \ \ mirror:\ https://deb.debian.org/debian
+\ \ \ \ variant:\ minbase
+
+\ \ \-\ action:\ apt
+\ \ \ \ packages:\ [\ sudo,\ openssh\-server,\ adduser,\ systemd\-sysv,\ firmware\-linux\ ]
+
+\ \ \-\ action:\ run
+\ \ \ \ chroot:\ true
+\ \ \ \ command:\ echo\ debian\ >\ /etc/hostname
+
+\ \ \-\ action:\ pack
+\ \ \ \ file:\ {{\ $image\ }}
+\ \ \ \ compression:\ gz
+\f[]
+.fi
+.PP
+To run it, create a file named \f[C]example.yaml\f[] and run:
+.IP
+.nf
+\f[C]
+debos\ example.yaml
+\f[]
+.fi
+.PP
+The final tarball will be named \[lq]debian.tgz\[rq] if you would like
+to modify this name, you can provided a different name for the variable
+image like this:
+.IP
+.nf
+\f[C]
+debos\ \-t\ image:"debian\-arm64.tgz"\ example.yaml
+\f[]
+.fi
+.SH OTHER EXAMPLES
+.PP
+This example builds a customized image for a Raspberry Pi 3.
+https://github.com/go\-debos/debos\-recipes
+.SH SEE ALSO
+.PP
+fakemachine at https://github.com/go\-debos/fakemachine
diff --git a/filesystem.go b/filesystem.go
index a6e5239..b5c41fa 100644
--- a/filesystem.go
+++ b/filesystem.go
@@ -48,7 +48,13 @@ func CopyFile(src, dst string, mode os.FileMode) error {
os.Remove(tmp.Name())
return err
}
- return os.Rename(tmp.Name(), dst)
+
+ if err = os.Rename(tmp.Name(), dst); err != nil {
+ os.Remove(tmp.Name())
+ return err
+ }
+
+ return nil
}
func CopyTree(sourcetree, desttree string) error {
@@ -63,7 +69,10 @@ func CopyTree(sourcetree, desttree string) error {
target := path.Join(desttree, suffix)
switch info.Mode() & os.ModeType {
case 0:
- CopyFile(p, target, info.Mode())
+ err := CopyFile(p, target, info.Mode())
+ if err != nil {
+ log.Panicf("Failed to copy file %s: %v", p, err)
+ }
case os.ModeDir:
os.Mkdir(target, info.Mode())
case os.ModeSymlink: