├── .dockerignore ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.centos7 ├── INSTALL.md ├── LICENSE ├── README.md ├── bootcd ├── .gitignore ├── README.md ├── create-image.sh ├── dmsquash-live-root.toram.sh ├── dracut.toram.patch ├── genesis.ks.template ├── iptables.content.sample └── rpms │ └── genesis_scripts │ ├── genesis_scripts.spec │ └── src │ ├── agetty-wrapper │ ├── autologin │ ├── genesis-bootloader │ ├── genesis.init │ ├── genesis.sysconfig │ ├── mingetty-wrapper │ ├── network-prep.init │ ├── root-bash_profile │ └── run-genesis-bootloader ├── docker-entrypoint.sh ├── src ├── .gitignore ├── README.md ├── framework │ ├── .gitignore │ ├── bin │ │ └── genesis │ ├── genesis_framework.gemspec │ └── lib │ │ ├── genesisframework.rb │ │ └── genesisframework │ │ ├── facter │ │ └── test_fact.rb │ │ ├── task.rb │ │ ├── tasks.rb │ │ └── utils.rb ├── promptcli │ ├── .gitignore │ ├── Gemfile │ ├── README.md │ ├── genesis_promptcli.gemspec │ └── lib │ │ └── promptcli.rb └── retryingfetcher │ ├── .gitignore │ ├── Gemfile │ ├── README.md │ ├── genesis_retryingfetcher.gemspec │ └── lib │ └── retryingfetcher.rb ├── tasks ├── AssetCreation.rb ├── BiosConfigrC6105.rb ├── BiosConfigrR720.rb ├── DSL.md ├── FixDellFatPartitions.rb ├── IpmiStart.rb ├── README.md ├── Rakefile ├── Reboot.rb ├── SetupNTP.rb ├── Shutdown.rb ├── TimedBurnin.rb ├── modules │ ├── README.md │ ├── facter │ │ ├── asset_tag.rb │ │ ├── bmc_firmware_version.rb │ │ ├── mode.rb │ │ └── system_serial.rb │ ├── helpers │ │ └── dell │ │ │ └── ipmi.rb │ ├── logging │ │ ├── collins.rb │ │ └── syslog.rb │ └── mixins │ │ └── .gitkeep └── targets.yaml └── testenv ├── README.md ├── bootbox ├── .gitignore ├── Vagrantfile ├── bootbox-shared │ └── .gitignore ├── puppet │ ├── manifests │ │ └── bootbox.pp │ └── modules │ │ ├── bind │ │ └── manifests │ │ │ ├── init.pp │ │ │ └── server.pp │ │ ├── dhcp │ │ ├── manifests │ │ │ ├── init.pp │ │ │ └── server.pp │ │ └── templates │ │ │ └── dhcpd.conf.erb │ │ ├── gemserver │ │ ├── manifests │ │ │ └── init.pp │ │ └── templates │ │ │ └── gemserver.erb │ │ ├── genesis │ │ ├── files │ │ │ └── genesis.init │ │ ├── manifests │ │ │ └── init.pp │ │ └── templates │ │ │ ├── config.yaml.erb │ │ │ ├── menu.ipxe.erb │ │ │ └── stage2.erb │ │ ├── iptables │ │ ├── files │ │ │ ├── iptables │ │ │ └── sysctl.conf │ │ └── manifests │ │ │ └── init.pp │ │ ├── ipxe │ │ ├── files │ │ │ └── undionly.kpxe │ │ └── manifests │ │ │ └── init.pp │ │ ├── selinux │ │ └── manifests │ │ │ └── permissive.pp │ │ ├── tftp │ │ ├── files │ │ │ └── tftp.conf │ │ └── manifests │ │ │ ├── init.pp │ │ │ └── server.pp │ │ └── yumrepo │ │ ├── files │ │ ├── epel.repo │ │ ├── nginx.repo │ │ └── ruby193.repo │ │ └── manifests │ │ └── init.pp └── web │ ├── config.ru │ ├── config │ └── unicorn.rb │ ├── genesis.rb │ ├── genesis │ └── config.rb │ └── tasks └── testnode.ova /.dockerignore: -------------------------------------------------------------------------------- 1 | bootcd/output 2 | bootcd/rpms/*rpm 3 | **.rpm 4 | output/* 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | 4 | # gh_pages branch related 5 | _site 6 | .sass-cache 7 | vendor 8 | .bundle 9 | Gemfile.lock 10 | 11 | # emacs 12 | *~ 13 | \#*\# 14 | 15 | # built rpms 16 | **/genesis_scripts-*.noarch.rpm 17 | **/genesis_scripts-*.src.rpm 18 | 19 | output 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We want to make contributing to Genesis as easy and transparent as possible. 4 | If you run into problems, please open an issue. We also actively welcome pull requests. 5 | 6 | ## Pull Requests 7 | 8 | 1. Fork the repo and create your branch from `master`. 9 | 2. If you've added code that should be tested, add tests. 10 | 3. If you've changed APIs, update the documentation. 11 | 4. Ensure the test suite passes. 12 | 5. If you haven't already, complete the Contributor License Agreement ("CLA"). 13 | 14 | ## Contributor License Agreement ("CLA") 15 | 16 | In order to accept your contribution, we need you to submit a CLA. If you open a pull request, a bot will automatically check if you have already submitted 17 | one. If not it will ask you to do so by visiting a link and signing in with 18 | GitHub. 19 | 20 | The CLA, contact information, and GitHub sign-in can be found here: 21 | [https://yahoocla.herokuapp.com](https://yahoocla.herokuapp.com). 22 | 23 | ## License 24 | 25 | By contributing to Genesis you agree that your contributions will be licensed under its Apache 2.0 license. 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:6 2 | MAINTAINER Tumblr Genesis Support 3 | 4 | ENV OUTPUT_DIR /output 5 | ENV GENESIS_DIR /genesis 6 | VOLUME /output 7 | 8 | # needed for livecd-creator 9 | RUN curl -o epel.rpm https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm && \ 10 | rpm -ivh epel.rpm && \ 11 | rm epel.rpm 12 | 13 | RUN yum install -y livecd-tools createrepo curl rpm-build && \ 14 | yum clean all 15 | 16 | 17 | COPY . /genesis 18 | WORKDIR /genesis 19 | 20 | # perform the build of the image at runtime 21 | CMD ["/genesis/docker-entrypoint.sh"] 22 | -------------------------------------------------------------------------------- /Dockerfile.centos7: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | MAINTAINER Gabe Conradi 3 | 4 | ENV OUTPUT_DIR /output 5 | ENV GENESIS_DIR /genesis 6 | ENV KICKSTART genesis-sl7.ks 7 | VOLUME /output 8 | 9 | RUN yum install -y livecd-tools createrepo rpm-build && \ 10 | yum clean all 11 | 12 | COPY . /genesis 13 | WORKDIR /genesis 14 | 15 | # perform the build of the image at runtime 16 | CMD ["/genesis/docker-entrypoint.sh"] 17 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Prerequisites 4 | 5 | For operation, genesis needs the following services 6 | - DHCP server 7 | - TFTP server 8 | - HTTP file server 9 | 10 | ## Build 11 | 12 | Build the boot image. This needs to be built in Linux with livecd-tools. The 13 | Genesis-bootbox virtualbox VM provided in the test environment has all the tools 14 | needed. 15 | 16 | linuxbox$ cd bootscript && sudo ./create-image.sh 17 | localmachine$ scp linuxbox:genesis/bootcd/output/genesis'*' genesis/web/public/ipxe-images/ 18 | 19 | ## Configuration 20 | 21 | ### TFTP server 22 | The TFTP server serves the iPXE binary to the PXE firmware. We use the xinetd 23 | server daemon to launch the tftp daemon for incoming requests. 24 | 25 | ``` 26 | service tftp 27 | { 28 | disable = no 29 | socket_type = dgram 30 | protocol = udp 31 | wait = yes 32 | user = root 33 | server = /usr/sbin/in.tftpd 34 | server_args = -s /var/lib/tftpboot 35 | per_source = 11 36 | cps = 1000 2 37 | instances = 1000 38 | flags = IPv4 39 | } 40 | ``` 41 | 42 | Place the undionly.kpxe file (see 43 | [the iPXE docs](http://ipxe.org/download#chainloading_from_an_existing_pxe_rom) 44 | for more information) in /var/lib/tftpboot. 45 | 46 | Example puppet code: 47 | * [TFTP](https://github.com/tumblr/genesis/tree/master/testenv/bootbox/puppet/modules/tftp/manifests) 48 | * [iPXE](https://github.com/tumblr/genesis/tree/master/testenv/bootbox/puppet/modules/ipxe/manifests) 49 | 50 | ### DHCP server 51 | The DHCP server needs to be set up to chain load the iPXE firmware from the TFTP 52 | server. Using ISC DHCPd, a simplified configuration looks like 53 | 54 | ``` 55 | subnet 192.168.1.0 netmask 255.255.255.0 { 56 | range 192.168.1.200 192.168.1.229; 57 | option subnet-mask 255.255.255.0; 58 | option broadcast-address 192.168.1.255; 59 | option domain-name-servers ; 60 | 61 | if exists ipxe.http { 62 | filename ""; 63 | } else { 64 | filename "undionly.kpxe"; 65 | next-server ; 66 | } 67 | } 68 | ``` 69 | More information on DHCP for loading iPXE can be found at 70 | [iPXE.org](http://ipxe.org/howto/dhcpd#pxe_chainloading). 71 | 72 | Example puppet code: 73 | * [DHCP](https://github.com/tumblr/genesis/tree/master/testenv/bootbox/puppet/modules/dhcp/manifests) 74 | 75 | ### HTTP file server 76 | The file server serves the genesis OS initrd and kernel so that iPXE can boot 77 | them. It also serves the genesis config. 78 | 79 | It can also serve the iPXE menu configuration and the stage 2 ruby script. 80 | In a more complex setup however, these might be generated on the fly by a script. 81 | 82 | ## Configuration files 83 | Examples to all the configuration files mentioned above can be found in the 84 | test environment puppet code. Here are a couple of sample files that are good 85 | to check out. 86 | 87 | * [Sample genesis config file](https://github.com/tumblr/genesis/blob/master/testenv/bootbox/puppet/modules/genesis/templates/config.yaml.erb) 88 | Fetched by Genesis OS to configure various URLs and other settings 89 | * [Sample menu.ipxe](https://github.com/tumblr/genesis/blob/master/testenv/bootbox/puppet/modules/genesis/templates/menu.ipxe.erb) 90 | Used by iPXE to present a menu to the user. Note the GENESIS_MODE and 91 | GENESIS_CONF_URL kernel parameters. 92 | * [Sample stage2 script](https://github.com/tumblr/genesis/blob/master/testenv/bootbox/puppet/modules/genesis/templates/stage2.erb) 93 | 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Genesis 2 | 3 | ## Introduction and motivation 4 | Genesis is a tool for data center automation. The primary motivation for 5 | developing Genesis at Tumblr was to streamline the process of discovering new 6 | machines and reporting their hardware details to 7 | [Collins](https://github.com/tumblr/collins), our inventory management system, 8 | without having to do a bunch of data entry by hand. In addition, we've also 9 | extended Genesis to be a convenient way to do hardware configuration such as 10 | altering BIOS settings and configuring RAID cards before provisioning an 11 | operating system on to the host. 12 | 13 | From a high-level point of view, Genesis consists of a stripped down linux image 14 | suitable to boot over PXE and a ruby DSL for describing tasks to be executed on 15 | the host. 16 | 17 | This repository also includes a [test environment](https://github.com/tumblr/genesis/tree/master/testenv) 18 | which is suitable for building the linux image. 19 | 20 | ## Framework 21 | 22 | [genesis_framework](https://rubygems.org/gems/genesis_framework) [![framework](https://badge.fury.io/rb/genesis_framework.svg)](http://badge.fury.io/rb/genesis_framework) 23 | 24 | [genesis_retryingfetcher](https://rubygems.org/gems/genesis_retryingfetcher) [![retryingfetcher](https://badge.fury.io/rb/genesis_retryingfetcher.svg)](http://badge.fury.io/rb/genesis_retryingfetcher) 25 | 26 | [genesis_promptcli](https://rubygems.org/gems/genesis_promptcli) [![promptcli](https://badge.fury.io/rb/genesis_promptcli.svg)](http://badge.fury.io/rb/genesis_promptcli) 27 | 28 | 29 | The [Genesis framework](https://github.com/tumblr/genesis/tree/master/src/framework) and 30 | supporting gem can be found in [src](https://github.com/tumblr/genesis/tree/master/src) 31 | 32 | ## Tasks 33 | Tasks are created using the [Genesis 34 | DSL](https://github.com/tumblr/genesis/blob/master/tasks/README.md) which makes 35 | it easy to run commands, install packages, etc. in the stripped down 36 | environment. 37 | 38 | Examples of tasks are the 39 | [TimedBurnin](https://github.com/tumblr/genesis/blob/master/tasks/TimedBurnin.rb) 40 | task, which performs a stress test on the system to rule out hardware errors 41 | before putting it into production, and 42 | [BiosConfigrR720](https://github.com/tumblr/genesis/blob/master/tasks/BiosConfigrR720.rb), 43 | which sets up the BIOS on Dell R720s just the way we want it. 44 | 45 | ## General workflow 46 | There are a couple of systems apart from Genesis that need to be in place for a 47 | successful deployment. These are 48 | 49 | * a DHCP server, 50 | * a TFTP server, 51 | * and a file server (serving static files over HTTP) 52 | 53 | More detail on setting these up is documented in 54 | [INSTALL.md](https://github.com/tumblr/genesis/blob/master/INSTALL.md). 55 | 56 | When a machine boots, the DHCP server tells the PXE firmware to chain boot into 57 | iPXE. We then use iPXE to present a list of menu choices, fetched from a remote 58 | server. When the user makes a choice we load the Genesis kernel and initrd (from 59 | the file server) along with parameters on the kernel command line. Once the 60 | Genesis OS has loaded, the genesis-bootloader fetches and executes a ruby script 61 | describing a second stage where we install gems, a few base RPMs, and fetch our 62 | tasks from a remote server. Finally, we execute the relevant tasks. 63 | 64 | For a real world example; consider a brand new server that boots up. It makes a 65 | DHCP request and loads the iPXE menu. In this case, we know that we haven't seen 66 | this MAC address before, so it must be a new machine. We boot Genesis in to 67 | discovery mode, where the tasks it runs are written to fetch all the hardware 68 | information we need and report it back to the Collins. In our setup this 69 | includes information such as hard drives and their capacity and the number of 70 | CPUs, but also more detailed information such as service tags, which memory 71 | banks are in use, and even the name of the switchports all interfaces are 72 | connected to. We then follow this up with 48 hours of hardware stress-test using 73 | the TimedBurnin task. 74 | 75 | ## Test environment 76 | To avoid testing Genesis in production, we've set up a virtual test environment 77 | based on VirtualBox. This allows for end-to-end testing of changes to the 78 | framework, new tasks, etc. 79 | 80 | More information about the test environment and setting it up can be found in 81 | [testenv/README.md](https://github.com/tumblr/genesis/blob/master/testenv/README.md). 82 | 83 | ## Building an Image 84 | 85 | To make it easy to get started with genesis, we have included a `Dockerfile` that will allow you to compile a bootable live image without needing a lot of client side configuration. If you have docker running on your machine and just want to build the latest images: 86 | 87 | ``` 88 | # mkdir output 89 | # docker run --privileged=true -v $(pwd)/output:/output tumblr/genesis-builder 90 | # ls output 91 | ``` 92 | 93 | It is possible to customize the image with extra content specific to your site, such as adding iptables rules. To do so, create a directory and put your custom code into one or more files named `.content`. Bind mount this directory, read-only, as `/conf` when running genesis-builder. Any file with the *.content* extension under /conf will be added to the build. See `bootcd/iptable.content.sample` for an example. 94 | 95 | ``` 96 | # docker run --privileged=true -v $(pwd)/bootcd:/conf:ro -v $(pwd)/output:/output tumblr/genesis-builder 97 | # ls output 98 | ``` 99 | 100 | To build a custom image, if you have tweaked something about genesis, you can present build a custom builder image: 101 | 102 | ``` 103 | # docker build -f Dockerfile -t genesis-builder . 104 | # docker run --privileged=true -v $(pwd)/output:/output genesis-builder 105 | # ls output 106 | ``` 107 | 108 | > NOTE: the genesis-builder uses livecd-creator which depends on loopback mounts. These don't currently (2016-08-25) work with Docker for Mac. On linux you should make sure you have at least 2 spare /dev/loop* devices, `losetup -a` will show which ones are busy and `mknod /dev/loop# -m0600 b 7 #` a couple if needed. Also you may need to cleanup/free (`losetup -d`) devices after this has run. 109 | 110 | ## Contact 111 | Please feel free to open issues on GitHub for any feedback or problems you might 112 | run in to. We also actively encourage pull requests. Please also make 113 | sure to check [CONTRIBUTING.md](https://github.com/tumblr/genesis/blob/master/CONTRIBUTING.md). 114 | 115 | ## License 116 | Copyright 2016 Tumblr Inc. 117 | 118 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 119 | this file except in compliance with the License. You may obtain a copy of the 120 | License at 121 | 122 | http://www.apache.org/licenses/LICENSE-2.0 123 | 124 | Unless required by applicable law or agreed to in writing, software distributed 125 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 126 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 127 | specific language governing permissions and limitations under the License. 128 | -------------------------------------------------------------------------------- /bootcd/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | -------------------------------------------------------------------------------- /bootcd/README.md: -------------------------------------------------------------------------------- 1 | # Boot image 2 | This directory contains sources for building the image that is booted to run genesis 3 | tasks. You can use the [test environment](https://github.com/tumblr/genesis/blob/master/testenv/README.md) to build the genesis image, or use a SL6 4 | installation. The instructions below assume you are using the test environment. 5 | 6 | ## The easiest way: 7 | 8 | Get the prebuilt binaries from the github release :-) 9 | 10 | ## The easy (docker) way: 11 | 12 | You probably just want to build a genesis live image with no hassle. Just get docker running on your host, and run: 13 | 14 | ``` 15 | $ docker run -v $PWD/output:/output tumblr/genesis-builder 16 | $ ls $PWD/output 17 | genesis.iso 18 | genesis-initrd.img 19 | genesis-vmlinuz 20 | ``` 21 | 22 | NOTE: if you get an error about not being able to do a loopback mount add ```--privileged``` 23 | 24 | Now, you just need to copy the bootable `vmlinuz` and `initrd` somewhere where your PXE/iPXE server can fetch them over HTTP. 25 | 26 | ## The super hard way: 27 | 28 | ### Pre-requisites: 29 | 30 | Pre-requisites are installed when using the test environment and include 31 | 32 | - livecd-tools and createrepo RPMs installed 33 | - python and SimpleHTTPServer module 34 | 35 | ### Building the Genesis Scripts RPM: 36 | 37 | The Genesis scripts rpm includes scripts and configuration files used by Genesis in the bootcd image 38 | 39 | - Bring up the testenv and ssh into the bootbox (vagrant ssh) or ensure all of the [pre requisites](#pre-requisites) are installed correctly 40 | - Build the RPMs using rpmbuild: 41 | 42 | ```cd /genesis/bootcd/rpms/genesis_scripts``` 43 | 44 | - Build the source rpm 45 | 46 | ```rpmbuild --define '_tmppath /tmp' --define '_sourcedir src' --define '_srcrpmdir .' --nodeps -bs genesis_scripts.spec``` 47 | 48 | - Build the rpm (ensure the src rpm version is correct, when copy-pasting from here, it is likely to be incorrect) 49 | 50 | ```rpmbuild --rebuild --rebuild genesis_scripts-0.8-1.el6.src.rpm``` 51 | 52 | - Resulting RPM can be found in your RPM build path (/root/rpmbuild/RPMS/noarch/ if building as root with no rpmconfig) 53 | - Copy the RPM into [bootcd/rpms](https://github.com/tumblr/genesis/tree/master/bootcd/rpms) 54 | - ```cp /root/rpmbuild/RPMS/noarch//genesis_scripts-0.8-1.el6.noarch.rpm ../``` 55 | - The ```create-image.sh``` script will look for the RPM in this location 56 | 57 | ### Building the boot image: 58 | - Bring up the testenv and ssh into the bootbox (vagrant ssh) or ensure all of the [pre requisites](#pre-requisites) are installed correctly 59 | - Build the ``genesis_scripts`` rpm, and copy it to ``/genesis/bootcd/rpms``. [See these docs for instructions.](#building-the-genesis-scripts-rpm) 60 | - Build the genesis gems found in `/genesis/src`. Do not move or install them. [See these instructions for how to build the gems.](https://github.com/tumblr/genesis/blob/master/src/README.md) 61 | - ```cd /genesis/bootcd``` 62 | - ```sudo ./create-image.sh``` 63 | - The ```create-image``` script will create the initrd and kernel in ```/genesis/bootcd/output``` 64 | 65 | ### Deploying the boot image: 66 | - Copy the files from the output to where PXEBoot is expecting it, this is typically your file server. 67 | 68 | # Notes 69 | 70 | ## Tmpfs Root for Production 71 | 72 | If you have tasks that tend to pull down lots of files to "disk", you may run into the issue where the overlay filesystem overtop of the squashfs root becomes filled. To avoid this, the bootcd ships with a patched ```/usr/share/dracut/modules.d/90dmsquash-live/dmsquash-live-root``` that supports the ```toram``` kernel parameter. If you pass ```toram``` to the kernel in your ipxe config, dracut will copy your squashfs root over to a tmpfs volume and mount that as the root filesystem. You can see the patch we apply [here](dracut.toram.patch) and the fully patched file [here](dmsquash-live-root.toram.sh). 73 | 74 | More info here: http://www.espenbraastad.no/post/el6-rootfs-on-tmpfs/?p=160 75 | 76 | Sample ipxe config for booting genesis with tmpfs root instead of squashfs+overlay: 77 | 78 | ``` 79 | #!ipxe 80 | ..... 81 | initrd <%= @ipxe_initrd %> 82 | kernel <%= @ipxe_kernel %> <%= @ipxe_kernel_flags %> toram GENESIS_MODE=${target} GENESIS_CONF_URL=<%= @ipxe_config_url %> <%= @raid_level.nil? ? "" : "GENESIS_RAID_LEVEL=#{raid_level}" %> 83 | ``` 84 | 85 | Please note, the ```toram``` option will not work in the testenv, as the Genesis ova has only 2g of ram, and the default / partition is 4g. As such, this option is suitable for production environments, but not testing. 86 | -------------------------------------------------------------------------------- /bootcd/create-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | REPO=repo 4 | export OUTPUT_DIR="${OUTPUT_DIR:-/output}" 5 | 6 | set -ex 7 | 8 | if [[ $EUID -ne 0 ]]; then 9 | echo "This script must be run as root" 10 | exit 1 11 | fi 12 | for cmd in createrepo livecd-creator livecd-iso-to-pxeboot; do 13 | if [[ `command -v $cmd >/dev/null 2>&1` ]]; then 14 | echo "$cmd required, but not found in PATH. Aborting." 1>&2 15 | exit 1 16 | fi 17 | done 18 | if [[ ! -d $OUTPUT_DIR ]] ; then 19 | echo "OUTPUT_DIR must be a directory" 20 | exit 1 21 | fi 22 | 23 | cleanup() { 24 | [[ -n "$pid" ]] && kill $pid 25 | [[ -n "$REPO" ]] && rm -rf "$REPO" 26 | rm -rf base dell epel it local ruby193 tftpboot updates fastbugs security 27 | rm -rf tftpboot 28 | [[ -n "$tmpdir" ]] && rm -rf "$tmpdir/live" "$tmpdir/livecache" 29 | } 30 | trap cleanup SIGINT EXIT 31 | 32 | if [ `df /dev/shm | tail -1 | awk '{print $2}'` -gt 3000000 ] 33 | then 34 | echo "### building image in ${TMP_DIR:-/dev/shm/genesis}" 35 | tmpdir="${TMP_DIR:-/dev/shm/genesis}" 36 | else 37 | echo "### /dev/shm isn't big enough, building in ${TMP_DIR:-/tmp}" 38 | tmpdir="${TMP_DIR:-/tmp}" 39 | fi 40 | 41 | rm -f genesis.iso 42 | 43 | echo '### creating local repo' 44 | rm -rf $REPO 45 | mkdir -p $REPO 46 | cp rpms/*.rpm $REPO 47 | createrepo $REPO 48 | echo '### local repo contains these RPMs' 49 | ls -l $REPO/*.rpm 50 | 51 | echo '### starting http://localhost:8000 yum repro' 52 | # port must match genesis.ks expected value 53 | python -m SimpleHTTPServer 8000 $REPO & 54 | pid=$! 55 | 56 | echo '### fixing resolv.conf in genesis.ks' 57 | ns=`grep nameserver /etc/resolv.conf` 58 | [[ -z $ns ]] && ns='nameserver 8.8.8.8 59 | nameserver 8.8.4.4' 60 | perl -pe "s/%%LocalNameservers%%/$ns/" genesis.ks.template > "$tmpdir/genesis.ks" 61 | 62 | # Insert extra content into the %POST section of the ks file 63 | # used to build the genesis image. 64 | # All files of the form: /conf/*.content 65 | # which should be bind mounted via: docker run -v /Some/Dir:/conf:ro .... 66 | # are inserted in the genesis.ks file replacing the %%ExtraPostContent%% 67 | # line in that file. 68 | extras=( $(find /conf -name \*.content 2>/dev/null || true) ) 69 | if [[ ${#extras[@]} -gt 0 ]]; then 70 | echo '### inserting Extra Post Content' 71 | sed -i -e " 72 | /%%ExtraPostContent%%/{ 73 | s/%%ExtraPostContent%%// 74 | $(for f in "${extras[@]}"; do echo r "$f"; done) 75 | } 76 | " "$tmpdir/genesis.ks" 77 | else 78 | echo '### not inserting Extra Post Content, none found' 79 | sed -i -e 's/%%ExtraPostContent%%/# intentionally blank/' \ 80 | "$tmpdir/genesis.ks" 81 | fi 82 | 83 | echo '### creating livecd' 84 | livecd-creator -c "$tmpdir/genesis.ks" -f genesis -t "$tmpdir/live/" --cache="$tmpdir/livecache/" -v 85 | if [[ $? != 0 ]] ; then 86 | echo "Error creating livecd image" 87 | exit 1 88 | fi 89 | 90 | echo '### cleanup local rpm repo' 91 | kill $pid 92 | unset pid 93 | rm -rf $REPO 94 | 95 | echo '### cleanup unused directories' 96 | rm -rf base epel local tftpboot updates fastbugs security 97 | 98 | echo '### create genesis.iso' 99 | livecd-iso-to-pxeboot genesis.iso 100 | 101 | mv tftpboot/initrd0.img $OUTPUT_DIR/genesis-initrd.img 102 | mv tftpboot/vmlinuz0 $OUTPUT_DIR/genesis-vmlinuz 103 | chmod 644 $OUTPUT_DIR/genesis-vmlinuz 104 | mv genesis.iso $OUTPUT_DIR/ 105 | rm -rf tftpboot 106 | 107 | printf "### $0 completed successfully results in $OUTPUT_DIR" 108 | ls -l $OUTPUT_DIR 109 | -------------------------------------------------------------------------------- /bootcd/dmsquash-live-root.toram.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . /lib/dracut-lib.sh 4 | [ -f /tmp/root.info ] && . /tmp/root.info 5 | 6 | PATH=$PATH:/sbin:/usr/sbin 7 | 8 | if getarg rdlivedebug; then 9 | exec > /tmp/liveroot.$$.out 10 | exec 2>> /tmp/liveroot.$$.out 11 | set -x 12 | fi 13 | 14 | [ -z "$1" ] && exit 1 15 | livedev="$1" 16 | 17 | # parse various live image specific options that make sense to be 18 | # specified as their own things 19 | live_dir=$(getarg live_dir) 20 | [ -z "$live_dir" ] && live_dir="LiveOS" 21 | getarg live_ram && live_ram="yes" 22 | getarg no_eject && no_eject="yes" 23 | getarg reset_overlay && reset_overlay="yes" 24 | getarg readonly_overlay && readonly_overlay="--readonly" || readonly_overlay="" 25 | overlay=$(getarg overlay) 26 | 27 | getarg toram && toram="yes" 28 | 29 | # FIXME: we need to be able to hide the plymouth splash for the check really 30 | [ -e $livedev ] & fs=$(blkid -s TYPE -o value $livedev) 31 | if [ "$fs" = "iso9660" -o "$fs" = "udf" ]; then 32 | check="yes" 33 | fi 34 | getarg check || check="" 35 | if [ -n "$check" ]; then 36 | checkisomd5 --verbose $livedev || : 37 | if [ $? -ne 0 ]; then 38 | die "CD check failed!" 39 | exit 1 40 | fi 41 | fi 42 | 43 | getarg ro && liverw=ro 44 | getarg rw && liverw=rw 45 | [ -z "$liverw" ] && liverw=ro 46 | # mount the backing of the live image first 47 | mkdir -p /dev/.initramfs/live 48 | mount -n -t $fstype -o $liverw $livedev /dev/.initramfs/live 49 | RES=$? 50 | if [ "$RES" != "0" ]; then 51 | die "Failed to mount block device of live image" 52 | exit 1 53 | fi 54 | 55 | # overlay setup helper function 56 | do_live_overlay() { 57 | # create a sparse file for the overlay 58 | # overlay: if non-ram overlay searching is desired, do it, 59 | # otherwise, create traditional overlay in ram 60 | OVERLAY_LOOPDEV=$( losetup -f ) 61 | 62 | l=$(blkid -s LABEL -o value $livedev) || l="" 63 | u=$(blkid -s UUID -o value $livedev) || u="" 64 | 65 | if [ -z "$overlay" ]; then 66 | pathspec="/${live_dir}/overlay-$l-$u" 67 | elif ( echo $overlay | grep -q ":" ); then 68 | # pathspec specified, extract 69 | pathspec=$( echo $overlay | sed -e 's/^.*://' ) 70 | fi 71 | 72 | if [ -z "$pathspec" -o "$pathspec" = "auto" ]; then 73 | pathspec="/${live_dir}/overlay-$l-$u" 74 | fi 75 | devspec=$( echo $overlay | sed -e 's/:.*$//' ) 76 | 77 | # need to know where to look for the overlay 78 | setup="" 79 | if [ -n "$devspec" -a -n "$pathspec" -a -n "$overlay" ]; then 80 | mkdir /overlayfs 81 | mount -n -t auto $devspec /overlayfs || : 82 | if [ -f /overlayfs$pathspec -a -w /overlayfs$pathspec ]; then 83 | losetup $OVERLAY_LOOPDEV /overlayfs$pathspec 84 | if [ -n "$reset_overlay" ]; then 85 | dd if=/dev/zero of=$OVERLAY_LOOPDEV bs=64k count=1 2>/dev/null 86 | fi 87 | setup="yes" 88 | fi 89 | umount -l /overlayfs || : 90 | fi 91 | 92 | if [ -z "$setup" ]; then 93 | if [ -n "$devspec" -a -n "$pathspec" ]; then 94 | warn "Unable to find persistent overlay; using temporary" 95 | sleep 5 96 | fi 97 | 98 | dd if=/dev/null of=/overlay bs=1024 count=1 seek=$((512*1024)) 2> /dev/null 99 | losetup $OVERLAY_LOOPDEV /overlay 100 | fi 101 | 102 | # set up the snapshot 103 | echo 0 `blockdev --getsz $BASE_LOOPDEV` snapshot $BASE_LOOPDEV $OVERLAY_LOOPDEV p 8 | dmsetup create $readonly_overlay live-rw 104 | } 105 | 106 | # live cd helper function 107 | do_live_from_base_loop() { 108 | do_live_overlay 109 | } 110 | 111 | # we might have a genMinInstDelta delta file for anaconda to take advantage of 112 | if [ -e /dev/.initramfs/live/${live_dir}/osmin.img ]; then 113 | OSMINSQFS=/dev/.initramfs/live/${live_dir}/osmin.img 114 | fi 115 | 116 | if [ -n "$OSMINSQFS" ]; then 117 | # decompress the delta data 118 | dd if=$OSMINSQFS of=/osmin.img 2> /dev/null 119 | OSMIN_SQUASHED_LOOPDEV=$( losetup -f ) 120 | losetup -r $OSMIN_SQUASHED_LOOPDEV /osmin.img 121 | mkdir -p /squashfs.osmin 122 | mount -n -t squashfs -o ro $OSMIN_SQUASHED_LOOPDEV /squashfs.osmin 123 | OSMIN_LOOPDEV=$( losetup -f ) 124 | losetup -r $OSMIN_LOOPDEV /squashfs.osmin/osmin 125 | umount -l /squashfs.osmin 126 | fi 127 | 128 | # we might have just an embedded ext3 to use as rootfs (uncompressed live) 129 | if [ -e /dev/.initramfs/live/${live_dir}/ext3fs.img ]; then 130 | EXT3FS="/dev/.initramfs/live/${live_dir}/ext3fs.img" 131 | fi 132 | 133 | if [ -n "$EXT3FS" ] ; then 134 | BASE_LOOPDEV=$( losetup -f ) 135 | losetup -r $BASE_LOOPDEV $EXT3FS 136 | 137 | # Create overlay only if toram is not set 138 | if [ -z "$toram" ] ; then 139 | do_live_from_base_loop 140 | fi 141 | fi 142 | 143 | # we might have an embedded ext3 on squashfs to use as rootfs (compressed live) 144 | if [ -e /dev/.initramfs/live/${live_dir}/squashfs.img ]; then 145 | SQUASHED="/dev/.initramfs/live/${live_dir}/squashfs.img" 146 | fi 147 | 148 | if [ -e "$SQUASHED" ] ; then 149 | if [ -n "$live_ram" ] ; then 150 | echo "Copying live image to RAM..." 151 | echo "(this may take a few minutes)" 152 | dd if=$SQUASHED of=/squashed.img bs=512 2> /dev/null 153 | umount -n /dev/.initramfs/live 154 | echo "Done copying live image to RAM." 155 | if [ ! -n "$no_eject" ]; then 156 | eject -p $livedev || : 157 | fi 158 | SQUASHED="/squashed.img" 159 | fi 160 | 161 | SQUASHED_LOOPDEV=$( losetup -f ) 162 | losetup -r $SQUASHED_LOOPDEV $SQUASHED 163 | mkdir -p /squashfs 164 | mount -n -t squashfs -o ro $SQUASHED_LOOPDEV /squashfs 165 | 166 | BASE_LOOPDEV=$( losetup -f ) 167 | losetup -r $BASE_LOOPDEV /squashfs/LiveOS/ext3fs.img 168 | 169 | umount -l /squashfs 170 | 171 | # Create overlay only if toram is not set 172 | if [ -z "$toram" ] ; then 173 | do_live_from_base_loop 174 | fi 175 | fi 176 | 177 | # If the kernel parameter toram is set, create a tmpfs device and copy the 178 | # filesystem to it. Continue the boot process with this tmpfs device as 179 | # a writable root device. 180 | if [ -n "$toram" ] ; then 181 | blocks=$( blockdev --getsz $BASE_LOOPDEV ) 182 | 183 | echo "Create tmpfs ($blocks blocks) for the root filesystem..." 184 | mkdir -p /image 185 | mount -n -t tmpfs -o nr_blocks=$blocks tmpfs /image 186 | 187 | echo "Copy filesystem image to tmpfs... (this may take a few minutes)" 188 | dd if=$BASE_LOOPDEV of=/image/rootfs.img 189 | 190 | ROOTFS_LOOPDEV=$( losetup -f ) 191 | echo "Create loop device for the root filesystem: $ROOTFS_LOOPDEV" 192 | losetup $ROOTFS_LOOPDEV /image/rootfs.img 193 | 194 | echo "It's time to clean up.. " 195 | 196 | echo " > Umounting images" 197 | umount -l /image 198 | umount -l /dev/.initramfs/live 199 | 200 | echo " > Detach $OSMIN_LOOPDEV" 201 | losetup -d $OSMIN_LOOPDEV 202 | 203 | echo " > Detach $OSMIN_SQUASHED_LOOPDEV" 204 | losetup -d $OSMIN_SQUASHED_LOOPDEV 205 | 206 | echo " > Detach $BASE_LOOPDEV" 207 | losetup -d $BASE_LOOPDEV 208 | 209 | echo " > Detach $SQUASHED_LOOPDEV" 210 | losetup -d $SQUASHED_LOOPDEV 211 | 212 | echo " > Detach /dev/loop0" 213 | losetup -d /dev/loop0 214 | 215 | losetup -a 216 | 217 | echo "Root filesystem is now on $ROOTFS_LOOPDEV." 218 | echo 219 | 220 | ln -s $ROOTFS_LOOPDEV /dev/root 221 | printf '/bin/mount -o rw %s %s\n' "$ROOTFS_LOOPDEV" "$NEWROOT" > /mount/01-$$-live.sh 222 | exit 0 223 | fi 224 | 225 | if [ -b "$OSMIN_LOOPDEV" ]; then 226 | # set up the devicemapper snapshot device, which will merge 227 | # the normal live fs image, and the delta, into a minimzied fs image 228 | if [ -z "$toram" ] ; then 229 | echo "0 $( blockdev --getsz $BASE_LOOPDEV ) snapshot $BASE_LOOPDEV $OSMIN_LOOPDEV p 8" | dmsetup create --readonly live-osimg-min 230 | fi 231 | fi 232 | 233 | ROOTFLAGS="$(getarg rootflags)" 234 | if [ -n "$ROOTFLAGS" ]; then 235 | ROOTFLAGS="-o $ROOTFLAGS" 236 | fi 237 | 238 | ln -fs /dev/mapper/live-rw /dev/root 239 | printf '/bin/mount %s /dev/mapper/live-rw %s\n' "$ROOTFLAGS" "$NEWROOT" > /mount/01-$$-live.sh 240 | 241 | exit 0 242 | -------------------------------------------------------------------------------- /bootcd/dracut.toram.patch: -------------------------------------------------------------------------------- 1 | --- original 2013-03-20 16:25:23.698846581 +0100 2 | +++ new 2013-03-21 08:58:11.175339694 +0100 3 | @@ -24,6 +24,8 @@ 4 | getarg readonly_overlay && readonly_overlay="--readonly" || readonly_overlay="" 5 | overlay=$(getarg overlay) 6 | 7 | +getarg toram && toram="yes" 8 | + 9 | # FIXME: we need to be able to hide the plymouth splash for the check really 10 | [ -e $livedev ] & fs=$(blkid -s TYPE -o value $livedev) 11 | if [ "$fs" = "iso9660" -o "$fs" = "udf" ]; then 12 | @@ -132,7 +134,10 @@ 13 | BASE_LOOPDEV=$( losetup -f ) 14 | losetup -r $BASE_LOOPDEV $EXT3FS 15 | 16 | - do_live_from_base_loop 17 | + # Create overlay only if toram is not set 18 | + if [ -z "$toram" ] ; then 19 | + do_live_from_base_loop 20 | + fi 21 | fi 22 | 23 | # we might have an embedded ext3 on squashfs to use as rootfs (compressed live) 24 | @@ -163,13 +168,66 @@ 25 | 26 | umount -l /squashfs 27 | 28 | - do_live_from_base_loop 29 | + # Create overlay only if toram is not set 30 | + if [ -z "$toram" ] ; then 31 | + do_live_from_base_loop 32 | + fi 33 | +fi 34 | + 35 | +# If the kernel parameter toram is set, create a tmpfs device and copy the 36 | +# filesystem to it. Continue the boot process with this tmpfs device as 37 | +# a writable root device. 38 | +if [ -n "$toram" ] ; then 39 | + blocks=$( blockdev --getsz $BASE_LOOPDEV ) 40 | + 41 | + echo "Create tmpfs ($blocks blocks) for the root filesystem..." 42 | + mkdir -p /image 43 | + mount -n -t tmpfs -o nr_blocks=$blocks tmpfs /image 44 | + 45 | + echo "Copy filesystem image to tmpfs... (this may take a few minutes)" 46 | + dd if=$BASE_LOOPDEV of=/image/rootfs.img 47 | + 48 | + ROOTFS_LOOPDEV=$( losetup -f ) 49 | + echo "Create loop device for the root filesystem: $ROOTFS_LOOPDEV" 50 | + losetup $ROOTFS_LOOPDEV /image/rootfs.img 51 | + 52 | + echo "It's time to clean up.. " 53 | + 54 | + echo " > Umounting images" 55 | + umount -l /image 56 | + umount -l /dev/.initramfs/live 57 | + 58 | + echo " > Detach $OSMIN_LOOPDEV" 59 | + losetup -d $OSMIN_LOOPDEV 60 | + 61 | + echo " > Detach $OSMIN_SQUASHED_LOOPDEV" 62 | + losetup -d $OSMIN_SQUASHED_LOOPDEV 63 | + 64 | + echo " > Detach $BASE_LOOPDEV" 65 | + losetup -d $BASE_LOOPDEV 66 | + 67 | + echo " > Detach $SQUASHED_LOOPDEV" 68 | + losetup -d $SQUASHED_LOOPDEV 69 | + 70 | + echo " > Detach /dev/loop0" 71 | + losetup -d /dev/loop0 72 | + 73 | + losetup -a 74 | + 75 | + echo "Root filesystem is now on $ROOTFS_LOOPDEV." 76 | + echo 77 | + 78 | + ln -s $ROOTFS_LOOPDEV /dev/root 79 | + printf '/bin/mount -o rw %s %s\n' "$ROOTFS_LOOPDEV" "$NEWROOT" > /mount/01-$$-live.sh 80 | + exit 0 81 | fi 82 | 83 | if [ -b "$OSMIN_LOOPDEV" ]; then 84 | # set up the devicemapper snapshot device, which will merge 85 | # the normal live fs image, and the delta, into a minimzied fs image 86 | - echo "0 $( blockdev --getsz $BASE_LOOPDEV ) snapshot $BASE_LOOPDEV $OSMIN_LOOPDEV p 8" | dmsetup create --readonly live-osimg-min 87 | + if [ -z "$toram" ] ; then 88 | + echo "0 $( blockdev --getsz $BASE_LOOPDEV ) snapshot $BASE_LOOPDEV $OSMIN_LOOPDEV p 8" | dmsetup create --readonly live-osimg-min 89 | + fi 90 | fi 91 | 92 | ROOTFLAGS="$(getarg rootflags)" 93 | -------------------------------------------------------------------------------- /bootcd/genesis.ks.template: -------------------------------------------------------------------------------- 1 | bootloader --location=mbr --append="toram" 2 | lang en_US.UTF-8 3 | keyboard us 4 | timezone America/New_York 5 | auth --useshadow --enablemd5 6 | selinux --disabled 7 | firewall --disabled 8 | rootpw --iscrypted $1$zva3nR9O$xdHSmelk7Nl2AFY7Luwmz0 9 | services --enabled sshd 10 | # latest stable SL-6 11 | url --url=http://ftp.scientificlinux.org/linux/scientific/6x/x86_64/os/ 12 | repo --name=base --baseurl=http://ftp.scientificlinux.org/linux/scientific/6x/x86_64/os/ 13 | repo --name=epel --baseurl=http://mirrors.rit.edu/epel/6/x86_64/ 14 | repo --name=fastbugs --baseurl=http://ftp.scientificlinux.org/linux/scientific/6x/x86_64/updates/fastbugs/ 15 | repo --name=security --baseurl=http://ftp.scientificlinux.org/linux/scientific/6x/x86_64/updates/security/ 16 | repo --name=local --baseurl=http://localhost:8000/repo/ 17 | 18 | %packages 19 | acl 20 | attr 21 | authconfig 22 | basesystem 23 | bash 24 | bind-utils 25 | chkconfig 26 | coreutils 27 | cpio 28 | device-mapper 29 | device-mapper-event 30 | dhclient 31 | dmidecode 32 | e2fsprogs 33 | filesystem 34 | glibc 35 | initscripts 36 | iproute 37 | iptables-ipv6 38 | iptables 39 | iputils 40 | kernel 41 | ncurses 42 | ntpdate 43 | openssh-server 44 | openssh-clients 45 | passwd 46 | policycoreutils 47 | procps 48 | rootfiles 49 | rpm 50 | rsyslog 51 | screen 52 | setup 53 | shadow-utils 54 | sudo 55 | system-config-firewall-base 56 | tmux 57 | traceroute 58 | util-linux-ng 59 | vim-minimal 60 | yum 61 | # needed for rvm 62 | gpgme 63 | which 64 | 65 | genesis_scripts 66 | 67 | # for gem install 68 | gcc 69 | glibc-headers 70 | 71 | %end 72 | 73 | %post 74 | set -e 75 | repo_base_url="http://ftp.scientificlinux.org/linux/scientific/6x/x86_64" 76 | # Apply Genesis Live OS customizations 77 | echo '*****************************************' 78 | echo '*** Genesis Live OS Image Customizr ***' 79 | 80 | # See: http://www.espenbraastad.no/post/el6-rootfs-on-tmpfs/?p=160 81 | # This is to swap our rootfs from an overlay to tmpfs so we dont blow 82 | # out the 512m overlay FS when downloading gems and such on a running image 83 | echo '>>>> updating dracut-live-root to support tmpfs' 84 | # FYI this is just base64 of the already-patched dracut-live-root file 85 | cat >/root/dracut-live-root-tmpfs.base64 <<"_EOF_" 86 | IyEvYmluL3NoCiAKLiAvbGliL2RyYWN1dC1saWIuc2gKWyAtZiAvdG1wL3Jvb3QuaW5mbyBdICYmIC4gL3RtcC9yb290LmluZm8KIApQQVRIPSRQQVRIOi9zYmluOi91c3Ivc2JpbgogCmlmIGdldGFyZyByZGxpdmVkZWJ1ZzsgdGhlbgogICAgZXhlYyA+IC90bXAvbGl2ZXJvb3QuJCQub3V0CiAgICBleGVjIDI+PiAvdG1wL2xpdmVyb290LiQkLm91dAogICAgc2V0IC14CmZpCiAKWyAteiAiJDEiIF0gJiYgZXhpdCAxCmxpdmVkZXY9IiQxIgogCiMgcGFyc2UgdmFyaW91cyBsaXZlIGltYWdlIHNwZWNpZmljIG9wdGlvbnMgdGhhdCBtYWtlIHNlbnNlIHRvIGJlCiMgc3BlY2lmaWVkIGFzIHRoZWlyIG93biB0aGluZ3MKbGl2ZV9kaXI9JChnZXRhcmcgbGl2ZV9kaXIpClsgLXogIiRsaXZlX2RpciIgXSAmJiBsaXZlX2Rpcj0iTGl2ZU9TIgpnZXRhcmcgbGl2ZV9yYW0gJiYgbGl2ZV9yYW09InllcyIKZ2V0YXJnIG5vX2VqZWN0ICYmIG5vX2VqZWN0PSJ5ZXMiCmdldGFyZyByZXNldF9vdmVybGF5ICYmIHJlc2V0X292ZXJsYXk9InllcyIKZ2V0YXJnIHJlYWRvbmx5X292ZXJsYXkgJiYgcmVhZG9ubHlfb3ZlcmxheT0iLS1yZWFkb25seSIgfHwgcmVhZG9ubHlfb3ZlcmxheT0iIgpvdmVybGF5PSQoZ2V0YXJnIG92ZXJsYXkpCiAKZ2V0YXJnIHRvcmFtICYmIHRvcmFtPSJ5ZXMiCiAKIyBGSVhNRTogd2UgbmVlZCB0byBiZSBhYmxlIHRvIGhpZGUgdGhlIHBseW1vdXRoIHNwbGFzaCBmb3IgdGhlIGNoZWNrIHJlYWxseQpbIC1lICRsaXZlZGV2IF0gJiBmcz0kKGJsa2lkIC1zIFRZUEUgLW8gdmFsdWUgJGxpdmVkZXYpCmlmIFsgIiRmcyIgPSAiaXNvOTY2MCIgLW8gIiRmcyIgPSAidWRmIiBdOyB0aGVuCiAgICBjaGVjaz0ieWVzIgpmaQpnZXRhcmcgY2hlY2sgfHwgY2hlY2s9IiIKaWYgWyAtbiAiJGNoZWNrIiBdOyB0aGVuCiAgICBjaGVja2lzb21kNSAtLXZlcmJvc2UgJGxpdmVkZXYgfHwgOgogICAgaWYgWyAkPyAtbmUgMCBdOyB0aGVuCiAgZGllICJDRCBjaGVjayBmYWlsZWQhIgogIGV4aXQgMQogICAgZmkKZmkKIApnZXRhcmcgcm8gJiYgbGl2ZXJ3PXJvCmdldGFyZyBydyAmJiBsaXZlcnc9cncKWyAteiAiJGxpdmVydyIgXSAmJiBsaXZlcnc9cm8KIyBtb3VudCB0aGUgYmFja2luZyBvZiB0aGUgbGl2ZSBpbWFnZSBmaXJzdApta2RpciAtcCAvZGV2Ly5pbml0cmFtZnMvbGl2ZQptb3VudCAtbiAtdCAkZnN0eXBlIC1vICRsaXZlcncgJGxpdmVkZXYgL2Rldi8uaW5pdHJhbWZzL2xpdmUKUkVTPSQ/CmlmIFsgIiRSRVMiICE9ICIwIiBdOyB0aGVuCiAgICBkaWUgIkZhaWxlZCB0byBtb3VudCBibG9jayBkZXZpY2Ugb2YgbGl2ZSBpbWFnZSIKICAgIGV4aXQgMQpmaQogCiMgb3ZlcmxheSBzZXR1cCBoZWxwZXIgZnVuY3Rpb24KZG9fbGl2ZV9vdmVybGF5KCkgewogICAgIyBjcmVhdGUgYSBzcGFyc2UgZmlsZSBmb3IgdGhlIG92ZXJsYXkKICAgICMgb3ZlcmxheTogaWYgbm9uLXJhbSBvdmVybGF5IHNlYXJjaGluZyBpcyBkZXNpcmVkLCBkbyBpdCwKICAgICMgICAgICAgICAgICAgIG90aGVyd2lzZSwgY3JlYXRlIHRyYWRpdGlvbmFsIG92ZXJsYXkgaW4gcmFtCiAgICBPVkVSTEFZX0xPT1BERVY9JCggbG9zZXR1cCAtZiApCiAKICAgIGw9JChibGtpZCAtcyBMQUJFTCAtbyB2YWx1ZSAkbGl2ZWRldikgfHwgbD0iIgogICAgdT0kKGJsa2lkIC1zIFVVSUQgLW8gdmFsdWUgJGxpdmVkZXYpIHx8IHU9IiIKIAogICAgaWYgWyAteiAiJG92ZXJsYXkiIF07IHRoZW4KICAgICAgICBwYXRoc3BlYz0iLyR7bGl2ZV9kaXJ9L292ZXJsYXktJGwtJHUiCiAgICBlbGlmICggZWNobyAkb3ZlcmxheSB8IGdyZXAgLXEgIjoiICk7IHRoZW4KICAgICAgICAjIHBhdGhzcGVjIHNwZWNpZmllZCwgZXh0cmFjdAogICAgICAgIHBhdGhzcGVjPSQoIGVjaG8gJG92ZXJsYXkgfCBzZWQgLWUgJ3MvXi4qOi8vJyApCiAgICBmaQogCiAgICBpZiBbIC16ICIkcGF0aHNwZWMiIC1vICIkcGF0aHNwZWMiID0gImF1dG8iIF07IHRoZW4KICAgICAgICBwYXRoc3BlYz0iLyR7bGl2ZV9kaXJ9L292ZXJsYXktJGwtJHUiCiAgICBmaQogICAgZGV2c3BlYz0kKCBlY2hvICRvdmVybGF5IHwgc2VkIC1lICdzLzouKiQvLycgKQogCiAgICAjIG5lZWQgdG8ga25vdyB3aGVyZSB0byBsb29rIGZvciB0aGUgb3ZlcmxheQogICAgc2V0dXA9IiIKICAgIGlmIFsgLW4gIiRkZXZzcGVjIiAtYSAtbiAiJHBhdGhzcGVjIiAtYSAtbiAiJG92ZXJsYXkiIF07IHRoZW4KICAgICAgICBta2RpciAvb3ZlcmxheWZzCiAgICAgICAgbW91bnQgLW4gLXQgYXV0byAkZGV2c3BlYyAvb3ZlcmxheWZzIHx8IDoKICAgICAgICBpZiBbIC1mIC9vdmVybGF5ZnMkcGF0aHNwZWMgLWEgLXcgL292ZXJsYXlmcyRwYXRoc3BlYyBdOyB0aGVuCiAgICAgICAgICAgIGxvc2V0dXAgJE9WRVJMQVlfTE9PUERFViAvb3ZlcmxheWZzJHBhdGhzcGVjCiAgICAgICAgICAgIGlmIFsgLW4gIiRyZXNldF9vdmVybGF5IiBdOyB0aGVuCiAgICAgICAgICAgICAgIGRkIGlmPS9kZXYvemVybyBvZj0kT1ZFUkxBWV9MT09QREVWIGJzPTY0ayBjb3VudD0xIDI+L2Rldi9udWxsCiAgICAgICAgICAgIGZpCiAgICAgICAgICAgIHNldHVwPSJ5ZXMiCiAgICAgICAgZmkKICAgICAgICB1bW91bnQgLWwgL292ZXJsYXlmcyB8fCA6CiAgICBmaQogCiAgICBpZiBbIC16ICIkc2V0dXAiIF07IHRoZW4KICAgICAgICBpZiBbIC1uICIkZGV2c3BlYyIgLWEgLW4gIiRwYXRoc3BlYyIgXTsgdGhlbgogICAgICAgICAgIHdhcm4gIlVuYWJsZSB0byBmaW5kIHBlcnNpc3RlbnQgb3ZlcmxheTsgdXNpbmcgdGVtcG9yYXJ5IgogICAgICAgICAgIHNsZWVwIDUKICAgICAgICBmaQogCiAgICAgICAgZGQgaWY9L2Rldi9udWxsIG9mPS9vdmVybGF5IGJzPTEwMjQgY291bnQ9MSBzZWVrPSQoKDUxMioxMDI0KSkgMj4gL2Rldi9udWxsCiAgICAgICAgbG9zZXR1cCAkT1ZFUkxBWV9MT09QREVWIC9vdmVybGF5CiAgICBmaQogCiAgICAjIHNldCB1cCB0aGUgc25hcHNob3QKICAgIGVjaG8gMCBgYmxvY2tkZXYgLS1nZXRzeiAkQkFTRV9MT09QREVWYCBzbmFwc2hvdCAkQkFTRV9MT09QREVWICRPVkVSTEFZX0xPT1BERVYgcCA4IHwgZG1zZXR1cCBjcmVhdGUgJHJlYWRvbmx5X292ZXJsYXkgbGl2ZS1ydwp9CiAKIyBsaXZlIGNkIGhlbHBlciBmdW5jdGlvbgpkb19saXZlX2Zyb21fYmFzZV9sb29wKCkgewogICAgZG9fbGl2ZV9vdmVybGF5Cn0KIAojIHdlIG1pZ2h0IGhhdmUgYSBnZW5NaW5JbnN0RGVsdGEgZGVsdGEgZmlsZSBmb3IgYW5hY29uZGEgdG8gdGFrZSBhZHZhbnRhZ2Ugb2YKaWYgWyAtZSAvZGV2Ly5pbml0cmFtZnMvbGl2ZS8ke2xpdmVfZGlyfS9vc21pbi5pbWcgXTsgdGhlbgogICAgT1NNSU5TUUZTPS9kZXYvLmluaXRyYW1mcy9saXZlLyR7bGl2ZV9kaXJ9L29zbWluLmltZwpmaQogCmlmIFsgLW4gIiRPU01JTlNRRlMiIF07IHRoZW4KICAgICMgZGVjb21wcmVzcyB0aGUgZGVsdGEgZGF0YQogICAgZGQgaWY9JE9TTUlOU1FGUyBvZj0vb3NtaW4uaW1nIDI+IC9kZXYvbnVsbAogICAgT1NNSU5fU1FVQVNIRURfTE9PUERFVj0kKCBsb3NldHVwIC1mICkKICAgIGxvc2V0dXAgLXIgJE9TTUlOX1NRVUFTSEVEX0xPT1BERVYgL29zbWluLmltZwogICAgbWtkaXIgLXAgL3NxdWFzaGZzLm9zbWluCiAgICBtb3VudCAtbiAtdCBzcXVhc2hmcyAtbyBybyAkT1NNSU5fU1FVQVNIRURfTE9PUERFViAvc3F1YXNoZnMub3NtaW4KICAgIE9TTUlOX0xPT1BERVY9JCggbG9zZXR1cCAtZiApCiAgICBsb3NldHVwIC1yICRPU01JTl9MT09QREVWIC9zcXVhc2hmcy5vc21pbi9vc21pbgogICAgdW1vdW50IC1sIC9zcXVhc2hmcy5vc21pbgpmaQogCiMgd2UgbWlnaHQgaGF2ZSBqdXN0IGFuIGVtYmVkZGVkIGV4dDMgdG8gdXNlIGFzIHJvb3RmcyAodW5jb21wcmVzc2VkIGxpdmUpCmlmIFsgLWUgL2Rldi8uaW5pdHJhbWZzL2xpdmUvJHtsaXZlX2Rpcn0vZXh0M2ZzLmltZyBdOyB0aGVuCiAgRVhUM0ZTPSIvZGV2Ly5pbml0cmFtZnMvbGl2ZS8ke2xpdmVfZGlyfS9leHQzZnMuaW1nIgpmaQogCmlmIFsgLW4gIiRFWFQzRlMiIF0gOyB0aGVuCiAgICBCQVNFX0xPT1BERVY9JCggbG9zZXR1cCAtZiApCiAgICBsb3NldHVwIC1yICRCQVNFX0xPT1BERVYgJEVYVDNGUwogCiAgICAjIENyZWF0ZSBvdmVybGF5IG9ubHkgaWYgdG9yYW0gaXMgbm90IHNldAogICAgaWYgWyAteiAiJHRvcmFtIiBdIDsgdGhlbgogICAgICAgIGRvX2xpdmVfZnJvbV9iYXNlX2xvb3AKICAgIGZpCmZpCiAKIyB3ZSBtaWdodCBoYXZlIGFuIGVtYmVkZGVkIGV4dDMgb24gc3F1YXNoZnMgdG8gdXNlIGFzIHJvb3RmcyAoY29tcHJlc3NlZCBsaXZlKQppZiBbIC1lIC9kZXYvLmluaXRyYW1mcy9saXZlLyR7bGl2ZV9kaXJ9L3NxdWFzaGZzLmltZyBdOyB0aGVuCiAgU1FVQVNIRUQ9Ii9kZXYvLmluaXRyYW1mcy9saXZlLyR7bGl2ZV9kaXJ9L3NxdWFzaGZzLmltZyIKZmkKIAppZiBbIC1lICIkU1FVQVNIRUQiIF0gOyB0aGVuCiAgICBpZiBbIC1uICIkbGl2ZV9yYW0iIF0gOyB0aGVuCiAgICAgICAgZWNobyAiQ29weWluZyBsaXZlIGltYWdlIHRvIFJBTS4uLiIKICAgICAgICBlY2hvICIodGhpcyBtYXkgdGFrZSBhIGZldyBtaW51dGVzKSIKICAgICAgICBkZCBpZj0kU1FVQVNIRUQgb2Y9L3NxdWFzaGVkLmltZyBicz01MTIgMj4gL2Rldi9udWxsCiAgICAgICAgdW1vdW50IC1uIC9kZXYvLmluaXRyYW1mcy9saXZlCiAgICAgICAgZWNobyAiRG9uZSBjb3B5aW5nIGxpdmUgaW1hZ2UgdG8gUkFNLiIKICAgICAgICBpZiBbICEgLW4gIiRub19lamVjdCIgXTsgdGhlbgogICAgICAgICAgICBlamVjdCAtcCAkbGl2ZWRldiB8fCA6CiAgICAgICAgZmkKICAgICAgICBTUVVBU0hFRD0iL3NxdWFzaGVkLmltZyIKICAgIGZpCiAKICAgIFNRVUFTSEVEX0xPT1BERVY9JCggbG9zZXR1cCAtZiApCiAgICBsb3NldHVwIC1yICRTUVVBU0hFRF9MT09QREVWICRTUVVBU0hFRAogICAgbWtkaXIgLXAgL3NxdWFzaGZzCiAgICBtb3VudCAtbiAtdCBzcXVhc2hmcyAtbyBybyAkU1FVQVNIRURfTE9PUERFViAvc3F1YXNoZnMKIAogICAgQkFTRV9MT09QREVWPSQoIGxvc2V0dXAgLWYgKQogICAgbG9zZXR1cCAtciAkQkFTRV9MT09QREVWIC9zcXVhc2hmcy9MaXZlT1MvZXh0M2ZzLmltZwogCiAgICB1bW91bnQgLWwgL3NxdWFzaGZzCiAKICAgICMgQ3JlYXRlIG92ZXJsYXkgb25seSBpZiB0b3JhbSBpcyBub3Qgc2V0CiAgICBpZiBbIC16ICIkdG9yYW0iIF0gOyB0aGVuCiAgICAgICAgZG9fbGl2ZV9mcm9tX2Jhc2VfbG9vcAogICAgZmkKZmkKIAojIElmIHRoZSBrZXJuZWwgcGFyYW1ldGVyIHRvcmFtIGlzIHNldCwgY3JlYXRlIGEgdG1wZnMgZGV2aWNlIGFuZCBjb3B5IHRoZSAKIyBmaWxlc3lzdGVtIHRvIGl0LiBDb250aW51ZSB0aGUgYm9vdCBwcm9jZXNzIHdpdGggdGhpcyB0bXBmcyBkZXZpY2UgYXMKIyBhIHdyaXRhYmxlIHJvb3QgZGV2aWNlLgppZiBbIC1uICIkdG9yYW0iIF0gOyB0aGVuCiAgICBibG9ja3M9JCggYmxvY2tkZXYgLS1nZXRzeiAkQkFTRV9MT09QREVWICkKIAogICAgZWNobyAiQ3JlYXRlIHRtcGZzICgkYmxvY2tzIGJsb2NrcykgZm9yIHRoZSByb290IGZpbGVzeXN0ZW0uLi4iCiAgICBta2RpciAtcCAvaW1hZ2UKICAgIG1vdW50IC1uIC10IHRtcGZzIC1vIG5yX2Jsb2Nrcz0kYmxvY2tzIHRtcGZzIC9pbWFnZQogCiAgICBlY2hvICJDb3B5IGZpbGVzeXN0ZW0gaW1hZ2UgdG8gdG1wZnMuLi4gKHRoaXMgbWF5IHRha2UgYSBmZXcgbWludXRlcykiCiAgICBkZCBpZj0kQkFTRV9MT09QREVWIG9mPS9pbWFnZS9yb290ZnMuaW1nCiAKICAgIFJPT1RGU19MT09QREVWPSQoIGxvc2V0dXAgLWYgKQogICAgZWNobyAiQ3JlYXRlIGxvb3AgZGV2aWNlIGZvciB0aGUgcm9vdCBmaWxlc3lzdGVtOiAkUk9PVEZTX0xPT1BERVYiCiAgICBsb3NldHVwICRST09URlNfTE9PUERFViAvaW1hZ2Uvcm9vdGZzLmltZwogCiAgICBlY2hvICJJdCdzIHRpbWUgdG8gY2xlYW4gdXAuLiAiCiAKICAgIGVjaG8gIiA+IFVtb3VudGluZyBpbWFnZXMiCiAgICB1bW91bnQgLWwgL2ltYWdlCiAgICB1bW91bnQgLWwgL2Rldi8uaW5pdHJhbWZzL2xpdmUKIAogICAgZWNobyAiID4gRGV0YWNoICRPU01JTl9MT09QREVWIgogICAgbG9zZXR1cCAtZCAkT1NNSU5fTE9PUERFVgogCiAgICBlY2hvICIgPiBEZXRhY2ggJE9TTUlOX1NRVUFTSEVEX0xPT1BERVYiCiAgICBsb3NldHVwIC1kICRPU01JTl9TUVVBU0hFRF9MT09QREVWCiAgICAKICAgIGVjaG8gIiA+IERldGFjaCAkQkFTRV9MT09QREVWIgogICAgbG9zZXR1cCAtZCAkQkFTRV9MT09QREVWCiAgICAKICAgIGVjaG8gIiA+IERldGFjaCAkU1FVQVNIRURfTE9PUERFViIKICAgIGxvc2V0dXAgLWQgJFNRVUFTSEVEX0xPT1BERVYKICAgIAogICAgZWNobyAiID4gRGV0YWNoIC9kZXYvbG9vcDAiCiAgICBsb3NldHVwIC1kIC9kZXYvbG9vcDAKIAogICAgbG9zZXR1cCAtYQogCiAgICBlY2hvICJSb290IGZpbGVzeXN0ZW0gaXMgbm93IG9uICRST09URlNfTE9PUERFVi4iCiAgICBlY2hvCiAKICAgIGxuIC1zICRST09URlNfTE9PUERFViAvZGV2L3Jvb3QKICAgIHByaW50ZiAnL2Jpbi9tb3VudCAtbyBydyAlcyAlc1xuJyAiJFJPT1RGU19MT09QREVWIiAiJE5FV1JPT1QiID4gL21vdW50LzAxLSQkLWxpdmUuc2gKICAgIGV4aXQgMApmaQogCmlmIFsgLWIgIiRPU01JTl9MT09QREVWIiBdOyB0aGVuCiAgICAjIHNldCB1cCB0aGUgZGV2aWNlbWFwcGVyIHNuYXBzaG90IGRldmljZSwgd2hpY2ggd2lsbCBtZXJnZQogICAgIyB0aGUgbm9ybWFsIGxpdmUgZnMgaW1hZ2UsIGFuZCB0aGUgZGVsdGEsIGludG8gYSBtaW5pbXppZWQgZnMgaW1hZ2UKICAgIGlmIFsgLXogIiR0b3JhbSIgXSA7IHRoZW4KICAgICAgICBlY2hvICIwICQoIGJsb2NrZGV2IC0tZ2V0c3ogJEJBU0VfTE9PUERFViApIHNuYXBzaG90ICRCQVNFX0xPT1BERVYgJE9TTUlOX0xPT1BERVYgcCA4IiB8IGRtc2V0dXAgY3JlYXRlIC0tcmVhZG9ubHkgbGl2ZS1vc2ltZy1taW4KICAgIGZpCmZpCiAKUk9PVEZMQUdTPSIkKGdldGFyZyByb290ZmxhZ3MpIgppZiBbIC1uICIkUk9PVEZMQUdTIiBdOyB0aGVuCiAgICBST09URkxBR1M9Ii1vICRST09URkxBR1MiCmZpCiAKbG4gLWZzIC9kZXYvbWFwcGVyL2xpdmUtcncgL2Rldi9yb290CnByaW50ZiAnL2Jpbi9tb3VudCAlcyAvZGV2L21hcHBlci9saXZlLXJ3ICVzXG4nICIkUk9PVEZMQUdTIiAiJE5FV1JPT1QiID4gL21vdW50LzAxLSQkLWxpdmUuc2gKIApleGl0IDAK 87 | _EOF_ 88 | cat /root/dracut-live-root-tmpfs.base64|base64 -d >/usr/share/dracut/modules.d/90dmsquash-live/dmsquash-live-root 89 | 90 | ls /lib/modules | while read kernel; do 91 | echo ">>>> updating initramfs for kernel ${kernel}" 92 | /sbin/dracut -f "/boot/initramfs-${kernel}.img" "$kernel" 93 | done 94 | 95 | ### iptables packages are pulled in no matter what %packages says 96 | ###echo '>>>> disabling iptables and ip6tables services' 97 | ###rm -f /etc/rc*.d/*ip{,6}tables 98 | 99 | echo '>>>> updating fstab' 100 | cat > /etc/fstab <<_EOF_ 101 | tmpfs / tmpfs defaults 0 0 102 | devpts /dev/pts devpts gid=5,mode=620 0 0 103 | tmpfs /dev/shm tmpfs defaults 0 0 104 | proc /proc proc defaults 0 0 105 | sysfs /sys sysfs defaults 0 0 106 | tmpfs /tmp tmpfs mode=1777 0 107 | tmpfs /var/tmp tmpfs mode=1777 0 108 | tmpfs /var/cache/yum tmpfs mode=1777 0 109 | _EOF_ 110 | 111 | 112 | echo '>>>> setting hostname' 113 | sed -i -e 's/HOSTNAME=.*/HOSTNAME=genesis/' /etc/sysconfig/network 114 | 115 | echo '>>>> setting /etc/resolv.conf to point to public resolvers' 116 | # this is necessary to resolve get.rvm.io and friends in %post 117 | cat >/etc/resolv.conf <<"__EOF__" 118 | %%LocalNameservers%% 119 | __EOF__ 120 | 121 | echo '>>>> rewriting /etc/motd' 122 | cat > /etc/motd <<"__EOF__" 123 | 124 | o ___ ( ( ( _ _ !!! 125 | '*` ` /_\ ' /_\ `* '. ___ .' o' \,=./ `o ` _ _ ' 126 | (o o) - (o o) - (o o) ' (> <) ' (o o) - (OXO) - 127 | ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo-ooO--(_)--Ooo- 128 | 129 | ,----.. 130 | / / \ ,--, 131 | | : : ,---, ,--.'| 132 | . | ;. / ,-+-. / | .--.--. | |, .--.--. 133 | . ; /--` ,---. ,--.'|' | ,---. / / ' `--'_ / / ' 134 | ; | ; __ / \| | ,"' | / \ | : /`./ ,' ,'| | : /`./ 135 | | : |.' .' / / | | / | | / / || : ;_ ' | | | : ;_ 136 | . | '_.' :. ' / | | | | |. ' / | \ \ `. | | : \ \ `. 137 | ' ; : \ |' ; /| | | |/ ' ; /| `----. \' : |__ `----. \ 138 | ' | '/ .'' | / | | |--' ' | / | / /`--' /| | '.'| / /`--' / 139 | | : / | : | |/ | : |'--'. / ; : ;'--'. / 140 | \ \ .' \ \ /'---' \ \ / `--'---' | , / `--'---' 141 | `---` `----' `----' ---`-' 142 | 143 | __EOF__ 144 | 145 | 146 | echo '>>>> rewriting /etc/issue' 147 | cat < /etc/issue 148 | Genesis OS v0.1 149 | Kernel \r 150 | BuildDate: $(date) 151 | 152 | EOF 153 | 154 | echo '>>>> configuring rsyslog' 155 | cat >> /etc/rsyslog.conf <>>> disabling selinux' 163 | #http://serverfault.com/questions/340679/centos-6-kickstart-ignoring-selinux-disabled 164 | ##ls -ld /etc/selinux/config /etc/sysconfig/selinux 165 | ##sed -i -e 's/^SELINUX=.*/SELINUX=disabled/g' /etc/selinux/config 166 | # for some reason, /etc/sysconfig/selinux isnt a symlink to /etc/selinux/config 167 | ##rm -f /etc/sysconfig/selinux 168 | ##ln -s ../selinux/config /etc/sysconfig/selinux 169 | 170 | 171 | # Install RVM 172 | echo '>>>> setting up rvm' 173 | # in kickstart post, $PATH isnt set so make sure rvm doesnt freak out 174 | export PATH=/usr/bin:/bin:$PATH 175 | echo '>>>> building ruby' 176 | bash -c 'gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 && \curl -sSL https://get.rvm.io | bash -s stable' 177 | bash -c 'source /etc/profile.d/rvm.sh && rvm install --default 2.2 && rvm cleanup sources' 178 | bash -c 'source /etc/profile.d/rvm.sh && rvm list' 179 | bash -c 'source /etc/profile.d/rvm.sh && ruby --version' 180 | 181 | echo '>>>> creating /etc/gemrc' 182 | cat >> /etc/gemrc <>>> installing bundler gem' 189 | su - -c 'source /etc/profile.d/rvm.sh && gem install bundler' 190 | 191 | echo '>>>> creating /root/Gemfile' 192 | # gem versions match Gemfiles in src//Gemfile 193 | cat > /root/Gemfile < 0.9.4' 197 | gem 'httparty' 198 | gem 'json' 199 | EOF 200 | 201 | echo '>>>> loading gems' 202 | su - -c 'source /etc/profile.d/rvm.sh && bundle install --system --gemfile /root/Gemfile' 203 | 204 | # TODO get these into the Gemfile 205 | echo '>>>> installing basic genesis framework gems' 206 | bash -c "source /etc/profile.d/rvm.sh && gem install genesis_retryingfetcher" 207 | bash -c "source /etc/profile.d/rvm.sh && gem install genesis_promptcli" 208 | bash -c "source /etc/profile.d/rvm.sh && gem install genesis_framework" 209 | bash -c "source /etc/profile.d/rvm.sh && gem list" 210 | 211 | # image build time local customizations 212 | %%ExtraPostContent%% 213 | # end of image build time local customizations 214 | 215 | #echo '>>>> cleanup now unneeds RPMs to make image smaller' 216 | # yum erase -y readline libyaml ncurses gdbm make 217 | # yum erase -y libcurl-devel autoconf automake libidn-devel 218 | # yum erase -y glibc-devel glibc-headers kernel-headers gcc cloog-ppl cpp libgomp mpfr ppl 219 | #yum erase -y readline libyaml libyaml-devel readline-devel ncurses ncurses-devel gdbm gdbm-devel glibc-devel tcl-devel gcc unzip openssl-devel db4-devel byacc make libffi-devel 220 | yum clean all 221 | 222 | # delete stuff we don't need 223 | echo ">>>>> Removing bloat from /usr/share" 224 | rm -rf /usr/share/doc 225 | rm -rf /usr/share/icons 226 | 227 | echo '>>>>> all done with %post' 228 | echo '*****************************************' 229 | %end 230 | 231 | %post --nochroot 232 | echo "Copy initramfs outside the chroot:" 233 | ls $INSTALL_ROOT/lib/modules | while read kernel; do 234 | src="$INSTALL_ROOT/boot/initramfs-${kernel}.img" 235 | dst="$LIVE_ROOT/isolinux/initrd0.img" 236 | echo " > $src -> $dst" 237 | cp -f $src $dst 238 | done 239 | %end 240 | -------------------------------------------------------------------------------- /bootcd/iptables.content.sample: -------------------------------------------------------------------------------- 1 | # this is added from iptables.content 2 | echo '>>>> setting /etc/sysconfig/iptables to allow only select inbound IPs' 3 | cat >/etc/sysconfig/iptables <<"__EOF__" 4 | # Generated by iptables-save v1.4.7 on Wed Aug 1 14:24:25 2018 5 | *filter 6 | :INPUT ACCEPT [0:0] 7 | :FORWARD ACCEPT [0:0] 8 | :OUTPUT ACCEPT [184:31792] 9 | -A INPUT -s 1.2.3.4/30 -j ACCEPT 10 | -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT 11 | -A INPUT -j DROP 12 | COMMIT 13 | # Completed on Wed Aug 1 14:24:25 2018 14 | __EOF__ 15 | # end of addition from iptables.content 16 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/genesis_scripts.spec: -------------------------------------------------------------------------------- 1 | Name: genesis_scripts 2 | Version: 0.11 3 | Release: 0%{?dist} 4 | License: Apache License, 2.0 5 | URL: http://tumblr.github.io/genesis 6 | BuildArch: noarch 7 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root 8 | Source0: src/root-bash_profile 9 | Source1: src/network-prep.init 10 | Source2: src/agetty-wrapper 11 | Source3: src/mingetty-wrapper 12 | Source4: src/genesis-bootloader 13 | Source5: src/autologin 14 | Source6: src/genesis.init 15 | Source7: src/run-genesis-bootloader 16 | Source8: src/genesis.sysconfig 17 | Summary: Scripts used by Genesis in the bootcd image 18 | Group: System Environment/Base 19 | Requires: initscripts rootfiles patch 20 | 21 | %description 22 | Scripts and configuration files used by Genesis in the bootcd image 23 | 24 | %prep 25 | # noop 26 | 27 | %build 28 | # noop 29 | 30 | %install 31 | # add root's bash_profile 32 | mkdir -p $RPM_BUILD_ROOT/root 33 | install -m 644 -T %{SOURCE0} $RPM_BUILD_ROOT/root/.bash_profile.genesis_scripts 34 | 35 | # add our init scripts 36 | mkdir -p $RPM_BUILD_ROOT/etc/init.d 37 | install -m 755 -T %{SOURCE1} $RPM_BUILD_ROOT/etc/init.d/network-prep 38 | install -m 755 -T %{SOURCE6} $RPM_BUILD_ROOT/etc/init.d/genesis 39 | 40 | # add our config file 41 | mkdir -p $RPM_BUILD_ROOT/etc/sysconfig 42 | install -m 644 -T %{SOURCE8} $RPM_BUILD_ROOT/etc/sysconfig/genesis 43 | 44 | # add our binaries 45 | mkdir -p $RPM_BUILD_ROOT/sbin/ 46 | mkdir -p $RPM_BUILD_ROOT/usr/bin/ 47 | # add helper for agetty 48 | install -m 555 -T %{SOURCE5} $RPM_BUILD_ROOT/usr/bin/autologin 49 | # add *getty wrappers 50 | install -m 555 -T %{SOURCE2} $RPM_BUILD_ROOT/sbin/agetty-wrapper 51 | install -m 555 -T %{SOURCE3} $RPM_BUILD_ROOT/sbin/mingetty-wrapper 52 | # add the bootloader and its wrapper 53 | install -m 555 -T %{SOURCE4} $RPM_BUILD_ROOT/usr/bin/genesis-bootloader 54 | install -m 755 -T %{SOURCE7} $RPM_BUILD_ROOT/usr/bin/run-genesis-bootloader 55 | 56 | %clean 57 | # noop 58 | 59 | %files 60 | %defattr(-, root, root) 61 | /etc/init.d/genesis 62 | /etc/init.d/network-prep 63 | %config /etc/sysconfig/genesis 64 | /root/.bash_profile.genesis_scripts 65 | /sbin/agetty-wrapper 66 | /sbin/mingetty-wrapper 67 | /usr/bin/genesis-bootloader 68 | /usr/bin/run-genesis-bootloader 69 | /usr/bin/autologin 70 | 71 | %post 72 | # root should attempt to tail the genesis log file when logged in 73 | grep -q genesis_scripts /root/.bash_profile || \ 74 | echo '. /root/.bash_profile.genesis_scripts' >> /root/.bash_profile 75 | 76 | # use wrappers to dynamically allow autologin 77 | sed -e 's|^exec /sbin/mingetty|exec /sbin/mingetty-wrapper|' /etc/init/tty.conf > /etc/init/tty.override 78 | sed -e 's|^exec /sbin/agetty|exec /sbin/agetty-wrapper|' /etc/init/serial.conf > /etc/init/serial.override 79 | 80 | chkconfig --add network-prep 81 | chkconfig --add genesis 82 | 83 | %changelog 84 | * Tue Jun 21 2017 Nahum Shalman 0.10 85 | - use an init script to launch genesis bootloader 86 | - with new kernel command line flag GENESIS_AUTOTAIL: 87 | - all ttys log in and tail the log file until done 88 | - enable serial console to do autologin too 89 | 90 | * Fri Jan 09 2015 Roy Marantz 0.5-1 91 | - redo networking setup 92 | 93 | * Tue Dec 16 2014 Roy Marantz 0.3-1 94 | - add genesis-bootloader 95 | 96 | * Mon Jul 07 2014 Jeremy Johnstone 0.2-2 97 | - bringing up all 4 possible nics on the host machine when doing genesis boot 98 | 99 | * Mon Jul 07 2014 Jeremy Johnstone 0.2-1 100 | - Bringing up all 4 possible nics on the host machine via dhcp 101 | 102 | * Tue May 06 2014 Jeremy Johnstone 0.1-7 103 | - fixing patch in %%post block (jeremy@tumblr.com) 104 | 105 | * Tue May 06 2014 Jeremy Johnstone 0.1-6 106 | - fixing package to get it to install properly (jeremy@tumblr.com) 107 | 108 | * Tue May 06 2014 Jeremy Johnstone 0.1-5 109 | - cleaned up spec file for proper building 110 | * Tue May 06 2014 Jeremy Johnstone 0.1-4 111 | - updating spec for proper building (jeremy@tumblr.com) 112 | 113 | * Tue May 06 2014 Jeremy Johnstone 0.1-3 114 | - removing stale bootloader from sources (jeremy@tumblr.com) 115 | 116 | * Tue May 06 2014 Jeremy Johnstone 0.1-2 117 | - new package built with tito 118 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/agetty-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if grep -q GENESIS_AUTOTAIL /proc/cmdline; then 4 | exec /sbin/agetty -n -l /usr/bin/autologin "$@" 5 | else 6 | exec /sbin/agetty "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/autologin: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # this file is used by agetty to log root in on a serial console. 4 | 5 | # for some reason $HOME doesn't get set otherwise... 6 | export HOME=/root 7 | exec /bin/bash --login 8 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/genesis-bootloader: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # The genesis bootloader should rarely change and be site independent. 4 | # It's purpose is to load the config file and stage2 loader and run 5 | # that. Put all local code modifications in stage2. 6 | 7 | require 'rubygems' 8 | require 'yaml' 9 | # TODO eliminate the need for these someday 10 | require 'retryingfetcher' 11 | require 'promptcli' 12 | 13 | # don't buffer stdout so tail -f of log file is more useful 14 | $stdout.sync=true 15 | 16 | # parameters from kernel boot 17 | CMD_FILE = ENV['GENESIS_CMD_FILE'] || '/proc/cmdline' 18 | PROC_CMDLINE = (File.exists?(CMD_FILE) \ 19 | && File.open(CMD_FILE, 'r') { |file| file.gets }) || '' 20 | 21 | def getoption name, default='' 22 | [ENV[name], PROC_CMDLINE[/#{name}=(\S+)/, 1], default].find {|x| !x.nil?} 23 | end 24 | 25 | GENESIS_MODE = getoption 'GENESIS_MODE', 'intake' 26 | GENESIS_CONF_URL = getoption 'GENESIS_CONF_URL' 27 | GENESIS_RAID_LEVEL = getoption 'GENESIS_RAID_LEVEL' 28 | 29 | puts '' 30 | puts 'Genesis operating mode is: %s' % [GENESIS_MODE] 31 | puts 'Genesis conf URL is: %s' % [GENESIS_CONF_URL] 32 | puts 'Genesis raid level is: %s' % [GENESIS_RAID_LEVEL.empty? \ 33 | ? '' \ 34 | : GENESIS_RAID_LEVEL] 35 | 36 | raise 'ERROR: GENESIS_CONF_URL not set in kernel command line' \ 37 | if GENESIS_CONF_URL.empty? 38 | 39 | # bring the configuration to this node 40 | genesis_conf = nil 41 | # don't put in a block since any raise will look like it is coming from the get 42 | data = Genesis::RetryingFetcher.get(GENESIS_CONF_URL) 43 | begin 44 | genesis_config = YAML::load(data) 45 | rescue => e 46 | if STDIN.tty? 47 | puts "GENESIS_CONF_URL has invalid yaml: #{e.message}" 48 | end 49 | end 50 | raise "ERROR: #{GENESIS_CONF_URL} did not contain valid yaml or was empty" \ 51 | if (genesis_config.nil? || genesis_config.empty?) 52 | 53 | # fix the system clock if possible to keep log entries more accurate 54 | # and prevent false cert expired verification problems 55 | ntp_server = genesis_config.fetch('ntp_server', nil) 56 | if ntp_server and File.executable?('/usr/sbin/ntpdate') 57 | puts "Setting the system time from #{ntp_server.inspect}" 58 | system('/usr/sbin/ntpdate', '-u', '-b', ntp_server) \ 59 | or puts "ERROR: failed to set system time ntpdate returned: #{$?.inspect}" 60 | end 61 | 62 | # allow GENESIS_ROOT to be overridden from config 63 | GENESIS_ROOT = genesis_config.fetch(:root, '/var/run/genesis') 64 | puts 'Genesis root is: %s' % [GENESIS_ROOT] 65 | 66 | GENESIS_CONFIG_FILE = genesis_config.fetch(:config_file, 67 | File.join(GENESIS_ROOT, 'config.yaml')) 68 | 69 | GENESIS_STAGE2_URL = genesis_config.fetch(:stage2_url, 70 | File.join(File.dirname(GENESIS_CONF_URL), 'stage2')) 71 | GENESIS_STAGE2_SCRIPT = genesis_config.fetch(:stage2_file, 72 | File.join(GENESIS_ROOT, "stage2")) 73 | 74 | # Create our basic directory tree 75 | Dir.mkdir(GENESIS_ROOT, 0755) unless File.directory? GENESIS_ROOT 76 | 77 | File.open(GENESIS_CONFIG_FILE, "w", 0444) {|file| file.puts data} 78 | 79 | puts 'Genesis config file is: %s' % [GENESIS_CONFIG_FILE] 80 | puts 'Genesis stage2 url is: %s' % [GENESIS_STAGE2_URL] 81 | puts 'Genesis stage2 program is: %s' % [GENESIS_STAGE2_SCRIPT] 82 | puts '---', '' 83 | 84 | 85 | # fetch stage2 86 | loop do 87 | Genesis::RetryingFetcher.get(GENESIS_STAGE2_URL) do |data| 88 | File.open(GENESIS_STAGE2_SCRIPT, 'w', 0555) { |file| file.puts data } 89 | end 90 | 91 | # verify stage 2 92 | syntax_valid = File.exists?(GENESIS_STAGE2_SCRIPT) && \ 93 | !`ruby -c #{GENESIS_STAGE2_SCRIPT}`.strip.match(/^Syntax OK$/).nil? 94 | break if syntax_valid 95 | 96 | puts '' 97 | try_fetching = Genesis::PromptCLI.ask "Genesis Stage 2 is corrupt. Would you like to retry?" 98 | unless try_fetching 99 | raise RuntimeError, msg + "Genesis Stage 2 is corrupt. Execution halted!" 100 | end 101 | puts '' 102 | end 103 | 104 | # setup the environment for stage2 105 | ENV['GENESIS_ROOT'] = GENESIS_ROOT 106 | ENV['GENESIS_CONF'] = GENESIS_CONFIG_FILE 107 | ENV['GENESIS_MODE'] = GENESIS_MODE 108 | ENV['GENESIS_RAID_LEVEL'] = GENESIS_RAID_LEVEL if GENESIS_RAID_LEVEL 109 | 110 | # Execute the stage2 loader 111 | puts "\nSwitching to '" + GENESIS_ROOT + "' and executing stage 2..." 112 | Dir::chdir(GENESIS_ROOT) 113 | Kernel.exec('/usr/bin/env', 'ruby', GENESIS_STAGE2_SCRIPT) 114 | raise "ERROR: exec of stage2 script '#{GENESIS_STAGE2_SCRIPT}' failed" 115 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/genesis.init: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # chkconfig: 2345 09 89 4 | # description: run genesis bootloader 5 | # 6 | 7 | ### BEGIN INIT INFO 8 | # Required-Start: $local_fs $network $syslog sshd udev-post 9 | # Default-Start: 2 3 4 5 10 | # Description: run genesis bootloader 11 | ### END INIT INFO 12 | 13 | . /etc/rc.d/init.d/functions 14 | 15 | case "$1" in 16 | start) 17 | echo -n $"Starting genesis bootloader: " 18 | nohup /usr/bin/run-genesis-bootloader &>/dev/null & 19 | success 20 | echo 21 | ;; 22 | *) ;; 23 | esac 24 | 25 | exit 0 26 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/genesis.sysconfig: -------------------------------------------------------------------------------- 1 | # Where should genesis put its files 2 | 3 | # wrapper pidfile 4 | GENESIS_PIDFILE=/var/run/genesis.pid 5 | 6 | # wrapper log file 7 | GENESIS_LOGFILE=/var/log/genesis-bootloader.log 8 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/mingetty-wrapper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if grep -q GENESIS_AUTOTAIL /proc/cmdline; then 4 | exec /sbin/mingetty --autologin root "$@" 5 | else 6 | exec /sbin/mingetty "$@" 7 | fi 8 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/network-prep.init: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # chkconfig: 2345 09 89 4 | # descripttion: setup network interfaces for dhclient to configure 5 | # 6 | # these should be a better way to do this. TODO find it! 7 | 8 | ### BEGIN INIT INFO 9 | # X-Start-Before: $network 10 | # Default-Start: 2 3 4 5 11 | # Description: setup network interfaces for dhclient to configure 12 | ### END INIT INFO 13 | 14 | case "$1" in 15 | start) 16 | for int in $(ip link | grep -v lo | awk '/ qlen / {print $2}' | sed 's/://g'); do 17 | cat >"/etc/sysconfig/network-scripts/ifcfg-$int" <&2 "Something went wrong trying to tail the Genesis logfile:" 25 | echo >&2 "GENESIS_PIDFILE=${GENESIS_PIDFILE}" 26 | echo >&2 "GENESIS_PID=${GENESIS_PID}" 27 | echo >&2 "GENESIS_LOGFILE=${GENESIS_LOGFILE}" 28 | fi 29 | 30 | else 31 | # automatic log tailing is not enabled 32 | echo "Genesis logs should be found in ${GENESIS_LOGFILE}" 33 | echo 34 | fi 35 | 36 | # for some reason we find ourselves in /dev 37 | cd "${HOME}" 38 | -------------------------------------------------------------------------------- /bootcd/rpms/genesis_scripts/src/run-genesis-bootloader: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /etc/sysconfig/genesis 4 | # If the config file is broken, use sane defaults 5 | GENESIS_PIDFILE=${GENESIS_PIDFILE:-/var/run/genesis.pid} 6 | GENESIS_LOGFILE=${GENESIS_LOGFILE:-/var/log/genesis-bootloader.log} 7 | 8 | # send stdout and stderr to the log file 9 | exec &>"${GENESIS_LOGFILE}" 10 | 11 | echo $$>"${GENESIS_PIDFILE}" 12 | 13 | GENESIS_MODE="$(grep -o -e 'GENESIS_MODE=[^ ]*' /proc/cmdline | cut -c14-)" 14 | 15 | # setup the ruby environment we need 16 | source /etc/profile.d/rvm.sh 17 | rvm list 18 | ruby --version 19 | 20 | if [[ ! -z $GENESIS_MODE ]] 21 | then 22 | # disable task execution prompting when doing initial target run 23 | GENESIS_PROMPT_TIMEOUT=0 genesis-bootloader 24 | fi 25 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export GENESIS_DIR="${GENESIR_DIR:-/genesis}" 3 | export OUTPUT_DIR="${OUTPUT_DIR:-/output}" 4 | 5 | set -ex 6 | echo "Performing build of Genesis Framework" 7 | cd $GENESIS_DIR 8 | 9 | echo "===> Building Genesis Scripts RPM" 10 | pushd $GENESIS_DIR/bootcd/rpms/genesis_scripts 11 | rpmbuild --define '_tmppath /tmp' --define '_sourcedir src' --define '_srcrpmdir .' --nodeps -bs genesis_scripts.spec 12 | rpmbuild --rebuild genesis_scripts-*.src.rpm 13 | cp /root/rpmbuild/RPMS/noarch/genesis_scripts-*.noarch.rpm $GENESIS_DIR/bootcd/rpms/ 14 | popd 15 | echo ":: Built RPM:" 16 | ls -la $GENESIS_DIR/bootcd/rpms/ 17 | 18 | echo "===> Building Genesis Image" 19 | cd bootcd 20 | # this is necessary to avoid devicemapper syncronization raceconditions creating 21 | # devices in livecdcreator. Just use the old fallback logic when run in a container: 22 | # https://www.redhat.com/archives/lvm-devel/2012-November/msg00069.html 23 | export DM_DISABLE_UDEV=1 24 | ./create-image.sh 25 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Contents 3 | 4 | This directory contains the Genesis framework and supporting Gems. The framework includes sources which implement the Genesis task DSL. The gems incldue code supporting the execution of tasks. 5 | 6 | ## Building instructions: 7 | 8 | There are three GEMs of which the framework does most of the work. These gems are built using ```gem build``` 9 | 10 | 1. `cd ` 11 | where <gem> is one of framework/retryingfetcher/prompt-cli 12 | 2. edit the <gem>.gemspec file. Increment the version number and adjust dependencies as needed. 13 | 3. run: `gem build genesis_.gemspec` 14 | 3. publish it to your gemserver 15 | 4. update the genesis `config.yaml` file version, if it is version pinned 16 | 17 | Note: The testenv and bootcd will find the gems here so leave a copy. The bootcd includes the gems as it is required using bootstrapping. 18 | 19 | ## Testing changes: 20 | 21 | In your testenv or an already booted genesis image: 22 | 1. `scp source-host:-.gem` 23 | 2. `gem install ./-.gem` 24 | 3. use irb or genesis command to exercize 25 | 26 | Caveat: The genesis-bootloader will install the versions of these gems as specified in your configuration file. 27 | -------------------------------------------------------------------------------- /src/framework/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /src/framework/bin/genesis: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | begin 4 | require 'rake' 5 | rescue LoadError 6 | require 'rubygems' 7 | require 'rake' 8 | end 9 | 10 | ENV['GENESIS_ROOT'] ||= '/var/run/genesis' 11 | 12 | # rake does chdir also, so don't put this in a block to avoid the warning 13 | Dir::chdir( File.join(ENV['GENESIS_ROOT'], '/tasks') ) 14 | 15 | # run the normal rake application, but change its name to genesis 16 | rake = Rake.application 17 | rake.init('genesis') 18 | rake.load_rakefile 19 | rake.top_level 20 | -------------------------------------------------------------------------------- /src/framework/genesis_framework.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |gem| 2 | gem.name = "genesis_framework" 3 | gem.homepage = 'https://github.com/Tumblr/genesis' 4 | gem.license = 'Apache License, 2.0' 5 | gem.summary = %q{Generic server onboarding framework} 6 | gem.description = %q{Genesis is a project used to manage provisioning of hardware. This is the framework which runs the specified tasks.} 7 | gem.authors = ["Jeremy Johnstone", 'Roy Marantz', 'Gabe Conradi'] 8 | gem.email = 'opensourcesoftware@tumblr.com' 9 | 10 | gem.date = '2017-06-15' 11 | gem.version = '0.6.12' 12 | gem.add_dependency('genesis_promptcli', '~> 0.2', '>= 0.2.0') 13 | gem.add_dependency('genesis_retryingfetcher', '~> 0.4', '>= 0.4.0') 14 | gem.add_dependency('collins_client', '~> 0.2', '>= 0.2.0') 15 | gem.add_dependency('facter', '~> 2.0', '>= 2.0.0') 16 | gem.add_dependency('hashwithindifferentaccess') 17 | 18 | gem.files = Dir["bin/*", "lib/**/*", "*.md", "*.txt", "test/*.rb"].reject do |f| 19 | f[%r{~$|^#|\.bak$}] 20 | end 21 | gem.executables << "genesis" 22 | end 23 | -------------------------------------------------------------------------------- /src/framework/lib/genesisframework.rb: -------------------------------------------------------------------------------- 1 | # put the facter path in the LOAD_PATH so facter looks for new facts there 2 | # this path needs to have a 'facter' dir as an immediate subdir 3 | $LOAD_PATH << File.join(File.expand_path(File.dirname(__FILE__)),'genesisframework') 4 | require 'genesisframework/task' 5 | require 'genesisframework/tasks' 6 | require 'genesisframework/utils' 7 | -------------------------------------------------------------------------------- /src/framework/lib/genesisframework/facter/test_fact.rb: -------------------------------------------------------------------------------- 1 | Facter.add(:test_fact) do 2 | setcode do 3 | "gabes_test_fact" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /src/framework/lib/genesisframework/task.rb: -------------------------------------------------------------------------------- 1 | require 'retryingfetcher' 2 | require 'promptcli' 3 | require 'facter' 4 | require 'open3' 5 | 6 | module Genesis 7 | module Framework 8 | module Task 9 | def self.included base 10 | base.extend TaskDslMethods 11 | end 12 | 13 | module TaskDslMethods 14 | attr_accessor :blocks, :options 15 | 16 | def description desc 17 | set_option :description, desc 18 | end 19 | 20 | def precondition description, &block 21 | add_block :precondition, description, block 22 | end 23 | 24 | def init &block 25 | set_block :init, block 26 | end 27 | 28 | def condition description, &block 29 | add_block :condition, description, block 30 | end 31 | 32 | def run &block 33 | set_block :run, block 34 | end 35 | 36 | def rollback &block 37 | set_block :rollback, block 38 | end 39 | 40 | def success &block 41 | set_block :success, block 42 | end 43 | 44 | def timeout secs 45 | set_option :timeout, secs 46 | end 47 | 48 | def retries count 49 | # see Genesis::Framework::Tasks.execute for :retries usage 50 | # and the reason for this test implementation 51 | if count.respond_to? 'each_with_index' then 52 | # Array or Enumerable 53 | set_option :retries, count 54 | else 55 | # Interger 56 | set_option :retries, count.times 57 | end 58 | end 59 | 60 | def collins 61 | Genesis::Framework::Utils.collins 62 | end 63 | 64 | def facter 65 | # lets cache the facts on first use 66 | # TODO symbolize these keys? 67 | # TODO implement method_missing? on this hash for easy access 68 | Genesis::Framework::Utils.facter 69 | end 70 | 71 | def run_cmd *cmd, stdin_data: '', return_both_streams: false, return_merged_streams: false 72 | if return_both_streams && return_merged_streams 73 | raise "Invalid run_cmd invocation, can's specify both and merged together" 74 | end 75 | 76 | if return_merged_streams 77 | output, status = Open3.capture2e(*cmd, stdin_data: stdin_data) 78 | else 79 | stdout, stderr, status = Open3.capture3(*cmd, stdin_data: stdin_data) 80 | if return_both_streams 81 | output = [stdout, stderr] 82 | else 83 | output = stdout 84 | end 85 | end 86 | 87 | if status.exitstatus != 0 88 | log("Run Command failed for '%s' with exit status '%d' and output: %s" % [cmd.to_s, status.exitstatus, output.to_s], :critical ) 89 | raise 'run_cmd exited with status: ' + status.exitstatus.to_s 90 | end 91 | 92 | return output 93 | end 94 | 95 | def config 96 | # We are intentionally causing a deep copy here so one task 97 | # can't pollute another task's config setup 98 | # TODO: consider possibly patching hash to not allow setting members? 99 | @config ||= Marshal.load(Marshal.dump(Genesis::Framework::Utils.config_cache)) 100 | end 101 | 102 | # @param[String] message 103 | # @param[String or Symbol] level 104 | def log message, level = nil 105 | Genesis::Framework::Utils.log(task_name, message, level) 106 | end 107 | 108 | def prompt message, seconds=15, default=false 109 | Genesis::PromptCLI.ask message, seconds, default 110 | end 111 | 112 | def install provider, *what 113 | if provider == :rpm 114 | Kernel.system("yum", "install", "-y", *what) 115 | if $?.exitstatus != 0 116 | raise 'yum install exited with status: ' + $?.exitstatus.to_s 117 | end 118 | elsif provider == :gem 119 | # convert arguments in "what" to a gem_name => [requires_list] structure 120 | gems = {} 121 | what.each { |item| 122 | if item.is_a?(Hash) 123 | gems.merge! item 124 | else 125 | gems[item] = [item] 126 | end 127 | } 128 | 129 | # We give a decent try at detecting if the gem is 130 | # installed before trying to reinstall again. 131 | # If it contains a - (aka you are specifying a specific version 132 | # or a / (aka you are specifying a path to find it) then 133 | # we punt on trying to determine if the gem is already 134 | # installed and just pass it to install anyway. 135 | install_gems = gems.select do |gem, requires| 136 | gem.include?("-") \ 137 | || gem.include?("/") \ 138 | || Gem::Dependency.new(gem).matching_specs.count == 0 139 | end 140 | 141 | if install_gems.size > 0 # make sure we still have something to do 142 | options = config.fetch(:gem_args, '').split 143 | args = (options << install_gems.keys).flatten 144 | Kernel.system('gem', 'install', *args) 145 | if $?.exitstatus != 0 146 | raise "gem install #{args.join(' ')} exited with status: " \ 147 | + $?.exitstatus.to_s 148 | end 149 | else # be noisy that we aren't doing anything 150 | puts "already installed gems: #{gems.keys.join(' ')}" 151 | end 152 | 153 | # now need to clear out the Gem cache so we can load it 154 | Gem.clear_paths 155 | 156 | # Attempt to require loaded gems, print a message if we can't. 157 | gems.each { |gem, requires| 158 | begin 159 | requires.each {|r| require r } 160 | rescue LoadError 161 | raise "Could not load gem #{gem} automatically. Maybe the gem name differs from its load path? Please specify the name to require." 162 | end 163 | } 164 | else 165 | raise 'Unknown install provider: ' + provider.to_s 166 | end 167 | end 168 | 169 | def fetch what, filename, base_url: ENV['GENESIS_URL'] 170 | filepath = tmp_path(filename) 171 | Genesis::RetryingFetcher.get(what, base_url) {|data| File.open(filepath, "w", 0755) { |file| file.write data }} 172 | end 173 | 174 | def tmp_path filename 175 | Genesis::Framework::Utils.tmp_path(filename, task_name) 176 | end 177 | 178 | def task_name 179 | self.ancestors.first.to_s.split('::').last 180 | end 181 | 182 | ############################################################# 183 | # These methods are private and not part of the exposed DSL. 184 | # Use at your own risk. 185 | ############################################################# 186 | 187 | def set_block sym, block 188 | self.init_defaults 189 | self.blocks[sym] = block 190 | end 191 | 192 | def add_block sym, description, block 193 | self.init_defaults 194 | if self.blocks[sym].has_key?(description) 195 | raise "Task defines multiple conditions with the same description" 196 | end 197 | self.blocks[sym][description] = block 198 | end 199 | 200 | def set_option sym, option 201 | self.init_defaults 202 | self.options[sym] = option 203 | end 204 | 205 | def init_defaults 206 | self.blocks ||= { :precondition => {}, :init => nil, :condition => {}, :run => nil, :rollback => nil, :success => nil } 207 | self.options ||= { :retries => 3.times.to_a, :timeout => 0, :description => nil } 208 | end 209 | 210 | end 211 | end 212 | end 213 | end 214 | 215 | -------------------------------------------------------------------------------- /src/framework/lib/genesisframework/tasks.rb: -------------------------------------------------------------------------------- 1 | require 'timeout' 2 | require 'promptcli' 3 | require 'yaml' 4 | 5 | module Genesis 6 | module Framework 7 | module Tasks 8 | def self.load_config file 9 | begin 10 | data = File.read(file) 11 | 12 | ## TODO: consider tokenizing the keys of the hash? needed??? 13 | Genesis::Framework::Utils.config_cache = YAML::load(data) 14 | rescue => e 15 | raise "Unable to parse config %s: %s" % [file, e.message] 16 | end 17 | end 18 | 19 | def self.has_block? blocks, sym 20 | blocks.has_key?(sym) && blocks[sym].respond_to?(:call) 21 | end 22 | 23 | def self.call_block blocks, sym, msg = nil 24 | if self.has_block? blocks, sym 25 | puts msg if msg 26 | blocks[sym].call 27 | else 28 | true 29 | end 30 | end 31 | 32 | def self.load_tasks dir 33 | # expand the LOAD_PATH to include modules, so facts are available 34 | $:.unshift File.join(File.expand_path(dir),'modules') 35 | puts "\nParsing tasks from directory: %s" % [dir] 36 | 37 | Dir.glob(File.join(dir,'*.rb')) do |f| 38 | begin 39 | Genesis::Framework::Tasks.class_eval File.read(f) 40 | rescue => e 41 | raise "Error parsing task %s: %s" % [f, e.message] 42 | end 43 | end 44 | 45 | @tasks = Genesis::Framework::Tasks.constants.select do |c| 46 | Genesis::Framework::Tasks.const_get(c).include?( Genesis::Framework::Task ) 47 | end 48 | 49 | @tasks.sort! 50 | end 51 | 52 | def self.execute task_name 53 | puts "\n#{task_name}\n=================================================" 54 | 55 | prompt_timeout = ENV['GENESIS_PROMPT_TIMEOUT'].to_i \ 56 | || Genesis::Framework::Utils.config_cache['task_prompt_timeout'] \ 57 | || 10 58 | if prompt_timeout > 0 59 | # only prompt if there is a resonable timeout 60 | return true unless Genesis::PromptCLI.ask("Would you like to run this task?", prompt_timeout, true) 61 | end 62 | 63 | task = Genesis::Framework::Tasks.const_get(task_name) 64 | 65 | if task.blocks.nil? 66 | puts "task is empty with nothing to do, skipping..." 67 | return true 68 | end 69 | 70 | begin 71 | puts "task is now testing if it needs to be initialized..." 72 | if task.blocks.has_key?(:precondition) 73 | task.blocks[:precondition].each do |description, block| 74 | puts "Testing: %s" % description 75 | unless self.call_block(task.blocks[:precondition], description) 76 | puts "task is being skipped..." 77 | return true 78 | end 79 | end 80 | end 81 | rescue => e 82 | task.log("task had error on testing if it needs initialization: %s" % e.message, :error) 83 | return false 84 | end 85 | 86 | begin 87 | puts "task is now initializing..." 88 | self.call_block(task.blocks, :init); 89 | puts "task is now initialized..." 90 | rescue => e 91 | task.log("task threw error on initialization: %s" % e.message, :error) 92 | return false 93 | end 94 | 95 | begin 96 | puts "task is now testing if it can run..." 97 | if task.blocks.has_key?(:condition) 98 | task.blocks[:condition].each do |description, block| 99 | puts "Checking: %s" % description 100 | unless self.call_block(task.blocks[:condition], description) 101 | puts "Conditional failed. Task is being skipped." 102 | return true 103 | end 104 | end 105 | end 106 | rescue => e 107 | task.log("task had error on testing if it needs running: %s" % e.message, :error) 108 | return false 109 | end 110 | 111 | success = nil 112 | task.options[:retries].each_with_index do |sleep_interval, index| 113 | attempt = index + 1 114 | begin 115 | task.log("task is attempting run #%d..." % [attempt]) 116 | Timeout::timeout(task.options[:timeout]) do 117 | success = self.call_block(task.blocks, :run) 118 | end 119 | # a run block should raise an error or be false for a failure 120 | success = true if success.nil? 121 | rescue => e 122 | task.log("run #%d caused error: %s" % [attempt, e.message], :error) 123 | success = nil # cause a retry 124 | end 125 | break unless success.nil? # if we got an answer, we're done 126 | task.log("task is sleeping for %d seconds..." % [sleep_interval]) 127 | Kernel.sleep(sleep_interval) 128 | end 129 | success = false if success.nil? # must have used all the retries, fail 130 | 131 | if success 132 | success = self.call_block(task.blocks, :success) 133 | task.log("task is successful!") 134 | else 135 | task.log 'task failed!!!', :error 136 | if self.has_block? task.blocks, :rollback 137 | success = self.call_block(task.blocks, :rollback, "rolling back!") 138 | end 139 | end 140 | 141 | puts "\n\n" 142 | return success 143 | end 144 | 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /src/framework/lib/genesisframework/utils.rb: -------------------------------------------------------------------------------- 1 | require 'collins_client' 2 | require 'facter' 3 | require 'hashwithindifferentaccess' 4 | 5 | module Genesis 6 | module Framework 7 | module Utils 8 | 9 | @@config_cache = HashWithIndifferentAccess.new 10 | @@collins_conn = nil 11 | @@facter = nil 12 | @@loggers = nil 13 | 14 | # A directory for downloaded stuff during Genesis workflow 15 | def self.tmp_path filename, sandbox = "" 16 | location = File.join(ENV['GENESIS_ROOT'], "tmp", sandbox) 17 | Dir.mkdir(location, 0755) unless File.directory? location 18 | File.join(location, filename) 19 | end 20 | 21 | # mimicking rail's cattr_accessor 22 | def self.config_cache 23 | @@config_cache 24 | end 25 | 26 | def self.config_cache= (obj) 27 | @@config_cache = obj 28 | end 29 | 30 | def self.collins 31 | if @@collins_conn.nil? 32 | 33 | # Collins_client initialization 34 | # http://www.rubydoc.info/gems/collins_client/0.2.17/Collins/Client#initialize-instance_method 35 | cfg = self.config_cache[:collins] 36 | @@collins_conn = ::Collins::Client.new(cfg) 37 | end 38 | 39 | @@collins_conn 40 | end 41 | 42 | # Allow usage of embedded Facts within Genesis Tasks modules 43 | def self.facter 44 | if config_cache.fetch(:facter_cache, true) 45 | if @@facter.nil? 46 | @@facter = Facter.to_hash 47 | end 48 | @@facter 49 | else 50 | Facter.to_hash 51 | end 52 | end 53 | 54 | # Send a message, from a task, to all loggers referenced in Genesis config 55 | # file, using the specified level. 56 | # @param[String] subsystem 57 | # @param[String] message 58 | # @param[String or Symbol] level (defaults to: nil) — severity level to use 59 | def self.log subsystem, message, level = nil 60 | logline = subsystem.to_s + " :: " + message 61 | 62 | # Normalize input as strings or symbols 63 | severity = self.log_level_from_string(level) 64 | 65 | # Format the output for basic STDOUT 66 | local_severity = severity.nil? ? 'INFO' : severity.upcase 67 | puts local_severity + " - " + logline 68 | 69 | # Load external logging modules 70 | if @@loggers.nil? 71 | @@loggers = self.config_cache[:loggers].map {|logger| 72 | begin 73 | require "logging/#{logger.downcase}" 74 | Logging.const_get(logger.to_sym) 75 | rescue LoadError 76 | puts "Could not load logger #{logger}" 77 | end 78 | }.compact 79 | end 80 | 81 | # Send log to them 82 | @@loggers.each {|logger| logger.log logline, severity } 83 | end 84 | 85 | # Copied from https://github.com/tumblr/collins/blob/master/support/ruby/collins-client/lib/collins/api/logging.rb#L155 86 | # A method used to validate the log level from user input and returns a String 87 | # 88 | # Notes: 89 | # - Each Genesis_framework logger modules will have to do a mapping 90 | # from Collins_client SEVERITY to their own. 91 | def self.log_level_from_string level 92 | return nil if (level.nil? || level.empty?) 93 | s = Collins::Api::Logging::Severity 94 | if s.valid? level then 95 | s.value_of level 96 | else 97 | raise Collins::ExpectationFailedError.new("#{level} is not a valid log level") 98 | end 99 | end 100 | 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /src/promptcli/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /src/promptcli/Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem "termios", "~> 0.9.4" 4 | 5 | group :development do 6 | gem "bundler", "~> 1.0" 7 | gem "jeweler", "~> 1.8.7" 8 | end 9 | -------------------------------------------------------------------------------- /src/promptcli/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/src/promptcli/README.md -------------------------------------------------------------------------------- /src/promptcli/genesis_promptcli.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "genesis_promptcli" 5 | gem.email = 'jeremy@tumblr.com' 6 | gem.homepage = 'https://github.com/Tumblr/genesis' 7 | gem.license = "MIT" 8 | gem.summary = %Q{Genesis CLI prompt} 9 | gem.description = %Q{Genesis is a replacement project for InvisibleTouch that is used to manage provisioning of hardware. The promptcli is what asks if you want to perform something and has a timeout going with a default value if nothing selected in specified time period.} 10 | gem.authors = ["Jeremy Johnstone"] 11 | 12 | gem.files = Dir["{lib}/*.rb", "*.md", "*.txt"] 13 | gem.date = '2014-04-10' 14 | gem.version = "0.2.0" 15 | #gem.add_dependency("termios", "~> 0.9.4") 16 | # if you want to use 1.9.2+, use ruby-termios -_- 17 | gem.add_dependency("ruby-termios", "~> 0.9.6") 18 | end 19 | -------------------------------------------------------------------------------- /src/promptcli/lib/promptcli.rb: -------------------------------------------------------------------------------- 1 | require "termios" 2 | 3 | module Genesis 4 | module PromptCLI 5 | def self.ask question, seconds = 30, default = false 6 | old_attributes = Termios.tcgetattr($stdin) 7 | new_attributes = old_attributes.dup 8 | new_attributes.lflag &= ~Termios::ICANON 9 | Termios::tcsetattr($stdin, Termios::TCSANOW, new_attributes) 10 | 11 | start_time = Time.now 12 | end_time = start_time + seconds 13 | begin 14 | prompt_format = "%s [%d] (%s/%s) " 15 | prompt = prompt_format % [question, seconds.to_i, default ? "Y" : "y", default ? "n" : "N"] 16 | prompt_length = seconds < 10 ? prompt.length+1 : prompt.length 17 | $stdout.write(prompt) 18 | $stdout.flush 19 | 20 | # Wait until input is available 21 | if select([$stdin], [], [], seconds % 1) 22 | case char = $stdin.getc 23 | when ?y, ?Y then return true 24 | when ?n, ?N then return false 25 | else # NOOP 26 | end 27 | end 28 | 29 | $stdout.write("\b" * prompt_length) 30 | $stdout.flush 31 | 32 | seconds = end_time - Time.now 33 | end while seconds > 0.0 34 | 35 | return default 36 | ensure 37 | Termios::tcsetattr($stdin, Termios::TCSANOW, old_attributes) 38 | $stdout.puts 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /src/retryingfetcher/.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /src/retryingfetcher/Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /src/retryingfetcher/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/src/retryingfetcher/README.md -------------------------------------------------------------------------------- /src/retryingfetcher/genesis_retryingfetcher.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |gem| 2 | gem.name = 'genesis_retryingfetcher' 3 | gem.email = 'opensourcesoftware@tumblr.com' 4 | gem.homepage = 'https://github.com/Tumblr/genesis' 5 | gem.license = 'Apache License, 2.0' 6 | gem.summary = %Q{Genesis remote resource fetcher} 7 | gem.description = %Q{Genesis is used to manage provisioning of hardware. The retryingfetcher is what fetches resources from remote locations with a specified number of retries and backoff between each.} 8 | gem.authors = ['Jeremy Johnstone', 'Roy Marantz'] 9 | gem.version = '0.4.0' 10 | gem.date = '2014-12-08' 11 | gem.add_dependency('httparty', '~> 0') 12 | gem.files = Dir['lib/*.rb', '*.md', '*.txt'] 13 | end 14 | -------------------------------------------------------------------------------- /src/retryingfetcher/lib/retryingfetcher.rb: -------------------------------------------------------------------------------- 1 | require "httparty" 2 | 3 | module Genesis 4 | module RetryingFetcher 5 | FETCH_RETRY_INTERVALS = [0,1,5,30,60,90,180,300,300,300,300,300,1800,3600] 6 | 7 | def self.get what, base_url = '', fetch_intervals = FETCH_RETRY_INTERVALS 8 | what = File.join(base_url, what) unless base_url.empty? 9 | fetch_intervals.each_with_index do |sleep_interval, index| 10 | Kernel.sleep(sleep_interval) 11 | puts "Fetching '%s' (Attempt #%d)..." % [what, index+1] 12 | 13 | begin 14 | response = HTTParty.get(what) 15 | next unless response.code == 200 16 | yield response.body if block_given? 17 | return response.body 18 | rescue => e 19 | puts "RetryingFetcher.get error: %s" % e.message 20 | end 21 | end 22 | nil 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /tasks/AssetCreation.rb: -------------------------------------------------------------------------------- 1 | class AssetCreation 2 | include Genesis::Framework::Task 3 | 4 | description "Create asset if not yet in Collins" 5 | retries [0,1,5,10,21,33,33,33,39,42,60,60,60,90,120,300].to_enum 6 | 7 | precondition "has asset tag?" do 8 | not facter['asset_tag'].nil? 9 | end 10 | 11 | precondition "asset tag doesn't exist in collins?" do 12 | not collins.exists?(facter['asset_tag']) 13 | end 14 | 15 | run do 16 | begin 17 | # the default is to generate_ipmi, but be explicit since we know we need it 18 | collins.create!(facter['asset_tag'], :generate_ipmi => true) 19 | rescue Collins::RequestError => e 20 | if e.code == 409 21 | log "Asset %s already exists in collins" % facter['asset_tag'] 22 | else 23 | log "Error trying to create asset in collins. Message: %s" % e.message 24 | raise e 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /tasks/BiosConfigrC6105.rb: -------------------------------------------------------------------------------- 1 | class BiosConfigrC6105 2 | include Genesis::Framework::Task 3 | 4 | description "Configure BIOS for C6105" 5 | 6 | precondition "has productname?" do 7 | not facter['productname'].nil? 8 | end 9 | 10 | precondition "is c6105?" do 11 | facter['productname'].match(/6105/i) 12 | end 13 | 14 | init do 15 | log "Fetching and installing 'setupbios' RPM" 16 | install :rpm, "setupbios" 17 | if config['bios_settings']['c6105'] 18 | log "bios-configr fetching bios settings file '%s'" % config['bios_settings']['c6105'] 19 | fetch config['bios_settings']['c6105'], 'bios-settings', base_url: config['bios_settings']['base_url'] 20 | end 21 | end 22 | 23 | condition "bios-settings file exists?" do 24 | File.exists? tmp_path('bios-settings') 25 | end 26 | 27 | run do 28 | log "Running setupbios to apply bios settings to this machine" 29 | run_cmd "cd /usr/local/dell; /usr/local/dell/setupbios setting readfile %s" % tmp_path('bios-settings') 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /tasks/BiosConfigrR720.rb: -------------------------------------------------------------------------------- 1 | class BiosConfigrR720 2 | include Genesis::Framework::Task 3 | 4 | description "Configure BIOS for R720s" 5 | 6 | precondition "has productname?" do 7 | not facter['productname'].nil? 8 | end 9 | 10 | precondition "is R720?" do 11 | facter['productname'].match(/720/i) 12 | end 13 | 14 | init do 15 | log "Fetching and installing 'setupbios' RPM" 16 | install :rpm, "setupbios" 17 | end 18 | 19 | run do 20 | log "Applying bios settings to this R720..." 21 | { 22 | 'AcPwrRcvry' => 'On', 23 | 'AcPwrRcvryDelay' => 'random', 24 | 'SerialComm' => 'onconredircom2', 25 | 'SerialPortAddress' => 'serial1com1serial2com2', 26 | 'ExtSerialConnector' => 'serial1', 27 | 'FailSafeBaud' => '115200', 28 | 'ConTermType' => 'vt100vt220', 29 | 'RedirAfterBoot' => 'disable', 30 | 'extserial' => 'com1', 31 | 'BootMode' => 'bios', 32 | 'IntNic1Port1BootProto' => 'pxe', 33 | 'IntNic1Port2BootProto' => 'pxe', 34 | 'IntNic1Port3BootProto' => 'pxe', 35 | 'IntNic1Port4BootProto' => 'pxe', 36 | 'BootSeq' => 'NIC.Integrated.1-1-1,NIC.Integrated.1-2-1,NIC.Integrated.1-3-1,NIC.Integrated.1-4-1,HardDisk.List.1-1', 37 | 'SysProfile' => 'perfoptimized' 38 | }.map do |key,value| 39 | self.apply_bios_setting key, value 40 | end 41 | end 42 | 43 | def self.apply_bios_setting key, value 44 | log "Setting bios setting \"#{key}\" to value \"#{value}\"..." 45 | run_cmd("/opt/dell/toolkit/bin/syscfg --#{key}=#{value}") 46 | end 47 | end 48 | 49 | -------------------------------------------------------------------------------- /tasks/DSL.md: -------------------------------------------------------------------------------- 1 | # Tasks DSL 2 | 3 | Genesis tasks are ruby code extended with a small DSL described below. 4 | 5 | ## Blocks 6 | 7 | A task should contain one or more of the following blocks which are run in this order: 8 | 9 | 1. `precondition` [1] - test that must succeed before this task can be considered for runing 10 | 2. `init` [2] - sets up the tasks environment before execution 11 | 3. `condition` [1] - tests for for things being ready to run 12 | 4. `run` [3] - the retriable action 13 | 5. `success` [3] - this runs if the action succeeded 14 | 6. `rollback` [3] - this runs if the action failed 15 | 16 | * [1] the task is skipped if any of these returns false. They take a **description** as their first argument. 17 | * [2] return result ignored 18 | * [3] the task fails, i.e. returns false, if any of these fail 19 | 20 | Also, if an `exception` happens while executing any block, the task will fail. 21 | 22 | ## Run block options 23 | 24 | The `run` block will be retried if the `retries` options is set and 25 | must finish within `timeout` seconds. 26 | 27 | The following are helper function and options available in run blocks to help 28 | out with performing the task. 29 | 30 | * `retries count` 31 | 32 | Takes an argument which is either an Enumerator, with a list of timeouts for 33 | each retry, or a count which is the same as [0 .. (count-1)]. The default value 34 | of [0, 1, 2]. 35 | 36 | Example: 37 | [AssetCreation.rb](https://github.com/tumblr/genesis/blob/master/tasks/AssetCreation.rb#L4) 38 | 39 | * `timeout secs` 40 | 41 | Sets the number of seconds to wait for a `run` block to complete, default 0. 42 | 43 | * `run_cmd *cmd, stdin_data: '', return_both_streams: false, return_merged_streams: 44 | false` 45 | 46 | Will execute an external command (like open3) logging errors and returning the 47 | output. 48 | 49 | Example: 50 | [TimedBurnin.rb](https://github.com/tumblr/genesis/blob/master/tasks/TimedBurnin.rb#L31) 51 | Function arguments: 52 | 53 | * `config` 54 | 55 | Gives access to the Genesis configuration information. 56 | 57 | Example: 58 | [BiosConfigrC6105.rb](https://github.com/tumblr/genesis/blob/master/tasks/BiosConfigrC6105.rb#L15) 59 | 60 | * `log message` 61 | 62 | Logs a message. 63 | 64 | Example: 65 | [AssetCreation.rb](https://github.com/tumblr/genesis/blob/master/tasks/AssetCreation.rb#L20) 66 | 67 | * `install provider, *what` 68 | 69 | Uses either the **yum** provider or the **gem** provider to (possibly) install 70 | software. 71 | 72 | When using the **gem** provider genesis will also try to require the gems. If 73 | the name of your gem does not match what needs to be required, you can specify 74 | paths to require like this: 75 | ``` 76 | install :gem, 'gem1', 'gem2' => ['gem2/foo', 'gem2/bar'] 77 | ``` 78 | This will install `gem1` and `gem2` and require `gem1`, `gem2/foo` and 79 | `gem2/bar`. 80 | 81 | Example: 82 | [TimedBurnin.rb](https://github.com/tumblr/genesis/blob/master/tasks/TimedBurnin.rb#L13) 83 | 84 | * `fetch what, filename, base_url: ENV['GENESIS_URL']` 85 | 86 | Downloads a file from a remote server to the specified filename, retrying if 87 | needed. 88 | 89 | Example: 90 | [BiosConfigrC6105.rb](https://github.com/tumblr/genesis/blob/master/tasks/BiosConfigrC6105.rb#L17) 91 | 92 | * `tmp_path filename` 93 | 94 | Returns a temporary file with the specified name. 95 | 96 | `tmp_path filename` returns a temporary file named *filename* 97 | 98 | `description "My task description here"` sets the description of the Task, to appear in things like ```rake -T``` 99 | 100 | ## Targets and Tasks 101 | 102 | Tasks are grouped into top-level "targets", similar to systemd targets. You can specify what tasks belong to what target (and the order they execute in) in ```targets.yaml```, bundled in your tasks directory. 103 | 104 | Example: 105 | [BiosConfigrC6105.rb](https://github.com/tumblr/genesis/blob/master/tasks/BiosConfigrC6105.rb#L22) 106 | -------------------------------------------------------------------------------- /tasks/FixDellFatPartitions.rb: -------------------------------------------------------------------------------- 1 | class FixDellFatPartitions 2 | include Genesis::Framework::Task 3 | 4 | description "Wipe any existing FAT partitions on all disks for Dells" 5 | 6 | precondition "is Dell?" do 7 | facter['manufacturer'].match(/dell/i) 8 | end 9 | 10 | precondition "is c6220 or r720?" do 11 | facter['productname'].match(/6220/i) or facter['productname'].match(/720/i) 12 | end 13 | 14 | init do 15 | install :rpm, 'parted' 16 | end 17 | 18 | condition "has broken fat partitions?" do 19 | output = run_cmd "/sbin/blkid | grep 'vfat' | grep -i 'DellUtility' || true" 20 | output.lines.count > 0 21 | end 22 | 23 | run do 24 | log "Grabbing the list of all FAT partitions on the system" 25 | fat_partitions = run_cmd("/sbin/blkid | grep 'vfat' | awk -F ':' '{print $1}'").lines 26 | fat_partitions.each do |part| 27 | matches = part.match(/\/dev\/(sd[a-z])(\d)/) 28 | if matches 29 | log "Found partition: " + part 30 | device = matches[1] 31 | partition_id = matches[2] 32 | 33 | is_usb = run_cmd("readlink $(dirname $(dirname $(ls -d /sys/bus/scsi/devices/**/block/" + device + "))) | grep -i 'usb2' || true").lines.count > 0 34 | 35 | if(!is_usb) 36 | log "Partition is not on a USB drive, so nuking..." 37 | run_cmd("/sbin/parted -s /dev/" + device + " rm " + partition_id) 38 | log "Partition successfully nuked..." 39 | end 40 | end 41 | end 42 | end 43 | 44 | end 45 | 46 | -------------------------------------------------------------------------------- /tasks/IpmiStart.rb: -------------------------------------------------------------------------------- 1 | class IpmiStart 2 | include Genesis::Framework::Task 3 | 4 | description "Start up the IPMI services" 5 | 6 | init do 7 | install :rpm, 'dmidecode', 'OpenIPMI', 'OpenIPMI-tools', 'syscfg' 8 | end 9 | 10 | condition "ipmi service present?" do 11 | %w[/etc/init.d/ipmi].all? do |f| 12 | x = File.executable? f 13 | log "#{f} missing!" unless x 14 | x 15 | end 16 | end 17 | 18 | run do 19 | run_cmd('service ipmi start') 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /tasks/README.md: -------------------------------------------------------------------------------- 1 | # Tasks and Targets 2 | 3 | This directory contains sample tasks. written in ruby, and an example 4 | Rakefile to control their execution. 5 | 6 | Documentaion on coding tasks can be found in the DSL.md file. 7 | 8 | ## Targets Configuration 9 | 10 | Tasks are grouped into top-level targets, which define the order of task execution, and provide a way to boot into Genesis and execute a set of tasks. Targets are defined (by default, feel free to write your own targets.yaml and/or ```Rakefile```) in a ```targets.yaml```, which looks something like the following: 11 | 12 | ``` 13 | intake: 14 | description: Perform intake on hardware 15 | tasks_array: &intake_tasks 16 | - SetupNTP 17 | - IpmiStart 18 | - AssetCreation 19 | - BiosConfigrC6105 20 | - BiosConfigrR720 21 | - FixDellFatPartitions 22 | tasks: 23 | - *intake_tasks 24 | - Reboot 25 | burnin: 26 | description: Cook CPUs 27 | tasks: &burnin_tasks 28 | - TimedBurnin 29 | reboot: 30 | description: Reboot the machine 31 | tasks: 32 | - Reboot 33 | shutdown: 34 | description: Halt the machine 35 | tasks: 36 | - Shutdown 37 | classic: 38 | description: Run intake, burnin, then shutdown 39 | tasks: 40 | - *intake_tasks 41 | - *burnin_tasks 42 | - Shutdown 43 | util: 44 | description: Utility Shell 45 | tasks: [] 46 | ``` 47 | 48 | Each top level key is a target name, containing an optional description with the key ```description```, and the required key ```tasks``` is a list list of classes to run. In the provided ```Rakefile```, we flatten the ```tasks``` so you can leverage tagged lists in YAML to leverage some "inheritance", as you can see in the ```classic``` target. Tasks are executed sequentially, and ordering ```[A,B,C]``` implies that task C cannot be run until A and then B are run. 49 | 50 | ## Running Targets 51 | 52 | The ```targets.yaml``` is used by the provided Rakefile to create a list of tasks, and handle setting up their dependencies. Genesis will automatically launch the rake task specified by the ```GENESIS_MODE``` kernel parameter passed via ipxe. 53 | 54 | For example, booting your kernel with ```GENESIS_MODE=intake``` will automatically execute the ```intake``` rake task when Genesis is booted. 55 | 56 | To test your ordering, you could run something like 57 | 58 | user@host tasks $ DRYRUN=true GENESIS_CONFIG=../myconfig.yaml GENESIS_TASKS_DIR=. GENESIS_LOG_DIR=/tmp rake intake 59 | 60 | 61 | ## Customizing for your environment 62 | 63 | We provide a reasonable ```Rakefile``` that can consume ```targets.yaml``` that you are free to extend for your own purposes. However, nothing is preventing you from writing your own ```Rakefile``` that totally ignores the YAML config, and instead does crazy things like parallel tasks, complex dependency ordering, etc. 64 | 65 | Tumblr uses Genesis by maintaining a separate repository of our tasks and targets.yaml that are specific to our hardware and business needs. Genesis config.yaml's ```tasks_url``` points to the deployed tar.gz of our tasks bundle (rakefile and targets.yaml inclusive). 66 | 67 | We welcome any contributions for tasks that are generally useful to the community into the public repository. 68 | 69 | ## Facts, Modules, and Helpers 70 | 71 | Each task has its ```LOAD_PATH``` expanded so it may include extras under the ```modules/``` path. We have provided some example facts (available in Tasks with the ```facter``` method) that you can extend. 72 | 73 | Other shared ruby mixins/helpers/what-have-you code can be loaded when bundled with your tasks when placed under ```modules```. 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tasks/Rakefile: -------------------------------------------------------------------------------- 1 | require 'genesisframework' 2 | require 'yaml' 3 | 4 | # support testing using rake(1) directly 5 | 6 | root_dir = ENV['GENESIS_ROOT'] || '/var/run/genesis' 7 | tasks_dir = ENV['GENESIS_TASKS_DIR'] || File.join(root_dir,'tasks') 8 | log_dir = ENV['GENESIS_LOG_DIR'] || '/var/log/genesis' 9 | config_file = ENV['GENESIS_CONFIG'] || File::join(root_dir, 'config.yaml') 10 | dry_run = ENV.has_key? 'DRYRUN' 11 | 12 | puts 'DRY RUN set' if dry_run 13 | 14 | # TODO remove dead code once transition to OSS genesis is complete 15 | begin 16 | # closed source genesis 17 | Genesis::Framework::Tasks.parse_config( 18 | Genesis::Framework::Utils.tmp_path("genesis_config.json")) 19 | rescue 20 | # open source genesis 21 | Genesis::Framework::Tasks.load_config(config_file) 22 | end 23 | 24 | # a place to store timestamps of when the task last ran 25 | `mkdir -p #{log_dir}` unless FileTest::directory?(log_dir) 26 | 27 | # load up the targets.yaml 28 | targets_file = File.join(tasks_dir,'targets.yaml') 29 | raise "No targets.yaml file found in the task bundle at #{tasks_dir}! You need to provide a targets.yaml to define targets and tasks" unless File.readable? targets_file 30 | begin 31 | target_config = YAML.load_file(targets_file) 32 | # in order to support joining arrays of tasks in yaml with !join, lets flatten every tasks list 33 | target_config.each do |target_name, target| 34 | raise "Target #{target_name} is missing a 'tasks' key! If you meant no tasks, use []" if target['tasks'].nil? 35 | target['tasks'].flatten! unless target['tasks'].nil? 36 | end 37 | rescue => e 38 | raise "Unable to parse targets definitions (#{targets_file}): #{e.message}" 39 | end 40 | 41 | Genesis::Framework::Tasks.load_tasks(tasks_dir) 42 | 43 | target_config.each do |target_name, config| 44 | # translate tasks into their loaded modules 45 | tasks_as_syms = (config['tasks'] || []).map(&:to_sym) 46 | tasks = tasks_as_syms.map do |task_name| 47 | begin 48 | Genesis::Framework::Tasks.const_get(task_name) 49 | rescue NameError => e 50 | raise "Unable to find task #{task_name} in target #{target_name} that includes Genesis::Framework::Task! Is it part of your tasks bundle? #{e.message}" 51 | end 52 | end 53 | 54 | # create the master rake task for this target 55 | desc (config['description'] || "#{target_name} target") 56 | task target_name => "#{target_name}:all" 57 | 58 | # namespace underneath target to prevent creating duplicate tasks with 59 | # the same names to avoid running tasks multiple times if they appear in 60 | # multiple targets 61 | namespace target_name do 62 | task :all => tasks_as_syms 63 | # create tasks for all child tasks of the target 64 | tasks.each_with_index do |tsk,i| 65 | task_name = tasks_as_syms[i] 66 | prev_task_name = tasks_as_syms[i-1] unless i==0 67 | desc tsk.options.fetch(:description, task_name) 68 | task task_name do 69 | if dry_run 70 | puts " not running #{target_name}:#{task_name} in dry-run" 71 | else 72 | unless Genesis::Framework::Tasks.execute task_name 73 | puts 'ERROR: task failed - aborting target' 74 | exit(1) 75 | end 76 | end 77 | end 78 | # create the dependency chain if there is a next task 79 | task(task_name => prev_task_name) unless prev_task_name.nil? 80 | end 81 | end 82 | end 83 | 84 | 85 | #TODO remove this, or convert it to logging to a file 86 | # each task should indicate when it ran (last) 87 | Rake::Task.tasks.each do |t| 88 | t.enhance do 89 | touch File.join(log_dir, t.name + '.ran') 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /tasks/Reboot.rb: -------------------------------------------------------------------------------- 1 | class Reboot 2 | include Genesis::Framework::Task 3 | run do 4 | log "Rebooting machine..." 5 | run_cmd '/sbin/reboot -nf' 6 | log "Sleeping waiting for reboot" 7 | sleep 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /tasks/SetupNTP.rb: -------------------------------------------------------------------------------- 1 | class SetupNTP 2 | include Genesis::Framework::Task 3 | 4 | precondition "has ntp server?" do 5 | not config['ntp_server'].nil? 6 | end 7 | 8 | init do 9 | install :rpm, "ntpdate", "util-linux-ng" 10 | prod = facter['productname'].nil? ? 'UNKNOWN' : facter['productname'] 11 | log "Executing ntp setup for model #{prod} and asset #{facter['asset_tag']}" 12 | end 13 | 14 | run do 15 | run_cmd "/usr/sbin/ntpdate -u -b #{config['ntp_server']}" 16 | log "System time synced with #{config['ntp_server']}" 17 | run_cmd "/sbin/hwclock -u --systohc" 18 | log "Wrote system time to hardware clock" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /tasks/Shutdown.rb: -------------------------------------------------------------------------------- 1 | class Shutdown 2 | include Genesis::Framework::Task 3 | run do 4 | log "Shutting down machine..." 5 | run_cmd '/sbin/shutdown', '-h', 'now' 6 | log "Sleeping waiting for shutdown" 7 | sleep 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /tasks/TimedBurnin.rb: -------------------------------------------------------------------------------- 1 | class TimedBurnin 2 | include Genesis::Framework::Task 3 | 4 | description "Performs burnin for a specified duration" 5 | 6 | precondition "has asset tag?" do 7 | not facter['asset_tag'].nil? 8 | end 9 | 10 | precondition "is running in burnin mode?" do 11 | ENV['GENESIS_MODE'] == "burnin" or ENV['GENESIS_MODE'] == "classic" 12 | end 13 | 14 | init do 15 | install :rpm, "breakin", "hpl", "screen" 16 | end 17 | 18 | run do 19 | burnin_duration = 48 20 | burnin_cmd = "/usr/bin/screen -dmS genesis_burnin /etc/breakin/startup.sh" 21 | 22 | log "Executing new system burnin for #{burnin_duration} hours..." 23 | 24 | start = Time.now 25 | 26 | begin 27 | log "Burnin starting via command: #{burnin_cmd}" 28 | 29 | # this is a print rather than a log as Collins has timestamps already 30 | print "Burnin started at: #{Time.now}" 31 | 32 | # turning on the IPMI light to blinking 33 | run_cmd("/usr/bin/ipmitool chassis identify 255") 34 | 35 | # begin burnin process 36 | run_cmd(burnin_cmd) 37 | 38 | # for a simple logging buffer, we log every 39 | # logging_interval loops of the below do/while loop 40 | # (aka every 2hrs with current 3m sleep duration) 41 | logging_interval = 40 42 | # starting at 39 so it will write a log after first sleep duration 43 | logging_counter = 39 44 | 45 | # this has to stay below ~200 seconds to be safe as the max time we 46 | # can pass to the ipmitool is 255 for it to keep the light blinking 47 | sleep_duration = 3*60 48 | 49 | loop_continue = true 50 | 51 | begin 52 | sleep sleep_duration 53 | 54 | logging_counter += 1 55 | runtime_seconds = Time.now - start 56 | runtime_hours = runtime_seconds.to_i / 3600 57 | 58 | self.parse_breakin_state 59 | successful_tests = self.get_breakin_success_count 60 | failed_tests = self.get_breakin_failure_count 61 | 62 | if failed_tests > 0 63 | 64 | run_cmd("/usr/bin/ipmitool chassis identify force") 65 | 66 | self.log_failed_tests 67 | 68 | loop_continue = false 69 | 70 | else 71 | 72 | # refresh the IPMI blinker fluid 73 | run_cmd("/usr/bin/ipmitool chassis identify 255") 74 | 75 | if logging_counter > logging_interval 76 | begin 77 | log "Burnin has been executing for #{runtime_hours} hours with #{successful_tests} tests ran successfully..." 78 | rescue Exception 79 | # if collins is down, don't blow up the Burnin process 80 | # we only rescue this one and not the other logging lines 81 | # as this one is the only one which repeats multiple times 82 | # during execution and we don't "want" it to break on failure 83 | end 84 | 85 | logging_counter = 0 86 | end 87 | 88 | if runtime_hours >= burnin_duration 89 | # turn off the IPMI light 90 | run_cmd("/usr/bin/ipmitool chassis identify 0") 91 | 92 | # tell Collins we are done so we don't run Burnin automatically again on reboot 93 | collins.set_attribute!(facter['asset_tag'], :BURNIN_COMPLETE, true) 94 | 95 | log "Burnin complete! Machine will power off in 30 seconds (unless you hit cntrl-c now)..." 96 | sleep 30 97 | run_cmd("shutdown -h now") 98 | end 99 | 100 | end 101 | 102 | end while loop_continue 103 | 104 | rescue Exception => e 105 | # turn the IPMI light on solid 106 | run_cmd("/usr/bin/ipmitool chassis identify force") 107 | log "The burnin process threw an exception... #{e.message}" 108 | end 109 | end 110 | 111 | def self.parse_breakin_state 112 | begin 113 | breakin_data_file = '/var/run/breakin.dat' 114 | @@breakin_state = Hash[File.read(breakin_data_file).scan(/(.+?)="(.*)"\n/)] 115 | rescue Exception => e 116 | log "Caught exception #{e.message} while trying to parse breakin state file!" 117 | @@breakin_state ||= {} 118 | end 119 | end 120 | 121 | def self.get_breakin_success_count 122 | total_success = 0 123 | burnin_qty = @@breakin_state["BURNIN_QTY"].to_i - 1 124 | 125 | for test_id in 0..burnin_qty 126 | total_success += @@breakin_state["BURNIN_#{test_id}_PASS_QTY"].to_i 127 | end 128 | 129 | total_success 130 | end 131 | 132 | def self.get_breakin_failure_count 133 | @@breakin_state["BURNIN_TOTAL_FAIL_QTY"].to_i 134 | end 135 | 136 | def self.log_failed_tests 137 | burnin_qty = @@breakin_state["BURNIN_QTY"].to_i - 1 138 | 139 | for test_id in 0..burnin_qty 140 | test_failures = @@breakin_state["BURNIN_#{test_id}_FAIL_QTY"].to_i 141 | if test_failures > 0 142 | test_name = @@breakin_state["BURNIN_#{test_id}_NAME"] 143 | test_successes = @@breakin_state["BURNIN_#{test_id}_PASS_QTY"].to_i 144 | log "Burnin test #{test_name} failed #{test_failures} times and succeeded #{test_successes} times..." 145 | end 146 | end 147 | 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /tasks/modules/README.md: -------------------------------------------------------------------------------- 1 | This directory should be pushed onto the ```$LOAD_PATH``` by something (Task framework module?) so facts under ```./facter``` are loaded correctly. 2 | -------------------------------------------------------------------------------- /tasks/modules/facter/asset_tag.rb: -------------------------------------------------------------------------------- 1 | Facter.add(:raw_asset_tag) do 2 | setcode do 3 | product = Facter.value(:productname) 4 | case product 5 | when /R720/i 6 | # FRU in R720 is shady, we cant use dmidecode or ipmitool fru print reliably 7 | output = Facter::Util::Resolution.exec('/opt/dell/toolkit/bin/syscfg --asset') 8 | output.split('=')[1].strip 9 | else 10 | # we will just fall back to dmidecode 11 | Facter::Util::Resolution.exec('/usr/sbin/dmidecode -s chassis-asset-tag') 12 | end 13 | end 14 | end 15 | 16 | Facter.add(:asset_tag) do 17 | # this will be nil if the claimed asset tag doesnt match our required naming scheme 18 | setcode do 19 | tag = Facter.value(:raw_asset_tag) 20 | unless tag.nil? 21 | tag.strip! 22 | case tag 23 | when /^(\d{6})$/ 24 | $1 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /tasks/modules/facter/bmc_firmware_version.rb: -------------------------------------------------------------------------------- 1 | # dell bmc firmware version 2 | Facter.add(:bmc_firmware_version) do 3 | setcode do 4 | vendor = Facter.value(:manufacturer) 5 | case vendor 6 | when /dell/i 7 | bmc_firmware_version = Facter::Util::Resolution.exec("/usr/bin/ipmitool mc info | sed -n '/Firmware Revision/p' | tr -d [:space:] | cut -d':' -f2") 8 | bmc_firmware_version.strip 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /tasks/modules/facter/mode.rb: -------------------------------------------------------------------------------- 1 | Facter.add(:mode) do 2 | setcode do 3 | cmdline = File.new("/proc/cmdline", "r").gets 4 | md = /GENESIS_MODE=(\S*)/.match(cmdline) 5 | md.captures[0] 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /tasks/modules/facter/system_serial.rb: -------------------------------------------------------------------------------- 1 | # dell system serial 2 | Facter.add(:service_tag) do 3 | setcode do 4 | vendor = Facter.value(:manufacturer) 5 | case vendor 6 | when /dell/i 7 | system_serial = Facter::Util::Resolution.exec('/usr/sbin/dmidecode -s system-serial-number') 8 | system_serial.strip 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /tasks/modules/helpers/dell/ipmi.rb: -------------------------------------------------------------------------------- 1 | module Helpers 2 | module Dell 3 | class IPMI 4 | 5 | RESET_DELAY = 15 6 | 7 | def self.log message 8 | Genesis::Framework::Utils.log "Helpers::Dell::IPMI", message 9 | end 10 | 11 | def self.configure_bmc_ipmi(asset) 12 | %W{ 13 | Lan_Conf:IP_Address_Source=Static 14 | Lan_Conf:IP_Address=#{asset.ipmi.address} 15 | Lan_Conf:Subnet_Mask=#{asset.ipmi.netmask} 16 | Lan_Conf:Default_Gateway_IP_Address=#{asset.ipmi.gateway} 17 | Lan_Channel:Volatile_Access_Mode=Always_Available 18 | Lan_Channel:Non_Volatile_Access_Mode=Always_Available 19 | User1:Password=#{asset.ipmi.password} 20 | User2:Password=#{asset.ipmi.password} 21 | }.map do |conf| 22 | self.log "Setting BMC #{conf}" 23 | Kernel.system("/usr/sbin/bmc-config -c -e '#{conf}'") 24 | end.all? 25 | end 26 | 27 | def self.verify_bmc_ipmi(asset) 28 | { 29 | 'IP_Address' => asset.ipmi.address, 30 | 'Subnet_Mask' => asset.ipmi.netmask, 31 | 'Default_Gateway_IP_Address' => asset.ipmi.gateway 32 | }.map do |attr,conf| 33 | output = %x{/usr/sbin/bmc-config -o -e 'Lan_Conf:#{attr}'} 34 | line = output.lines.grep(/^[\s]*#{attr}/).first 35 | if line.nil? 36 | self.log "Error: Unable to find #{attr}" if line.nil? 37 | false 38 | else 39 | begin 40 | val = line.split(/\s+/)[2] 41 | comp = val == conf 42 | self.log "Error: Found #{val} for #{attr}, but expected #{conf}" unless comp 43 | comp 44 | rescue 45 | self.log "Error: unable to determine config setting for #{attr}. Got #{line.inspect}" 46 | false 47 | end 48 | end 49 | end.all? {|x| x} 50 | end 51 | 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /tasks/modules/logging/collins.rb: -------------------------------------------------------------------------------- 1 | module Logging 2 | module Collins 3 | 4 | def self.collins 5 | Genesis::Framework::Utils.collins 6 | end 7 | 8 | def self.facter 9 | Genesis::Framework::Utils.facter 10 | end 11 | 12 | def self.log message, level = nil 13 | if facter['asset_tag'] 14 | collins.log! facter['asset_tag'], message, level 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /tasks/modules/logging/syslog.rb: -------------------------------------------------------------------------------- 1 | require 'syslog' 2 | 3 | module Logging 4 | module Syslog 5 | 6 | def self.log message, level = nil 7 | # Tumbler Collin severity mapping: 8 | # - http://www.rubydoc.info/gems/collins_client/0.2.17/Collins/Api/Logging/Severity 9 | # 10 | # Compared to Syslog, Collins has an additional level 'note', for an operator input. 11 | # We should not come into this level via Genesis. 12 | # Anyway, handle it via the default level. 13 | 14 | # Default 15 | if (level.nil? || level.empty?) 16 | severity = ::Syslog::LOG_INFO 17 | else 18 | severity = case level.downcase.to_sym 19 | when :emergency 20 | ::Syslog::LOG_EMERG 21 | when :alert 22 | ::Syslog::LOG_ALERT 23 | when :critical 24 | ::Syslog::LOG_CRIT 25 | when :error 26 | ::Syslog::LOG_ERR 27 | when :error 28 | ::Syslog::LOG_WARNING 29 | when :notice 30 | ::Syslog::LOG_NOTICE 31 | when :debug 32 | ::Syslog::LOG_DEBUG 33 | else 34 | ::Syslog::LOG_INFO 35 | end 36 | end 37 | 38 | ::Syslog.open("genesis", ::Syslog::LOG_PID, ::Syslog::LOG_USER) unless ::Syslog.opened? 39 | ::Syslog.log(severity, message) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /tasks/modules/mixins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/tasks/modules/mixins/.gitkeep -------------------------------------------------------------------------------- /tasks/targets.yaml: -------------------------------------------------------------------------------- 1 | intake: 2 | description: Perform intake on hardware 3 | tasks_array: &intake_tasks 4 | - SetupNTP 5 | - IpmiStart 6 | - AssetCreation 7 | - BiosConfigrC6105 8 | - BiosConfigrR720 9 | - FixDellFatPartitions 10 | tasks: 11 | - *intake_tasks 12 | - Reboot 13 | burnin: 14 | description: Cook CPUs 15 | tasks: &burnin_tasks 16 | - TimedBurnin 17 | reboot: 18 | description: Reboot the machine 19 | tasks: 20 | - Reboot 21 | shutdown: 22 | description: Halt the machine 23 | tasks: 24 | - Shutdown 25 | classic: 26 | description: Run intake, burnin, then shutdown 27 | tasks: 28 | - *intake_tasks 29 | - *burnin_tasks 30 | - Shutdown 31 | util: 32 | description: Utility Shell 33 | tasks: [] 34 | -------------------------------------------------------------------------------- /testenv/README.md: -------------------------------------------------------------------------------- 1 | # Genesis test environment 2 | 3 | This is the test environment for Genesis which allows you to run 4 | end-to-end testing of changes without risking accidentally taking down 5 | the production setup. It uses two or three virtual machines: 6 | 7 | * a bootbox used to support netbooting 8 | * a target host which runs genesis 9 | 10 | The bootbox and snooper are managed by Vagrant for ease of 11 | provisioning while target host is naked machine based on a Virtualbox 12 | .ova file. 13 | 14 | The target host uses netbooting via a downloaded iPXE image to more 15 | accurately reflect the typical production boot ROMs which only support 16 | PXE. 17 | 18 | You can use the test environment to build the genesis image. 19 | 20 | ## Requirements: 21 | 22 | * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (We currently develop with version 4.3.20 and have tested with prior versions up to 4.3.10) 23 | * [Vagrant](https://www.vagrantup.com/downloads.html) 24 | * Network access to fileserver (or having the Vagrant basebox previously installed) 25 | 26 | ## Configuration: 27 | 28 | The testenv has configuration options available in vagrant and puppet 29 | 30 | You can customize these in 31 | 32 | 1. Vagrantfile 33 | 2. bootbox/puppet/manifests/bootbox.pp 34 | 3. bootbox/puppet/modules/genesis/templates/config.yaml.erb 35 | 4. bootbox/puppet/modules/genesis/templates/stage2.erb. 36 | 37 | and modify settings as desired. While the current values of the options 38 | allow you to proceed in the test environment without updates by executing 39 | ```vagrant up```, you MUST provide production settings for 40 | 41 | * a DHCP server, 42 | * a TFTP server, 43 | * and a file server (serving static files over HTTP) 44 | * RPM repository that includes RPMs required by Genesis tasks 45 | 46 | in ```bootbox/puppet/manifests/bootbox.pp``` 47 | 48 | ## Setup: 49 | 50 | 1. Import the testnode.ova virtual machine image into Virtualbox 51 | 2. Go into the testenv/bootbox folder and run ```vagrant up``` 52 | 3. Once the vagrant machine is running, use it to build the gems for the boot 53 | image. See also 54 | [src/README](https://github.com/tumblr/genesis/blob/master/src/README.md). 55 | ``` 56 | [vagrant@genesis-bootbox ~]$ cd /genesis/src/ 57 | [vagrant@genesis-bootbox src]$ for gem in framework promptcli retryingfetcher; do cd $gem; gem build "genesis_$gem.gemspec"; cd ..; done 58 | WARNING: no rubyforge_project specified 59 | Successfully built RubyGem 60 | Name: genesis_framework 61 | Version: 0.5.2 62 | File: genesis_framework-0.5.2.gem 63 | WARNING: no rubyforge_project specified 64 | Successfully built RubyGem 65 | Name: genesis_promptcli 66 | Version: 0.2.0 67 | File: genesis_promptcli-0.2.0.gem 68 | WARNING: no rubyforge_project specified 69 | Successfully built RubyGem 70 | Name: genesis_retryingfetcher 71 | Version: 0.4.0 72 | File: genesis_retryingfetcher-0.4.0.gem 73 | ``` 74 | 4. Follow the [bootcd build 75 | instructions](https://github.com/tumblr/genesis/blob/master/bootcd/README.md) 76 | to build the images. 77 | 5. Start the imported virtual machine and it will network boot from the vagrant box 78 | 79 | ## Notes: 80 | 81 | * All packages needed on the bootbox Vagrant VM to simulate the prod env are installed via puppet apply. See the puppet dir inside bootbox/ to see the manifests applied to the VM on startup. The puppet manifests applied to the VM on startup are in [bootbox](https://github.com/tumblr/genesis/tree/master/testenv/bootbox) 82 | * To apply puppet changes to a running bootbox, use `vagrant provision` 83 | * Network booting goes across a virtualbox private network named 'genesis' 84 | * Password for the bootbox follows normal vagrant scheme and can be ssh'd into via vagrant ssh 85 | * vagrant sets up sharing of this directory tree under /genesis on the genesis-bootbox 86 | 87 | ## Bootbox details: 88 | 89 | ### Vagrant specification: 90 | 91 | We only cover some of the important configuration options from the vagrant file. Please see [vagrant docs](https://docs.vagrantup.com/v2/vagrantfile/) for an exhaustive list. 92 | 93 | `config.vm.box = "sl-base-v4.3.10"` 94 | 95 | Configures the vm to be brought up with sl-base-v4.3.10 96 | 97 | `config.vm.network "private_network", ip: "192.168.33.10", virtualbox__intnet: "genesis" ` 98 | 99 | Configures the genesis network on the machines 100 | 101 | `config.vm.synced_folder "bootbox-shared", "/vagrant"` 102 | 103 | `config.vm.synced_folder "../../", "/genesis"` 104 | 105 | `config.vm.synced_folder "web", "/web"` 106 | 107 | Sync testenv folders on your host machine 108 | 109 | The bootbox runs the following services: 110 | * dhcp 111 | * tftp 112 | * a file server (serving static files over HTTP) 113 | 114 | ### Target startup details: 115 | 116 | The following details have a line of descriptive text, details on what the bootbox service does, and other files under puppet/ which are involed 117 | 118 | 1. VirtualBox iPXE asks dhcp what to do 119 | dhcpd says load /tftpboot/undionly.kpxe from @genesis_ipaddress via tftp 120 | dhcp server.pp dhcpd.conf.erb 121 | 2. iPXE/undionly.kpxe asks dhcp what to do 122 | dhcpd says ipxe load filename http://@genesis_ipaddress/testenv/menu.ipxe 123 | genesis.pp menu.erb 124 | 3. iPXE menu boots genesis image 125 | 4. kernel loads and starts 126 | 5. On boot-up, genesis-bootloader in launched via a specification in [/root/.bash_profile](https://github.com/tumblr/genesis/blob/master/bootcd/rpms/genesis_scripts/src/root-bash_profile) 127 | 5. genesis-bootloader downloads config.yaml and stage2 using the [bootbox file server](https://github.com/tumblr/genesis/blob/master/testenv/bootbox/web/genesis.rb) then starts stage2 128 | 6. [Stage2](https://github.com/tumblr/genesis/blob/master/testenv/bootbox/puppet/modules/genesis/templates/stage2.erb.sample) includes site specific genesis startup. setup yum repos, load framework gem, download tasks, start genesis task 129 | 130 | * How to test or develop 131 | 132 | Following is basic information about testing or developing the different parts of genesis and the test environment. 133 | 134 | * Updating a Gem 135 | - Modify source 136 | - update version number in .gemspec file 137 | - gem build ...gemspec 138 | - cp .gem file to testenv/bootbox/bootbox-shared/ 139 | - vagrant ssh into the genesis bootbox 140 | - gem install /vagrant/...gem --no-rdoc --no-ri 141 | - boot the target 142 | 143 | * Modifying Stage2 144 | - edit testenv/bootbox/puppet/modules/genesis/templates/stage2.erb 145 | - ```vagrant up``` or ```vagrant provision``` to install it on bootbox 146 | - boot the target 147 | 148 | * genesis-bootstrap 149 | - edit bootcd/rpms/bootloader/bin/genesis-bootloader 150 | - cp genesis-bootloader testenv/bootbox/bootbox-shared 151 | - run /vagrant/genesis-bootloader on snooper node 152 | -------------------------------------------------------------------------------- /testenv/bootbox/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | bootbox-shared 3 | -------------------------------------------------------------------------------- /testenv/bootbox/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | config.vm.box = "sl-base-v4.3.10" 9 | config.vm.box_url = "https://www.dropbox.com/s/pvydgdcnf4o00im/sl-base-v4.3.10.box?dl=1" 10 | 11 | config.vm.hostname = 'genesis-bootbox' 12 | 13 | config.ssh.forward_agent = true 14 | 15 | config.vm.network "private_network", ip: "192.168.33.10", virtualbox__intnet: "genesis" 16 | config.vm.network "forwarded_port", guest: 80, host: 33008 17 | config.vm.network "forwarded_port", guest: 8000, host: 33800 18 | config.vm.network "forwarded_port", guest: 8080, host: 33808 19 | 20 | config.vm.synced_folder "bootbox-shared", "/vagrant" 21 | config.vm.synced_folder "../../", "/genesis" 22 | config.vm.synced_folder "web", "/web" 23 | 24 | config.vm.provision "puppet" do |puppet| 25 | puppet.manifests_path = "puppet/manifests" 26 | puppet.manifest_file = "bootbox.pp" 27 | puppet.module_path = "puppet/modules" 28 | end 29 | 30 | config.vm.provider "virtualbox" do |v| 31 | v.name = "Genesis-bootbox" 32 | # minimum should be considered to be 2048, but feel free to use any 33 | # value between 2048 and half your available system memory 34 | v.memory = 2048 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /testenv/bootbox/bootbox-shared/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/testenv/bootbox/bootbox-shared/.gitignore -------------------------------------------------------------------------------- /testenv/bootbox/puppet/manifests/bootbox.pp: -------------------------------------------------------------------------------- 1 | # well known facts/values 2 | $genesis_ipaddress = '192.168.33.10' # must match Vagrantfile 3 | $collins_url = "https://collins.example.com" # FIXME 4 | $genesis_service = "${genesis_ipaddress}:8000" 5 | $genesis_user = '' 6 | $genesis_passw = '' 7 | # we're using Google and Level 3 resolvers here to get you started since we need 8 | # to reach rubygems. this should be your internal nameservers. 9 | $dhcp_dns_servers = '8.8.8.8, 4.4.2.2' # FIXME 10 | $nexusserver = 'nexus.example.com' # FIXME 11 | $file_service = "${nexusserver}:8888" 12 | $gem_service = "${nexusserver}:8808" 13 | $image_service = "${file_service}" 14 | $ntp_server = "ntp.example.com" # FIXME 15 | $rpm_server = 'repo.example.com' # FIXME 16 | $rpm_base_url = "http://${rpm_server}/mrepo/RPMS.epel" # FIXME 17 | 18 | # where static test configuration files are kept 19 | $testenv = 'testenv' 20 | 21 | # info that phil would supply 22 | $ipxe_config_url = "http://${genesis_ipaddress}:8888/${testenv}/config.yaml" 23 | $ipxe_menu_url = "http://${genesis_ipaddress}:8888/${testenv}/menu.ipxe" 24 | # to test non-published boot images switch kernel_source lines 25 | $kernel_source = "http://${genesis_ipaddress}:8888/ipxe-images" 26 | #$kernel_source = "http://${file_service}/genesis/ipxe-images" 27 | $ipxe_initrd = "${kernel_source}/genesis-initrd.img" 28 | $ipxe_kernel = "${kernel_source}/genesis-vmlinuz" 29 | $ipxe_kernel_flags = 'rootflags=loop root=live:/genesis.iso rootfstype=auto ro liveimg vga=791 rd_NO_LUKS rd_NO_MD rd_NO_DM console=tty0 console=ttyS1,115200' 30 | 31 | node default { 32 | include yumrepo 33 | include tftp::server 34 | include dhcp::server 35 | include bind::server 36 | include ipxe 37 | include iptables 38 | include genesis 39 | include gemserver 40 | include selinux::permissive 41 | } 42 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/bind/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class bind { 2 | package { 3 | ['bind-sdb', 'bind', 'bind-chroot']: ensure => latest 4 | } 5 | } 6 | 7 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/bind/manifests/server.pp: -------------------------------------------------------------------------------- 1 | class bind::server { 2 | 3 | include bind 4 | 5 | service { 6 | 'named': 7 | ensure => running, 8 | enable => true; 9 | } 10 | 11 | Service['named'] -> Package['bind-chroot'] 12 | } 13 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/dhcp/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class dhcp { 2 | 3 | package { 4 | 'dhcp': ensure => latest 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/dhcp/manifests/server.pp: -------------------------------------------------------------------------------- 1 | class dhcp::server { 2 | include dhcp 3 | 4 | file { '/etc/dhcp/dhcpd.conf': 5 | owner => 'root', 6 | group => 'root', 7 | mode => '0644', 8 | content => template('dhcp/dhcpd.conf.erb'); 9 | } 10 | 11 | service { 'dhcpd': 12 | ensure => running, 13 | enable => true, 14 | require => [ 15 | File['/etc/dhcp/dhcpd.conf'], 16 | Exec['set selinux permissive mode'] 17 | ]; 18 | } 19 | 20 | Package['dhcp'] -> File['/etc/dhcp/dhcpd.conf'] 21 | } 22 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/dhcp/templates/dhcpd.conf.erb: -------------------------------------------------------------------------------- 1 | ddns-update-style none; 2 | default-lease-time 86400; 3 | max-lease-time 604800; 4 | authoritative; 5 | 6 | option space ipxe; 7 | option ipxe-encap-opts code 175 = encapsulate ipxe; 8 | option ipxe.priority code 1 = signed integer 8; 9 | option ipxe.keep-san code 8 = unsigned integer 8; 10 | option ipxe.skip-san-boot code 9 = unsigned integer 8; 11 | option ipxe.syslogs code 85 = string; 12 | option ipxe.cert code 91 = string; 13 | option ipxe.privkey code 92 = string; 14 | option ipxe.crosscert code 93 = string; 15 | option ipxe.no-pxedhcp code 176 = unsigned integer 8; 16 | option ipxe.bus-id code 177 = string; 17 | option ipxe.bios-drive code 189 = unsigned integer 8; 18 | option ipxe.username code 190 = string; 19 | option ipxe.password code 191 = string; 20 | option ipxe.reverse-username code 192 = string; 21 | option ipxe.reverse-password code 193 = string; 22 | option ipxe.version code 235 = string; 23 | option iscsi-initiator-iqn code 203 = string; 24 | # Feature indicators 25 | option ipxe.pxeext code 16 = unsigned integer 8; 26 | option ipxe.iscsi code 17 = unsigned integer 8; 27 | option ipxe.aoe code 18 = unsigned integer 8; 28 | option ipxe.http code 19 = unsigned integer 8; 29 | option ipxe.https code 20 = unsigned integer 8; 30 | option ipxe.tftp code 21 = unsigned integer 8; 31 | option ipxe.ftp code 22 = unsigned integer 8; 32 | option ipxe.dns code 23 = unsigned integer 8; 33 | option ipxe.bzimage code 24 = unsigned integer 8; 34 | option ipxe.multiboot code 25 = unsigned integer 8; 35 | option ipxe.slam code 26 = unsigned integer 8; 36 | option ipxe.srp code 27 = unsigned integer 8; 37 | option ipxe.nbi code 32 = unsigned integer 8; 38 | option ipxe.pxe code 33 = unsigned integer 8; 39 | option ipxe.elf code 34 = unsigned integer 8; 40 | option ipxe.comboot code 35 = unsigned integer 8; 41 | option ipxe.efi code 36 = unsigned integer 8; 42 | option ipxe.fcoe code 37 = unsigned integer 8; 43 | option ipxe.vlan code 38 = unsigned integer 8; 44 | option ipxe.menu code 39 = unsigned integer 8; 45 | option ipxe.sdi code 40 = unsigned integer 8; 46 | option ipxe.nfs code 41 = unsigned integer 8; 47 | 48 | subnet 192.168.33.0 netmask 255.255.255.0 { 49 | range 192.168.33.200 192.168.33.229; 50 | option subnet-mask 255.255.255.0; 51 | option domain-name-servers <%= @dhcp_dns_servers %>; 52 | option broadcast-address 192.168.33.255; 53 | option routers <%= @genesis_ipaddress %>; 54 | option ipxe.no-pxedhcp 1; # speed optimization since everything is local 55 | # even though ipxe.org says this is the way to do it, it doesn't work!!! 56 | # if exists user-class and option user-class = "iPXE" { 57 | if exists ipxe.http { 58 | filename "<%= @ipxe_menu_url %>"; 59 | } else { 60 | filename "undionly.kpxe"; 61 | next-server <%= @genesis_ipaddress %>; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/gemserver/manifests/init.pp: -------------------------------------------------------------------------------- 1 | # This module manages a Ruby gem server. 2 | # 3 | # Parameters: 4 | # @port - space-separated string listing ports for gemserver to listen on 5 | # 6 | # Actions: 7 | # 8 | # Requires: 9 | # 10 | # [Remember: No empty lines between comments and class definition] 11 | class gemserver ( 12 | $ports = '8808' 13 | ) { 14 | 15 | file { '/etc/init.d/gemserver': 16 | content => template('gemserver/gemserver.erb'), 17 | mode => '0755', 18 | owner => 'root', 19 | } 20 | 21 | service { 'gemserver': 22 | ensure => running, 23 | enable => true, 24 | require => File['/etc/init.d/gemserver']; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/gemserver/templates/gemserver.erb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # gemserver a daemon which hosts Ruby gems 4 | # 5 | # chkconfig: 345 20 80 6 | # description: a daemon which hosts Ruby gems 7 | 8 | ### BEGIN INIT INFO 9 | # Provides: gemserver 10 | # Default-Start: 3 4 5 11 | # Default-Stop: 0 1 2 6 12 | # Short-Description: a daemon which hosts Ruby gems 13 | # Description: a daemon which hosts Ruby gems 14 | ### END INIT INFO 15 | 16 | # Source function library. 17 | . /etc/rc.d/init.d/functions 18 | 19 | exec="/usr/bin/gem" 20 | name="gemserver" 21 | # Space-separated list of ports to run all gemservers on. 22 | ports="<%= @ports %>" 23 | 24 | [ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name 25 | 26 | pidfile=/var/run/gemserver 27 | lockfile=/var/lock/subsys/$name 28 | 29 | start() { 30 | final_retval=0 31 | for port in $ports; do 32 | echo -n $"Starting $name on port $port: " 33 | daemon $exec server --port $port --daemon 34 | retval=$? 35 | PID=$(ps ax -o pid,command | grep "gem server --port $port" | grep daemon | awk '{print $1}') 36 | echo 37 | # Creates lockfile and PID file for gemserver running on this port. 38 | [ $retval -eq 0 ] && touch $lockfile-$port 39 | [ $retval -eq 0 ] && echo $PID > $pidfile-$port.pid 40 | # Records a nonzero status code if we encounter it to signal failure. 41 | [ $retval -ne 0 ] && final_retval=$retval 42 | done 43 | return $final_retval 44 | } 45 | 46 | stop_on_port() { 47 | port=$1 48 | echo -n $"Stopping $name on port $port: " 49 | killproc -p $pidfile-$port.pid $name-$port 50 | retval=$? 51 | echo 52 | # Deletes lockfile and PID file for gemserver running on this port. 53 | [ $retval -eq 0 ] && rm -f $lockfile-$port 54 | [ $retval -eq 0 ] && rm -f $pidfile-$port.pid 55 | return $retval 56 | } 57 | 58 | restart() { 59 | stop 60 | start 61 | } 62 | 63 | reload() { 64 | restart 65 | } 66 | 67 | force_reload() { 68 | restart 69 | } 70 | 71 | status_for_port() { 72 | port=$1 73 | status -p $pidfile-$port.pid $name-$port 74 | } 75 | 76 | status_for_port_q() { 77 | status_for_port $1 >/dev/null 2>&1 78 | } 79 | 80 | stop() { 81 | final_retval=0 82 | for port in $ports; do 83 | status_for_port_q $port && stop_on_port $port 84 | retval=$? 85 | [ $retval -ne 0 ] && final_retval=$retval 86 | done 87 | return $final_retval 88 | } 89 | 90 | rh_status() { 91 | final_retval=0 92 | for port in $ports; do 93 | status_for_port $port 94 | retval=$? 95 | [ $retval -ne 0 ] && final_retval=$retval 96 | done 97 | return $final_retval 98 | } 99 | 100 | rh_status_q() { 101 | rh_status >/dev/null 2>&1 102 | } 103 | 104 | 105 | case "$1" in 106 | start) 107 | rh_status_q && exit 0 108 | $1 109 | ;; 110 | stop) 111 | stop || exit 7 112 | $1 113 | ;; 114 | restart) 115 | $1 116 | ;; 117 | reload) 118 | rh_status_q || exit 7 119 | $1 120 | ;; 121 | force-reload) 122 | force_reload 123 | ;; 124 | status) 125 | rh_status 126 | ;; 127 | condrestart|try-restart) 128 | rh_status_q || exit 0 129 | restart 130 | ;; 131 | *) 132 | echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" 133 | exit 2 134 | esac 135 | exit $? 136 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/genesis/files/genesis.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # genesis 4 | # 5 | # chkconfig: 35 96 5 6 | # description: genesis web 7 | 8 | # Source function library. 9 | . /etc/rc.d/init.d/functions 10 | 11 | prog=genesis 12 | user=daemon 13 | 14 | UNICORN=/usr/bin/unicorn 15 | UNICORN_PID=/var/run/genesis/unicorn.pid 16 | UNICORN_CONF=/web/config/unicorn.rb 17 | 18 | 19 | # local config options 20 | [ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog 21 | 22 | exec="--user=$user --pidfile=$UNICORN_PID $UNICORN -D -c $UNICORN_CONF" 23 | 24 | lockfile=/var/lock/subsys/$prog 25 | 26 | start() { 27 | echo -n $"Starting $prog: " 28 | daemon $exec 29 | retval=$? 30 | echo 31 | [ $retval -eq 0 ] && touch $lockfile 32 | return $retval 33 | } 34 | 35 | stop() { 36 | echo -n $"Stopping $prog: " 37 | killproc -p $UNICORN_PID 38 | retval=$? 39 | echo 40 | [ $retval -eq 0 ] && rm -f $lockfile 41 | return $retval 42 | } 43 | 44 | restart() { 45 | stop 46 | start 47 | } 48 | 49 | case "$1" in 50 | start|stop|restart) 51 | $1 52 | ;; 53 | status) 54 | status $prog 55 | ;; 56 | *) 57 | echo $"Usage: $0 {start|stop|status|restart}" 58 | exit 2 59 | esac 60 | 61 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/genesis/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class genesis{ 2 | $testenv_dir = "/${::testenv}" 3 | $web_root = '/web' 4 | 5 | # needed so mock can be use to build RPMs 6 | user {'vagrant': groups => 'mock'} 7 | 8 | package { 9 | ['gcc', 'gcc-c++', 'libxslt-devel', 'libxml2-devel', 'ruby-devel']: 10 | ensure => present, 11 | require => File['/etc/yum.repos.d/ruby193.repo']; 12 | # sinatra contrib needs tilt 13 | 'tilt': 14 | ensure => '1.3', 15 | provider => gem, 16 | require => Package['ruby-devel']; 17 | ['sinatra', 'unicorn']: 18 | provider => gem, 19 | require => Package['ruby-devel']; 20 | 'sinatra-contrib': 21 | provider => gem, 22 | require => Package['tilt']; 23 | } 24 | 25 | service { 26 | 'genesis': 27 | ensure => running, 28 | enable => true, 29 | hasstatus => true, 30 | require => File[$web_root]; 31 | } 32 | 33 | file { 34 | ['/var/log/genesis', '/var/run/genesis']: 35 | ensure => directory, 36 | mode => '0755', 37 | owner => daemon; 38 | [$web_root, '/genesis']: # vagrant creates these 39 | ensure => directory; 40 | '/etc/init.d/genesis': 41 | owner => 'root', 42 | group => 'root', 43 | mode => '0755', 44 | source => 'puppet:///modules/genesis/genesis.init', 45 | notify => Service['genesis'], 46 | require => Package['unicorn']; 47 | "${web_root}/tasks": 48 | ensure => link, 49 | target => '/genesis/tasks', 50 | require => File['/genesis']; 51 | 52 | $testenv_dir: 53 | ensure => directory; 54 | "${testenv_dir}/config.yaml": 55 | content => template('genesis/config.yaml.erb'), 56 | require => File[$testenv_dir]; 57 | "${testenv_dir}/menu.ipxe": 58 | content => template('genesis/menu.ipxe.erb'), 59 | require => File[$testenv_dir]; 60 | "${testenv_dir}/stage2": 61 | content => template('genesis/stage2.erb'), 62 | require => File[$testenv_dir]; 63 | } 64 | 65 | Package['sinatra'] -> Package['unicorn'] -> Service['genesis'] 66 | } 67 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/genesis/templates/config.yaml.erb: -------------------------------------------------------------------------------- 1 | --- 2 | :ipxe: 3 | :images: 4 | :initrd: <%= @ipxe_initrd %> 5 | :kernel: <%= @ipxe_kernel %> 6 | :kernel_flags: <%= @ipxe_kernel_flags %> 7 | :menu_default: intake 8 | :image_server: http://<%= @image_service %>/genesis 9 | :genesis_server: http://<%= @genesis_service %> 10 | :tasks_url: http://<%= @genesis_ipaddress %>:8888/tasks 11 | :yum_repos: 12 | :epel: 13 | :label: "[epel]" 14 | :name: "epel" 15 | :descr: "Epel" 16 | :baseurl: "<%= @rpm_base_url %>" 17 | :enabled: 1 18 | :priority: 1 19 | :gpgcheck: 0 20 | :gem_files: 21 | "genesis_framework": http://<%= @genesis_ipaddress %>:8888/gem/genesis_framework 22 | :gem_args: &gem_args --no-ri --no-rdoc # Arguments to pass to gem install. Can include --source for internal repos 23 | :collins: 24 | :host: <%= @collins_url %> 25 | :ntp_server: <%= @ntp_server %> 26 | :loggers: 27 | - Collins 28 | :facter_cache: true # If true, genesis will cache the facts from facter for the whole run. Defaults to true. 29 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/genesis/templates/menu.ipxe.erb: -------------------------------------------------------------------------------- 1 | #!ipxe 2 | 3 | :genesis_menu 4 | menu Genesis 0.1 5 | item --gap Manual diagnostics mode: 6 | item --key 0 local 0: Local Boot - Boot the hard drive 7 | item --key 1 util 1: Utility Mode - No tasks run automatically 8 | item 9 | item --gap Non-destructive Genesis Operations: 10 | item --key 2 intake 2: Intake - Register with Collins (if new asset) and run machine setup 11 | item --key 3 hw_report 3: Hardware Report - Send new hardware report to Collins 12 | item --key 4 verify_sh 4: Verify - Run verification script and send notification to SRE team 13 | item 14 | item --gap Destructive Genesis Operations (THESE ERASE DATA): 15 | item --key 5 classic 5: Classic Mode - Intake and Burn-In 16 | item --key 6 burnin 7: Burn-In - Stress test the hardware 17 | item --key 7 provision_prep 7: Provisioning Prep - Setup RAID array and make ready for provisioning 18 | item --key 8 disk_wipe 8: Permanently destroy contents of attached disks 19 | 20 | choose --default util --timeout 10000 target && goto ${target} 21 | 22 | :local 23 | sanboot --no-describe --drive 0x80 || goto genesis_menu 24 | 25 | :util 26 | :intake 27 | :hw_report 28 | :classic 29 | :burnin 30 | :verify_sh 31 | :provision_prep 32 | :disk_wipe 33 | initrd <%= @ipxe_initrd %> 34 | kernel <%= @ipxe_kernel %> <%= @ipxe_kernel_flags %> GENESIS_MODE=${target} GENESIS_CONF_URL=<%= @ipxe_config_url %> <%= @raid_level.nil? ? "" : "GENESIS_RAID_LEVEL=#{raid_level}" %> 35 | boot 36 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/genesis/templates/stage2.erb: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env ruby 2 | 3 | # localisms for bootstrapping genesis. See genesis-bootstrap for the 4 | # environment that this runs in. 5 | 6 | # The following code assumes our working directory is GENESIS_ROOT 7 | 8 | require "rubygems" 9 | require "yaml" 10 | require "retryingfetcher" 11 | require "promptcli" 12 | 13 | # support outputing some debugging info 14 | def runcmd cmd 15 | puts 'running: ' + cmd 16 | Kernel.system cmd 17 | end 18 | 19 | # no buffering of stdout so we see messages immediately 20 | $stdout.sync = true 21 | 22 | genesis_mode = ENV['GENESIS_MODE'] 23 | puts "Stage2 starting. genesis_mode: '#{genesis_mode}'" 24 | 25 | @genesis_config = {} 26 | begin 27 | cfile = ENV['GENESIS_CONF'] 28 | puts '', "loading Genesis config file '#{cfile}'" 29 | @genesis_config = YAML::load( File.read(cfile) ) 30 | rescue => e 31 | # genesis-bootstrap has already parsed this so the contents must be good 32 | raise %q|reading genesis conf file '%s' failed: %s| % \ 33 | [cfile, e.message] 34 | end 35 | 36 | puts "\nEnsuring temp directory for downloads exists" 37 | Dir.mkdir("tmp", 0755) unless File.directory? "tmp" 38 | Dir.mkdir("/root/repo", 0755) unless File.directory? "/root/repo" 39 | Dir.mkdir("/root/repo/gems", 0755) unless File.directory? "/root/repo/gems" 40 | 41 | puts '' 42 | # protect against funny configs 43 | unless @genesis_config.fetch(:gems, nil).nil? 44 | @genesis_config.fetch(:gems).each do |gem, flags| 45 | puts 'Installing %s gem...' % [gem] 46 | runcmd ['gem install', gem, flags].reject {|e| e.nil?}.join(' ') 47 | if $?.exitstatus != 0 48 | raise 'gem install exited with status: ' + $?.exitstatus.to_s 49 | end 50 | end 51 | end 52 | # support for testenv 53 | unless @genesis_config.fetch(:gem_files, nil).nil? 54 | @genesis_config.fetch(:gem_files, []).each do |gem, source| 55 | puts 'Installing %s gem...' % [gem] 56 | gem_file = "/root/repo/gems/#{gem}.gem" 57 | Genesis::RetryingFetcher.get(source) do |data| 58 | File.open(gem_file, 'w', 0755) { |file| file.write data } 59 | end 60 | runcmd "gem install #{gem_file}" 61 | end 62 | end 63 | 64 | puts "\nInstalling repo files" 65 | unless @genesis_config.fetch(:yum_repos, nil).nil? 66 | @genesis_config.fetch(:yum_repos, []).each do |name, params| 67 | puts " #{name}" 68 | File.open("/etc/yum.repos.d/#{name}.repo", 'w') do |file| 69 | file.puts params[:label] 70 | params.reject {|k| k == :label}.each do |k,v| 71 | puts " #{k} #{v}" # DEBUG 72 | file.puts "#{k} = #{v}" 73 | end 74 | end 75 | end 76 | end 77 | 78 | raise 'ERROR: no tasks_url specified in configuration' \ 79 | if @genesis_config.fetch(:tasks_url, nil).nil? 80 | puts "\nDownloading tasks package..." 81 | Genesis::RetryingFetcher.get(@genesis_config[:tasks_url]) do |data| 82 | File.open('tmp/tasks.tgz', 'w', 0755) { |file| file.write data } 83 | end 84 | 85 | puts "\nExtracting tasks..." 86 | Kernel.system('tar', '-xzvf', 'tmp/tasks.tgz') 87 | 88 | puts "\nStarting genesis #{genesis_mode}" 89 | system('/usr/bin/genesis', genesis_mode) 90 | 91 | puts '', 'Stage2 all done!' 92 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/iptables/files/iptables: -------------------------------------------------------------------------------- 1 | *nat 2 | :PREROUTING ACCEPT [0:0] 3 | :POSTROUTING ACCEPT [0:0] 4 | :OUTPUT ACCEPT [0:0] 5 | -A POSTROUTING -o eth0 -j MASQUERADE 6 | COMMIT 7 | *filter 8 | :INPUT ACCEPT [0:0] 9 | :FORWARD ACCEPT [0:0] 10 | :OUTPUT ACCEPT [0:0] 11 | -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 12 | -A INPUT -p icmp -j ACCEPT 13 | -A INPUT -i lo -j ACCEPT 14 | -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT 15 | -A INPUT -m state --state NEW -m tcp -p tcp --dport 8888 -j ACCEPT 16 | -A INPUT -m state --state NEW -m tcp -p tcp --dport 8808 -j ACCEPT 17 | -A INPUT -m state --state NEW -m udp -p udp --dport 69 -j ACCEPT 18 | -A INPUT -j REJECT --reject-with icmp-host-prohibited 19 | -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT 20 | -A FORWARD -i eth1 -o eth0 -j ACCEPT 21 | -A FORWARD -i eth1 -j ACCEPT 22 | -A FORWARD -j REJECT --reject-with icmp-host-prohibited 23 | COMMIT 24 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/iptables/files/sysctl.conf: -------------------------------------------------------------------------------- 1 | # Kernel sysctl configuration file for Red Hat Linux 2 | # 3 | # For binary values, 0 is disabled, 1 is enabled. See sysctl(8) and 4 | # sysctl.conf(5) for more details. 5 | 6 | # Controls IP packet forwarding 7 | net.ipv4.ip_forward = 1 8 | 9 | # Controls source route verification 10 | net.ipv4.conf.default.rp_filter = 1 11 | 12 | # Do not accept source routing 13 | net.ipv4.conf.default.accept_source_route = 0 14 | 15 | # Controls the System Request debugging functionality of the kernel 16 | kernel.sysrq = 0 17 | 18 | # Controls whether core dumps will append the PID to the core filename. 19 | # Useful for debugging multi-threaded applications. 20 | kernel.core_uses_pid = 1 21 | 22 | # Controls the use of TCP syncookies 23 | net.ipv4.tcp_syncookies = 1 24 | 25 | # Controls the default maxmimum size of a mesage queue 26 | kernel.msgmnb = 65536 27 | 28 | # Controls the maximum size of a message, in bytes 29 | kernel.msgmax = 65536 30 | 31 | # Controls the maximum shared segment size, in bytes 32 | kernel.shmmax = 68719476736 33 | 34 | # Controls the maximum number of shared memory segments, in pages 35 | kernel.shmall = 4294967296 36 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/iptables/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class iptables { 2 | file { 3 | '/etc/sysconfig/iptables': 4 | owner => 'root', 5 | group => 'root', 6 | mode => '0600', 7 | source => 'puppet:///modules/iptables/iptables'; 8 | '/etc/sysctl.conf': 9 | owner => 'root', 10 | group => 'root', 11 | mode => '0644', 12 | source => 'puppet:///modules/iptables/sysctl.conf'; 13 | } 14 | 15 | service { 16 | 'iptables': 17 | enable => true, 18 | } 19 | 20 | exec { 21 | 'sysctl_refresh': 22 | command => 'sysctl -p', 23 | path => '/sbin', 24 | require => File['/etc/sysctl.conf']; 25 | } 26 | 27 | File['/etc/sysconfig/iptables'] ~> Service['iptables'] 28 | } 29 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/ipxe/files/undionly.kpxe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/testenv/bootbox/puppet/modules/ipxe/files/undionly.kpxe -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/ipxe/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class ipxe { 2 | require tftp::server 3 | 4 | $tftproot = '/tftpboot' 5 | 6 | File { owner => root, group => root, mode => 0644 } 7 | file { 8 | "${tftproot}/undionly.kpxe": 9 | ensure => present, 10 | source => 'puppet:///modules/ipxe/undionly.kpxe'; 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/selinux/manifests/permissive.pp: -------------------------------------------------------------------------------- 1 | class selinux::permissive { 2 | exec { 'set selinux permissive mode': 3 | command => '/usr/sbin/setenforce 0', 4 | onlyif => '/usr/sbin/sestatus | /bin/egrep "Current mode: +enforcing"'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/tftp/files/tftp.conf: -------------------------------------------------------------------------------- 1 | # default: off 2 | # description: The tftp server serves files using the trivial file transfer \ 3 | # protocol. The tftp protocol is often used to boot diskless \ 4 | # workstations, download configuration files to network-aware printers, \ 5 | # and to start the installation process for some operating systems. 6 | service tftp 7 | { 8 | disable = no 9 | socket_type = dgram 10 | protocol = udp 11 | wait = yes 12 | user = root 13 | server = /usr/sbin/in.tftpd 14 | server_args = -s /tftpboot 15 | per_source = 11 16 | cps = 1000 2 17 | instances = 1000 18 | flags = IPv4 19 | } 20 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/tftp/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class tftp { 2 | package { 'tftp-server': ensure => latest } 3 | } -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/tftp/manifests/server.pp: -------------------------------------------------------------------------------- 1 | class tftp::server { 2 | include tftp 3 | 4 | service { 5 | 'xinetd': 6 | ensure => running, 7 | enable => true, 8 | require => File['/etc/xinetd.d/tftp']; 9 | } 10 | 11 | file { 12 | '/tftpboot': 13 | ensure => directory; 14 | '/etc/xinetd.d/tftp': 15 | source => 'puppet:///modules/tftp/tftp.conf', 16 | require => File['/tftpboot']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/yumrepo/files/epel.repo: -------------------------------------------------------------------------------- 1 | [epel] 2 | name=EPEL6 3 | baseurl=http://dl.fedoraproject.org/pub/epel/6/$basearch/ 4 | enable=1 5 | gpgcheck=0 6 | 7 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/yumrepo/files/nginx.repo: -------------------------------------------------------------------------------- 1 | [nginx] 2 | name=nginx repo 3 | baseurl=http://nginx.org/packages/rhel/6/$basearch/ 4 | gpgcheck=0 5 | enabled=1 6 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/yumrepo/files/ruby193.repo: -------------------------------------------------------------------------------- 1 | [ruby193] 2 | name=Software Collections ruby 1.9.3 3 | baseurl=http://ftp.scientificlinux.org/linux/scientific/6x/external_products/softwarecollections/$basearch/ruby193/ 4 | enable=1 5 | gpgcheck=1 6 | gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-sl file:///etc/pki/rpm-gpg/RPM-GPG-KEY-sl6 file:///etc/pki/rpm-gpg/RPM-GPG-KEY-cern 7 | -------------------------------------------------------------------------------- /testenv/bootbox/puppet/modules/yumrepo/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class yumrepo { 2 | package { 3 | ['livecd-tools','createrepo','tito','mock']: 4 | ensure => latest, 5 | require => File['/etc/yum.repos.d/epel.repo']; 6 | } 7 | 8 | file { 9 | '/etc/yum.repos.d/nginx.repo': 10 | source => 'puppet:///modules/yumrepo/nginx.repo'; 11 | '/etc/yum.repos.d/ruby193.repo': 12 | source => 'puppet:///modules/yumrepo/ruby193.repo'; 13 | '/etc/yum.repos.d/epel.repo': 14 | source => 'puppet:///modules/yumrepo/epel.repo'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /testenv/bootbox/web/config.ru: -------------------------------------------------------------------------------- 1 | require './genesis' 2 | 3 | run Genesis::App 4 | 5 | -------------------------------------------------------------------------------- /testenv/bootbox/web/config/unicorn.rb: -------------------------------------------------------------------------------- 1 | # unicorn settings 2 | preload_app true 3 | timeout 60 4 | worker_processes 2 5 | 6 | # unicorn paths 7 | pid '/var/run/genesis/unicorn.pid' 8 | listen 8888, :backlog => 2048 9 | 10 | # app paths 11 | stderr_path '/var/log/genesis/genesis.log' 12 | stdout_path '/var/log/genesis/debug.log' 13 | working_directory '/web' 14 | 15 | # reload logic 16 | before_fork do |server, worker| 17 | demoted_server_pid = "#{server.config[:pid]}.oldbin" 18 | 19 | unless demoted_server_pid == server.pid 20 | begin 21 | signal = case 22 | when (worker.nr + 1) >= server.worker_processes 23 | :QUIT 24 | else 25 | :TTOU 26 | end 27 | 28 | Process.kill signal, File.read(demoted_server_pid).to_i 29 | rescue Errno::ENOENT, Errno::ESRCH 30 | end 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /testenv/bootbox/web/genesis.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Simple file server for genesis testing support 3 | 4 | require 'sinatra/base' 5 | 6 | module Genesis 7 | class App < Sinatra::Base 8 | configure do 9 | enable :logging, :dump_errors, :raise_errors 10 | end 11 | 12 | def test_and_send_file file, *send_file_args 13 | if file.size > 0 14 | send_file file, *send_file_args 15 | else 16 | [404, 'file not found or empty'] 17 | end 18 | end 19 | 20 | get '/ipxe-images/:file?' do 21 | file = File.join('', 'genesis', 'bootcd', 'output', params[:file]) 22 | test_and_send_file file 23 | end 24 | 25 | get '/gem/:name?' do 26 | # get the latest version gem of that name 27 | base = File.join('', 'genesis', 'src' ,'*', params[:name]) 28 | file = Dir.glob(base + '*.gem').sort.last 29 | test_and_send_file file 30 | end 31 | 32 | get '/testenv/:file?' do 33 | file = File.join('', 'testenv', params[:file]) 34 | if ['.yaml', '.yml'].include? File.extname(file) 35 | test_and_send_file file, :type => 'application/x-yaml' 36 | else 37 | test_and_send_file file, :type => 'text/plain' 38 | end 39 | end 40 | 41 | get '/tasks' do 42 | content_type "application/x-gzip" 43 | `GZIP=-f tar -chz -C /genesis -f - tasks 2>/dev/null` 44 | end 45 | 46 | get '/health' do 47 | content_type 'text/plain' 48 | "OK" 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /testenv/bootbox/web/genesis/config.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Genesis 4 | class Config 5 | def self.hash_from_yaml_file filename 6 | begin 7 | config = YAML::load(File.open(filename)) 8 | hash = self.symbolize_hash(config) 9 | self.resolve_yaml_configs hash 10 | rescue Exception => e 11 | raise StandardError.new("Unable to parse yaml in #{filename}: #{e}") 12 | end 13 | end 14 | 15 | def self.symbolize_hash hash 16 | (raise StandardError.new("symbolize_hash called without a hash")) unless hash.is_a?(Hash) 17 | tmp = {} 18 | hash.inject({}) do |result, (k,v)| 19 | if v.is_a?(Hash) then 20 | result[k.to_sym] = self.symbolize_hash(v) 21 | else 22 | result[k.to_sym] = v 23 | end 24 | result 25 | end 26 | end 27 | 28 | # Find keys in hash matching {foo}_cfg_yaml, parse the referenced yaml 29 | # file, and turn the parsed yaml into the value associated with the key 30 | # {foo} in the original hash 31 | def self.resolve_yaml_configs hash 32 | hash.inject({}) do |result, (k,v)| 33 | if k.to_s =~ /^(.*)_cfg_yaml$/ then 34 | thash = self.hash_from_yaml_file v 35 | if thash[$1.to_sym] then 36 | result[$1.to_sym] = thash[$1.to_sym] 37 | else 38 | result[$1.to_sym] = thash 39 | end 40 | else 41 | result[k] = v 42 | end 43 | result 44 | end 45 | end 46 | 47 | end 48 | end 49 | 50 | -------------------------------------------------------------------------------- /testenv/bootbox/web/tasks: -------------------------------------------------------------------------------- 1 | /genesis/tasks -------------------------------------------------------------------------------- /testenv/testnode.ova: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tumblr/genesis/fcdc1426c138682c1a8ef2796a6939d0be441b8a/testenv/testnode.ova --------------------------------------------------------------------------------