linecook-gem 0.6.10 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/linecook-gem.rb +4 -6
- data/lib/linecook-gem/baker.rb +110 -0
- data/lib/linecook-gem/baker/docker.rb +114 -0
- data/lib/linecook-gem/cli.rb +63 -115
- data/lib/linecook-gem/config.rb +45 -0
- data/lib/linecook-gem/image.rb +77 -0
- data/lib/linecook-gem/image/crypt.rb +7 -40
- data/lib/linecook-gem/image/s3.rb +14 -14
- data/lib/linecook-gem/packager.rb +30 -0
- data/lib/linecook-gem/packager/packer.rb +249 -0
- data/lib/linecook-gem/packager/route53.rb +62 -0
- data/lib/linecook-gem/packager/squashfs.rb +51 -0
- data/lib/linecook-gem/util/downloader.rb +3 -42
- data/lib/linecook-gem/util/secrets.rb +47 -0
- data/lib/linecook-gem/version.rb +1 -1
- data/man/LINECOOK.1 +117 -86
- metadata +62 -110
- data/lib/linecook-gem/builder/build.rb +0 -44
- data/lib/linecook-gem/builder/darwin_backend.rb +0 -98
- data/lib/linecook-gem/builder/linux_backend.rb +0 -11
- data/lib/linecook-gem/builder/lxc.rb +0 -286
- data/lib/linecook-gem/builder/manager.rb +0 -79
- data/lib/linecook-gem/image/github.rb +0 -27
- data/lib/linecook-gem/image/manager.rb +0 -73
- data/lib/linecook-gem/packager/ebs.rb +0 -373
- data/lib/linecook-gem/packager/manager.rb +0 -23
- data/lib/linecook-gem/provisioner/chef-zero.rb +0 -149
- data/lib/linecook-gem/provisioner/manager.rb +0 -47
- data/lib/linecook-gem/provisioner/packer.rb +0 -82
- data/lib/linecook-gem/util/config.rb +0 -134
- data/lib/linecook-gem/util/executor.rb +0 -33
- data/lib/linecook-gem/util/ssh.rb +0 -190
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Linecook
|
4
|
+
module Route53
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def upsert_record(name, ami, region)
|
8
|
+
ami_config = Linecook.config[:packager][:ami]
|
9
|
+
|
10
|
+
zone = [ami_config[:zone], ami_config[:regions][region.to_sym], ami_config[:domain]].compact.join('.')
|
11
|
+
record = "#{name}.#{zone}"
|
12
|
+
|
13
|
+
resp = client.list_hosted_zones_by_name({
|
14
|
+
dns_name: zone,
|
15
|
+
max_items: 1,
|
16
|
+
})
|
17
|
+
|
18
|
+
if resp.hosted_zones.size < 1
|
19
|
+
puts "Failed to find dns zone: #{resp.dns_name}"
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
zone_id = resp.hosted_zones[0].id
|
25
|
+
client.change_resource_record_sets({
|
26
|
+
hosted_zone_id: zone_id,
|
27
|
+
change_batch: {
|
28
|
+
comment: "create #{ami}",
|
29
|
+
changes: [
|
30
|
+
{
|
31
|
+
action: "UPSERT",
|
32
|
+
resource_record_set: {
|
33
|
+
name: record,
|
34
|
+
type: 'TXT',
|
35
|
+
ttl: 1,
|
36
|
+
resource_records: [
|
37
|
+
{
|
38
|
+
value: "\"#{ami}\"",
|
39
|
+
},
|
40
|
+
],
|
41
|
+
},
|
42
|
+
},
|
43
|
+
],
|
44
|
+
},
|
45
|
+
})
|
46
|
+
puts "Saved #{ami} to #{record}"
|
47
|
+
rescue Aws::Route53::Errors::ServiceError => e
|
48
|
+
puts "AWS Error: #{e.code} #{e.context.http_response.body_contents}"
|
49
|
+
return false
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def client
|
55
|
+
@client ||= begin
|
56
|
+
Aws.config[:credentials] = Aws::Credentials.new(Linecook.config[:aws][:access_key], Linecook.config[:aws][:secret_key])
|
57
|
+
Aws.config[:region] = 'us-east-1'
|
58
|
+
Aws::Route53::Client.new
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
require 'linecook-gem/image'
|
6
|
+
|
7
|
+
module Linecook
|
8
|
+
class Squashfs
|
9
|
+
|
10
|
+
EXCLUDE_PROFILES = {
|
11
|
+
common: [
|
12
|
+
'dev/*',
|
13
|
+
'sys/*',
|
14
|
+
'proc/*',
|
15
|
+
'run/*',
|
16
|
+
'tmp/*',
|
17
|
+
'home/kitchen',
|
18
|
+
'etc/sudoers.d/kitchen',
|
19
|
+
'.docker*',
|
20
|
+
],
|
21
|
+
ubuntu: [
|
22
|
+
'usr/src',
|
23
|
+
'var/lib/apt/lists/archive*',
|
24
|
+
'var/cache/apt/archives*'
|
25
|
+
]
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
def initialize(config)
|
29
|
+
@excludes = []
|
30
|
+
@excludes << EXCLUDE_PROFILES[:common]
|
31
|
+
@excludes << EXCLUDE_PROFILES[config[:distro]] if config[:distro]
|
32
|
+
@excludes << config[:excludes] if config[:excludes]
|
33
|
+
@excludes.flatten!
|
34
|
+
@outdir = config[:outdir] || Dir.pwd
|
35
|
+
end
|
36
|
+
|
37
|
+
def package(image)
|
38
|
+
FileUtils.mkdir_p(@outdir)
|
39
|
+
tmpdir = Dir.mktmpdir("#{image.id}-squashfs")
|
40
|
+
outfile = File.join(@outdir, "#{image.id}.squashfs")
|
41
|
+
puts "Extracting #{image.id} to temporary directory #{tmpdir}..."
|
42
|
+
system("sudo tar -C #{tmpdir} -xpf #{image.path}")
|
43
|
+
system("sudo mksquashfs #{tmpdir} #{outfile} -noappend -wildcards -e #{@excludes.map { |e| "'#{e}'" }.join(' ')}")
|
44
|
+
puts "Squashed image is at #{outfile}"
|
45
|
+
ensure
|
46
|
+
puts "Cleaning up #{tmpdir}..."
|
47
|
+
system("sudo rm -rf #{tmpdir}")
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -5,18 +5,14 @@ require 'securerandom'
|
|
5
5
|
require 'zip'
|
6
6
|
require 'ruby-progressbar'
|
7
7
|
|
8
|
-
require 'linecook-gem/image/crypt'
|
9
|
-
|
10
8
|
module Linecook
|
11
9
|
module Downloader
|
12
10
|
LOCK_WAIT_TIMEOUT = 180
|
13
11
|
|
14
|
-
def
|
15
|
-
acquire_lock(path)
|
12
|
+
def download(url, path)
|
16
13
|
FileUtils.mkdir_p(File.dirname(path))
|
17
14
|
cryptfile = "#{File.basename(path)}-encrypted-#{SecureRandom.hex(4)}"
|
18
|
-
|
19
|
-
File.open(destination, 'w') do |f|
|
15
|
+
File.open(path, 'w') do |f|
|
20
16
|
pbar = ProgressBar.create(title: File.basename(path), total: nil)
|
21
17
|
IO.copy_stream(open(url,
|
22
18
|
content_length_proc: lambda do|t|
|
@@ -26,17 +22,9 @@ module Linecook
|
|
26
22
|
pbar.progress = s
|
27
23
|
end), f)
|
28
24
|
end
|
29
|
-
|
30
|
-
if encrypted
|
31
|
-
Linecook::Crypto.new.decrypt_file(destination, dest: path)
|
32
|
-
FileUtils.rm_f(destination)
|
33
|
-
end
|
34
|
-
ensure
|
35
|
-
unlock(path)
|
36
25
|
end
|
37
26
|
|
38
|
-
|
39
|
-
def self.unzip(source, dest: nil)
|
27
|
+
def unzip(source, dest: nil)
|
40
28
|
puts "Extracting #{source}..."
|
41
29
|
dest ||= File.dirname(source)
|
42
30
|
Zip::File.open(source) do |zip_file|
|
@@ -47,32 +35,5 @@ module Linecook
|
|
47
35
|
end
|
48
36
|
end
|
49
37
|
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def self.acquire_lock(path)
|
54
|
-
attempts = 0
|
55
|
-
while attempts < LOCK_WAIT_TIMEOUT
|
56
|
-
return lock(path) unless locked?(path)
|
57
|
-
attempts += 1
|
58
|
-
sleep(1)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.locked?(path)
|
63
|
-
File.exists?(lockfile(path)) && (true if Process.kill(0, File.read(lockfile(path))) rescue false)
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.lock(path)
|
67
|
-
File.write(lockfile(path), Process.pid.to_s)
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.unlock(path)
|
71
|
-
FileUtils.rm_f(lockfile(path))
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.lockfile(path)
|
75
|
-
"/tmp/#{File.basename(path)}-flock"
|
76
|
-
end
|
77
38
|
end
|
78
39
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'kitchen/configurable'
|
4
|
+
|
5
|
+
# FIXME - overhaul linecook config
|
6
|
+
# be able to read kitchen configs
|
7
|
+
|
8
|
+
module Linecook
|
9
|
+
class Secrets
|
10
|
+
|
11
|
+
SECRETS_PATH = File.join(Dir.pwd, 'secrets.ejson').freeze
|
12
|
+
|
13
|
+
include Configurable
|
14
|
+
|
15
|
+
def initialize(config = {})
|
16
|
+
init_config(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
# CONFIG_PATH = File.join(Dir.pwd, 'linecook.yml').freeze # File.expand_path('../../../config/config.yml', __FILE__)
|
20
|
+
# LINECOOK_HOME = File.expand_path('~/.linecook').freeze
|
21
|
+
# DEFAULT_CONFIG = {
|
22
|
+
# packager: {
|
23
|
+
# provider: :ebs,
|
24
|
+
# ebs: {
|
25
|
+
# hvm: true,
|
26
|
+
# size: 10,
|
27
|
+
# region: 'us-east-1',
|
28
|
+
# copy_regions: [],
|
29
|
+
# account_ids: []
|
30
|
+
# }
|
31
|
+
# },
|
32
|
+
# }
|
33
|
+
|
34
|
+
def secrets
|
35
|
+
@secrets ||= begin
|
36
|
+
secrets_path = ENV['LINECOOK_SECRETS_PATH'] || SECRETS_PATH
|
37
|
+
if File.exists?(secrets_path)
|
38
|
+
ejson_path = File.join(Gem::Specification.find_by_name('ejson').gem_dir, 'build', "#{Linecook::Config.platform}-amd64", 'ejson' )
|
39
|
+
command = "#{ejson_path} decrypt #{secrets_path}"
|
40
|
+
secrets = JSON.load(`sudo #{command}`)
|
41
|
+
secrets.deep_symbolize_keys
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/linecook-gem/version.rb
CHANGED
data/man/LINECOOK.1
CHANGED
@@ -1,139 +1,170 @@
|
|
1
|
-
.TH LINECOOK 1 "
|
1
|
+
.TH LINECOOK 1 "June 2016" Unix "User Manuals"
|
2
2
|
.SH NAME
|
3
3
|
.PP
|
4
|
-
linecook \- Linux system image builder
|
4
|
+
linecook \- Linux system image builder based on test kitchen
|
5
5
|
.SH SYNOPSIS
|
6
6
|
.PP
|
7
|
-
linecook setup \- interactive setup
|
8
|
-
.PP
|
9
7
|
linecook help [\fB\fCCOMMAND\fR]\- for specific command help
|
10
8
|
.SH DESCRIPTION
|
11
9
|
.PP
|
12
|
-
Linecook
|
10
|
+
Linecook is a workflow tool that allows you to use test kitchen to build generic system images. Linecook works with arbitrary test\-kitchen provisioners, and generations a neutral format that can be packaged into specific output formats.
|
11
|
+
.PP
|
12
|
+
Currently, linecook supports the following test kitchen drivers:
|
13
|
+
.RS
|
14
|
+
.IP \(bu 2
|
15
|
+
kitchen\-docker
|
16
|
+
.RE
|
17
|
+
.PP
|
18
|
+
And linecook uses packer to generate output. It currently supports:
|
19
|
+
.RS
|
20
|
+
.IP \(bu 2
|
21
|
+
AMIs via packer's ebs_chroot builder.
|
22
|
+
.RS
|
23
|
+
.IP \(bu 2
|
24
|
+
The amis may also update a TXT record in a route53 zone once the build is complete
|
25
|
+
.RE
|
26
|
+
.IP \(bu 2
|
27
|
+
squashfs, a provider neutral format.
|
28
|
+
.RE
|
13
29
|
.PP
|
14
|
-
Linecook
|
30
|
+
Linecook builds may be saved and annotated according to the folliwng convention:
|
15
31
|
.RS
|
16
32
|
.IP \(bu 2
|
17
|
-
|
33
|
+
name \- a descriptive name for the build, based on the name of the test kitchen suite.
|
18
34
|
.IP \(bu 2
|
19
|
-
|
35
|
+
group \- an arbitrary grouping of suites, intended to group builds by branches.
|
20
36
|
.IP \(bu 2
|
21
|
-
|
37
|
+
tag \- a numeric tag for a build, intended to increment. If 'latest' is specified when resolving a build, the latest uploaded build is used.
|
22
38
|
.RE
|
39
|
+
.PP
|
40
|
+
These three attributes are composed to make a build id in a very simple manor:
|
41
|
+
.RS
|
42
|
+
.IP \(bu 2
|
43
|
+
name is always required
|
44
|
+
.IP \(bu 2
|
45
|
+
if group is specified, it will be joined with name using a '\-' character.
|
46
|
+
.IP \(bu 2
|
47
|
+
if tag is specified, it will be joined with the name and the gorup using a '\-' character.
|
48
|
+
.IP \(bu 2
|
49
|
+
For example, the resulting id for a base build on the master group, with id of '5' would be 'base\-master\-5'. If this is the latest build for the base\-master group, 'base\-master\-latest' will resolve to this.
|
50
|
+
.RE
|
51
|
+
.PP
|
52
|
+
If linecook uploads a build, it will always encrypt it using rbnacl.
|
23
53
|
.SH USAGE
|
24
54
|
.PP
|
55
|
+
To test linecook builds locally, it is best to use test kitchen directly:
|
56
|
+
.PP
|
25
57
|
.RS
|
26
58
|
.nf
|
27
|
-
|
28
|
-
\-\-name \- The name
|
29
|
-
\-\-snapshot \- Snapshot the resulting image for later use
|
30
|
-
\-\-encrypt \- Encrypt the snapshot using the configured key. Implies snapshot.
|
31
|
-
\-\-upload \- Upload the resulting image to the configured destination. Implies snapshot.
|
32
|
-
\-\-all \- Snapshot, encrypt, and upload the resulting image.
|
33
|
-
Build a linecook image defined by SPEC, with an optional name to help identify it. The default will be the SPEC name
|
34
|
-
linecook builder
|
35
|
-
start \- start a new builder
|
36
|
-
stop \- stop a running builder
|
37
|
-
info \- show the info about the builder
|
38
|
-
ip \- show the builder's ip
|
39
|
-
linecook config
|
40
|
-
setup
|
41
|
-
check \- validate config
|
42
|
-
show
|
43
|
-
linecook build
|
44
|
-
list
|
45
|
-
info NAME
|
46
|
-
ip NAME
|
47
|
-
stop NAME
|
48
|
-
linecook image
|
49
|
-
list
|
50
|
-
keygen \- generate a new secret key for image encryption
|
51
|
-
fetch
|
52
|
-
find [`REGEX`] \- list available remote images filtered by an optional regex
|
53
|
-
linecook ami [`image`] [\-r \-\-region `REGION1,REGION2`] [\-x \-\-xen\-type `PV|HVM`] [\-r \-\-root\-size GIGABYTES] \- create an AMI (Amazon Machine Image) from a snapshot.
|
59
|
+
bundle exec kitchen converge [SUITE NAME]
|
54
60
|
.fi
|
55
61
|
.RE
|
56
|
-
.SH CONFIGURATION
|
57
62
|
.PP
|
58
|
-
|
59
|
-
.SH
|
63
|
+
For more specific usage, use \fIlinecook help\fP
|
64
|
+
.SH CONFIGURATION
|
60
65
|
.PP
|
61
|
-
|
62
|
-
|
63
|
-
\[la]https://rubygems.org/gems/chefdepartie\[ra] gems to have first\-class support for local chef\-zero builds.
|
66
|
+
See test kitchen's documentation for configuring suites and provisioners.
|
67
|
+
.SH KITCHEN EXTENSIONS
|
64
68
|
.PP
|
65
|
-
|
66
|
-
|
69
|
+
kitchen.yml is extended to support the following additional attributes:
|
70
|
+
.TP
|
71
|
+
\fBinherit\fP
|
72
|
+
Inherit from a previous linecook build. This saves time if there are several builds based on the same ancestor.
|
67
73
|
.RS
|
68
74
|
.IP \(bu 2
|
69
|
-
|
75
|
+
name \- the name of the build to inherit
|
70
76
|
.IP \(bu 2
|
71
|
-
|
77
|
+
group \- the group / branch of the build to inherit
|
72
78
|
.IP \(bu 2
|
73
|
-
|
79
|
+
tag \- the explicit tag, or 'latest' to discovery the latest tag.
|
80
|
+
.RE
|
81
|
+
.SH PACKAGER
|
82
|
+
.PP
|
83
|
+
Right now there are two packagers supported. The interface may change.
|
84
|
+
.TP
|
85
|
+
\fBsquashfs\fP
|
86
|
+
Package the resulting build as a squashfs image.
|
87
|
+
.RS
|
74
88
|
.IP \(bu 2
|
75
|
-
|
89
|
+
\fIexcludes\fP \- a list of glob expressions to exclude from the archive
|
76
90
|
.IP \(bu 2
|
77
|
-
|
91
|
+
\fIdistro\fP \- inherit a specific set of presets for paths to exclude by distro. Currently only ubuntu is supported.
|
78
92
|
.IP \(bu 2
|
79
|
-
|
93
|
+
outdir \- the output directory for the image
|
80
94
|
.RE
|
81
|
-
.PP
|
82
|
-
See the packer documentation for how to configure these provisioners.
|
83
|
-
.PP
|
84
|
-
To use a packerfile with linecook, just leave out the 'builder' section, or have the builder section be an empty array. You need only specify the 'provisioner' section, and other sections may cause errors. Linecook will automatically insert a null builder with the appropriate connection string for you.
|
85
|
-
.PP
|
86
|
-
Linecook with packer is a powerful combination, as it allows you to leverage packer's 'null builder' to take advantage of all of the provisioners packer already has really good support for. The result is an intermediate image format (squashfs) that can be easily applied to any target.
|
87
|
-
.SH FILES
|
88
95
|
.TP
|
89
|
-
\
|
90
|
-
|
91
|
-
.TP
|
92
|
-
\fI~/linecook/config.yml\fP
|
93
|
-
The system wide configuration file, base or 'common' configuration. Other configurations get deep merged on top of this.
|
94
|
-
.SH DEPENDENCIES
|
95
|
-
.PP
|
96
|
-
Ruby 2.0 or greater, gem, and bundler.
|
97
|
-
.PP
|
98
|
-
Does not work and will never work on Windows.
|
99
|
-
.SS Linux
|
100
|
-
.PP
|
101
|
-
Only tested on Gentoo and Ubuntu
|
96
|
+
\fBpacker\fP
|
97
|
+
Package an AMI using packer. Currently only AMIs are supported, but any packer builder could be implemented relatively easily.
|
102
98
|
.RS
|
103
99
|
.IP \(bu 2
|
104
|
-
|
100
|
+
hvm \- build an HVM instance (defaults to true).
|
105
101
|
.IP \(bu 2
|
106
|
-
|
102
|
+
root_size \- the size of the root volume to snapshot for the AMI (in GB).
|
107
103
|
.IP \(bu 2
|
108
|
-
|
104
|
+
region \- the region to build the AMI in.
|
109
105
|
.IP \(bu 2
|
110
|
-
|
106
|
+
copy_regions \- additional regions to copy the AMI to.
|
111
107
|
.IP \(bu 2
|
112
|
-
|
108
|
+
account_ids \- a list of account ids that are permitted to launch this AMI.
|
109
|
+
.IP \(bu 2
|
110
|
+
ami \- details for storing the AMI ID in a DNS TXT record on route53.
|
111
|
+
.RS
|
112
|
+
.IP \(bu 2
|
113
|
+
update_txt \- should a TXT record be written? (true or false)
|
114
|
+
.IP \(bu 2
|
115
|
+
regions \- a dictionary of regional aliases
|
116
|
+
.IP \(bu 2
|
117
|
+
domain \- the route53 domain to write to
|
118
|
+
.IP \(bu 2
|
119
|
+
zone \- the zone within the domain.
|
120
|
+
.RE
|
113
121
|
.RE
|
114
|
-
.
|
122
|
+
.SH SECRETS
|
123
|
+
.PP
|
124
|
+
Linecook will look for secrets in config.ejson. In particular:
|
125
|
+
.TP
|
126
|
+
\fBimagekey\fP
|
127
|
+
the key to use when encrypting images. Generate one with the \fIimage keygen\fP command.
|
128
|
+
.TP
|
129
|
+
\fBaws\fP
|
130
|
+
This is used access to S3, as well as to create EBS based AMIs and update TXT records on route53. A sample IAM policy is provided in the github repo.
|
115
131
|
.RS
|
116
132
|
.IP \(bu 2
|
117
|
-
|
133
|
+
{ "s3": { "bucket" : "name" } } can be used to set the name of the bucket
|
134
|
+
.IP \(bu 2
|
135
|
+
{ "access_key" : "ACCESS_KEY" } can be used to set the access key for the IAM user associated with the profile with the necessary access.
|
136
|
+
.IP \(bu 2
|
137
|
+
{ "secret_key" : "ACCESS_KEY" } can be used to set the secret key for the IAM user associated with the profile with the necessary access.
|
118
138
|
.RE
|
119
|
-
.
|
120
|
-
|
139
|
+
.TP
|
140
|
+
\fBchef\fP
|
141
|
+
To decrypt data bags securely, you can set the \fIencrypted_data_bag_secret\fP here. Make sure any newlines are replaced with \[rs]n.
|
142
|
+
.SH PROVISIONERS
|
143
|
+
.PP
|
144
|
+
Currently only the docker driver is supported for provisioning. You must have docker installed to use this provisioner.
|
145
|
+
.SH DEPENDENCIES
|
146
|
+
.PP
|
147
|
+
\fBCommon\fP
|
121
148
|
.RS
|
122
149
|
.IP \(bu 2
|
123
|
-
|
124
|
-
\[la]https://github.com/mist64/xhyve/issues/60\[ra] is resolved. Linecook will setuid on the xhyve binary.
|
150
|
+
Ruby 2.0 or greater, gem, and bundler.
|
125
151
|
.RE
|
126
|
-
.
|
152
|
+
.PP
|
153
|
+
\fBLinux\fP
|
127
154
|
.RS
|
128
155
|
.IP \(bu 2
|
129
|
-
|
156
|
+
mksquashfs \- to generate squashfs output.
|
157
|
+
.RE
|
158
|
+
.PP
|
159
|
+
\fBOS X\fP
|
160
|
+
.RS
|
130
161
|
.IP \(bu 2
|
131
|
-
|
162
|
+
docker for mac
|
132
163
|
.RE
|
133
164
|
.SH BUGS
|
134
165
|
.PP
|
135
|
-
Report bugs against github.com/
|
166
|
+
Report bugs against github.com/shopify/linecook\-gem
|
136
167
|
.SH AUTHOR
|
137
168
|
.PP
|
138
169
|
Dale Hamel
|
139
|
-
\[la]dale.hamel@
|
170
|
+
\[la]dale.hamel@shopify.com\[ra]
|