 So this is part two, and we're going to dive way deeper into Ansible. Phil's going to show you how Ansible will work not only with a group of servers, but with a non-homogeneous group of servers, so variety here. We got CentOS and Debian. We were going to do Freeniasp or TBSD, but that was a whole other can of worms. We ran into some problems with Vagrant, but that's a problem for another time. It's a Vagrant problem, not Ansible problem. That's a different topic. So we're going to have two different platforms, one set of controls through Ansible. Phil's going to dive in here and teach you how to do it. Does that sound about right? Sounds right to me. Here we go. All right. Okay. In tutorial two, our setup is much the same as the first tutorial. We have a very basic Ansible config. We have an Ansible inventory. Now something has changed in here. We now have this colon vars group. These vars are applied to everything in the group name. So all the Debian servers will get these vars. All the CentOS servers will get these vars. And again, we are aggregating the CentOS and Debian servers into a group called fleet. That way we can hit every box at once. These names don't particularly matter. I just happen to choose these. You can do whatever you want. Our playbook has been expanded some. You can see some several new things in here. We've got pre-tasks and post-tasks. Pre-tasks run before any roles or tasks. And post-tasks run after any roles or tasks defined in the playbook. What I like to use these for is to send a notification to Slack or IRC or Mattermost or Git or any other chat application just so the other engineers can see that, hey, something's going on on a server. And you can see when it started and when it finished and then return codes times all of that. Other things to note in here are that we are using an Ansible register, which is storing the value of a command. And then we are going to use the debug module to actually display what that command returned. And then we're going to run these pre and post tasks only on local host. So that way we don't have to see the same thing over and over. Now our role is going to install and manage our Vim config for Santas and Debian. You can find the final role that also manages free BSD and OSX on my GitHub page, which will be linked in the show notes. Yep, they're in the comments below. Perfect. Or in the description below. And then I will walk you through a very basic setup of a tool called Kitchen that I used to test my configuration management. Now, working with infrastructure, typically people don't like to test that their code is doing what they say it does. But I think that infrastructure is part of an application. So we should be writing tests. Tests can be as simple as a bash command that says, is this package installed? Is this port open? Is this web server response returning specific string that I'm looking for? Or it can get very, very in depth. I like to keep it simple. So as we go through the testing procedure, I will show that off. And then just like before, I've got links to Ansible documentation and then all of the commands that we'll be running today. So the very first thing we're going to do is we're going to make sure that we can even ping these servers. So we're going to specify Ansible playbook, pick our inventory, and our playbook file. We see our three servers, two devian and one centos. We can see our pre tasks as highlighted by my mouse cursor. And then since there was nothing in the actual role, we don't see that even being run. And then we see the post tasks. So let's start to build out this role. Since we'll be using the systems package manager, we'll need to we'll need to elevate our privileges. And we will do that through become. And the method will be through pseudo. We're going to use the generic Ansible package module, which will determine if you're running PKG for free BSD, yum for centos or fedora, and apt if you're on Debian or Ubuntu, and there's a bunch of others that get managed to. So Ansible already knows once it understands the OS, it's going to make the determination of once OS families assigned, then it makes determination which package manager to use. Yes, okay. Now this now you could absolutely do this with a shell script, but you would have to write all of that logic yourself. Using using Ansible or salt or puppet or chef, you can use a common set of pre built scripts that other engineers have written to do all of that heavy lifting for you. So you can get to the actual meat of what you're trying to do, get them installed. Right. So now we're going to run this. The Debian servers returned so quickly because Vim was probably pre installed, but you can see that a change was made to sent OS. Now to test item potency, we're going to run this script again. And we should see no changes. There you go. Because I use my Debian base install. I can verify Vim was already installed. We just installed sent to us before this. So there's nothing customized. That's out of the box sent to us. Now let's say that we want to install Vim tiny. So we get no best top environment files and we just get the absolute bare bones Vim. Vim tiny doesn't exist in sent to us. That's that's a Debian ism. So what do we do when we need to install a similar package across disparate systems? For that, we're going to use what are called Ansible vars. This is a folder that is loaded at a different time than Ansible defaults. This is a concept called variable precedence. I've linked to that in the read me. So you'll see several things happen here. We're going to use an Ansible fact to determine the type of system that we're running on and then load a new configuration file based on that. So now I'm going to go and create this setup templated YAML file. Now just from using facts as much as I have, I know that the fact for a Debian based system is going to be called Debian with a capital D and the same fact for a sent to system is going to be called Red Hat. So just just like any other Ansible file we define visually that this is a YAML file with our three dashes and our three dots. We're going to set ourselves a temporary variable name. I like to designate this with two underscores so that way I know where this comes from in my code. This is a style that I've that I've adapted from a community member named Geerling guy. If you've never heard of Geerling guy, I highly suggest that you check out his roles on GitHub. He wrote the book for Ansible. Oh, now the important thing to note here is that we're going to be installing Vim Tiny for Debian, but we still want just Vim for CentOS. And now that we are logically loading in the vars file for our specific type of system, we need to have Ansible set a brand new fact based on that. So we can use the set fact module define ourselves our actual variable that we'll be using and set the value to our temporary variable. And then we're going to use an Ansible conditional that will help protect us or allow us to override this for a system that is not yet supported by this code. So if I wanted to manually set my Vim underscore package variable, we can still do that. And then let's also install git for good measure. That'll come in handy later. So with items is a loop. It will loop through this list of packages that we have defined underneath it. Now, before we run this on actual production servers are to Debian and our CentOS servers, let's go back to kitchen and run it against some test virtual machines before trying to apply this potentially destructive configuration change to production or staging. If we deploy locally in our development environment, then we can greatly reduce the risk that our code will end up breaking staging your production and allow us to iterate and change code faster and safer. This is why it's so important to have a lab environment that you run these against before you push it out. Absolutely. I've used this kitchen tool for several years now. It is it has helped me at a couple of my previous jobs. I would like to say that it has made me a better system administrator being able to test my code and verifying that my code is doing what I think it should be doing rather than the prayer based method. Well, hope this doesn't break it. Yeah. So kitchen is a Ruby gem. It comes out of the chef world. Say what you want about chef or Ruby. I believe that the tool is extremely helpful. I'm going to be running kitchen through another tool called bundler. If you are familiar with Python development, PIP and bundle are PIP and gem are kind of related and a Python requirements file is similar to a bundle gem file. It allows me to get a selection of of gems specific to my project and not clobber another project that I have. Again, you'll be able to see this code on GitHub going to make sure that my virtual machines are running for this particular test on my laptop, which I'm considering development. I have a cent seven and a devian nine stretch VM. So they've been created. Now I'm going to converge them and this will run Ansible against these two virtual machines before I run it against the virtual machines that Tom has provided just to verify that my changes are doing what I think they should be doing. And we'll notice that we have an error. If this was to be committed to staging or production, that could potentially break the pipeline and cause other engineers to get mad at you. No one wants that. No, it failed just on the CentOS one and the devian one. Oh, and a devian one. We are unable to find the devian dot yaml file. So what that means to me is that we need to go back to this determine OS family task and see what we are actually trying to load there. So in the determine OS family task, I am trying to logically load devian dot yaml. However, my vars are called setup underscore. So let's do that. We've made this change. And let's run this again in development and see what happens. We've still got some sort of problem, which is fine. It's a different problem. We've progressed. Yes. As long as the problems are different, you're moving in a direction, not necessarily the right direction. Our previous task of determine the OS family completed successfully. Oh, good. And you can see that on this devian system, we're loading VIM tiny. And then on the CentOS system, we're loading just the VIM package. So what I'm doing here is dealing with the apt package manager. Sometimes you may have a system that removes the apt cache. It's not happening in this case, but it might happen for you. So what I'm doing is loading another file that is actually a task instead of a var, despite these files having the same name. And we're going to add a helper command. And this will run apt update for us. And since we don't really care that we're running the apt update, we're going to set this changed when to tell Ansible that we don't care if this ever changes, just return okay. This is typically a thing you want to avoid. As you progress through learning Ansible and using it for yourself, you'll become more comfortable with this command. I have chosen not to be alerted about this, so I just want it to return okay. So now let's run our code again. You can see that the CentOS box has skipped loading that setup file because it's not of the Debian family. And that Debian has included that file and has run the apt update. Both of these servers have returned okay. So now let us verify that VIM is actually there because just because Ansible said that it did something. Well, you should verify it. Absolutely. That's important. The way we're going to do that is through a through our test harness kitchen. Kitchen runs what is called a test suite that can be anything from a shell script to a tool called server spec or chef in spec. You can use Python or Ruby or whatever you want. As long as you're testing your code, there is a method to do it. I like to use a tool called bats the bash automated testing software and bats is just shell script except you can define all of the code that you want to run per function. And in here you can see that I have two functions. One will tell me if VIM is installed. One is going to check if a specific string exists in a VMRC that I'm expecting it to be in. And then I have another file to test a dump it in. So that way I don't have to keep running Ansible myself. I'll have the code do it. And any automation that you can add into your process, the better. So let's run a verify so we can see that our first two tests passed. And then our third test, the adeptance test passed. But then on WN, we've got some sort of a failure. So let's log into that box to actually debug this. Okay, so our problem is that VIM wasn't installed in Debian. Now, why is that? That is because VIM tiny doesn't actually provide the runtime. Well, we need to get VIM dash no X. Now we could very well just install VIM the package. But that wouldn't be too fun or show off the cool things that we can do. So now let's let's run a converge again. So the package name is VIM dash no X. Yes. Okay. But there's also the package VIM. Yeah. But no X contains the VIM tiny executable, right? It contains the VIM runtime that VIM tiny didn't. Okay. And then we're going to verify that our code is doing what we think it should be doing. And you can see that all of the tests are passing on both of our systems now. So now our code should be just fine to run against Tom servers. And another thing you can do, and I highly recommend this is before you run any sort of configuration management against staging or QA or production environments is to use the built in check mode to see any sort of differences between what you're about to apply versus what the actual state of the server is. This has saved me from potential outages many, many times. For Ansible, we use the check and the diff flag. We can see that the pre and post tasks are complaining about something. But that's okay, because we're just checking. But inside of the WNVMs, there is a potential change. Now, we know that we want to make this change. This looks okay to us. Let's run this code live in prod. Hooray. Let's run it again. Just to test adempotence in prod. We know nothing is going to change. But let's see it for ourselves. Yep. It runs much faster the second time. There's no changes. Everything's green. So here comes the bigger question. So when you ever you set up a new server and what was exactly what we're doing right now, there's a lot of dot vim the dem profile in bash profile and all those profile settings we pull from our dot files that we want that we want to comment across our servers because once you have an environment set up, you back it up and you move it to everywhere you set. Absolutely. I've got a Tmux config and a screen config and a git config and I could go on and on and on. Yes. All of the config options I never want to set again. Yes. So let's let's build ourselves a method that we can take a file from a known location and dump it onto our servers. These are those little things that make me love Linux as well. When I have to reload my laptop, I just, you know, or my desktop, I just pull those files right back down there. And I'm like, oh, look, I'm back at home and I seem comfortable environment. Windows does not make that as easy. Well, technically, because you can low bash on Windows, you kind of get some of those easy things that that's absolutely true. So as they as Microsoft's realizing that this is the way. So here's your this get URL is going to make a network request. It's going to use a Python version of curl or W get under the hood. I could very easily do this with curl or W get, but we'll stick with Ansible. They handle letting us know if anything is changing or not. Now does it handle both HTTP and HTTPS? Yes, it does. Awesome. So we are going to define ourselves a new variable. We'll call this the vim upstream vmrc. I like to namespace my variables. So if I'm writing the ansible role, vim, I'm going to preface all of my variables with vim. Yes. It makes so much sense because a lot of you don't want naming collisions later and things like that. Absolutely. So we're going to give the destination, which is our user's home directory. And we're going to dump this into the dot vmrc because it's a dot file. We're going to set the file mode. You can use octals or UGO. I prefer seeing the numbers myself. We're going to force this to run and overwrite the file if it's different. And then we need to go to our defaults and actually define where we're going to pull this file from. Learning the octals, I think, is important. Just a side note to that. Take a few minutes to try to grasp them. There's plenty of quick cheat sheets you can have in the year wall. Now I've had this set of dot file configuration stored on GitHub and Bitbucket and everywhere else on the Internet for a long time. So I've got this URL basically memorized. So we're going to pull my config, which could very easily be your own config. And you can override this based on variable precedents if you were to use this role. So let's run a converge on our testing servers. And if you ever need Phil to help you with the problem, it's best if you preload your servers with his Vimart config, as I've learned. Well, we've got our config. Now let's run it on Tom's boxes. We'll check if anything's going to change. We are expecting something to change. Yeah, I don't have that installed yet because there are new boxes. My production servers actually have Phil's scripts on. And we'll run it again for adept latency and we have our configuration. So let's say there's a file that's not on GitHub or conveniently located, but we have the file and we want to push it out. Because maybe not something that's publicly available or should be publicly available. We want to push it out to all the servers. So what's the method by which we do that? Okay. Okay. To keep it simple, we could define a variable that is just a multi-line string. We could write the literal file as a variable. So let's investigate that. Okay. We'll call this our Vim, Vmrc. And then to make a multi-line string in Ansible, you can either use the pipe character or this guy. Yeah. I'm in a loss as well. Yeah. Let's integrate that. Let's integrate it. One of those. The one on the screen. That's the important part. Bingo. This is not an audio tutorial. So we'll set this to be something super, super simple. We want line numbers. We want to see the, the location of our mouse cursor. We want the color scheme to be Elf Lord. And I think that's pretty good. So let's go and set a new task to actually use this. We're going to use the copy module. So we are going to copy the content of that variable into our user's Vmrc. And then we can also set the mode just like we did with get URL. And then we will test this. And thankfully we tested this because we saw that we deployed our previous configuration and then we overwrote it. So something has happened that we didn't exactly intend to happen. So the astute of you might have noticed that that problem before we actually ran it on a server. So what we need to do now is introduce some conditionals just like we did for loading the Debian app update up here. So the way that we are going to do this is we're going to set ourselves a Boolean flag. Now a Boolean is true or false, one or zero. And we'll call this flag the use upstream Vmrc. And then we can pipe this into the function bool. This is similar to a Unix pipe, but this is using a templating language called Jinja2, which Ansible and Salt use under the hood. Chef and Puppet use Ruby templates called erb to do extra functions for those tools. So what this conditional is saying is when Vm use upstream Vmrc is true, then we'll install this custom Vmrc. And just as easily, we can say when it's false, we'll use our own Vmrc that we have defined in our defaults file or overridden through variable precedents elsewhere in a playbook. And then we'll actually set this. I like to set all the variables to false. And then I like to comment out variables that would change my system in any way. This way it's a principle of least surprise. If I was to run this against a server without knowing what it did, which first of all, shame on me for running code that I haven't inspected, but I don't want to change anyone's server without them actually agreeing to the changes. So to handle that, we've got this boolean that's set to false. So now we won't use my Vmrc from GitHub, but we also need to handle commenting out this Vmrc section. And back to our, back to our tasks, we can chain these conditionals. The important portion here is this Vm underscore of MRC, which is our variable, and then determining if it is defined, which means that it's not commented out and it's not empty. And we'll do the same thing for the custom Vmrc just in case we have it enabled, but we haven't specified the URL to use. And we should probably comment this out as well. Now, since nothing will change, we're going to load up the kitchen playbook. I've chosen to call this one default.yaml because it's a default set of tests. So let's go investigate that. I have some cruft. Let's get rid of that and we'll set this to make a false change. We'll set it to make no changes by setting it to false. And let's go run this. We should see two skipped tasks at the end because we are not using an upstream Vmrc and we have not set our own Vmrc. And as indicated by the ansible output, we have verified that that is true. Now let's add in our own Vmrc. We'll set this to have a couple options and we'll test it again. We can see that there was changes and then we'll run our shell scripts to verify that things are working as intended. I've got a broken test because I know that I am not setting my Vmrc. So in the development process of building configuration management code, going back and forth between all of these files is something that happens. It gets easier the more you do it. If any of this looks complicated, spend an hour. You'll be surprised at how much faster you are at it than when you first started. For sure. Now you're using NerdTree on the... Yes, I am. Right there. So is NerdTree something else that needs to be installed like through the package manager? Yes, it is. And we can actually install that through this Vmrl so that way a server will get any sort of extra Vm plugins that we want. We'll take care of that next. The problem back to that testing failure problem was that I wasn't checking the return code of my grep line, which is looking for a specific file that I know that I set. This is a very, very simple test. So let's verify this again. We should see two testing failures, one for Cent and one for Debian. And this failure right now is exactly what I'm expecting to see, which sounds strange, but I know that it's working now because I can go and edit my playbook that gets deployed to the testing system to make it pass that test. The slime that I just added is for the Vim tool called Pathogen by Tim Pope. It's my preferred Vim plugin manager. There's also Vundal and all the other ones we don't use. We should now see these tests pass, except we didn't. So what did we do wrong? Let's make sure we're using the correct string. That's a very handy way I do it too. You put two things next to each other and try to... So I think what's happening here is that we have this pound symbol. And I think that might be causing an issue with a multi-line string. So let's log into one of these failed boxes and see what's actually in the file. The file doesn't exist. Ah, because we didn't actually deploy that change. Oh, okay. We just tried to run the test against something that we assumed changed. Got it. If this seems like mental hurdles, it gets easier. It's also a little harder when we're trying to explain it as we're going. It actually creates a little bit of a challenge. This is the tricky part, as Phil's learning how to make YouTube videos here. Now our tests are passing. Hooray! We can deploy this to Tom's boxes. I'm ready for it. Devian doesn't seem to be happy. What do I cut back to? Now that we've solved our problem with our test, we're going to verify, again, that we installed to the correct user that we expected to be installing to. Oh, that's an important aspect. And the expected user in this case is root. Something tells me Phil was not installing it to root. Bingo. Were you installing it to your own laptop with the user, Phil? I was installing it to the to the vagrant user without privileges and checking that a file was in root in a location that it's in their permission to see. Yep. Okay. Troubleshooting is fun. Yes. I should do a video about what happened before this video too. That's a different one. We had, we had grade problems, sand problems. Yeah, sand problems. So 99 problems. Okay. So we, we have, we have checked and dipped files. We're going to actually deploy this now. But you'll see that nothing has changed. And that's because our changes were only done in the testing environment. This is something to consider when you're working with multiple environments. Yes, there is some code reuse, but you have to make sure you also migrate that code up to the higher environments. So let's go edit the actual playbook that we're using. So we can see that we don't have any variables defined. What I'm going to do is copy them from testing and dump them in here. And then obviously I would make sure to verify that, yes, these are same configs that I want to set in production or staging. Let's go with Kohler for a color scheme. Let's check this again. We can see that there's changes that are going to happen. The old config that we pulled down from GitHub is getting removed as designated by all of this, all of the lines starting with a minus and it's red. And then it's being replaced by these lines starting with a plus that are in green. Since that's what we intend to be there, let's, let's actually deploy it. All right. And do we run out one more to double check? Absolutely. Make sure no other changes. Now a more advanced topic that we're not going to cover here is continuously running tests against staging and QA and production. That's, there is a lot of intricacies that go into that. And kind of the reason for it is because occasionally you'll have an engineer that you're supporting or a software developer who makes a change to one of those servers. And at the scale that a lot of this occurs at, there's obviously just groups of people working all the time. So you're doing this and they're doing their job all simultaneously. It's not like you can tell a global user base, please log out because I have a change to make. Yes. Communication is critical. You want to communicate with your team any changes that you're about to make, especially if you work in a remote environment or even across the office from each other, just giving the team a heads up. Hey, this is about to go out. Be aware that can, that can help recover from potentially catastrophic changes that much quicker because your team knows about it. Yes. Any other side is wonders, especially the newer team members, they don't, they think they want to install their own thing and they make some modification because back with their own place, they used to use this file and they copy it constantly running and looking for those changes before there's a problem is probably very helpful. Absolutely. And then the next person who comes along and runs this configuration management code will end up stomping that change. And that is not something that they had intended to have happened. Right. Again, the principle of least surprise. Right. So constantly running this makes a whole lot of sense. Now, a while back, you had mentioned that you want to install a Vim plugin called nerd tree or get some other Vim plugins. Yes. So let's go through the method of building that. Sounds good. We're going to be using the git module, which takes an argument called repo going to put an example of what I've placed an example of what I am about to template here just so that you can follow along. Since you're not in my brain, part of this URL structure is having the author of the project and then the actual project name. And since we are pulling a git repository, we end it with dot git. I think the beginning of this, we made sure git was installed on all these, correct? Yes, we did. Awesome. We did that up here on line 22. All right, perfect. So we need to set a destination. And since I like to use pathogen, I know that it dumps its files into dot vim slash bundle. And we only need the name of the repository. And pathogen takes care of the rest. We don't want these to automatically update. That can be taken care of at a later date. We just want to make sure that we have the base files there. Now, we're also going to need a list of plugins to install. And again, we're going to use this with items loop and define ourselves a variable called, let's just call it vim underscore plugins. And we'll comment it out just to make sure it doesn't change anything. And then let's try to run this code. So vim plugins, our new variable is undefined. Let's take care of that by having ansible check that the variable is actually defined before it runs that task. We'll test our code again. This is what we had expected to happen. That task was skipped because we didn't specify any extra plugins to install. And it returned successfully on both our systems. So now let's give us some actual plugins to use. I'm going to edit my test playbook. I'm going to call this author keep hope repo pathogen do a screw loose created a project called nerd tree, which you can see over here on the left side of my screen. And I think those two plugins are enough for right now. So let's run this code. For the astute of you, you may have noticed that our repository name was wrong. Now that we're back after a very long hair pulling frustrating good 30 minutes of trying to debug why git wasn't cloning. Here we are again. Let's give her one notice how much was cut watched a clock and you'll like, Hey, it moved to the next part for 30 minutes of fast. Such is development life. Yeah, all because we typed in the pack, the repository wrong, essentially. Yes. And Hey, we're getting the plugins that we expected to be there. This makes me happy because I want to log in. And we typed another one wrong. So let's let's retest this again. All right, now you're still testing these against yours. Yes. Okay. This is just against dev. Before we move up to our pseudo production servers was I provided. Yep. Cool. We got a pathogen and nerd tree on sent. Now here comes Debian. We're going to fast forward and check Tom servers. After we migrate our variables. That is exactly what we expect to be happening at this point. So now let's deploy these changes. All right. We'll run it again for giggles. Make sure it all works with no error messages. Perfect. And then since Tom is actually trying to use this, let's let's set him up with my actual bmrc. So we will define the URL to use. Yeah, I'm actually logged into one of the servers on my laptop. We are checking that a change will happen. We're on this again. And then our automated Tom testing. Yep. Tom's gonna test. I'm missing brogrammer. Oh, that's just a color scheme. Yeah. So through through our verification in production, we have noticed that we actually need to install pathogen before every other plugin because it needs to be auto loaded by Vin. So we're going to do that now. Yeah, we tried running them on my when I logged in while the servers, it did not work. So this is, you know, once getting part of the troubleshooting. So we got the install password for so the order matters a lot. Let's go edit our testing file, since that will now automatically be installed. And then let's edit our production playbook. We'll test these changes out. All right. What's the results of your test, Tom? Do you have a nice new pretty then config? I do. Now I got to figure out how to use nursery. That's a different good news is it's in there. Bad news is Tom doesn't know how to use it very well. I haven't installed on my other computer to that. I don't know how to use it either. So this is a theme. We'll do another tutorial on that. I like it though, because it lets me go through and find files and things like all right. Now we've come careening to the end of this. If you notice, there's probably some time jumps if you're paying attention to the clock. Because what was it? Pathogen is a file not a folder was the some of that. It's fixed now. So that's all that's all fixed. So you watch through some of the code iterations. We cut out some of that troubleshooting part to save you the pain of watching us fumble. But hopefully this gets you a better idea of how you can sync all these and install packages across different machines and how Ansible understands those different environments for machine links. So we couldn't get a via the first part of the show is trying to get a free BSD in vagrant, which that could be a topic into itself because apparently that does not work quite as well as expected. So we just did it with CentOS and Debian. But those are probably the more common distribution bases. It's either going to be a Debian base distribution or a Red Hat distribution for the most part, although you're two biggest ones. There's some arch guy screaming at us right now. But what you're going to see in large scale production environments, I mean, it's pretty much those two are the the facto standard across most of the deployment basis. Would you say if if you're looking at this video, you don't work at a Google or a Facebook. So you're not going to be needing much other than Debian support or cent support. Now, the final version of this code is available on GitHub, and it does have support for OS X. So if you are a developer on an Apple machine or even a system administrator on an Apple machine, and you want to get your own managed Vim config, by all means, check out the latest code that we've got. Because Phil has had to use a Mac and he married a person who develops on the Mac OS X environment. So undoubtedly you're deploying this on there too. Yes. Yeah, so we know Jenny at least uses some command line BIM stuff. She's learning it. She, my wife is a pro. She surprises me every day with with the amount of command line foo that that she has. Yes. So thanks for watching. If you like to count your like and subscribe. Phil has been doing really good with the last one. And of course, this one of replying to all the comments that come below. So feel free to reach out. And of course, in description is Phil's GitHub and methods by which you get a hold of them. And thanks for watching. Bye, everybody. All right.