At work, I support three operating systems right now for ourselves and our clients: Gentoo, Ubuntu and CentOS. I really like the first two, and I’m not really fond of the other one. However, I’ve also started doing some token research into *BSD, and I am really fascinated by what I’ve found so far. I like FreeBSD and OpenBSD the most, but those two and NetBSD are pretty similar in a lot of ways, that I’ve been shuffling between focusing solely on FreeBSD and occasionally comparing at the same time the other two distros.
As a sysadmin, I have a lot of tools that I use that I’ve put together to make sure things get done quickly. A major part of this is documentation, so I don’t have to remember everything in my head alone — which I can do, up to a point, it just gets really hard trying to remember certain arguments for some programs. In addition to reference docs, I sometimes use shell scripts to automate certain tasks that I don’t need to watch over so much.
In a typical situation, a client needs a new VPS setup, and I’ll pick a hosting site in a round-robin fashion (I’ve learned from experience to never put all your eggs in one basket), then I’ll use my reference docs to deploy a LAMP stack as quickly as possible. I’ve gotten my methods refined pretty well so that deploying servers goes really fast — in the case of doing an Ubuntu install, I can have the whole thing setup close to an hour. And when I say “setup” I don’t mean “having all the packages installed.” I mean everything installed *and* configured and ready with a user shell and database login and I can hand over access credentials and walk away. That includes things like mail server setup, system monitoring, correct permissions and modules, etc. Getting it done quickly is nice.
However, in those cases of quick deployments, I’ve been relying on my documentation, and it’s mostly just copy and paste commands manually, run some sed expressions, do a little vim editing and be on my way. Looking at FreeBSD right now, and wanting to deploy a BAMP stack, I’ve been trying things a little differently — using shell scripts to deploy them, and having that automate as much as possible for me.
I’ve been thinking about shell scripting lately for a number of reasons. One thing that’s finally clicked with me is that my skill set isn’t worth anything if a server actually goes down. It doesn’t matter if I can deploy it in 20 minutes or three days, or if I manage to use less memory or use Percona or whatever else if the stupid thing goes down and I haven’t done everything to prevent it.
So I’ve been looking at monit a lot closer lately, which is what I use to do systems monitoring across the board, and that works great. There’s only one problem though — monit depends on the system init scripts to run correctly, and that isn’t always the case. The init scripts will *run*, but they aren’t very fail-proof.
As an example, Gentoo’s init script for Apache can be broken pretty easily. If you tell it to start, and apache starts running, but crashes after initialization (there’s specifics, I just can’t remember them off the top of my head) the init script thinks that the web server is running simply because it managed to run it’s own commands successfully. So the init system thinks Apache is running, when it’s not. And the side effects from that are that, if you try to automatically restart it (as monit will do), the init scripts will insist that Apache is already running, and things like executing a restart won’t work, because running stop doesn’t work, and so on and so forth. (For the record, I think it’s fair that I’m using Apache as an example, because I plan on fixing the problem and committing the updates to Gentoo when I can. In other words, I’m not whining.)
Another reason I’m looking at shell scripting as well is that none of the three major BSD distros (FreeBSD, NetBSD, OpenBSD) ship with bash by default. I think all three of them ship with either csh or tcsh, and one or two of them have ksh as well. But, they all have the original Bourne shell. I’ve tried my hand and doing some basic scripting using csh because for FreeBSD, it’s the default, and I thought, “hey, why not, it’s best to use the default tools that it ships with.” I don’t like csh, and it’s confusing to try and script for, so I’ve given up on that dream. However, I’m finding that writing stuff for the Bourne shell is not only really simple, but it also adds on the fact that it’s going to be portable to *all* the distros I use it on.
All of this brings me back to the point that I’m starting to use shell scripts more and more to automate system tasks. For now, it’s system deployments and system monitoring. What’s interesting to me is that while I enjoy programming to fix interesting problems, all of my shell scripting has always been very basic. If this, do that, and that’s about it. I’ve been itching to patch up the init scripts for Gentoo (Apache is not the only service that has strange issues like that — again, I can’t remember which, but I know there were some other funky issues I ran into), and looking into (more) complex scripts like that pushes my little knowledge a bit.
So, I’m learning how to do some shell scripting. It’s kind of cool. People always talk about, in general, about how UNIX-based systems / clones are so powerful because of how shell scripting works .. piping commands, outputting to files, etc. I know my way around the basics well enough, but now I’m running into interesting problems that is pushing me a bit. I think that’s really cool too. I finally had to break down the other day and try and figure out how in the world awk actually does anything. Once I wrapped my head around it a bit, it makes more sense. I’m getting better with sed as well, though right now a lot of my usage is basically clubbing things to death. And just the other day I learned some cool options that grep has as well, like matching an exact string on a line (without regular expressions … I mean, ^ and $ is super easy).
Between working on FreeBSD, trying to automate server deployments, and wanting to fix init scripts, I realized that I’m tackling the same problem in all of them — writing good scripts. When it comes to programming, I have some really high standards for my scripts, almost to the point where I could be considered obsessive about it. In reality, I simply stick to some basic principles. One of them is that, under no circumstances, can the script fail. I don’t mean in the sense of running out of memory or the kernel segfaulting or something like that. I mean that any script should always anticipate and handle any kind of arbitrary input when it’s allowed. If you expect a string, make sure it’s a string, and that it’s contents are within the parameters you are looking for. In short, never assume anything. It could seem like that takes longer to write scripts, but for me it’s always been a standard principle that it’s just part of my style. Whenever I’m reviewing someone else’s code, I’ll point to some block and say, “what’s gonna happen if this data comes in incorrectly?” to which the answer is “well, that shouldn’t happen.” Then I’ll ask, “yes, but what if it *does*?” I’ve upset many developers this way. 🙂 In my mind, could != shouldn’t.
I’m looking forward to learning some more shell scripting. I find it frustrating when I’m trying to google some weird problem I’m running into though, because it’s so difficult to find specific results that match my issue. It usually ends up in me just sorting through man pages to see if I can find something relative. Heh, I remember when I was first starting to do some scripting in csh, and all the search results I got were on why I shouldn’t be using csh. I didn’t believe them at first, but now I’ve realized the error of my ways after banging my head against the wall a few times.
In somewhat unrelated news, I’ve started using Google Plus lately to do a headdump of all the weird problems I run into during the day doing sysadmin-ny stuff. Here’s my profile if you wanna add me to your circles. I can’t see a way for anyone to publicly view my profile or posts though, without signing into Google.
Well, that’s my life about right now (at work, anyway). The thing I like the most about my job (and doing systems administration full time in general) is that I’m constantly pushed to do new things, and learn how to improve. It’s pretty cool. I likey. Maybe some time soon I’ll post some cool shell scripts on here.
One last thing, I’ll post *part* of what I call a “base install” for an OS. In this case, it’s FreeBSD. I have a few programs I want to get installed just to get a familiar environment when I’m doing an install: bash, vim and sometimes tmux. Here’s the script I’m using right now, to get me up and running a little bit. [Edit: Upon taking a second look at this — after I wrote the blog post, I realized this script isn’t that interesting at all … oh well. The one I use for deploying a stack is much more interesting.]
I have a separate one that is more complex that deploys all the packages I need to get a web stack up and running. When those are complete, I want to throw them up somewhere. Anyway, this is pretty basic, but should give a good idea of the direction I’m going. Go easy on me. 🙂
Edit: I realized the morning after I wrote this post that not only is this shell script really basic, but I’m not even doing much error checking. I’ll add something else in a new post.
#!/bin/sh # # * Runs using Bourne shell # * shells/bash # * shells/bash-completion # * editors/vim-lite # Install bash, and set as default shell if [ ! -e /usr/local/bin/bash ] ; then echo "shells/bash" cd /usr/ports/shells/bash make -DBATCH install > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "make install failed" exit 1 fi else echo "shells/bash - found" fi if [ $SHELL != "/usr/local/bin/bash" ] ; then chsh -s /usr/local/bin/bash > /dev/null 2>&1 || echo "chsh failed" fi # Install bash-completion scripts if [ ! -e /usr/local/bin/bash_completion.sh ] ; then echo "shells/bash-completion" cd /usr/ports/shells/bash-completion make -DBATCH install > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "make install failed" exit 1 fi else echo "shells/bash-completion - found" fi # Install vim-lite if [ ! -e /usr/local/bin/vim ] ; then echo "editors/vim-lite" cd /usr/ports/editors/vim-lite make -DBATCH install > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "make install failed" exit 1 fi else echo "editors/vim-lite - found" fi # If using csh, rehash PATH cd if [ $SHELL = "/bin/csh" ] ; then rehash fi
Your script doesn’t account for bash being installed in another place than /usr/local/bin/bash. I’d rather use the command “which bash” and check the contents of $? which should be 0 if bash is found anywhere in the path, and 1 otherwise.
Good point, thanks.
tl;dr, but I did notice a few things. All BSDs contain the original bourne shell /bin/sh which is compatible everywhere. If you write your scripts to sh only and ignore the nice bash stuff, they can run anywhere.
Also, Gentoo init scripts suck. They shouldn’t keep track of state statically. Bad stuff, always hated that about many Gentoo init scripts.
Lastly, look at some config management tools to make your deployment easier. See: saltstack.org, puppet, chef, cfengine, etc. Then your process is: install OS, install (tool), and you’re done.
A few things. First, about ‘which bash’ mentioned in first comment – ‘which’ is sys-apps/which (pun not indended) which isn’t very portable, instead the prim and proper UNIX way is to use ‘command -v ‘ which does the same thing but is a shell thing so it works everywhere and *doesn’t fork* making it faster!
Secondly, shell language is untyped and that’s a feature – if you need type checking, you are doing it wrong and need to rewrite it in some good old C (if you ask the POSIX shell authors) or any other “real” programming langauge instead. Mind you I have written a bit of shell scripts myself but shell was specifically designed to write short, preferably 80 characters tops long “programms” not monsters that have three digit line numbers.
Thirdly, I suspect that you ensure type checking by using grep or some other cludge which usually fork a new process (busybox being about the only exception) and for good measure are slow on their own, and PCRE if you use that is probably yet slower. So what if it takes roughly half a milisecond longer, you ask? Same reason why systemd is faster than the much simpler sysvinit when not using busybox (untrivial for desktop system) – forking consumes resources that when there’s a lot of forking can cause some slowdown though the main problem with sysvinit is of course synchronisation – with sysvinit you can’t start a process before its dependencies have properly started up or it’s gonna crash and burn but let’s leave that for another time.
hmmm . . . I had the misfortune to have to use csh on some stuff at work once, lets say it wasn’t my favorite. If you’ve got ksh though you should be ok.
Most configuration management tools are shit, especially puppet which was designed by hipsters who have no clue about UNIX. You are better off with writing good, well documented and idempotent scripts (in whatever language you want).