duck-installer 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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