duck-installer 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ __version__ = (0, 3, 0)
2
+ __version_string__ = ".".join(map(str, __version__))
@@ -0,0 +1,48 @@
1
+ import dbm
2
+ import json
3
+ import contextlib
4
+
5
+ DEFAULT_DB = "/var/duck"
6
+ DEFAULT_ENCODING = 'utf-8'
7
+
8
+
9
+ class DBSession(object):
10
+ def __init__(self, db, encoding):
11
+ self._db = db
12
+ self._encoding = encoding
13
+
14
+ def get(self, key, default=None):
15
+ try:
16
+ value = self._db[key]
17
+ except KeyError:
18
+ return default
19
+
20
+ if value is None:
21
+ return None
22
+
23
+ value = value.decode(self._encoding)
24
+ value = json.loads(value)
25
+ return value
26
+
27
+ def set(self, key, value):
28
+ value = json.dumps(value)
29
+ value = value.encode(self._encoding)
30
+ self._db[key] = value
31
+
32
+ def keys(self):
33
+ return self._db.keys()
34
+
35
+
36
+ class DB(object):
37
+ def __init__(self, path=None, encoding=None):
38
+ if path is None:
39
+ path = DEFAULT_DB
40
+ if encoding is None:
41
+ encoding = DEFAULT_ENCODING
42
+ self._full_path = "{0}.dbm".format(path)
43
+ self._encoding = encoding
44
+
45
+ @contextlib.contextmanager
46
+ def open(self):
47
+ with contextlib.closing(dbm.open(self._full_path, "c")) as db:
48
+ yield DBSession(db, self._encoding)
@@ -0,0 +1,5 @@
1
+ import logging
2
+
3
+
4
+ def setup():
5
+ logging.basicConfig(level=logging.INFO)
data/files/sbin/duckdb ADDED
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/python
2
+ #
3
+ # AutoDB is used to store duckinstaller variables.
4
+ #
5
+
6
+ import duck.db as duck_db
7
+ import duck
8
+
9
+ import argparse
10
+ import urllib2
11
+ import json
12
+ import contextlib
13
+
14
+ VERSION = duck.__version_string__
15
+
16
+
17
+ def update_db(function):
18
+ def inner(ns):
19
+ with ns.db.open() as S:
20
+ for key, value in function(S, ns):
21
+ if ns.ns:
22
+ key = "%s/%s" % (ns.ns, key)
23
+ S.set(key, value)
24
+
25
+ return 0
26
+
27
+ return inner
28
+
29
+
30
+ def parse_text(fd):
31
+ for line in fd:
32
+ line = line.strip()
33
+
34
+ if line.startswith("#") or not line:
35
+ continue
36
+
37
+ key, value = line.split(" ", 2)
38
+ yield key, value
39
+
40
+
41
+ def flatten_dict(doc, keys=[]):
42
+ result = dict()
43
+
44
+ for key, value in doc.items():
45
+ this_keys = keys + [key]
46
+
47
+ if isinstance(value, dict):
48
+ result.update(flatten_dict(value, this_keys))
49
+ else:
50
+ result["/".join(this_keys)] = value
51
+
52
+ return result
53
+
54
+
55
+ def parse_json(fd):
56
+ doc = json.load(fd)
57
+
58
+ for key, value in doc.items():
59
+ yield key, value
60
+
61
+
62
+ def parse_cmdline(fd):
63
+ cmdline = fd.read()
64
+
65
+ for c in cmdline.split(" "):
66
+ c = c.strip()
67
+
68
+ if not c or '=' not in c:
69
+ continue
70
+
71
+ key, value = c.split("=", 2)
72
+ yield key, value
73
+
74
+
75
+ def read_dict(mode, fd):
76
+ if mode == 'cmdline':
77
+ return parse_cmdline(fd)
78
+
79
+ if mode == 'json':
80
+ return parse_json(fd)
81
+
82
+ if mode == 'text':
83
+ return parse_text(fd)
84
+
85
+ raise Exception("Unknown file mode: {0}".format(mode))
86
+
87
+
88
+ def read(ns, fd):
89
+ value = dict(read_dict(ns.mode, fd))
90
+
91
+ if ns.flatten:
92
+ return flatten_dict(value)
93
+
94
+ return value
95
+
96
+
97
+ def action_get(ns):
98
+ with ns.db.open() as S:
99
+ value = S.get(ns.key, ns.default)
100
+
101
+ if value is None:
102
+ ok = False
103
+ value = ""
104
+ else:
105
+ ok = True
106
+
107
+ if ns.sh:
108
+ print "DUCK_RETURN=\'%s\';" % (value,)
109
+ print "DUCK_OK=\"%s\"" % ("yes" if ok else "no",)
110
+ return 0
111
+
112
+ if not ok:
113
+ return 1
114
+
115
+ if ns.raw:
116
+ value = repr(value)
117
+
118
+ print value
119
+
120
+ return 0
121
+
122
+
123
+ @update_db
124
+ def action_set(S, ns):
125
+ if ns.json:
126
+ value = json.loads(ns.value)
127
+ elif ns.value == '-':
128
+ value = sys.stdin.read()
129
+ else:
130
+ value = ns.value
131
+
132
+ yield ns.key, value
133
+
134
+
135
+ @update_db
136
+ def action_url(S, ns):
137
+ with contextlib.closing(urllib2.urlopen(ns.url)) as fd:
138
+ for key, value in read(ns, fd).items():
139
+ yield key, value
140
+
141
+
142
+ def action_list(ns):
143
+ with ns.db.open() as S:
144
+ for key in S.keys():
145
+ print key, repr(S.get(key))
146
+
147
+
148
+ def main(args):
149
+ parser = argparse.ArgumentParser(
150
+ usage="usage: %(prog)s [options] <action> [action-options]")
151
+
152
+ parser.add_argument("-v", "--version", action='version', version=VERSION)
153
+
154
+ parser.add_argument(
155
+ "--db",
156
+ metavar="<path>",
157
+ default=None)
158
+
159
+ parser.add_argument(
160
+ "--ns",
161
+ metavar="<namespace>",
162
+ default=None)
163
+
164
+ parsers = parser.add_subparsers()
165
+
166
+ get_parser = parsers.add_parser("get", help="Get a value")
167
+ get_parser.add_argument("key")
168
+ get_parser.add_argument("default", nargs='?', default=None)
169
+ get_parser.add_argument("--sh",
170
+ help=("Output return value as a shell "
171
+ "evaluable string"),
172
+ default=False,
173
+ action='store_true')
174
+ get_parser.add_argument("--raw",
175
+ help=("Output raw (repr) value"),
176
+ default=False,
177
+ action='store_true')
178
+ get_parser.set_defaults(action=action_get)
179
+
180
+ set_parser = parsers.add_parser("set", help="Set a value")
181
+ set_parser.add_argument("--json",
182
+ help="treat argument as json",
183
+ default=False,
184
+ action='store_true')
185
+ set_parser.add_argument("key")
186
+ set_parser.add_argument("value")
187
+ set_parser.set_defaults(action=action_set)
188
+
189
+ list_parser = parsers.add_parser("list", help="List all values")
190
+ list_parser.set_defaults(action=action_list)
191
+
192
+ url_parser = parsers.add_parser("url",
193
+ help="Read values from an url")
194
+ url_parser.add_argument("url", help="Fetch values from specified url")
195
+ url_parser.add_argument("--json", dest='mode',
196
+ help="Treat input as a json document",
197
+ action='store_const', const='json')
198
+ url_parser.add_argument("--cmdline", dest='mode',
199
+ help="Treat input as a /proc/cmdline file",
200
+ action='store_const', const='cmdline')
201
+ url_parser.add_argument("--flatten",
202
+ help="Flatten a nested dictionary",
203
+ default=False, action='store_true')
204
+ url_parser.set_defaults(action=action_url, mode='file')
205
+
206
+ ns = parser.parse_args(args)
207
+ ns.db = duck_db.DB(path=ns.db)
208
+ return ns.action(ns)
209
+
210
+ if __name__ == "__main__":
211
+ import sys
212
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ . /lib/libduck.sh
3
+ a_get_into log duck/log "$DEFAULT_LOG"
4
+ a_get_into error_command 'duck/error-command' "$DUCK_LOGIN"
5
+ a_get_into success_command 'duck/success-command' "$DUCK_LOGIN"
6
+
7
+ info "Duck Installer $DUCK_VERSION"
8
+
9
+ if [[ -f $INSTALLER_STATUS ]]; then
10
+ info "Not running installation, $INSTALLER_STATUS exists"
11
+ exec $DUCK_LOGIN
12
+ fi
13
+
14
+ info "Running installation, logging to $log and syslog"
15
+
16
+ if ! run_installer 2>&1 | tee -a $log | logger -t duckinstall -s; then
17
+ exec $error_command
18
+ fi
19
+
20
+ touch $INSTALLER_STATUS
21
+ exec $success_command
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ read -p "(press 'enter' to login)"
3
+ exec /bin/login -f root
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # Remove any persistent udev rules
3
+ # If the host system runs it's own udev daemon, there is a chance that installation
4
+ # hooks will trigger the generation of persistent files.
5
+
6
+ set -e
7
+
8
+ case "$1" in
9
+ "final") rm -f /etc/udev/rules.d/*; ;;
10
+ esac
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Make sure that the kernel does not generate an initramfs.
3
+ # Clear /boot before packages are being configured.
4
+
5
+ set -e
6
+
7
+ status_code=0
8
+
9
+ case "$1" in
10
+ "pre-packages-configure")
11
+ echo "Disabling generation of an initramfs"
12
+
13
+ rm -rf /etc/kernel/postinst.d
14
+ rm -rf /etc/kernel/postrm.d
15
+
16
+ dpkg --get-selections | while read name _; do
17
+ case "$name" in
18
+ linux-image-*-dbg) continue ;;
19
+ linux-image-*) ;;
20
+ *) continue ;;
21
+ esac
22
+
23
+ version=${name#linux-image-*}
24
+
25
+ path="/var/lib/dpkg/info/$name.postinst"
26
+
27
+ [[ ! -f $path ]] && continue
28
+
29
+ if ! grep -E 'my \$initrd\s+=\s+"YES";' $path; then
30
+ echo "initrd already disabled: $name"
31
+ continue
32
+ fi
33
+
34
+ cp -a $path $path.original
35
+
36
+ echo "Disabling initrd for: $name"
37
+
38
+ if ! sed -r -i 's/my \$initrd\s+=\s+"YES";/my $initrd = "";/' $path; then
39
+ echo "failed to patch: $path"
40
+ exit 1
41
+ fi
42
+
43
+ echo "Generating modules.dep"
44
+ depmod $version
45
+ done
46
+ ;;
47
+ "final")
48
+ echo "Clearing /boot"
49
+ rm -rf /boot
50
+ ;;
51
+ esac
52
+
53
+ exit 0
data/fixes/squeeze-fix ADDED
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+ # fix for dash hooks that does not run properly on it's own.
3
+ # http://wiki.debian.org/Multistrap#Steps_for_Squeeze_and_later
4
+
5
+ set -e
6
+
7
+ case "$1" in
8
+ "pre-bootstrap-configure")
9
+ mkdir -p /usr/share/man/man1
10
+ ;;
11
+ *) ;;
12
+ esac
13
+
14
+ exit 0
data/lib/duck.rb ADDED
@@ -0,0 +1,191 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+ require 'yaml'
4
+ require 'find'
5
+ require 'logger'
6
+
7
+ require 'duck/logging'
8
+ require 'duck/build'
9
+ require 'duck/enter'
10
+ require 'duck/pack'
11
+ require 'duck/qemu'
12
+
13
+ module Duck
14
+ class << self
15
+ include Logging
16
+ end
17
+
18
+ # environment to prevent tasks from being interactive.
19
+ DEFAULT_SHELL = '/bin/bash'
20
+ CONFIG_NAME = 'duck.yaml'
21
+ CONFIG_ARRAYS = [:files, :packages, :transports, :preferences, :fixes, :services, :sources]
22
+
23
+ ACTTIONS = {
24
+ :build => Duck::Build,
25
+ :enter => Duck::Enter,
26
+ :pack => Duck::Pack,
27
+ :qemu => Duck::Qemu,
28
+ }
29
+
30
+ def self.resource_path(path)
31
+ File.expand_path File.join('..', '..', path), __FILE__
32
+ end
33
+
34
+ def self.parse_options(args)
35
+ o = Hash.new
36
+
37
+ working_directory = Dir.pwd
38
+
39
+ o[:temp] = File.join working_directory, 'tmp'
40
+ o[:target] = File.join o[:temp], 'initrd'
41
+ o[:initrd] = File.join o[:temp], 'initrd.gz'
42
+ o[:gpg_homedir] = File.join o[:temp], 'gpg'
43
+ o[:kernel] = File.join working_directory, 'vmlinuz'
44
+ o[:no_minimize] = false
45
+ o[:append] = nil
46
+ o[:keep_minimized] = false
47
+ o[:shell] = DEFAULT_SHELL
48
+ o[:_configs] = []
49
+ o[:_roots] = []
50
+
51
+ CONFIG_ARRAYS.each do |array|
52
+ o[array] = []
53
+ end
54
+
55
+ action_names = [:build, :pack]
56
+
57
+ opts = OptionParser.new do |opts|
58
+ opts.banner = 'Usage: duck [action] [options]'
59
+
60
+ opts.on('-t <dir>', '--target <dir>',
61
+ 'Build in the specified target directory') do |dir|
62
+ o[:target] = dir
63
+ end
64
+
65
+ opts.on('--no-minimize',
66
+ 'Do not minimize the installation right before packing') do |dir|
67
+ o[:no_minimize] = true
68
+ end
69
+
70
+ opts.on('--keep-minimized',
71
+ 'Keep the minimized version of the initrd around') do |dir|
72
+ o[:keep_minimized] = true
73
+ end
74
+
75
+ opts.on('--debug',
76
+ 'Switch on debug logging') do |dir|
77
+ Logging::set_level Logger::DEBUG
78
+ end
79
+
80
+ opts.on('-o <file>', '--output <file>',
81
+ 'Output the resulting initrd in the specified path') do |path|
82
+ o[:initrd] = path
83
+ end
84
+
85
+ opts.on('-k <kernel>', '--kernel <kernel>',
86
+ 'Specify kernel to use when running qemu') do |path|
87
+ o[:kernel] = path
88
+ end
89
+
90
+ opts.on('-a <append>', '--append <append>',
91
+ 'Specify kernel options to append') do |append|
92
+ o[:append] = append
93
+ end
94
+
95
+ opts.on('-c <path>', '--config <path>',
96
+ 'Use the specified configuration path') do |path|
97
+ o[:_configs] << path
98
+ end
99
+
100
+ opts.on('-s <shell>', '--shell <shell>',
101
+ 'Set the shell to use when chrooting') do |shell|
102
+ o[:shell] = shell
103
+ end
104
+
105
+ opts.on('-h', '--help', 'Show this message') do
106
+ puts opts
107
+ return nil
108
+ end
109
+ end
110
+
111
+ args = opts.parse! args
112
+
113
+ unless args.empty?
114
+ action_names = args.map{|a| a.to_sym}
115
+ end
116
+
117
+ # add default configuration if none is specified.
118
+ if o[:_configs].empty?
119
+ o[:_configs] << File.join(working_directory, CONFIG_NAME)
120
+ end
121
+
122
+ o[:_configs] = [resource_path(CONFIG_NAME)] + o[:_configs]
123
+
124
+ o[:_configs].uniq!
125
+ o[:_configs].reject!{|i| not File.file? i}
126
+ return action_names, o
127
+ end
128
+
129
+ def self.deep_symbolize(o)
130
+ return o.map{|i| deep_symbolize(i)} if o.is_a? Array
131
+ return o unless o.is_a? Hash
132
+ c = o.clone
133
+ c.keys.each {|k| c[k.to_sym] = deep_symbolize(c.delete(k))}
134
+ return c
135
+ end
136
+
137
+ def self.prepare_options(o)
138
+ raise "No configuration found" if o[:_configs].empty?
139
+
140
+ [:target].each do |s|
141
+ next if File.directory? o[s]
142
+ log.info "Creating directory '#{s}' on #{o[s]}"
143
+ FileUtils.mkdir_p o[s]
144
+ end
145
+
146
+ unless File.directory? o[:gpg_homedir]
147
+ log.info "Creating directory GPG home directory on #{o[:gpg_homedir]}"
148
+ FileUtils.mkdir_p o[:gpg_homedir]
149
+ FileUtils.chmod 0700, o[:gpg_homedir]
150
+ end
151
+
152
+ o[:_configs].each do |config_path|
153
+ log.info "Loading configuration from #{config_path}"
154
+ config = deep_symbolize YAML.load_file(config_path)
155
+ root = File.dirname config_path
156
+ # Special keys treated as accumulated arrays over all configurations.
157
+
158
+ CONFIG_ARRAYS.each do |n|
159
+ o[n] += (config.delete(n) || []).map{|i| [root, i]}
160
+ end
161
+
162
+ # Merge (overwrite) the rest.
163
+ o.merge! config
164
+ o[:_roots] << root
165
+ end
166
+ end
167
+
168
+ def self.main(args)
169
+ action_names, o = parse_options args
170
+ return 0 if o.nil?
171
+ prepare_options o
172
+
173
+ action_names.each do |action_name|
174
+ action_class = ACTTIONS[action_name]
175
+
176
+ if action_class.nil?
177
+ log.error "No such action: #{action_name}"
178
+ return 1
179
+ end
180
+
181
+ action_instance = action_class.new o
182
+ action_instance.execute
183
+ end
184
+
185
+ return 0
186
+ end
187
+ end
188
+
189
+ if __FILE__ == $0
190
+ exit Duck::main(ARGV)
191
+ end