Unix and Me

Dmitry's blog

Envio de e-mail com ssmtp

2014-05-20 12:19:00 -0300

Nota: Todos os comandos eu executei como root, caso não seja root use o sudo antes dos comandos.

Enviando e-mail de máquina unix usando o ssmtp.

No ubuntu instale com o apt-get:

# apt-get install ssmtp

Depois basta configurar para usar o Amazon SES no arquivo /etc/ssmtp/ssmtp.conf:

root=username@mydomain.com
mailhub=email-smtp.us-east-1.amazonaws.com:465
hostname=myserver.mydomain.com

FromLineOverride=YES

AuthUser=AAAAABBBBBCCCCCDDDDD
AuthPass=AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHHIIII
UseTLS=YES
AuthMethod=LOGIN

Se for usando o Gmail1:

root=username@gmail.com
mailhub=smtp.gmail.com:587
hostname=myserver.mydomain.com

FromLineOverride=YES

UseSTARTTLS=YES
AuthUser=username@gmail.com
AuthPass=MyPassword

Depois de fazer a configuração tem ainda que deixar as permissões corretas:

# chmod 640 /etc/ssmtp/ssmtp.conf
# chown root:mail /etc/ssmtp/ssmtp.conf

Se quiser que algum usuário envie e-mail, basta adicioná-lo ao grupo mail, editando diretamente o /etc/group ou:

# gpasswd -a username mail

Enviando e-mail

Para testar (caso seu usuário não seja do grupo mail use o sudo antes do ssmtp):

# echo "Testing" | /usr/sbin/ssmtp username@gmail.com

Ou se quiser use um arquivo, por exemplo:

From: username@mydomain.com
Subject: This is Sparta!

Hi.

E chamando via linha de comando:

# ssmtp username@mydomain.com < /tmp/mail.txt

Rails

Para configurar no Rails basta usar algum arquivo de configuração no initializers semelhante a:

Rails.application.configure do
  config.action_mailer.delivery_method = :sendmail
  config.action_mailer.sendmail_settings = { :arguments => '-i' }
  config.action_mailer.default_url_options  = { :host => "mydomain.com" }
end

Ou então colocar em config/environments/production.rb.

Exemplo de scripting

O script abaixo eu coloquei num servidor para me enviar um e-mail com as atualizações que são necessárias, no meu caso coloquei uma vez por dia no crontab:

#!/bin/bash

apt-get update >/dev/null 2>/dev/null
count=$(/usr/lib/update-notifier/apt-check --human-readable | grep -v ^0 | wc -l)

if [ $count -ne 0 ]
then
  file=/tmp/updates-notify-$$

  echo -e "To: username@mydomain.com
From: $(hostname)@mydomain.com
Subject: $(hostname -f) has updates

The server $(hostname -f) has updates:

$(/usr/lib/update-notifier/apt-check --human-readable)

$(apt-get -s -o Debug::NoLocking=true upgrade | grep 'Inst' | cut -d' ' -f2)
" >> $file

  ssmtp username@mydomain.com < $file
  rm $file
fi

Fontes

"SSMTP - ArchWiki". ArchWiki. Accessed May 19, 2014. https://wiki.archlinux.org/index.php/SSMTP.

"How to Use GMail as a Free SMTP Server and Overcome Captcha". Accessed May 19, 2014. http://stackoverflow.com/questions/1607828/how-to-use-gmail-as-a-free-smtp-server-and-overcome-captcha.

"Rails & Sendmail Recipients with -T Option Not Supported." Accessed May 31, 2014. http://stackoverflow.com/questions/6456205/rails-sendmail-recipients-with-t-option-not-supported.


1 Cuidado que o gmail tem um problema de autenticação, caso toda sua configuração esteja certa e mesmo assim receba erro de autenticação, visite: http://www.google.com/accounts/DisplayUnlockCaptcha ou https://www.google.com/a/yourdomain.com/UnlockCaptcha para autorizar o envio de e-mail pelo seu servidor.

Recomendação de Leitura: A Dança do Universo

2014-04-02 21:21:00 -0300

Livro de linguagem acessível sobre as teorias da criação do universo. Muito recomendado para quem gosta de ciência e teorias sobre a criação/existência do universo.

Antes de iniciar a leitura recomendo ouvir esses dois episódios do podcast Dragões de Garagem.

O primeiro fala claramente do livro, mas não achei que possar ter expoliers, até por que o livro fala de teorias científicas e querendo ou não você já ouviu falar de algumas delas, o que pode é não lembrar.

Foi até por causa do episódio #23 que iniciei a leitura.

Local para comprar:

Se gosta da série Cosmos recomendo o livro e o podcast.

Ip estático no Ubuntu/Debian

2013-12-30 14:07:00 -0300

O primeiro a fazer é editar o arquivo /etc/network/interfaces, inicialmente ele deve ser assim:

# interfaces(5) file used by ifup(8) and ifdown(8)
auto lo
iface lo inet loopback

Modifique-o para que fique similar a:

# interfaces(5) file used by ifup(8) and ifdown(8)

auto lo eth0
iface lo inet loopback
iface eth0 inet static
    address 192.168.10.100
    netmask 255.255.255.0
    gateway 192.168.10.1

Substitua eth0 conforme sua máquina.

Agora basta reiniciar o serviço de rede:

# restart networking
networking start/running

E para as opções de DNS edite o arquivo /etc/resolvconf/resolv.conf.d/head adicionando o seguinte conteúdo ao final:

nameserver 8.8.8.8
nameserver 8.8.4.4

E reiniciar o serviço relacionado:

# restart resolvconf
resolvconf start/running

Fonte: Linux Basics - Set A Static IP On Ubuntu.

Rotina

2013-09-10 12:50:00 -0300

Eventualmente acordo as 6h para fazer cooper, mas não está na minha rotina ainda.

Costumo a "entrar para trabalhar" as 8h, então reservo de 7h até as 8h para alguma tarefa criativa, por exemplo:

  • Feed 0 (não tenho problema com inbox 0);
  • Aquela funcionalidade de software que não estou conseguindo fazer funcionar;
  • Entre outras…

Nesse ínterim procuro não ler e-mails. E durante o resto do dia configurei o software que me notifica de novos e-mails para verificar a cada 15min.

Inicio o trabalho para a empresa da qual presto serviço as 8h.

Como trabalho em home office eu gosto de me manter focado ao máximo, evito conversas em casa, evito sair para beber água e ficar de "convercê", pois se por um momento mínimo eu fizer isso as pessoas vão imaginar que isso é normal.

Para o almoço reservo apenas 1h, já notei que preciso apenas disso, não sei as outras pessoas, mas meu organismo funciona assim.

"Fico no trabalho" até as 17h.

Depois das 17h não tem rotina, faço as seguintes tarefas (as vezes apenas uma delas):

  • Leio feed;
  • Ouço podcast;
  • Leio livro(s) (descobri que ler dois livros por vez faz melhor para o cérebro);
  • Faço caça-palavras ou palavras cruzadas (do tipo diretas);
  • Assisto a séries;
  • Estudo assuntos diversos;

Rede social

Eu acompanhava 3 redes sociais: Twitter, Facebook e Google Plus, mas percebi no post Somos adultos, certo? A geração social media que eu não gero conteúdo apenas quero chamar atenção.

Assim eu acesso somente o Twitter e apenas duas vezes por dia.

Ao invés de querer chamar tanta atenção estou tentando gerar mais conteúdo (como este post por exemplo ;).

Exercício

Usando o software Workrave eu paro a cada uma hora para fazer alguns exercícios. Meu braço agradece.

Next?

Minhas aulas comecarão em Outubro então provavelmente minha rotiva vai mudar, se mudar muito crio um outro post caso contrário apenas atualizo este.

Dúvidas, sugestões e correções são bem-vindas.

Porque Prefiro Ebooks

2013-09-03 12:54:00 -0300

Motivo troll

O primeiro motivo e mais troll de todos:

Por que eu tenho um tablet.

Gosto de ler HQ no computador, mas só. Ler livros não é tão agradável.

Meu tablet está morrendo aos poucos então minha próxima compra vai ser um kobo. Escolhi o kobo pois ele aceita mais livros em formato aberto do que a Amazon. No kindle eu teria que converter a maioria dos que tenho e isso não me agrada muito.

Segundo motivo

O segundo é um motivo trágico:

As pessoas não cuidam tão bem dos livros quanto eu.

Não estou me gabando nem nada do tipo, apenas é um fato que comprovei: sempre que empresto um livro ele volta "modificado", com riscos, folhas amassadas, e nem preciso falar mais :|.

Terceiro motivo

O terceiro motivo é de logística:

Espaço para quardar.

Com livros em pdf/epub/mobi eu posso quardar num diretório no meu pc, enviar para "as nuvens" (no meu caso eu uso o dropbox) e catalogar (eu uso o calibre).

Ou seja: tenho meus ebooks em qualquer lugar do mundo (com internet) e organizados.

Catalogar meus livros sempre foi algo que eu quis fazer, mas com eles impressos não sei o porquê, mas nunca consegui.

DataMapper - Introdução

2013-07-31 07:15:00 -0300

Ultimamente tenho passado a usar cada vez menos MongoDb e voltado a usar banco de dados SQL.

Para usar SQL gosto do ActiveRecord ou o DataMapper. Já falei de ActiveRecord em posts como ActiveRecord sem Rails e hoje veremos um exemplo similar usando DataMapper.

Como de costume um Gemfile:

source 'http://rubygems.org'

gem 'data_mapper'
gem 'dm-sqlite-adapter'

Vamos criar nosso model de mensagem no arquivo message.rb:

class Message
  include DataMapper::Resource

  property :id, Serial
  property :content, String
  property :created_at, DateTime
end

Para se conectar ao banco de dados é necessário usar o DataMapper#setup:

DataMapper.setup(:default, "sqlite://#{File.expand_path('database.db')}")

Nesse caso vou usar um banco de dados sqlite, mas se for usar um PostgreSQL:

DataMapper.setup(:default, 'postgres://user:password@hostname/database')

Ao contrário do ActiveRecord não precisamos criar um arquivo de migration. Depois de fazer o require do model message.rb temos duas formas de levantar o banco de dados: DataMapper.auto_migrate! e o DataMapper.auto_upgrade!.

O DataMapper.auto_migrate! tem a característica de APAGAR as tabelas antes de tentar criá-las, já o DataMapper.auto_upgrade! apenas vai tentar fazer com que o schema "case" com o(s) model(s).

Vou criar agora um arquivo chamado database.rb que terá a função de "levantar" e migrar o banco de dados:

require 'data_mapper'

DataMapper::Logger.new($stdout, :debug)
DataMapper.setup(:default, "sqlite://#{File.expand_path('database.db')}")

Dir['./models/*.rb'].entries.each do |model|
  require model
end

DataMapper.auto_upgrade!

Usando o irb como "console" podemos testar:

% irb -r ./database.rb
 ~ (0.000308) PRAGMA table_info("messages")
 ~ (0.000026) SELECT sqlite_version(*)
 ~ (0.109057) CREATE TABLE "messages" ("id" INTEGER NOT NULL PRIMARY KEY AU
TOINCREMENT, "content" VARCHAR(50), "created_at" TIMESTAMP)
irb(main):001:0> Message.count
 ~ (0.000055) SELECT COUNT(*) FROM "messages"
=> 0
irb(main):002:0> Message.create(:content => "Hello from DataMapper")
 ~ (0.111596) INSERT INTO "messages" ("content", "created_at") VALUES ('Hel
lo from DataMapper', '2013-07-31T07:39:21-03:00')
=> #<Message @id=1 @content="Hello from DataMapper" @created_at=#<DateTime:
 2013-07-31T07:39:21-03:00 ((2456505j,38361s,436990591n),-10800s,2299161j)>
>
irb(main):003:0> Message.first
 ~ (0.000085) SELECT "id", "content", "created_at" FROM "messages" ORDER BY
 "id" LIMIT 1
=> #<Message @id=1 @content="Hello from DataMapper" @created_at=#<DateTime:
 2013-07-31T07:39:21-03:00 ((2456505j,38361s,0n),-10800s,2299161j)>>

O código está disponível no github em: https://github.com/dmitrynix/data-mapper-intro.

Strong Parameters

2013-07-18 12:46:00 -0300

Como parte da migração do Rails 3 para o Rails 4 pode-se adicionar a gem strong_parameters para mudar a forma como os atributos são protegidos.

O que, ao meu ver, é melhor, pois pode-se controlar quais atributos são permitidos em que situação (controller) ao invés de apenas definir globalmente.

Configurando

É necessário desativar o config.active_record.whitelist_attributes da aplicação, normalmente essa configuração fica no config/application.rb:

config.active_record.whitelist_attributes = false

E fazer o include do ActiveModel::ForbiddenAttributesProtection em cada model. Uma configuração alternativa (e mais prática) é fazer esse include em todos os models, para isso basta criar um arquivo no config/initializers/ com o seguinte conteúdo:

ActiveRecord::Base.send(:include, ActiveModel::ForbiddenAttributesProtection)

Usos

Encontrei os seguintes comportamentos:

  • Não aceita passar o type (pode ser usando para trabalhar com herança no ActiveRecord);
  • Quando passa um relacionamento has and belongs to many tem que colocar um array como possível atributo, algo como: :author_ids => [];
  • Quando passa um nested form tem que colocar o :id e o :_destroy, se não passar o :id, quando for editar, será feito uma tentativa de criar um novo objeto e se não passar o :_destroy nenhum objeto poderá ser apagado;

Algo que precisei foi um formulário iniciar com valores provenientes de um link, por exemplo:

link_to 'Criar subitens', new_item_path(:item => { :parent_id => @item.id })

Para funcionar fiz algo semelhante a isso na action new do controller:

def new
  @item = Item.new(params[:item].present? ? item_params : {})
end

Blocos pending com Rspec

2013-01-05 07:39:00 -0300

O Rspec tem diferentes formas de colocar um bloco de código como pending, minhas formas de uso são:

  • Como TODO;
  • Colocar teste que quebra sem ainda saber o motivo como pendente;
  • Marcar o teste que quebra como certo, porém a interface não "preparada";

Como TODO

Vamos supor que está criando uma lib de criptografia (bem simples), como gosto de fazer é desenvolver pelo teste, normalmente, eu coloco todos os casos de uso que lembro, depois implemento cada teste e por fim o código:

require './lib/crypt'

describe Crypt do
  it 'encrypt'

  it 'decrypt'
end

O seu TODO pode ser visto no próprio rspec:

% rspec spec/crypt_spec.rb
**

Pending:
  Crypt encrypt
    # Not yet implemented
    # ./spec/crypt_spec.rb:4
  Crypt decrypt
    # Not yet implemented
    # ./spec/crypt_spec.rb:6

Finished in 0.0004 seconds
2 examples, 0 failures, 2 pending

Quando o teste está quebrando

Há vários motivos para um teste quebrar: pode ter mudado algo na interface/código, uma gem que você usa está quebrando, …

Mas e se seu teste estiver correto? Uma gem de terceiros pode estar quebrando, mas sua implementação de uso dela pode estar correta.

Basta colocar um pending no começo do teste, opcionalmente com um motivo.

Imagine que temos esse teste:

require './lib/crypt'

describe Crypt do
  it 'encrypt' do
    subject = Crypt.new('abc')

    expect(subject.encrypt).to eq 'bcd'
  end

  it 'decrypt'
end

Rodar ele vai resultar em:

% rspec spec/crypt_spec.rb
F*

Pending:
  Crypt decrypt
    # Not yet implemented
    # ./spec/crypt_spec.rb:10

Failures:

  1) Crypt encrypt
     Failure/Error: expect(subject.encrypt).to eq 'bcd'

       expected: "bcd"
            got: "zzz"

       (compared using ==)
     # ./spec/crypt_spec.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.00104 seconds
2 examples, 1 failure, 1 pending

Failed examples:

rspec ./spec/crypt_spec.rb:4 # Crypt encrypt

Vai falhar, mas e se colocar o pending com um bloco (o bloco e o motivo são opcionais) ele vai ser marcado como pendente:

require './lib/crypt'

describe Crypt do
  it 'encrypt' do
    pending "waiting to work right" do
      subject = Crypt.new('abc')

      expect(subject.encrypt).to eq 'bcd'
    end
  end

  it 'decrypt'
end

E executando o teste:

% rspec spec/crypt_spec.rb
**

Pending:
  Crypt encrypt
    # waiting to work right
    # ./spec/crypt_spec.rb:4
  Crypt decrypt
    # Not yet implemented
    # ./spec/crypt_spec.rb:12

Finished in 0.00107 seconds
2 examples, 0 failures, 2 pending

Se não mudarmos nada no teste, mas corrigir o código ele vai exibir a seguinte saída:

% rspec spec/crypt_spec.rb
F*

Pending:
  Crypt decrypt
    # Not yet implemented
    # ./spec/crypt_spec.rb:12

Failures:

  1) Crypt encrypt FIXED
     Expected pending 'waiting to work right' to fail. No Error was raised.
     # ./spec/crypt_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.00102 seconds
2 examples, 1 failure, 1 pending

Failed examples:

rspec ./spec/crypt_spec.rb:4 # Crypt encrypt

Note o FIXED.

Basicamente: "Você esperava que esse teste estivesse quebrando, MAS foi corrigido". Agora pode-se remover o pending.

Usando o xit

Outra forma de desativar o teste é usando o xit ao invés de it. Ele tem o mesmo efeito que usar o pending (sem bloco), mas sua saída é um pouco diferente.

No nosso exemplo:

require './lib/crypt'

describe Crypt do
  it 'encrypt' do
    pending
  end

  xit 'decrypt'
end

Executando:

% rspec spec/crypt_spec.rb
**

Pending:
  Crypt encrypt
    # No reason given
    # ./spec/todo_spec.rb:4
  Crypt decrypt
    # Temporarily disabled with xit
    # ./spec/todo_spec.rb:8

Finished in 0.00049 seconds
2 examples, 0 failures, 2 pending

No reason given é para o pending (sem bloco e sem motivo) e o Temporarily disabled with xit é para o uso do xit.

Existem muitas formas de desativar blocos de teste, poderiamos até mesmo comentar! Mas quando você entrega seu código para outro desenvolvedor é bom manter o mais claro possível o motivo de um teste estar pendente.

tmux - Introdução

2012-12-18 12:01:00 -0300

O Tmux é um "multiplexador de terminal", um software que nos permite de um ambiente único iniciar vários outros terminais, cada um rodando seu próprio processo.

Além de abrir programas simultaneamente o tmux também permite que a janela seja dividida na horizontal ou vertical, i. e., pode rodar dois programas simultamente na mesma janela, lado a lado.

Mas se você já faz isso no iTerm2, por exemplo, por que fazer usando tmux?

A resposta, para mim, seria: padronização. Imagine-se configurando um servidor remoto, e no "meio" disso você tenta fazer o "split" usando o que já está acostumado? Certamente funcionaria, mas teria que ser feito outra conexão ssh.

E uma terceira característica do tmux é a capacidade de anexar ou sair de uma sessão sem que ela seja fechada.

Este post é introdutório, então vamos ver o básico de criar "janelas" e sessões.

Instalando

Certifique-se de que tem o tmux instalado:

% tmux -V
tmux 1.6

Caso não tenha use no ubuntu o apt-get para instalar:

% sudo apt-get install tmux

E no mac, o homebrew:

% brew install tmux

Para começar vamos abrir o tmux:

% tmux

Caso queira sair basta apenas encerrar o terminal, i. e., usando o comando exit do terminal.

Janelas

Com o tmux aberto execute algum comando e crie uma nova janela com as combinações control + b, c, ou seja, tecle simultameamente control e b, solte-os e tecle c.

Com a nova janela aberta é possível mudar de uma para outra com o control + b, n ou control + b, p.

Para sair nesse caso é preciso sair de todas as janelas ou "matar" o tmux.

Sessões

Cada vez que se executa o comando tmux ele abre uma nova sessão.

Para ver as sessões abertas use o tmux ls:

% tmux ls
0: 3 windows (created Wed Dec 19 08:53:28 2012) [97x28]
1: 1 windows (created Wed Dec 19 11:15:37 2012) [80x22] (attached)

Para entrar numa sessão basta usar o comando attach e opcionalmente o parâmetro -t para escolher qual sessão deseja "se anexar" (nesse caso temos a sessão 0 e 1):

% tmux attach

Exemplo prático:

Abra uma nova seção no tmux e abra o vim (ou execute qualquer outro comando):

% vim ~/editando-no-tmux.txt

Insira algum conteúdo ou simplesmente deixe-o aberto, agora vamos sair do tmux e deixar tudo aberto usando control + b, d, se quiser pode até fechar o terminal e/ou fazer logout.

Usando o tmux ls você verá que tem uma sessão aberta, vamos entrar nela novamente e constatar que o conteúdo é o mesmo que deixamos.

Agora para entender melhor isso abra outro terminal deixando lado a lado com o primeiro, e digite novamente o tmux attach, começe a fazer mudanças em qualquer um para ver o outro "se atualizando".

pry no Terminal

2012-12-13 21:56:00 -0300

Pry Logo

Primeiro instale a gem:

% gem install pry

Se você usa o rvm com bundler pode colocar no gemset global.

A wiki do pry tem vários exemplos de como usar com rails, eu particularmente gostei mais da forma "sem colocar no Gemfile" (https://github.com/pry/pry/wiki/Setting-up-Rails-or-Heroku-to-use-Pry).

Como a wiki descreve: ele vai iniciar o pry fazendo require do config/environment.rb da sua aplicação, se o arquivo existir, caso contrário vai iniciar normalmente.

Também é sugerido que instale a gem pry-rails

No meu caso eu usei ele um pouco diferente:

config_environment = File.join Dir.getwd, 'config', 'environment.rb'

if File.exist?(config_environment) && ENV['SKIP_RAILS'].nil?
  require config_environment

  if Rails.version[0..0] == '3'
    require 'rails/console/app'
    require 'rails/console/helpers'

    extend Rails::ConsoleMethods
  else
    warn "[WARN] cannot load Rails console commands (Not on Rails 3?)"
  end
end

require "/home/dmitry/Developer/awesome_print/init"
  • Tirei suporte a doc;
  • Renomeei a var rails para config_environment (que, para mim, faz mais sentido);
  • Tirei o suporte a rails 2 (faz muito tempo que não uso, então seria apenas "deixar teias de aranha" no arquivo :P);
  • Para carregar a gem awesome_print sem usar o Gemfile tive que fazer um clone local e fazer require direto no init;

Links: