├── tests ├── ansible.cfg ├── local.ini ├── test1.yml ├── elastic_test.sh └── test1_var.yml ├── tasks ├── shield_config.yml ├── shield.yml ├── esusers.yml ├── timezone.yml ├── java.yml ├── es_snapshot.yml ├── es_restore.yml ├── post-run.yml ├── marvel.yml ├── custom-jars.yml ├── aws.yml ├── spm.yml ├── main.yml ├── plugins.yml └── elastic-install.yml ├── handlers └── main.yml ├── meta └── main.yml ├── vagrant-inventory.ini ├── vagrant-main.yml ├── .gitignore ├── setup.sh ├── vars ├── sample.yml └── vagrant.yml ├── defaults └── main.yml ├── Vagrantfile ├── .travis.yml ├── templates ├── elasticsearch.default.j2 ├── elasticsearch.in.sh.j2 └── elasticsearch.yml.j2 ├── library ├── es_snapshot_restore.py └── es_reindex.py └── README.md /tests/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path=../../ -------------------------------------------------------------------------------- /tests/local.ini: -------------------------------------------------------------------------------- 1 | localhost ansible_connection='local' -------------------------------------------------------------------------------- /tasks/shield_config.yml: -------------------------------------------------------------------------------- 1 | - copy: src={{elasticsearch_shield_files}} dest=/etc/elasticsearch/shield/ -------------------------------------------------------------------------------- /tests/test1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This playbook nexus 3 | - hosts: all 4 | sudo: true 5 | vars_files: 6 | - "test1_var.yml" 7 | roles: 8 | - ansible-elasticsearch 9 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Handlers 3 | 4 | # Restart Elasticsearch 5 | - name: Restart Elasticsearch 6 | service: name=elasticsearch state=restarted 7 | -------------------------------------------------------------------------------- /tasks/shield.yml: -------------------------------------------------------------------------------- 1 | - include: esusers.yml 2 | when: (elasticsearch_esusers is defined) 3 | 4 | - include: shield_config.yml 5 | tags: shield_config_files 6 | when: (elasticsearch_shield_files is defined) 7 | notify: Restart Elasticsearch 8 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Meta 3 | galaxy_info: 4 | author: "George Stathis" 5 | company: Traackr 6 | license: MIT 7 | min_ansible_version: 1.3 8 | platforms: 9 | - name: Ubuntu 10 | versions: 11 | - precise 12 | categories: 13 | - database:nosql 14 | dependencies: [] 15 | -------------------------------------------------------------------------------- /vagrant-inventory.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Inventory for provisioning with Vagrant 3 | # 4 | 5 | ##################### 6 | # Local Environment # 7 | ##################### 8 | [vagrant] 9 | 192.168.111.10 ansible_ssh_user=vagrant ansible_ssh_pass=vagrant ansible_connection=local 10 | 11 | [vagrant:vars] 12 | # spm_client_token= 13 | -------------------------------------------------------------------------------- /tests/elastic_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Put data 5 | curl -XPUT 'http://localhost:9200/blog/user/dilbert' -d '{ "name" : "Dilbert Brown" }' 6 | 7 | #Get data 8 | curl -XGET 'http://localhost:9200/blog/user/dilbert?pretty=true' | grep "\"name\" : \"Dilbert Brown\"" 9 | 10 | # Check if kopf is running 11 | curl -XGET 'http://localhost:9200/_plugin/kopf/' | grep "ng-app=\"kopf\"" 12 | -------------------------------------------------------------------------------- /vagrant-main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Playbook 3 | - hosts: all 4 | user: $user 5 | sudo: yes 6 | 7 | vars_files: 8 | - defaults/main.yml 9 | - vars/vagrant.yml 10 | 11 | tasks: 12 | - include: tasks/main.yml 13 | # Uncomment to install and enable SPM. Make sure to set the spm_client_token variable in your inventory.ini to your SPM key 14 | # - include: tasks/spm.yml 15 | 16 | handlers: 17 | - include: handlers/main.yml 18 | -------------------------------------------------------------------------------- /tasks/esusers.yml: -------------------------------------------------------------------------------- 1 | - name: Delete user from esuser realm if it exists 2 | action: > 3 | shell bin/shield/esusers userdel {{ item.username }} 4 | chdir={{ elasticsearch_home_dir }} 5 | ignore_errors: yes 6 | with_items: elasticsearch_esusers 7 | - name: Add user to esuser realm 8 | action: > 9 | shell bin/shield/esusers useradd "{{ item.username }}" -p "{{ item.password }}" -r "{{ item.role }}" 10 | chdir={{ elasticsearch_home_dir }} 11 | ignore_errors: no 12 | with_items: elasticsearch_esusers 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.orig 5 | *.log 6 | *.rej 7 | *.swo 8 | *.swp 9 | *.vi 10 | *~ 11 | *.sass-cache 12 | *.iml 13 | 14 | # OS or Editor folders 15 | .DS_Store 16 | Thumbs.db 17 | .cache 18 | .project 19 | .classpath 20 | .settings 21 | .tmproj 22 | *.esproj 23 | nbproject 24 | *.sublime-project 25 | *.sublime-workspace 26 | *.ipr 27 | *.iws 28 | 29 | # Folders to ignore 30 | .hg 31 | .svn 32 | .CVS 33 | intermediate 34 | publish 35 | .idea 36 | target 37 | target-eclipse 38 | .vagrant 39 | -------------------------------------------------------------------------------- /tasks/timezone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: timezone | check current timezone 4 | shell: cat /etc/timezone 5 | changed_when: 0 6 | register: current_zone 7 | 8 | - name: timezone | Set timezone variables 9 | copy: 10 | content={{elasticsearch_timezone}} 11 | dest=/etc/timezone 12 | owner=root 13 | group=root 14 | mode=0644 15 | when: current_zone.stdout != elasticsearch_timezone 16 | 17 | - name: timezone | Run dpkg-reconfigure to configure timezone 18 | shell: dpkg-reconfigure --frontend noninteractive tzdata 19 | when: current_zone.stdout != elasticsearch_timezone -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export PYTHONUNBUFFERED=1 3 | export ANSIBLE_FORCE_COLOR=1 4 | 5 | if [ $(dpkg-query -W -f='${Status}' ansible 2>/dev/null | grep -c "ok installed") -eq 0 ]; 6 | then 7 | echo "Add APT repositories" 8 | export DEBIAN_FRONTEND=noninteractive 9 | apt-get install -qq software-properties-common &> /dev/null || exit 1 10 | apt-add-repository ppa:ansible/ansible &> /dev/null || exit 1 11 | 12 | apt-get update -qq 13 | 14 | echo "Installing Ansible" 15 | apt-get install -qq ansible &> /dev/null || exit 1 16 | echo "Ansible installed" 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /tasks/java.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: java | Accept Oracle license prior JDK installation 4 | shell: echo debconf shared/accepted-oracle-license-v1-1 select true | sudo debconf-set-selections; echo debconf shared/accepted-oracle-license-v1-1 seen true | sudo debconf-set-selections 5 | creates=/usr/lib/jvm/java-8-oracle 6 | 7 | - name: java | Update repositories 8 | apt_repository: 9 | repo={{ item }} 10 | state=present 11 | update_cache=yes 12 | with_items: elasticsearch_apt_repos 13 | 14 | - name: java | Install dependencies 15 | apt: 16 | pkg={{elasticsearch_apt_java_package}} 17 | state=present 18 | -------------------------------------------------------------------------------- /tasks/es_snapshot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: check backup folder exists. 3 | sudo: true 4 | file: path="{{elasticsearch_repo_dir}}" state=directory owner={{elasticsearch_user}} group={{elasticsearch_group}} recurse=yes 5 | 6 | - name: install pip 7 | sudo: true 8 | apt: name=python-pip state=present 9 | 10 | - name: install elasticsearch pip 11 | pip: name=elasticsearch state=present 12 | 13 | - name: check elasticsearch repository directory exists 14 | file: path={{elasticsearch_repo_dir}} state=directory owner={{elasticsearch_user}} 15 | 16 | - name: create es backups 17 | es_snapshot_restore: elasticsearch_host={{elasticsearch_backup_host}} repository_path="{{elasticsearch_repo_dir}}" repository_name={{elasticsearch_repo_name }} snapshot_name="{{elasticsearch_snapshot_name}}" snapshot_indices="{{elasticsearch_snapshot_indices | default(omit) }}" mode=snapshot state=present 18 | -------------------------------------------------------------------------------- /tasks/es_restore.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: copy in backup folder from somewhere. 3 | sudo: true 4 | synchronize: src={{backup_source}} dest="{{elasticsearch_repo_dir}}" 5 | 6 | - name: install pip 7 | sudo: true 8 | apt: name=python-pip state=present 9 | 10 | - name: install elasticsearch pip 11 | pip: name=elasticsearch state=present 12 | 13 | - name: set es template mapping if provided 14 | shell: curl -sS -XPUT '{{elasticsearch_backup_host}}/_template/{{snapshot_indices}}' --data @{{snapshot_template_files }} 15 | when: snapshot_template_files is defined 16 | 17 | 18 | - name: restore es wiki backups 19 | es_snapshot_restore: elasticsearch_host={{elasticsearch_backup_host}} repository_path="{{elasticsearch_repo_dir}}" repository_name={{elasticsearch_repo_name }} snapshot_name="{{elasticsearch_snapshot_restore_name}}" snapshot_indices="{{elasticsearch_snapshot_indices | default(omit) }}" mode=restore state=present 20 | -------------------------------------------------------------------------------- /tests/test1_var.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | elasticsearch_version: 1.4.2 4 | elasticsearch_apt_java_package: oracle-java8-installer 5 | elasticsearch_java_home: /usr/lib/jvm/java-8-oracle 6 | elasticsearch_heap_size: 1g 7 | elasticsearch_max_open_files: 65535 8 | elasticsearch_timezone: "America/New_York" 9 | elasticsearch_node_max_local_storage_nodes: 1 10 | elasticsearch_index_mapper_dynamic: "true" 11 | elasticsearch_memory_bootstrap_mlockall: "true" 12 | elasticsearch_install_java: "true" 13 | elasticsearch_plugins: 14 | - { name: 'elasticsearch/elasticsearch-mapper-attachments/2.4.1' } 15 | - { name: 'com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb/2.0.5' } 16 | - { name: 'facet-script', url: 'http://dl.bintray.com/content/imotov/elasticsearch-plugins/elasticsearch-facet-script-1.1.2.zip' } 17 | - { name: 'lmenezes/elasticsearch-kopf' } 18 | elasticsearch_thread_pools: 19 | - "threadpool.bulk.type: fixed" 20 | - "threadpool.bulk.size: 50" 21 | - "threadpool.bulk.queue_size: 1000" 22 | -------------------------------------------------------------------------------- /tasks/post-run.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: post-run | Ensure plugins directory permission are correct 4 | file: 5 | path="{{ elasticsearch_plugin_dir }}" 6 | state=directory 7 | owner={{ elasticsearch_user }} 8 | group={{ elasticsearch_group }} 9 | recurse=yes 10 | 11 | - name: post-run | Ensure Elasticsearch is running and started on boot 12 | service: 13 | name=elasticsearch 14 | enabled={{ elasticsearch_service_startonboot }} 15 | state={{ elasticsearch_service_state }} 16 | when: installed_version.stat.exists 17 | 18 | # Flush handlers to restart elastic if needed 19 | - meta: flush_handlers 20 | 21 | - name: post-run | Check http port is open and running version. timeout 160s 22 | wait_for: 23 | host={{ '127.0.0.1' if elasticsearch_network_bind_host is not defined or elasticsearch_network_bind_host == '0.0.0.0' else elasticsearch_network_bind_host }} 24 | port={{ elasticsearch_network_http_port | default('9200') }} 25 | timeout=160 26 | when: elasticsearch_service_state == "started" -------------------------------------------------------------------------------- /vars/sample.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Sample Variables 3 | 4 | elasticsearch_version: 0.90.5 5 | elasticsearch_heap_size: 1g 6 | elasticsearch_max_open_files: 65535 7 | elasticsearch_max_locked_memory: unlimited 8 | elasticsearch_timezone: "America/New_York" 9 | elasticsearch_cluster_name: elasticsearch-ansible 10 | elasticsearch_node_name: elasticsearch-ansible-node 11 | elasticsearch_node_max_local_storage_nodes: 1 12 | elasticsearch_index_mapper_dynamic: "true" 13 | elasticsearch_memory_bootstrap_mlockall: "true" 14 | elasticsearch_gateway_type: local 15 | elasticsearch_gateway_recover_after_nodes: 1 16 | elasticsearch_gateway_recover_after_time: 2m 17 | elasticsearch_gateway_expected_nodes: 1 18 | elasticsearch_discovery_zen_minimum_master_nodes: 1 19 | elasticsearch_discovery_zen_ping_timeout: 30s 20 | elasticsearch_discovery_zen_ping_multicast_enabled: "true" 21 | elasticsearch_misc_auto_create_index: "true" 22 | elasticsearch_misc_query_bool_max_clause_count: 4096 23 | elasticsearch_misc_disable_delete_all_indices: "true" 24 | elasticsearch_java_opts: "-XX:-UseSuperWord" 25 | -------------------------------------------------------------------------------- /tasks/marvel.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install Marvel (see http://www.elasticsearch.org/guide/en/marvel/current/) 3 | # 4 | # The following variables need to be defined in your playbook or inventory: 5 | # - elasticsearch_plugin_marvel_version 6 | # 7 | # The following variables provide configuration for the plugin. 8 | # More options may be available in the future: 9 | # - elasticsearch_plugin_marvel_agent_enabled 10 | # - elasticsearch_plugin_marvel_agent_exporter_es_hosts 11 | # - elasticsearch_plugin_marvel_agent_indices 12 | # - elasticsearch_plugin_marvel_agent_interval 13 | # - elasticsearch_plugin_marvel_agent_exporter_es_index_timeformat 14 | 15 | - name: marvel | Removing Marvel Plugin if it exists 16 | shell: bin/plugin --remove marvel 17 | chdir={{ elasticsearch_home_dir }} 18 | ignore_errors: yes 19 | - name: marvel | Installing Marvel Plugin 20 | shell: bin/plugin -i elasticsearch/marvel/{{ elasticsearch_plugin_marvel_version }} 21 | chdir={{ elasticsearch_home_dir }} 22 | register: marvel_plugins_installed 23 | changed_when: "'Installed' in marvel_plugins_installed.stdout" 24 | failed_when: "marvel_plugins_installed.rc != 0 and marvel_plugins_installed.stdout.find('already exists. To update the plugin') == -1" 25 | -------------------------------------------------------------------------------- /tasks/custom-jars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install Custom JARs 3 | # 4 | # Custom jars are made available to the Elasticsearch classpath by being downloaded into the elasticsearch_home_dir/lib folder. 5 | # An example of a custom jar can include a custom Lucene Similarity Provider. You will need to define an array called 6 | # 'elasticsearch_custom_jars' in your playbook or inventory, such that: 7 | # 8 | # elasticsearch_custom_jars: 9 | # - { uri: '', filename: '', user: '', passwd: '' } 10 | # - ... 11 | 12 | # Loop though elasticsearch_custom_jars and install them 13 | - name: Installing Custom JARs 14 | action: > 15 | get_url url={{ item.uri }} 16 | url_username={{ item.user }} url_password={{ item.passwd }} dest="{{ elasticsearch_home_dir }}/lib/{{ item.filename }}" 17 | with_items: elasticsearch_custom_jars 18 | # Fix permissions 19 | - file: > 20 | path="{{ elasticsearch_home_dir }}/lib" state=directory 21 | owner={{ elasticsearch_user }} group={{ elasticsearch_group }} 22 | recurse=yes 23 | -------------------------------------------------------------------------------- /vars/vagrant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Variables 3 | 4 | elasticsearch_version: 1.7.3 5 | elasticsearch_apt_java_package: oracle-java8-installer 6 | elasticsearch_java_home: /usr/lib/jvm/java-8-oracle 7 | elasticsearch_heap_size: 1g 8 | elasticsearch_max_open_files: 65535 9 | elasticsearch_timezone: "Europe/Oslo" 10 | elasticsearch_node_max_local_storage_nodes: 1 11 | elasticsearch_index_mapper_dynamic: "true" 12 | elasticsearch_memory_bootstrap_mlockall: "true" 13 | elasticsearch_install_java: "true" 14 | elasticsearch_plugins: 15 | - { name: 'elasticsearch/elasticsearch-mapper-attachments/2.7.1', reinstall: false } 16 | - { name: 'com.github.richardwilly98.elasticsearch/elasticsearch-river-mongodb/2.0.9', reinstall: false } 17 | - { name: 'facet-script', url: 'http://dl.bintray.com/content/imotov/elasticsearch-plugins/elasticsearch-facet-script-1.1.2.zip', reinstall: false } 18 | - { name: 'http-basic', url: 'https://github.com/Asquera/elasticsearch-http-basic/releases/download/v1.5.1/elasticsearch-http-basic-1.5.1.jar', download_only: true, reinstall: false } 19 | elasticsearch_thread_pools: 20 | - "threadpool.bulk.type: fixed" 21 | - "threadpool.bulk.size: 50" 22 | - "threadpool.bulk.queue_size: 1000" 23 | elasticsearch_service_startonboot: yes 24 | -------------------------------------------------------------------------------- /tasks/aws.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Install AWS Plugin (see https://github.com/elasticsearch/elasticsearch-cloud-aws) 3 | # 4 | # The following variables need to be defined in your playbook or inventory: 5 | # - elasticsearch_plugin_aws_version 6 | # 7 | # The following variables provide a for now limited configuration for the plugin. 8 | # More options may be available in the future. 9 | # (see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-discovery-ec2.html): 10 | # - elasticsearch_plugin_aws_ec2_groups 11 | # - elasticsearch_plugin_aws_ec2_ping_timeout 12 | # - elasticsearch_plugin_aws_access_key 13 | # - elasticsearch_plugin_aws_secret_key 14 | # - elasticsearch_plugin_aws_region 15 | 16 | - name: aws | Removing AWS Plugin if it exists 17 | shell: bin/plugin --remove cloud-aws 18 | chdir={{ elasticsearch_home_dir }} 19 | when: elasticsearch_plugin_aws_reinstall is defined and elasticsearch_plugin_aws_reinstall == True 20 | ignore_errors: yes 21 | - name: aws | Installing AWS Plugin 22 | shell: bin/plugin -install elasticsearch/elasticsearch-cloud-aws/{{ elasticsearch_plugin_aws_version }} 23 | chdir={{ elasticsearch_home_dir }} 24 | register: aws_plugins_installed 25 | changed_when: "'Installed' in aws_plugins_installed.stdout" 26 | failed_when: "aws_plugins_installed.rc != 0 and aws_plugins_installed.stdout.find('already exists. To update the plugin') == -1" 27 | -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Elasticsearch Ansible Variables 3 | 4 | elasticsearch_user: elasticsearch 5 | elasticsearch_group: elasticsearch 6 | elasticsearch_download_url: https://download.elasticsearch.org/elasticsearch/elasticsearch 7 | elasticsearch_version: 1.7.3 8 | elasticsearch_apt_repos: 9 | - 'ppa:webupd8team/java' 10 | elasticsearch_apt_java_package: oracle-java8-installer 11 | elasticsearch_apt_dependencies: 12 | - htop 13 | - ntp 14 | - unzip 15 | elasticsearch_max_open_files: 65535 16 | elasticsearch_home_dir: /usr/share/elasticsearch 17 | elasticsearch_plugin_dir: /usr/share/elasticsearch/plugins 18 | elasticsearch_log_dir: /var/log/elasticsearch 19 | elasticsearch_data_dir: /var/lib/elasticsearch 20 | elasticsearch_work_dir: /tmp/elasticsearch 21 | elasticsearch_conf_dir: /etc/elasticsearch 22 | elasticsearch_pid_dir: /var/run 23 | elasticsearch_service_startonboot: no 24 | elasticsearch_timezone: "Etc/UTC" # Default to UTC 25 | 26 | #elasticsearch_http_cors_enabled: "false" 27 | elasticsearch_service_state: started 28 | 29 | # Non-Elasticsearch Defaults 30 | elasticsearch_timezone: UTC 31 | apt_cache_valid_time: 300 # seconds between "apt-get update" calls. 32 | elasticsearch_install_java: "true" 33 | #elasticsearch_repo_dir: path to snapshot restore location 34 | #uncomment to enable shield 35 | #elasticsearch_plugin_shield_enabled: "true" 36 | 37 | elasticsearch_repo_name: backups 38 | elasticsearch_backup_host: http://localhost:9200 39 | 40 | -------------------------------------------------------------------------------- /tasks/spm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # SPM Tasks (see http://sematext.com/spm/) 3 | 4 | - name: spm | Install Collectd 5 | apt: 6 | pkg=collectd 7 | state=present 8 | 9 | # TODO: Make idempotent 10 | - name: spm | Downloading and running package 11 | shell: wget --no-check-certificate -O installer.sh "https://apps.sematext.com/spm-reports/installerDownload.do?client_token={{ spm_client_token }}" && bash installer.sh 12 | 13 | # TODO: Make idempotent 14 | - name: spm | Preparing monitor configurations 15 | shell: bash /opt/spm/bin/spm-client-setup-conf.sh {{ spm_client_token }} es standalone 16 | 17 | - name: spm | Configuring JXM in Elasticsearch 18 | lineinfile: 19 | dest=/etc/default/elasticsearch 20 | regexp='^(ES_JAVA_OPTS="\$ES_JAVA_OPTS -Dcom.sun.management.jmxremote.*")$' 21 | insertafter='^(#ES_JAVA_OPTS=.*)$' 22 | line='ES_JAVA_OPTS="\$ES_JAVA_OPTS -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=3000 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false"' 23 | notify: Restart Elasticsearch 24 | 25 | - name: spm | Configuring JXM in SPM 26 | lineinfile: 27 | dest=/opt/spm/spm-monitor/conf/spm-monitor-config-{{ spm_client_token }}-default.properties 28 | regexp='^(SPM_MONITOR_JMX_PARAMS="")$' 29 | line='SPM_MONITOR_JMX_PARAMS="-Dspm.remote.jmx.url=localhost:3000"' 30 | 31 | - name: spm | Restarting SPM 32 | service: 33 | name=spm-monitor 34 | state=restarted 35 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | $script = <