├── .bundle └── config ├── .gitignore ├── .rvmrc ├── Gemfile ├── Gemfile.lock ├── History.txt ├── LICENSE ├── README.markdown ├── VERSION ├── bin └── ssh-forever ├── lib └── ssh-forever.rb └── ssh-forever.gemspec /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_DISABLE_SHARED_GEMS: "1" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | *.gem -------------------------------------------------------------------------------- /.rvmrc: -------------------------------------------------------------------------------- 1 | rvm use @ssh-forever --create -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | ssh-forever (0.3.0) 5 | open4 (>= 1.0.1) 6 | 7 | GEM 8 | remote: http://rubygems.org/ 9 | specs: 10 | open4 (1.0.1) 11 | 12 | PLATFORMS 13 | ruby 14 | 15 | DEPENDENCIES 16 | ssh-forever! 17 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | == 0.2.3 2010-02-01 2 | 3 | === New Features 4 | * Use the -n switch to automatically add a friendly name to your ~/.ssh/config (Tom Stuart) 5 | 6 | == 0.2.2 2009-09-27 7 | 8 | === New Features 9 | * Ability to specify an identity file other than id_rsa.pub (Matt Johnson)_ 10 | 11 | == 0.2.1 2009-09-03 12 | 13 | === New Features 14 | * Ability to specify a port number when connecting. (Jonas Grimfelt) 15 | 16 | === Changed Features 17 | * Better feedback when you enter no hostname or an invalid hostname to connect to. (Matt Wynne) 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Matt Wynne 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # ssh-forever 2 | 3 | Simple command to give you password-less SSH login on remote servers: 4 | 5 | ssh-forever username@yourserver.com [options] 6 | 7 | see `ssh-forever --help` for details on the options availlable. 8 | 9 | ## Installation 10 | 11 | gem install ssh-forever 12 | 13 | ## Example: 14 | 15 | [matt@bowie ssh-forever (master)]$ ssh-forever mattwynne@mattwynne.net -n dreamhost 16 | You do not appear to have a public key. I expected to find one at /Users/matt/.ssh/id_rsa.pub 17 | Would you like me to generate one? [Y/n]y 18 | Copying your public key to the remote server. Prepare to enter your password for the last time. 19 | mattwynne@mattwynne.net's password: 20 | Success. From now on you can just use plain old 'ssh'. Logging you in... 21 | Linux broncos 2.6.29-xeon-aufs2.29-ipv6-qos-grsec #1 SMP Thu Jul 9 16:42:58 PDT 2009 x86_64 22 | _ 23 | | |__ _ _ ___ _ _ __ ___ ___ 24 | | '_ \ '_/ _ \ ' \/ _/ _ (_-< 25 | |_.__/_| \___/_||_\__\___/__/ 26 | 27 | Welcome to broncos.dreamhost.com 28 | 29 | Any malicious and/or unauthorized activity is strictly forbidden. 30 | All activity may be logged by DreamHost Web Hosting. 31 | 32 | Last login: Sat Aug 15 17:24:17 2009 33 | [broncos]$ exit 34 | [matt@bowie ssh-forever (master)]$ ssh dreamhost 35 | 36 | ## Why? 37 | 38 | Because I can never remember how to do it by hand. Now I don't have to, and nor do you. 39 | 40 | ## What about ssh-copy-id? 41 | 42 | I'll admit that I wasn't aware of the [ssh-copy-id](http://linux.die.net/man/1/ssh-copy-id) unix 43 | command when I wrote this tool. The main advantage that `ssh-forever` has over `ssh-copy-id` is that it 44 | will automatically generate a public key for you if you don't already have one, making it a bit more 45 | user-friendly if you're not familiar with the SSH key system. 46 | 47 | You should probably try `ssh-copy-id` first. Many linux distros come with it built in. OSX users 48 | can install it with `brew install ssh-copy-id` 49 | 50 | ## Copyright 51 | 52 | Copyright (c) 2009 Matt Wynne. See LICENSE for details. 53 | 54 | ## Disclaimer 55 | 56 | This is open source. If you're worried, read the code before you run it. Don't come crying to me if it breaks something for you. -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2.2 2 | -------------------------------------------------------------------------------- /bin/ssh-forever: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') 4 | require 'ssh-forever' 5 | require 'optparse' 6 | 7 | banner = %{Usage: ssh-forever username@yourserver.com [options] 8 | 9 | More info: 10 | https://github.com/mattwynne/ssh-forever 11 | } 12 | 13 | options = {} 14 | OptionParser.new do |opts| 15 | opts.banner = banner 16 | opts.on('-a', '--auto', "Run without prompting.") do |boolean| 17 | options[:auto] = boolean 18 | end 19 | 20 | opts.on('-d', '--debug', "Run SSH verbosely.") do |boolean| 21 | options[:debug] = boolean 22 | end 23 | 24 | opts.on('-l', '--login', "Open SSH connection and stay logged in.") do |boolean| 25 | options[:login] = true 26 | end 27 | 28 | opts.on('-p', '--port [PORT]', Integer, "SSH server port.") do |port| 29 | options[:port] = port 30 | end 31 | 32 | opts.on('-i', '--identity_file [FILE]', String, "SSH identity file path (no .pub).") do |identity_file| 33 | options[:identity_file] = identity_file 34 | end 35 | 36 | opts.on('-n', '--name [NAME]', String, "SSH host name alias you wish to use.") do |name| 37 | options[:name] = name 38 | end 39 | 40 | opts.on('-q', '--quiet', "Run without output.") do |boolean| 41 | options[:quiet] = true 42 | options[:debug] = false 43 | end 44 | 45 | opts.on('-s', '--strict', "SSH with stricthostkeychecking=yes (default=no)") do |boolean| 46 | options[:strict] = boolean 47 | end 48 | 49 | end.parse! 50 | 51 | login = ARGV[0] 52 | 53 | if login.nil? 54 | puts banner + " ssh-forever --help\n" 55 | exit 1 56 | end 57 | 58 | SshForever::SecureShellForever.new(login, options).run 59 | -------------------------------------------------------------------------------- /lib/ssh-forever.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require "rubygems" 3 | require "bundler" 4 | Bundler.setup 5 | Bundler.require 6 | 7 | module SshForever 8 | VERSION = '0.3.0' 9 | 10 | class SecureShellForever 11 | def initialize(login, options = {}) 12 | @login = login 13 | @options = options 14 | local_key_path 15 | end 16 | 17 | def run 18 | unless File.exists?(public_key_path) 19 | STDERR.puts "You do not appear to have a public key. I expected to find one at #{public_key_path}" unless @options[:quiet] 20 | confirm_keygen 21 | generate_public_key 22 | end 23 | 24 | args = ssh_args() 25 | 26 | copy_public_key(args) 27 | 28 | puts "Success. From now on you can just use plain old 'ssh'. Logging you in..." unless @options[:quiet] 29 | status = run_shell_cmd(ssh_login(args)) 30 | exit 1 unless status.exitstatus.to_i == 0 31 | if @options[:login] 32 | if @options[:name] 33 | `ssh #{@options[:name]}#{args}` #TODO: fix this so that remote session is left open 34 | else 35 | `ssh #{@login}#{args}` 36 | end 37 | end 38 | end 39 | 40 | private 41 | 42 | def local_ssh_config_path 43 | @local_ssh_config_path ||= Pathname('~/').expand_path.realpath 44 | end 45 | 46 | def authorized_keys 47 | (local_ssh_config_path + '.ssh' + 'authorized_keys2').exist? ? 'authorized_keys2' : 'authorized_keys' 48 | end 49 | 50 | def append_ssh_config 51 | puts "Creating host entry in local ssh config with name #{@options[:name]}" unless @options[:quiet] 52 | (local_ssh_config_path + '.ssh' + 'config').open("a") do |config| 53 | config << ssh_config 54 | end 55 | end 56 | 57 | def existing_ssh_config? 58 | config = (local_ssh_config_path + '.ssh' + 'config').read 59 | chk1 = config[/IdentityFile #{public_key_path}/] ? true : false 60 | chk2 = config[/Host #{@options[:name]}/] ? true : false 61 | chk1 && chk2 ? true : false 62 | end 63 | 64 | def ssh_config 65 | host_config = <<-STUFF.gsub(/^ {6}/, '') 66 | 67 | Host #{@options[:name]} 68 | HostName #{@login.split("@")[1]} 69 | User #{@login.split("@")[0]} 70 | PreferredAuthentications publickey 71 | IdentityFile #{public_key_path} 72 | StrictHostKeyChecking #{@options[:strict] ? 'true' : 'false'} 73 | HostKeyAlias #{@options[:name]} 74 | STUFF 75 | end 76 | 77 | 78 | def run_shell_cmd(cmd) 79 | status = Open4::popen4('sh') do |pid, stdin, stdout, stderr| 80 | puts "debug: #{cmd}" if @options[:debug] 81 | stdin.puts cmd 82 | stdin.close 83 | end 84 | status 85 | end 86 | 87 | def confirm_keygen 88 | unless @options[:auto] 89 | STDERR.print "Would you like me to generate one? [Y/n]" unless @options[:quiet] 90 | result = STDIN.gets.strip 91 | unless result == '' or result == 'y' or result == 'Y' 92 | flunk %Q{Fair enough, I'll be off then. You can generate your own by hand using\n\n\tssh-keygen -t rsa} 93 | end 94 | end 95 | end 96 | 97 | 98 | def generate_public_key 99 | status = run_shell_cmd(ssh_keygen) 100 | flunk("Oh dear. I was unable to generate your public key. Run the command 'ssh-keygen -t rsa' manually to find out why.") unless status.exitstatus.to_i == 0 101 | end 102 | 103 | def copy_public_key(args) 104 | puts "Copying your public key to the remote server." unless @options[:quiet] 105 | puts "Prepare to enter your password for the last time..." unless @options[:quiet] 106 | status = run_shell_cmd(ssh_keycopy(args)) 107 | exit 1 unless status.exitstatus.to_i == 0 108 | end 109 | 110 | def ssh_args 111 | [ ' ', 112 | ("-p #{@options[:port]}" if @options[:port] =~ /^\d+$/), 113 | (@options[:strict] ? "-o stricthostkeychecking=yes" : "-o stricthostkeychecking=no"), 114 | (@options[:debug] ? "-vvv" : "-q") 115 | ].compact.join(' ') 116 | end 117 | 118 | def ssh_keygen 119 | "ssh-keygen -t rsa #{@options[:debug] ? "-v" : "-q"} -C '#{local_key_path} created by ssh-forever #{Time.now.utc}' -N '' -f #{local_key_path}" 120 | end 121 | 122 | def ssh_keycopy(args) 123 | "ssh-copy-id '-i #{@options[:identity_file]} #{args} #{@login}'" 124 | end 125 | 126 | def ssh_login(args) 127 | if @options[:name] 128 | append_ssh_config unless existing_ssh_config? 129 | login_command = "ssh-add; SSH_AUTH_SOCK=0 ssh #{@options[:name]}#{args} 'ssh-add;';" 130 | else 131 | login_command = "ssh-add; SSH_AUTH_SOCK=0 ssh #{@login}#{args} 'ssh-add;';" 132 | end 133 | login_command 134 | end 135 | 136 | 137 | def flunk(message) 138 | STDERR.puts message 139 | exit 1 140 | end 141 | 142 | def local_key_path 143 | @options[:identity_file] = Pathname(@options[:identity_file]).expand_path.realdirpath.to_s 144 | end 145 | 146 | def public_key_path 147 | File.expand_path(@options[:identity_file] || "#{local_ssh_config_path + '.ssh' + 'id_rsa.pub'}") 148 | end 149 | 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /ssh-forever.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = %q{ssh-forever} 3 | s.version = "0.3.0" 4 | 5 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 6 | s.authors = ["Matt Wynne"] 7 | s.date = %q{2009-09-27} 8 | s.default_executable = %q{ssh-forever} 9 | s.description = %q{Provides a replacement for the SSH command which automatically copies your public key while logging in} 10 | s.email = %q{matt@mattwynne.net} 11 | s.executables = ["ssh-forever"] 12 | s.extra_rdoc_files = [ 13 | "LICENSE", 14 | "README.markdown" 15 | ] 16 | s.files = [ 17 | ".gitignore", 18 | "History.txt", 19 | "LICENSE", 20 | "README.markdown", 21 | "VERSION", 22 | "bin/ssh-forever", 23 | "lib/ssh-forever.rb", 24 | "ssh-forever.gemspec" 25 | ] 26 | s.add_dependency('open4', '>= 1.0.1') 27 | s.homepage = %q{http://github.com/mattwynne/ssh-forever} 28 | s.rdoc_options = ["--charset=UTF-8"] 29 | s.require_paths = ["lib"] 30 | s.rubygems_version = %q{1.3.4} 31 | s.summary = %q{Butter-smooth password-less SSH setup.} 32 | 33 | if s.respond_to? :specification_version then 34 | s.specification_version = 3 35 | 36 | if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then 37 | else 38 | end 39 | else 40 | end 41 | end 42 | --------------------------------------------------------------------------------