Creating a clean and secure dev environment on the fly with Terraform and Ansible.
Creating the droplet in the first place. Note that details of images, sizes and regions are here and up to date as of Pi Day 2019: [[file:do_deets/][file:~/Dropbox/projects/devserve/do_deets/]provider "digitalocean" {
token = "${var.do_api_token}"
}
resource "digitalocean_droplet" "dev_server" {
name = "${var.server_name}"
image = "${var.image}"
size = "${var.server_size}"
region = "${var.region}"
ipv6 = true
private_networking = false
tags = ["${digitalocean_tag.dev.name}"]
ssh_keys = ["${var.ssh_key_hash}"]
provisioner "local-exec" {
command = <<EOD
cat <<EOF > hosts
[dev]
${digitalocean_droplet.dev_server.ipv4_address}
[dev:vars]
server=${var.server_name}
ansible_python_interpreter=/usr/bin/python3
EOF
EOD
}
provisioner "local-exec" {
command = "sleep 60 && ansible-playbook -i hosts gofish.yml"
}
}
resource "digitalocean_tag" "dev" {
name = "dev"
}
Setting up subdomain for ease of pointing and shooting.
resource "digitalocean_domain" "atcloudbase" {
name = "${var.dev_domain}"
ip_address = "${digitalocean_droplet.dev_server.ipv4_address}"
}
resource "digitalocean_record" "atcloudbase" {
name = "${var.server_name}"
type = "A"
domain = "${digitalocean_domain.atcloudbase.name}"
value = "${digitalocean_droplet.dev_server.ipv4_address}"
}
And firewall that lets me in from wherever I just set this up. And nowhere else.
resource "digitalocean_firewall" "dev" {
name = "only-ssh"
droplet_ids = ["${digitalocean_droplet.dev_server.id}"]
inbound_rule = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["${chomp(data.http.myip.body)}/32"]
},
{
protocol = "udp"
port_range = "60001"
source_addresses = ["${chomp(data.http.myip.body)}/32"]
},
{
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0"]
}
]
}
data "http" "myip" {
url = "http://ipv4.icanhazip.com"
}
output "instance_ip_addr" {
value = "${digitalocean_droplet.dev_server.ipv4_address}"
}
terraform fmt
Starting with updates and standard package install.
- hosts: dev
become: yes
remote_user: root
tasks:
- name: install system updates for centos systems
yum: name=* state=latest update_cache=yes
when: ansible_distribution == "CentOS"
- name: install system updates for ubuntu systems
apt: upgrade=dist update_cache=yes
when: ansible_distribution == "Ubuntu"
- name: install basic packages
action: >
{{ ansible_pkg_mgr }} name={{ item }} state=present update_cache=yes
with_items:
- vim
- tmux
- mosh
- tmate
- git
- python3
- python3-pip
- zsh
- fonts-powerline
I exist!
- name: "Create user accounts and add users to groups"
user:
name: "alexs"
groups: "admin,docker"
shell: "/bin/zsh"
- name: "Add authorized keys"
authorized_key:
user: "alexs"
key: "{{ lookup('file', 'keys/cloudbase.pub') }}"
- name: "Allow admin users to sudo without a password"
lineinfile:
path: "/etc/sudoers" # path: in version 2.3
state: "present"
regexp: "^%admin"
line: "%admin ALL=(ALL) NOPASSWD: ALL"
- name: Enable moshing
shell: "ufw allow 60000:61000/udp"
Dot files deployed
- name: Buy me a spaceship and fly
git:
repo: https://github.com/denysdovhan/spaceship-prompt.git
version: master
dest: /home/alexs/.spaceship
- name: Create symbolic link
file:
src: "/home/alexs/.spaceship/spaceship.zsh"
dest: "/usr/local/share/zsh/site-functions/prompt_spaceship_setup"
state: link
- name: Update .zshrc
copy:
src: "./dots/.zshrc"
dest: "/home/alexs/.zshrc"
become: yes
become_user: alexs
- name: Tmux config
copy:
src: "./dots/.tmux.conf"
dest: "/home/alexs/.tmux.conf"
become: yes
become_user: alexs
Download Doom Config
- name: Download Doom.d config
git:
repo: https://github.com/jalexspringer/doom-private-config.git
version: master
dest: /home/alexs/,doom.d
become: yes
become_user: alexs
Serving the freshest web
- name: ensure nginx is at the latest version
apt: name=nginx state=latest
- name: start nginx
service:
name: nginx
state: started
EMACS!
- name: resolve platform specific vars
include_vars: '{{item}}'
with_first_found:
- files:
- '{{ansible_distribution}}-{{ansible_distribution_release}}.yml'
- '{{ansible_distribution}}.yml'
- '{{ansible_os_family}}.yml'
skip: true
paths:
- '{{role_path}}/vars'
- name: os pkgs....
become: yes
become_user: root
with_items: '{{emacs_build_os_pkgs|default([])}}'
package:
name: '{{item}}'
state: present
- name: downloading...
become: yes
become_user: root
get_url:
url: '{{emacs_build_url}}'
dest: /tmp/{{emacs_build_tgz}}
timeout: '{{emacs_build_timeout_seconds}}'
mode: 0644
- name: unarchiving...
become: yes
become_user: root
unarchive:
remote_src: yes
src: /tmp/{{emacs_build_tgz}}
dest: '{{emacs_build_parent_src_dir}}'
creates: '{{emacs_build_src_dir}}'
- name: configuring...
become: yes
become_user: root
command: ./configure --with-x=no
args:
chdir: '{{emacs_build_src_dir}}'
creates: '{{emacs_build_src_dir}}/Makefile'
- name: building...
become: yes
become_user: root
command: make
args:
chdir: '{{emacs_build_src_dir}}'
creates: '{{emacs_build_src_dir}}/src/emacs'
- name: installing...
become: yes
become_user: root
command: make install
args:
chdir: '{{emacs_build_src_dir}}'
creates: /usr/local/bin/emacs
- name: cleanup...
become: yes
become_user: root
file:
path: '{{emacs_build_src_dir}}'
state: absent
Getting tricky and auto-configing AWS CLI based on current creds.
- name: Install AWS command line interface
sudo: yes
pip:
name: "awscli"
version: "1.7.39"
# - name: Create .aws directory in the home directory
# file:
# path: "/home/{{ansible_ssh_user}}/.aws/"
# state: directory
# owner: "{{ansible_ssh_user}}"
# group: "{{ansible_ssh_user}}"
# mode: 0755
# - name: Copy the aws config file to the box
# sudo: yes
# template:
# src: ../templates/aws_config.j2
# dest: "/home/alexs/.aws/config"
# owner: "{{ansible_ssh_user}}"
# group: "{{ansible_ssh_user}}"
# mode: 0600
**
- Development
- Web server
- DB
Tmux Config
# Tmux settings
# Reload the config
bind r source-file ~/.tmux.conf \; display "Reloaded!"
# Set change prefix key
set -g prefix C-a
bind C-a send-prefix
unbind C-b
# Window splitting
bind | split-window -h
bind - split-window -v
# Don't mess with my emacs
set -sg escape-time 1
# Set colors
set-option -g default-terminal "screen-256color"
# Set reload key to r
bind r source-file ~/.tmux.conf
# Count start at 1
set -g base-index 1
setw -g pane-base-index 1
# Use vim bindings
setw -g mode-keys vi
# Remap window navigation to vim
unbind-key j
bind-key j select-pane -D
unbind-key k
bind-key k select-pane -U
unbind-key h
bind-key h select-pane -L
unbind-key l
bind-key l select-pane -R
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# Set the title bar
set -g set-titles on
set -g set-titles-string '#(whoami) :: #h :: #(curl ipecho.net/plain;echo)'
# colors!
set -g default-terminal "screen-256color"
# Set status bar
# set -g status-utf8 on
set -g status-bg black
set -g status-fg white
set -g status-interval 5
set -g status-left-length 90
set -g status-right-length 60
set -g status-left "#[fg=Green]#(whoami)#[fg=white]::#[fg=Green]#(hostname -s)#[fg=white]::#[fg=yellow]#(curl ipecho.net/plain;echo)"
set -g status-right 'Session: #[fg=Cyan]#S #[fg=white]%a %d %b %R'
set -g status-justify centre
set -g message-fg white
set -g message-bg black
set -g message-attr bright
# notify me of things
setw -g monitor-activity on
set -g visual-activity on
ZSH Config
# Set up the prompt
autoload -U promptinit; promptinit
prompt spaceship
setopt histignorealldups sharehistory
# Use emacs keybindings even if our EDITOR is set to vi
bindkey -e
# Keep 1000 lines of history within the shell and save it to ~/.zsh_history:
HISTSIZE=1000
SAVEHIST=1000
HISTFILE=~/.zsh_history
# Use modern completion system
autoload -Uz compinit
compinit
zstyle ':completion:*' auto-description 'specify: %d'
zstyle ':completion:*' completer _expand _complete _correct _approximate
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' menu select=2
eval "$(dircolors -b)"
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-colors ''
zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' menu select=long
zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
zstyle ':completion:*' use-compctl false
zstyle ':completion:*' verbose true
zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'
# Emacs tramp fix
if [[ "$TERM" == "dumb" ]]
then
unsetopt zle
unsetopt prompt_cr
unsetopt prompt_subst
unfunction precmd
unfunction preexec
PS1='$ '
fi
One script to run them all
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/cloudbase
terraform plan -out="terraform.plan" && terraform apply "terraform.plan"
emacs "/ssh:alexs@`terraform output instance_ip_addr`:/home/alexs" &
mosh "alexs@`terraform output instance_ip_addr`"
eval "$(ssh-agent -k)"