Skip to content

Commit

Permalink
support for teleport --infer, which creates a sample Telfile
Browse files Browse the repository at this point in the history
  • Loading branch information
gurgeous committed Aug 10, 2011
1 parent f1fad43 commit 199d0cc
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 13 deletions.
1 change: 1 addition & 0 deletions lib/teleport.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
require "teleport/config" require "teleport/config"
require "teleport/mirror" require "teleport/mirror"
require "teleport/install" require "teleport/install"
require "teleport/infer"
require "teleport/main" require "teleport/main"
318 changes: 318 additions & 0 deletions lib/teleport/infer.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,318 @@
require "set"

# Many, many thanks to Blueprint!
# https://github.com/devstructure/blueprint

module Teleport
class Infer
include Util

MD5SUMS = {
'/etc/adduser.conf' => ['/usr/share/adduser/adduser.conf'],
'/etc/apparmor.d/tunables/home.d/ubuntu' =>
['2a88811f7b763daa96c20b20269294a4'],
'/etc/apt/apt.conf.d/00CDMountPoint' =>
['cb46a4e03f8c592ee9f56c948c14ea4e'],
'/etc/apt/apt.conf.d/00trustcdrom' =>
['a8df82e6e6774f817b500ee10202a968'],
'/etc/chatscripts/provider' => ['/usr/share/ppp/provider.chatscript'],
'/etc/default/console-setup' =>
['0fb6cec686d0410993bdf17192bee7d6',
'b684fd43b74ac60c6bdafafda8236ed3',
'/usr/share/console-setup/console-setup'],
'/etc/default/grub' => ['ee9df6805efb2a7d1ba3f8016754a119',
'ad9283019e54cedfc1f58bcc5e615dce'],
'/etc/default/irqbalance' => ['7e10d364b9f72b11d7bf7bd1cfaeb0ff'],
'/etc/default/keyboard' => ['06d66484edaa2fbf89aa0c1ec4989857'],
'/etc/default/locale' => ['164aba1ef1298affaa58761647f2ceba',
'7c32189e775ac93487aa4a01dffbbf76'],
'/etc/default/rcS' => ['/usr/share/initscripts/default.rcS'],
'/etc/environment' => ['44ad415fac749e0c39d6302a751db3f2'],
'/etc/hosts.allow' => ['8c44735847c4f69fb9e1f0d7a32e94c1'],
'/etc/hosts.deny' => ['92a0a19db9dc99488f00ac9e7b28eb3d'],
'/etc/initramfs-tools/modules' =>
['/usr/share/initramfs-tools/modules'],
'/etc/inputrc' => ['/usr/share/readline/inputrc'],
'/etc/iscsi/iscsid.conf' => ['6c6fd718faae84a4ab1b276e78fea471'],
'/etc/kernel-img.conf' => ['f1ed9c3e91816337aa7351bdf558a442'],
'/etc/ld.so.conf' => ['4317c6de8564b68d628c21efa96b37e4'],
'/etc/networks' => ['/usr/share/base-files/networks'],
'/etc/nsswitch.conf' => ['/usr/share/base-files/nsswitch.conf'],
'/etc/pam.d/common-account' => ['9d50c7dda6ba8b6a8422fd4453722324'],
'/etc/pam.d/common-auth' => ['a326c972f4f3d20e5f9e1b06eef4d620'],
'/etc/pam.d/common-password' => ['9f2fbf01b1a36a017b16ea62c7ff4c22'],
'/etc/pam.d/common-session' => ['e2b72dd3efb2d6b29698f944d8723ab1'],
'/etc/pam.d/common-session-noninteractive' =>
['508d44b6daafbc3d6bd587e357a6ff5b'],
'/etc/ppp/chap-secrets' => ['faac59e116399eadbb37644de6494cc4'],
'/etc/ppp/pap-secrets' => ['698c4d412deedc43dde8641f84e8b2fd'],
'/etc/ppp/peers/provider' => ['/usr/share/ppp/provider.peer'],
'/etc/profile' => ['/usr/share/base-files/profile'],
'/etc/python/debian_config' => ['7f4739eb8858d231601a5ed144099ac8'],
'/etc/rc.local' => ['10fd9f051accb6fd1f753f2d48371890'],
'/etc/rsyslog.d/50-default.conf' =>
['/usr/share/rsyslog/50-default.conf'],
'/etc/security/opasswd' => ['d41d8cd98f00b204e9800998ecf8427e'],
'/etc/sgml/xml-core.cat' => ['bcd454c9bf55a3816a134f9766f5928f'],
'/etc/shells' => ['0e85c87e09d716ecb03624ccff511760'],
'/etc/ssh/sshd_config' => ['e24f749808133a27d94fda84a89bb27b',
'8caefdd9e251b7cc1baa37874149a870'],
'/etc/sudoers' => ['02f74ccbec48997f402a063a172abb48'],
'/etc/ufw/after.rules' => ['/usr/share/ufw/after.rules'],
'/etc/ufw/after6.rules' => ['/usr/share/ufw/after6.rules'],
'/etc/ufw/before.rules' => ['/usr/share/ufw/before.rules'],
'/etc/ufw/before6.rules' => ['/usr/share/ufw/before6.rules'],
'/etc/ufw/ufw.conf' => ['/usr/share/ufw/ufw.conf']
}

NEW_FILES_WITHIN = %w(cron.d logrotate.d rsyslog.d init)
CHECKSUM_FILES = %w(bash.bashrc environment inputrc rc.local ssh/ssh_config ssh/sshd_config)

def initialize
@telfile = []

if fails?("uname -a | grep -q Ubuntu")
fatal "Sorry, --infer can only run on an Ubuntu machine."
end

append "#" * 72
append "# Telfile inferred from #{`hostname`.strip} at #{Time.now}"
append "#" * 72
append

user
ruby
apt
packages
files

banner "Done!"
$stderr.puts
@telfile.each { |i| puts i }
end

def append(s = nil)
@telfile << (s || "")
end

def user
append "user #{`whoami`.strip.inspect}"
end

def ruby
version = `ruby --version`
ruby = nil
case version
when /Ruby Enterprise Edition/ then ruby = "REE"
when /1\.8\.7/ then ruby = "1.8.7"
when /1\.9\.2/ then ruby = "1.9.2"
end
append "ruby #{ruby.inspect}" if ruby
end

def apt
banner "Calculating apt sources and keys..."
list = run_capture_lines("cat /etc/apt/sources.list /etc/apt/sources.list.d/*.list")
list = list.grep(/^deb /).sort
list.each do |line|
if line =~ /^deb http:\/\/(\S+)\s+(\S+)/
source, dist = $1, $2
file = source.chomp("/").gsub(/[^a-z0-9.-]/, "_")
file = "/var/lib/apt/lists/#{file}_dists_#{dist}_Release"
next if !File.exists?(file)

verify = run_capture("gpgv --keyring /etc/apt/trusted.gpg #{file}.gpg #{file} 2>&1")
key = verify[/key ID ([A-Z0-9]{8})$/, 1]
next if key == "437D05B5" # canonical key
append "apt #{line.inspect}, :key => #{key.inspect}"
end
end
end

def packages
banner "Looking for interesting packages..."
@packages = Apt.new.added
if !@packages.empty?
append
append "# Note: You should read this package list very carefully and remove"
append "# packages that you don't want on your server."
append
append "packages %w(#{@packages.join(" ")})"
end
end

def files
banner "Looking for interesting files..."
files = []

# read checksums from dpkg status
conf = { }
File.readlines("/var/lib/dpkg/status").each do |line|
if line =~ /^ (\S+) ([0-9a-f]{32})/
conf[$1] = $2
end
end

# look for changed conf files
$stderr.puts " scanning conf files from interesting packages..."
@packages.each do |pkg|
list = run_capture_lines("dpkg -L #{pkg}")
list = list.select { |i| i =~ /^\/etc/ }.sort
list = list.select { |i| File.file?(i) }
list = list.select { |i| conf[i] && conf[i] != md5sum(i) }
files += list
end

# look for new files in NEW_FILES_WITHIN
dirs = NEW_FILES_WITHIN.map { |i| "/etc/#{i}" }
dirs.sort.each do |dir|
$stderr.puts " scanning #{dir} for new files..."
list = Dir["#{dir}/*"].sort
list = list.select { |i| !MD5SUMS[i] }
list = list.select { |i| fails?("dpkg -S #{i}") }
files += list
end

# now look for changed files from CHECKSUM_FILES
scan = CHECKSUM_FILES.map { |i| "/etc/#{i}" }
scan = scan.select { |i| File.file?(i) }
scan.each do |i|
new_sum = md5sum(i)
if old_sum = MD5SUMS[i]
match = old_sum.any? do |sum|
sum = md5sum(sum) if sum =~ /^\//
new_sum == sum
end
files << i if !match
elsif old_sum = conf[i]
files << i if new_sum != old_sum
end
end

if !files.empty?
append
append "#" * 72
append "# Also, I think these should be included in files/"
append "#" * 72
append
files.sort.each do |i|
append "# #{i}"
end
append
append "# You can do that with this magical command:"
append "#"
append "# mkdir files && cd files && tar cf - #{files.join(" ")} | tar xf -"
end
end

class Apt
include Util

BLACKLIST = /^linux-(generic|headers|image)/

Package = Struct.new(:name, :status, :deps, :base, :parents)

def initialize
@packages = nil
@map = nil
end

def packages
if !@packages
# run dpkg
lines = run_capture_lines("dpkg-query '-f=${Package}\t${Status}\t${Pre-Depends},${Depends},${Recommends}\t${Essential}\t${Priority}\n' -W")
@packages = lines.map do |line|
name, status, deps, essential, priority = line.split("\t")
deps = deps.gsub(/\([^)]+\)/, "")
deps = deps.split(/[,|]/)
deps = deps.map(&:strip).select { |i| !i.empty? }.sort
base = false
base = true if essential == "yes"
base = true if priority =~ /^(important|required|standard)$/
Package.new(name, status, deps, base, [])
end

# calculate ancestors
@packages.each do |pkg|
pkg.deps.each do |i|
if d = self[i]
d.parents << pkg.name
end
end
end
@packages.each do |pkg|
pkg.parents = pkg.parents.sort.uniq
end
end

@packages
end

def [](name)
if !@map
@map = { }
packages.each { |i| @map[i.name] = i }
end
@map[name]
end

def base_packages
packages.select { |i| i.base }.map(&:name)
end

def ignored_packages
list = packages.select { |i| i.base }.map(&:name)
list += %w(grub-pc installation-report language-pack-en language-pack-gnome-en linux-generic-pae linux-server os-prober ubuntu-desktop ubuntu-minimal ubuntu-standard wireless-crda)
dependencies(list)
end

def dependencies(list)
check = list
while !check.empty?
check = check.map do |i|
if pkg = self[i]
pkg.deps
end
end
check = check.compact.flatten.uniq.sort
check -= list
list += check
end
list.sort
end

def added
# calculate raw list
ignored = Set.new(ignored_packages)
list = packages.select do |i|
i.status == "install ok installed" && !ignored.include?(i.name)
end
list = list.map(&:name)

# now calculate parents
roots = []
check = list
while !check.empty?
check = check.map do |i|
if pkg = self[i]
if !pkg.parents.empty?
pkg.parents
else
roots << pkg.name
nil
end
end
end
check = check.compact.flatten.uniq.sort
check -= list
list += check
end

# blacklist
roots = roots.reject { |i| i =~ BLACKLIST }

roots.sort
end
end
end
end
25 changes: 16 additions & 9 deletions lib/teleport/main.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@ class Main
TAR = "#{DIR}.tgz" TAR = "#{DIR}.tgz"


def initialize(cmd = :teleport) def initialize(cmd = :teleport)
$stderr = $stdout
cli(cmd) cli(cmd)


case @options[:cmd] case @options[:cmd]
when :teleport when :teleport
$stderr = $stdout
teleport teleport
when :install when :install
$stderr = $stdout
install install
when :infer
infer
end end
end end


Expand All @@ -31,6 +34,9 @@ def cli(cmd)
o.on("-f", "--file FILE", "use this file instead of Telfile") do |f| o.on("-f", "--file FILE", "use this file instead of Telfile") do |f|
@options[:file] = f @options[:file] = f
end end
o.on("-i", "--infer", "infer a new Telfile from YOUR machine") do |f|
@options[:cmd] = :infer
end
o.on_tail("-h", "--help", "print this help text") do o.on_tail("-h", "--help", "print this help text") do
puts opt puts opt
exit(0) exit(0)
Expand All @@ -47,23 +53,19 @@ def cli(cmd)
if @options[:cmd] == :teleport if @options[:cmd] == :teleport
# print this error message early, to give the user a hint # print this error message early, to give the user a hint
# instead of complaining about command line arguments # instead of complaining about command line arguments
look_for_config! if ARGV.length != 1
if !(@options[:host] = ARGV.shift)
puts opt puts opt
exit(1) exit(1)
end end
@options[:host] = ARGV.shift
end end
end end


def look_for_config! # Read Telfile
def read_config
if !File.exists?(@options[:file]) if !File.exists?(@options[:file])
fatal("Sadly, I can't find #{@options[:file]} here. Please create one.") fatal("Sadly, I can't find #{@options[:file]} here. Please create one.")
end end
end

# Read Telfile
def read_config
look_for_config!
@config = Config.new(@options[:file]) @config = Config.new(@options[:file])
end end


Expand Down Expand Up @@ -142,5 +144,10 @@ def install
end end
Install.new(@config) Install.new(@config)
end end

# try to infer a new Telfile based on the current machine
def infer
Infer.new
end
end end
end end
Loading

0 comments on commit 199d0cc

Please sign in to comment.