diff --git a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/03-exercise-navigate.md b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/03-exercise-navigate.md
index 9d0ff15..bc98cee 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/03-exercise-navigate.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/03-exercise-navigate.md
@@ -12,9 +12,23 @@ Explore servera's directory tree with `pwd`, `cd` and `ls`, using absolute and r
objectives=["ch03.hierarchy","ch03.paths"]
id="navigate"
title="Find your way around"
+ exercise="sa-navigate"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Move around with absolute and relative paths, ., .. and ~.","Read long listings and find hidden files.","Recognise a directory you may not enter."] %}
+ {% task id="task-9812fc5d33ae" title="Start the exercise" %}
+ On workstation, start the exercise. It removes any practice files from an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-navigate
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-13825aae8cdb" title="Log in to servera and see where you start" %}
```console
@@ -106,4 +120,8 @@ total 0
The quotes keep `team notes.txt` as one name, and `ls` shows it quoted for the same reason, so you could copy it into a command as it is. The next lesson shows how to remove files. To put servera back as it was, run `rht-vmctl reset servera` on the Ubuntu host.
{% /task %}
+
+ {% task id="task-60a9e9b52693" title="Grade and finish" %}
+ {% lab-finish exercise="sa-navigate" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/05-exercise-manage-files.md b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/05-exercise-manage-files.md
index 07e6186..b790c42 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/05-exercise-manage-files.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/05-exercise-manage-files.md
@@ -12,9 +12,23 @@ A colleague left a website's files in one heap. On servera, sort them into direc
objectives=["ch03.manage","ch03.paths"]
id="manage-files"
title="Organise a website's files"
+ exercise="sa-manage-files"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create directories and empty files.","Move, rename and copy files and whole directories.","Remove files and directories, and see why rmdir is the safe choice."] %}
+ {% task id="task-d8e2a9960119" title="Start the exercise" %}
+ On workstation, start the exercise. It removes any site and archive directories from an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-manage-files
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-5bf66e090adf" title="Create the site directory and its three subdirectories" %}
```console
@@ -142,4 +156,8 @@ rm: remove directory 'archive'? y
Leave `~/site` for now, or run `rht-vmctl reset servera` on the host to put servera back.
{% /task %}
+
+ {% task id="task-71399a5f96b8" title="Grade and finish" %}
+ {% lab-finish exercise="sa-manage-files" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/07-exercise-links.md b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/07-exercise-links.md
index 4445c87..3f71945 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/07-exercise-links.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/07-exercise-links.md
@@ -12,9 +12,23 @@ Many applications keep each release in its own directory and point a link called
objectives=["ch03.links"]
id="links"
title="Switch releases with a link"
+ exercise="sa-links"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create and repoint a symbolic link to a directory.","Keep a file's data alive with a hard link.","Recognise a dangling link."] %}
+ {% task id="task-e87e124c7f4e" title="Start the exercise" %}
+ On workstation, start the exercise. It removes any release directories and links from an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-links
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-eaf78d3fd850" title="Create two releases" %}
On servera, as student, make two release directories, each with its own `app.conf`. `echo "text" > file` writes a line into a file (chapter 5 explains it):
@@ -111,11 +125,7 @@ version 2
`current` now dangles: it points to a name that is gone. The hard link kept the data, and its link count is back to 1.
{% /task %}
- {% task id="task-4a9e86fa7e3c" title="Clean up" %}
-
-```console
-[student@servera ~]$ rm -r release current app.conf.saved
-[student@servera ~]$ exit
-```
+ {% task id="task-d7942db186ed" title="Grade and finish" %}
+ {% lab-finish exercise="sa-links" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/09-lab.md
index f167355..c0abbe0 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch03-files-and-directories/09-lab.md
@@ -162,6 +162,6 @@ podcast_s2_e2.mp3 podcast_s2_e4.mp3 podcast_s2_e6.mp3
{% task id="task-c7f85590af3a" title="Grade and finish" %}
Log out of serverb, then on workstation:
- {% lab-finish exercise="sa-files-review" grade=true /%}
+ {% lab-finish exercise="sa-files-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/02-exercise-redirection.md b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/02-exercise-redirection.md
index e04bd7e..c56c9ff 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/02-exercise-redirection.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/02-exercise-redirection.md
@@ -12,9 +12,23 @@ On servera, save command output to files, keep errors apart from results, discar
objectives=["ch05.redirect"]
id="redirection"
title="Capture and combine output"
+ exercise="sa-redirect"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write and append output to files.","Separate errors from results, and discard them.","Build pipelines, and keep a copy with tee."] %}
+ {% task id="task-199503c05e6f" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the result files of an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-redirect
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-79966807d096" title="Save a list of configuration files" %}
On servera, as student, list the `.conf` files directly in `/etc` into `conf-files.txt`, then count and peek at it.
@@ -111,11 +125,7 @@ devtmpfs 455M 0 455M 0% /dev
{% /reveal %}
{% /task %}
- {% task id="task-fec86414a779" title="Clean up" %}
-
-```console
-[student@servera ~]$ rm conf-files.txt found.txt errors.txt disk-report.txt
-[student@servera ~]$ exit
-```
+ {% task id="task-14c9a44075b1" title="Grade and finish" %}
+ {% lab-finish exercise="sa-redirect" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/06-exercise-vim.md b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/06-exercise-vim.md
index 7b2a01a..034d028 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/06-exercise-vim.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/06-exercise-vim.md
@@ -12,9 +12,23 @@ Work through the first lesson of vim's own tutorial, then use what it taught to
objectives=["ch05.vim"]
id="vim"
title="Create and edit a file with vim"
+ exercise="sa-vim"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Move between normal, insert and command-line mode.","Copy, paste, delete and undo lines, and replace text.","Save, or quit without saving."] %}
+ {% task id="task-e3f7ce51483c" title="Start the exercise" %}
+ On workstation, start the exercise. It removes welcome.txt of an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-vim
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-727284164744" title="Do lesson 1 of vimtutor" %}
On servera, start the tutorial:
@@ -94,4 +108,8 @@ Contact: admin@lab.example.com
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-062ea72bb36e" title="Grade and finish" %}
+ {% lab-finish exercise="sa-vim" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/08-exercise-environment.md b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/08-exercise-environment.md
index c0c136a..0d3026c 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/08-exercise-environment.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/08-exercise-environment.md
@@ -12,9 +12,23 @@ On servera, use variables and PATH to run your own command, create an alias, and
objectives=["ch05.environment","ch05.vim"]
id="environment"
title="Customise your shell"
+ exercise="sa-environment"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Use and export variables.","Run your own script through PATH.","Make an alias and environment settings permanent."] %}
+ {% task id="task-76e145a40cdd" title="Start the exercise" %}
+ On workstation, start the exercise. It restores student's start-up files on servera to the originals and removes ~/bin.
+
+```console
+[student@workstation ~]$ lab start sa-environment
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-8ef6dff987b9" title="See exporting at work" %}
On servera, set a variable, check that a child shell doesn't see it, export it, and check again.
@@ -110,4 +124,8 @@ vim
[student@servera ~]$ exit
```
{% /task %}
+
+ {% task id="task-33d39b3f4165" title="Grade and finish" %}
+ {% lab-finish exercise="sa-environment" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/09-lab.md
index d57a508..df5ab60 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch05-text-and-editors/09-lab.md
@@ -140,6 +140,6 @@ vim
{% /task %}
{% task id="task-28043cd64cf1" title="Grade and finish" %}
- {% lab-finish exercise="sa-text-review" grade=true /%}
+ {% lab-finish exercise="sa-text-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/03-exercise-superuser.md b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/03-exercise-superuser.md
index 31d93ac..512c5ff 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/03-exercise-superuser.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/03-exercise-superuser.md
@@ -12,9 +12,23 @@ On servera, compare su and sudo, create a user who may not use sudo, then give t
objectives=["ch06.superuser"]
id="superuser"
title="Grant limited root access"
+ exercise="sa-sudo"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Switch to root with su - and sudo -i.","Write, check and test a narrow sudo rule.","Find sudo's record of allowed and refused commands."] %}
+ {% task id="task-fa89e230c528" title="Start the exercise" %}
+ On workstation, start the exercise. It removes a tester account and sudo rule left by an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-sudo
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-9afd16d38260" title="Become root with su -, then with sudo -i" %}
On servera, as student. root's password in the lab is `redhat`; student's is `student`.
@@ -117,4 +131,8 @@ Sorry, user tester is not allowed to execute '/bin/systemctl restart sshd' as ro
Each line shows who, from which directory, as whom and what, and whether sudo allowed it.
{% /task %}
+
+ {% task id="task-049f8d2e2c27" title="Grade and finish" %}
+ {% lab-finish exercise="sa-sudo" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/05-exercise-accounts.md b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/05-exercise-accounts.md
index 6fb73af..52b16ff 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/05-exercise-accounts.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/05-exercise-accounts.md
@@ -12,9 +12,23 @@ On servera, create a group for a web team, two people in it, and a service accou
objectives=["ch06.accounts","ch06.concepts"]
id="accounts"
title="Set up a web team"
+ exercise="sa-accounts"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create a group with a chosen GID and users with supplementary groups.","Create a service account with nologin.","Recover from replacing a user's groups by mistake."] %}
+ {% task id="task-4067a94bdade" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the accounts and the group of an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-accounts
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-e9566e8670be" title="Create the group" %}
On servera, open a root shell with `sudo -i`, and create the group `webteam` with GID 40000.
@@ -95,14 +109,7 @@ drwx------. 2 webapp webteam 4096 Oct 3 10:36 /home/webapp
Mode `700` (`drwx------`): each owner, and nobody else, may enter their home. `/home/webapp` belongs to the group `webteam` because that is webapp's primary group.
{% /task %}
- {% task id="task-4d710a1ff0e6" title="Clean up" %}
- Delete the three accounts with their home directories, then the group. If `userdel` reports that a user *is currently used by process* N, a process still runs as that account (for example from an earlier `su`): wait a moment and try again, or reset servera.
-
-```console
-[root@servera ~]# userdel -r alice; userdel -r bob; userdel -r webapp
-[root@servera ~]# groupdel webteam
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-16d7cf000ea3" title="Grade and finish" %}
+ {% lab-finish exercise="sa-accounts" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/07-exercise-passwords.md b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/07-exercise-passwords.md
index 39ee6fe..6e61aea 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/07-exercise-passwords.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/07-exercise-passwords.md
@@ -12,9 +12,23 @@ An intern starts on servera today. Give them a temporary password they must chan
objectives=["ch06.passwords"]
id="passwords"
title="Onboard and offboard an intern"
+ exercise="sa-passwords"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Set a temporary password and force a change at the next login.","Apply password ageing with chage.","See the difference between locking and expiring an account."] %}
+ {% task id="task-05f7cae6c3c5" title="Start the exercise" %}
+ On workstation, start the exercise. It removes an intern account left by an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-passwords
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-f7644ce3f4a5" title="Create the account with a temporary password" %}
On servera as root (`sudo -i`), create `intern` ("Summer intern"), set the password `Welcome-2026`, and force a change at the next login.
@@ -100,11 +114,7 @@ su: User account has expired
Unlike a lock, expiry blocks every kind of login, including SSH keys. The account and its files remain, for the handover.
{% /task %}
- {% task id="task-8faa4ecab9f2" title="Clean up" %}
-
-```console
-[student@servera ~]$ sudo userdel -r intern
-[student@servera ~]$ exit
-```
+ {% task id="task-a28b7e5c2b0f" title="Grade and finish" %}
+ {% lab-finish exercise="sa-passwords" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/08-lab.md b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/08-lab.md
index 9706f07..cef3b8a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/08-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch06-users-and-groups/08-lab.md
@@ -109,6 +109,6 @@ backup:x:3200:35000::/home/backup:/sbin/nologin
{% task id="task-08e46a024567" title="Grade and finish" %}
Leave serverb, then on workstation:
- {% lab-finish exercise="sa-users-review" grade=true /%}
+ {% lab-finish exercise="sa-users-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/02-exercise-reading.md b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/02-exercise-reading.md
index 6c9d3fd..ceeb1b0 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/02-exercise-reading.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/02-exercise-reading.md
@@ -12,30 +12,27 @@ Predict, then test. You will set up three users and a report file, work out for
objectives=["ch07.reading"]
id="reading"
title="Who can read this?"
+ exercise="sa-reading"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Read owner, group and other permissions and predict access.","See that directory permissions decide who can list, enter and delete.","Use namei -l to find a blocked path."] %}
- {% task id="task-39ebad7f9c3a" title="Create the users and the file" %}
- On servera, open a root shell with `sudo -i`. Create the group `project` (GID 4500) and users `maria` and `john`, both in `project`, and `priya`, who is not. Then create `/srv/lab7/report.txt` containing a line of text, owned by `maria:project`, mode 640.
+ {% task id="task-8f0c9c3dd818" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the group project (4500), the users maria, john and priya, and `/srv/lab7/report.txt` (owned by maria, group project, mode 640).
- {% reveal title="Show solution" %}
+```console
+[student@workstation ~]$ lab start sa-reading
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+
+ Then, as root on servera, change to the exercise directory:
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# groupadd -g 4500 project
-[root@servera ~]# useradd -m -G project maria
-[root@servera ~]# useradd -m -G project john
-[root@servera ~]# useradd -m priya
-[root@servera ~]# mkdir /srv/lab7
[root@servera ~]# cd /srv/lab7
-[root@servera lab7]# echo "Q3 figures" > report.txt
-[root@servera lab7]# chown maria:project report.txt
-[root@servera lab7]# chmod 640 report.txt
-[root@servera lab7]# ls -l report.txt
--rw-r-----. 1 maria project 11 Oct 3 16:11 report.txt
```
- {% /reveal %}
{% /task %}
{% task id="task-6a8712ce4075" title="Predict and test" %}
@@ -132,17 +129,7 @@ dr-xr-xr-x maria root box
{% /reveal %}
{% /task %}
- {% task id="task-b71d77a9b856" title="Clean up" %}
-
- If `userdel` says a user is still in use, a login session from `su` has not closed yet: wait a few seconds and run it again.
-
-```console
-[root@servera lab7]# cd
-[root@servera ~]# rm -rf /srv/lab7
-[root@servera ~]# userdel -r maria; userdel -r john; userdel -r priya
-[root@servera ~]# groupdel project
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-547ea3f80e70" title="Grade and finish" %}
+ {% lab-finish exercise="sa-reading" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/04-exercise-changing.md b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/04-exercise-changing.md
index 658611b..fb26c20 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/04-exercise-changing.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/04-exercise-changing.md
@@ -12,32 +12,21 @@ A small website lives in `/srv/site`. Its owner maria and the group `webteam` ne
objectives=["ch07.changing"]
id="changing"
title="Secure a web folder"
+ exercise="sa-changing"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Change owner and group recursively with chown -R.","Set modes with symbols, octal numbers and the capital X.","Verify access by acting as different users."] %}
- {% task id="task-d9cf5dfc07d5" title="Build the tree" %}
- On servera, open a root shell with `sudo -i`. Create the group `webteam` (GID 4600), the users `maria` (in `webteam`) and `john` (not in it), and the tree `/srv/site/html/index.html` plus `/srv/site/bin/deploy.sh`, using `mkdir -p` and `touch`. List it with `ls -lR /srv/site`.
-
- {% reveal title="Show solution" %}
+ {% task id="task-c43425e35dd2" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the group webteam (4600), the users maria and john, and the tree `/srv/site` with an empty `index.html` and `deploy.sh`, all owned by root.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# groupadd -g 4600 webteam
-[root@servera ~]# useradd -m -G webteam maria
-[root@servera ~]# useradd -m john
-[root@servera ~]# mkdir -p /srv/site/html /srv/site/bin
-[root@servera ~]# touch /srv/site/html/index.html /srv/site/bin/deploy.sh
-[root@servera ~]# ls -lR /srv/site
-/srv/site:
-total 8
-drwxr-xr-x. 2 root root 4096 Oct 3 16:12 bin
-drwxr-xr-x. 2 root root 4096 Oct 3 16:12 html
-...output omitted...
+[student@workstation ~]$ lab start sa-changing
```
- Everything belongs to root and is readable by everyone. That is what we will change.
- {% /reveal %}
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-0bd717699754" title="Give it to maria and webteam" %}
@@ -133,16 +122,7 @@ ok
{% /reveal %}
{% /task %}
- {% task id="task-844e07e9a91b" title="Clean up" %}
-
- If `userdel` says a user is still in use, a login session from `su` has not closed yet: wait a few seconds and run it again.
-
-```console
-[root@servera ~]# rm -rf /srv/site
-[root@servera ~]# userdel -r maria; userdel -r john
-[root@servera ~]# groupdel webteam
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-31b287046207" title="Grade and finish" %}
+ {% lab-finish exercise="sa-changing" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/06-exercise-shared.md b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/06-exercise-shared.md
index 32caf34..a2b8fbe 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/06-exercise-shared.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/06-exercise-shared.md
@@ -12,28 +12,21 @@ Two shared spaces on servera: a team directory where `maria` and `john` work on
objectives=["ch07.defaults"]
id="shared"
title="Team directory and drop box"
+ exercise="sa-shared"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create a setgid directory so new files inherit the team group.","Use the umask so team members can write each other's files.","Protect a public directory with the sticky bit."] %}
- {% task id="task-5339c3f48094" title="Create the people and the team directory" %}
- On servera as root (`sudo -i`), create the group `team` (GID 4700); users `maria` and `john` in `team`; and `priya`, who is not. Then create `/srv/team` owned by `root:team` with mode 2770, in a single command.
-
- {% reveal title="Show solution" %}
+ {% task id="task-213d49a6a335" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the group team (4700), the users maria, john and priya, and the directory `/srv/team` (root:team, mode 2770).
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# groupadd -g 4700 team
-[root@servera ~]# useradd -m -G team maria
-[root@servera ~]# useradd -m -G team john
-[root@servera ~]# useradd -m priya
-[root@servera ~]# install -d -o root -g team -m 2770 /srv/team
-[root@servera ~]# ls -ld /srv/team
-drwxrws---. 2 root team 4096 Oct 3 16:13 /srv/team
+[student@workstation ~]$ lab start sa-shared
```
- `install -d` creates a directory and sets its owner, group and mode together.
- {% /reveal %}
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-b56b62e149ac" title="See the group inherited, but not group write" %}
@@ -114,16 +107,7 @@ priya.txt
{% /reveal %}
{% /task %}
- {% task id="task-89d73f7b02cf" title="Clean up" %}
-
- If `userdel` says a user is still in use, a login session from `su` has not closed yet: wait a few seconds and run it again.
-
-```console
-[root@servera ~]# rm -rf /srv/team /srv/dropbox
-[root@servera ~]# userdel -r maria; userdel -r john; userdel -r priya
-[root@servera ~]# groupdel team
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-771aac8cd855" title="Grade and finish" %}
+ {% lab-finish exercise="sa-shared" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/08-exercise-acls.md b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/08-exercise-acls.md
index 0c5a0e4..5e2219d 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/08-exercise-acls.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/08-exercise-acls.md
@@ -12,29 +12,21 @@ maria's quarterly report is private. The auditors must be able to read it, one n
objectives=["ch07.acls"]
id="acls"
title="Give the auditors read access"
+ exercise="sa-acls"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Grant a group read access to one file with setfacl.","Explain an effective permission cut by the mask.","Set a default ACL so new files inherit access."] %}
- {% task id="task-bbcafb964453" title="Create the private report" %}
- On servera as root (`sudo -i`), create the group `auditors` (GID 4800), the users `priya` (in `auditors`), `maria` and `john`, and the files `/srv/reports/q3/summary.txt` owned by maria. Make the whole tree private to maria and confirm that priya is refused.
-
- {% reveal title="Show solution" %}
+ {% task id="task-fd5a39e536f0" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the group auditors (4800), the users priya (an auditor), maria and john, and the private report `/srv/reports/q3/summary.txt` that only maria can read.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# groupadd -g 4800 auditors
-[root@servera ~]# useradd -m -G auditors priya
-[root@servera ~]# useradd -m maria
-[root@servera ~]# useradd -m john
-[root@servera ~]# mkdir -p /srv/reports/q3
-[root@servera ~]# echo figures > /srv/reports/q3/summary.txt
-[root@servera ~]# chown -R maria:maria /srv/reports
-[root@servera ~]# chmod -R u=rwX,g=,o= /srv/reports
-[root@servera ~]# su - priya -c 'cat /srv/reports/q3/summary.txt'
-cat: /srv/reports/q3/summary.txt: Permission denied
+[student@workstation ~]$ lab start sa-acls
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-ea7177a7713e" title="Grant the group read access" %}
@@ -152,16 +144,7 @@ drwx------. 2 maria maria 4096 Oct 3 16:14 /srv/reports/q3
{% /reveal %}
{% /task %}
- {% task id="task-5226ce8b142c" title="Clean up" %}
-
- If `userdel` says a user is still in use, a login session from `su` has not closed yet: wait a few seconds and run it again.
-
-```console
-[root@servera ~]# rm -rf /srv/reports
-[root@servera ~]# userdel -r priya; userdel -r maria; userdel -r john
-[root@servera ~]# groupdel auditors
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-c35a2b6e4c9d" title="Grade and finish" %}
+ {% lab-finish exercise="sa-acls" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/09-lab.md
index 8a89ddb..2189af7 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch07-permissions-and-acls/09-lab.md
@@ -101,6 +101,6 @@ ls: cannot open directory '/srv/analysis': Permission denied
{% task id="task-17e922d81973" title="Grade" %}
Leave servera, then on workstation:
- {% lab-finish exercise="sa-perms-review" grade=true /%}
+ {% lab-finish exercise="sa-perms-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch08-processes/06-exercise-signals.md b/content/programs/rhel9-sysadmin/chapters/ch08-processes/06-exercise-signals.md
index 0d4c7a3..e3132fb 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch08-processes/06-exercise-signals.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch08-processes/06-exercise-signals.md
@@ -12,27 +12,21 @@ Compare a clean shutdown with a forced one, pause and resume a CPU hog, and end
objectives=["ch08.signals"]
id="signals"
title="Stop a runaway process the right way"
+ exercise="sa-signals"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Send SIGTERM, SIGKILL, SIGSTOP and SIGCONT and see the difference.","Preview matches with pgrep before using pkill.","End all of one user's processes."] %}
- {% task id="task-ad7d36659974" title="Write a program that cleans up" %}
- On servera, create `~/cleanup.sh` with the content below, and make it executable. It creates a lock file and removes it when it receives SIGTERM.
-
-```bash
-#!/bin/bash
-trap 'echo "cleaning up"; rm -f /tmp/work.lock; exit 0' TERM
-touch /tmp/work.lock
-while true; do sleep 1; done
-```
-
- {% reveal title="Show solution" %}
+ {% task id="task-3c38194bb33c" title="Start the exercise" %}
+ On workstation, start the exercise. It puts the script `~/cleanup.sh` (a loop that creates `/tmp/work.lock` and removes it when it receives SIGTERM) in student's home on servera.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ vim cleanup.sh
-[student@servera ~]$ chmod +x cleanup.sh
+[student@workstation ~]$ lab start sa-signals
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-8dac31281941" title="Terminate politely" %}
@@ -122,15 +116,7 @@ ls: cannot access '/tmp/work.lock': No such file or directory
{% /reveal %}
{% /task %}
- {% task id="task-01dbf4b6e00c" title="Clean up" %}
-
-```console
-[root@servera ~]# userdel -r bob
-[root@servera ~]# exit
-[student@servera ~]$ rm -f cleanup.sh
-[student@servera ~]$ exit
-```
-
- If `userdel` says bob is still in use, wait a moment and run it again.
+ {% task id="task-0f55f19ffce0" title="Grade and finish" %}
+ {% lab-finish exercise="sa-signals" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch08-processes/08-exercise-monitoring.md b/content/programs/rhel9-sysadmin/chapters/ch08-processes/08-exercise-monitoring.md
index 6478eff..cc0dc58 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch08-processes/08-exercise-monitoring.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch08-processes/08-exercise-monitoring.md
@@ -12,9 +12,23 @@ Two CPU-hungry processes are fighting for servera's CPU. Find them, watch how `n
objectives=["ch08.monitoring"]
id="monitoring"
title="Find the slowdown and tune the server"
+ exercise="sa-monitoring"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Read load average, top and free.","Compare a normal and a nice process.","Install tuned and switch profiles."] %}
+ {% task id="task-4c53c1c672e9" title="Start the exercise" %}
+ On workstation, start the exercise. It makes sure the tuned package is not installed yet on servera.
+
+```console
+[student@workstation ~]$ lab start sa-monitoring
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-e65f5036b47e" title="Take the baseline" %}
On servera, record the load average, the number of CPUs and the memory figures before you start any load.
@@ -123,11 +137,7 @@ Current active profile: virtual-guest
{% /reveal %}
{% /task %}
- {% task id="task-e5d7824a63af" title="Clean up" %}
-
-```console
-[student@servera ~]$ sudo dnf remove -y tuned
-[student@servera ~]$ exit
-```
+ {% task id="task-784c0dd399df" title="Grade and finish" %}
+ {% lab-finish exercise="sa-monitoring" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch08-processes/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch08-processes/09-lab.md
index 5a11554..d136bcd 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch08-processes/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch08-processes/09-lab.md
@@ -99,6 +99,6 @@ Current active profile: throughput-performance
{% task id="task-63bbe29381d5" title="Record the signal numbers and grade" %}
Leave servera, then on workstation edit `answers.txt` (use `kill -l` to look the numbers up) and grade:
- {% lab-finish exercise="sa-proc-review" grade=true /%}
+ {% lab-finish exercise="sa-proc-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/02-exercise-services.md b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/02-exercise-services.md
index 07da9f6..84b1a6a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/02-exercise-services.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/02-exercise-services.md
@@ -12,9 +12,23 @@ Take the scheduler service `crond` through all of its states, and see exactly wh
objectives=["ch09.services"]
id="services"
title="Control a service"
+ exercise="sa-services"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Read systemctl status and the is-active and is-enabled answers.","Separate start/stop from enable/disable.","Mask and unmask a service."] %}
+ {% task id="task-d20cf68d9eb5" title="Start the exercise" %}
+ On workstation, start the exercise. It makes sure crond runs and is enabled, and that bluetooth is not masked.
+
+```console
+[student@workstation ~]$ lab start sa-services
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-44a476ebef43" title="Read the status" %}
On servera, open a root shell with `sudo -i`. Show the status of `crond`. What are its main PID, its state, and its enabled state? Then ask the two short questions.
@@ -126,4 +140,8 @@ Removed "/etc/systemd/system/bluetooth.service".
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-a2c0be26893e" title="Grade and finish" %}
+ {% lab-finish exercise="sa-services" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/04-exercise-unit-files.md b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/04-exercise-unit-files.md
index a733ae5..3ba4426 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/04-exercise-unit-files.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/04-exercise-unit-files.md
@@ -12,28 +12,21 @@ Turn a small shell script into a proper service: write the unit, start it, watch
objectives=["ch09.units"]
id="units"
title="Write your own service"
+ exercise="sa-unit-files"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a service unit and enable it.","See Restart=on-failure bring a killed service back.","Override a setting with a drop-in and diagnose status 203."] %}
- {% task id="task-0a43b748f06a" title="Create the program" %}
- On servera as root (`sudo -i`), create `/usr/local/bin/reporter.sh` that appends `HH:MM:SS reporter alive` to `/var/log/reporter.log` every `${INTERVAL:-10}` seconds, and make it executable.
-
- {% reveal title="Show solution" %}
+ {% task id="task-fdd776e1cca3" title="Start the exercise" %}
+ On workstation, start the exercise. It installs the program `/usr/local/bin/reporter.sh` on servera: a loop that appends a heartbeat line to `/var/log/reporter.log` every `$INTERVAL` seconds (default 10).
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# vim /usr/local/bin/reporter.sh
-[root@servera ~]# cat /usr/local/bin/reporter.sh
-#!/bin/bash
-# Append a timestamp to the report every few seconds.
-while true; do
- echo "$(date +%T) reporter alive" >> /var/log/reporter.log
- sleep "${INTERVAL:-10}"
-done
-[root@servera ~]# chmod 755 /usr/local/bin/reporter.sh
+[student@workstation ~]$ lab start sa-unit-files
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-6b3bbe70e5ef" title="Write the unit and start it" %}
@@ -142,15 +135,7 @@ active
{% /reveal %}
{% /task %}
- {% task id="task-0f4394e6e68d" title="Clean up" %}
-
-```console
-[root@servera ~]# systemctl disable --now reporter
-Removed "/etc/systemd/system/multi-user.target.wants/reporter.service".
-[root@servera ~]# rm -rf /etc/systemd/system/reporter.service /etc/systemd/system/reporter.service.d /usr/local/bin/reporter.sh /var/log/reporter.log
-[root@servera ~]# systemctl daemon-reload
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-afe68587c470" title="Grade and finish" %}
+ {% lab-finish exercise="sa-unit-files" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/06-exercise-boot.md b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/06-exercise-boot.md
index 0dee097..bcb061e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/06-exercise-boot.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/06-exercise-boot.md
@@ -12,9 +12,23 @@ Make servera boot to the text-mode target, add a kernel argument, reboot, and pr
objectives=["ch09.boot"]
id="boot"
title="Change how the server boots"
+ exercise="sa-boot"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Set the default target and check it after a reboot.","Add and remove a kernel argument with grubby.","Read boot timing with systemd-analyze."] %}
+ {% task id="task-7fe20de7b383" title="Start the exercise" %}
+ On workstation, start the exercise. It makes sure servera starts from the default target graphical.target without the extra kernel argument.
+
+```console
+[student@workstation ~]$ lab start sa-boot
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-05d504378a68" title="Record the starting point" %}
On servera as root (`sudo -i`), show the default target, the kernel command line and the boot time.
@@ -107,4 +121,8 @@ args="console=tty1 console=ttyS0 ro"
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-e50569b8f094" title="Grade and finish" %}
+ {% lab-finish exercise="sa-boot" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/08-exercise-fstab.md b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/08-exercise-fstab.md
index 9998056..ce2e4c1 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/08-exercise-fstab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/08-exercise-fstab.md
@@ -12,9 +12,23 @@ You add a mount for a disk that is not there yet. Learn to catch the mistake wit
objectives=["ch09.recovery"]
id="fstab"
title="Check /etc/fstab before you reboot"
+ exercise="sa-fstab"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Verify an fstab file without rebooting.","Use mount -a to test entries.","Understand what nofail changes."] %}
+ {% task id="task-8cee2fe9aef7" title="Start the exercise" %}
+ On workstation, start the exercise. It removes a test mount from `/etc/fstab` if an earlier run left one.
+
+```console
+[student@workstation ~]$ lab start sa-fstab
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-bc9d2b67d349" title="Back up and look" %}
On servera as root, copy `/etc/fstab` to `/root/fstab.bak` and show its entries without comments.
@@ -104,4 +118,8 @@ status=0
[student@servera ~]$ exit
```
{% /task %}
+
+ {% task id="task-eaa4e2a36bd1" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fstab" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/09-lab.md
index 29ec6ab..a1bab56 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch09-systemd-and-boot/09-lab.md
@@ -109,6 +109,6 @@ multi-user.target
{% task id="task-c35fd4a57c68" title="Grade" %}
Leave servera, then on workstation:
- {% lab-finish exercise="sa-systemd-review" grade=true /%}
+ {% lab-finish exercise="sa-systemd-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/04-exercise-keys.md b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/04-exercise-keys.md
index 7204c6c..af3ffda 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/04-exercise-keys.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/04-exercise-keys.md
@@ -12,19 +12,21 @@ Set up key-based login from workstation to the `ops` account on servera, check t
objectives=["ch10.keys"]
id="keys"
title="Passwordless access for an account"
+ exercise="sa-keys"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create a key pair and install it with ssh-copy-id.","Diagnose a key refused because of permissions.","Use a passphrase with ssh-agent."] %}
- {% task id="task-c734fcaadb7f" title="Make sure the account exists" %}
- If you reset the servers since the last exercise, create `ops` again on servera, with the password `Ops-Pass-2026`. (Use `ssh root@servera`.)
-
- {% reveal title="Show solution" %}
+ {% task id="task-d19e45fd21eb" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the account `ops` on servera with the password `Ops-Pass-2026` and removes key files of an earlier run from workstation.
```console
-[student@workstation ~]$ ssh root@servera "id ops || (useradd -m ops && echo 'Ops-Pass-2026' | passwd --stdin ops)"
-uid=1001(ops) gid=1001(ops) groups=1001(ops)
+[student@workstation ~]$ lab start sa-keys
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-dba8dc306c6b" title="Create the key pair" %}
@@ -136,4 +138,8 @@ echo Agent pid 1400 killed;
The copy has the same public half, so the server accepted it without any change. Without the agent, ssh would have asked for the passphrase.
{% /reveal %}
{% /task %}
+
+ {% task id="task-d76dc44caa6f" title="Grade and finish" %}
+ {% lab-finish exercise="sa-keys" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/06-exercise-transfer.md b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/06-exercise-transfer.md
index 26ffa33..16f9dac 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/06-exercise-transfer.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/06-exercise-transfer.md
@@ -12,9 +12,23 @@ Define an alias for the `ops` account, then use scp, sftp and rsync to move file
objectives=["ch10.transfer"]
id="transfer"
title="Move files between machines"
+ exercise="sa-transfer"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a Host block in ~/.ssh/config.","Upload and download with scp and sftp.","Mirror a directory with rsync and preview with -n."] %}
+ {% task id="task-13cc9ad0b055" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the account `ops` on servera and the key `~/.ssh/id_ops` on workstation, installed for ops (the result of the previous exercise).
+
+```console
+[student@workstation ~]$ lab start sa-transfer
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-f20aebf82d9f" title="Prepare files and an alias" %}
This exercise needs the key from the previous exercise (`~/.ssh/id_ops`, installed for `ops@servera`). Create a working directory `~/sshlab` with `inventory.txt` (any line) and `reports/a.txt`, `reports/b.txt`. Then add a `Host opsa` block to `~/.ssh/config` and test it.
@@ -140,4 +154,8 @@ c.txt
Always add `-n` first when `--delete` is involved.
{% /reveal %}
{% /task %}
+
+ {% task id="task-e6b9ebfb2272" title="Grade and finish" %}
+ {% lab-finish exercise="sa-transfer" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/08-exercise-hardening.md b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/08-exercise-hardening.md
index 3f5c0ff..a03ae94 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/08-exercise-hardening.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/08-exercise-hardening.md
@@ -12,9 +12,23 @@ Turn off password logins on servera, prove the change with a failed password att
objectives=["ch10.hardening"]
id="hardening"
title="Harden sshd, safely"
+ exercise="sa-hardening"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a sshd drop-in and verify it with sshd -T.","Demonstrate that password logins are refused and key logins still work.","Explain the first-value-wins rule."] %}
+ {% task id="task-902b9a7b40a6" title="Start the exercise" %}
+ On workstation, start the exercise. It creates the account `ops` on servera with a key (`~/.ssh/id_ops` on workstation) that you will keep using after passwords are switched off.
+
+```console
+[student@workstation ~]$ lab start sa-hardening
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-2fc1cb637c22" title="Open the spare session" %}
Open **two** terminals on workstation, both logged in to servera as root (`ssh root@servera`). Leave the second one alone: it is your way back in if the change goes wrong. Show the current values of the options you will change.
@@ -98,19 +112,7 @@ sshd-session[1049]: pam_unix(sshd:session): session opened for user ops(uid=1001
{% /reveal %}
{% /task %}
- {% task id="task-255297d55311" title="Put it back" %}
- Remove the drop-in, reload, confirm that the defaults are back, and close the spare session.
-
- {% reveal title="Show solution" %}
-
-```console
-[root@servera ~]# rm /etc/ssh/sshd_config.d/10-hardening.conf
-[root@servera ~]# sshd -t && systemctl reload sshd
-[root@servera ~]# sshd -T | grep -E '^(passwordauthentication|permitrootlogin) '
-permitrootlogin without-password
-passwordauthentication yes
-[root@servera ~]# exit
-```
- {% /reveal %}
+ {% task id="task-501bd5eab37d" title="Grade and finish" %}
+ {% lab-finish exercise="sa-hardening" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/09-lab.md
index d826a94..bc854e4 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch10-ssh-and-remote-access/09-lab.md
@@ -102,6 +102,6 @@ c.txt
{% task id="task-ddaf4ab39986" title="Record the fingerprint and grade" %}
Fill in `answers.txt` (use `ssh-keygen -lF servera`; the type is the word ED25519 in the listing), then:
- {% lab-finish exercise="sa-ssh-review" grade=true /%}
+ {% lab-finish exercise="sa-ssh-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/04-exercise-persistence.md b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/04-exercise-persistence.md
index 58a721d..d81851e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/04-exercise-persistence.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/04-exercise-persistence.md
@@ -12,9 +12,23 @@ Make the journal persistent and prove it across a reboot, send a facility to its
objectives=["ch11.retention"]
id="retention"
title="Keep and rotate your logs"
+ exercise="sa-persistence"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Make the journal persistent and verify it after a reboot.","Add an rsyslog rule for a facility.","Write, test and force a logrotate rule."] %}
+ {% task id="task-e55c88aec5ed" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the logging settings of an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-persistence
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-0b7282cb49ac" title="Check the journal's lifetime" %}
On servera as root, show how many boots the journal holds and how much space it uses.
@@ -117,15 +131,7 @@ EOT
{% /reveal %}
{% /task %}
- {% task id="task-b71ceeaceb3f" title="Clean up" %}
-
-```console
-[root@servera ~]# rm -f /etc/rsyslog.d/10-myapp.conf /etc/logrotate.d/myapp /var/log/myapp.log*
-[root@servera ~]# systemctl restart rsyslog
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
-
- The persistent journal can stay: it is good practice.
+ {% task id="task-012ddc2e2403" title="Grade and finish" %}
+ {% lab-finish exercise="sa-persistence" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/06-exercise-time.md b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/06-exercise-time.md
index dbc3a5a..81bfaf4 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/06-exercise-time.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/06-exercise-time.md
@@ -12,9 +12,23 @@ Read the state of servera's clock, move it to another time zone, inspect where i
objectives=["ch11.time"]
id="time"
title="Time zone and time sources"
+ exercise="sa-time"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Read timedatectl, chronyc sources and chronyc tracking.","Change the time zone.","Add an NTP server and verify it."] %}
+ {% task id="task-749cd80a2bb6" title="Start the exercise" %}
+ On workstation, start the exercise. It puts servera in the UTC time zone and removes an extra time source from an earlier run.
+
+```console
+[student@workstation ~]$ lab start sa-time
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-e8defc8ba9e0" title="Check the clock" %}
On servera as root, show the time status, the time sources, and the clock offset.
@@ -113,4 +127,8 @@ System clock synchronized: no
Right after a restart, `synchronized` may say `no` for a few moments, until chronyd has chosen a source again. It says `yes` again within a minute or two.
{% /task %}
+
+ {% task id="task-d05e32874b50" title="Grade and finish" %}
+ {% lab-finish exercise="sa-time" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/08-exercise-diagnosing.md b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/08-exercise-diagnosing.md
index 60ff21d..e7a3a62 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/08-exercise-diagnosing.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/08-exercise-diagnosing.md
@@ -12,55 +12,21 @@ A colleague has installed a small service called `billing`, and it does not run.
objectives=["ch11.diagnosis"]
id="diagnosis"
title="Find and fix a failing service"
+ exercise="sa-diagnosing"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Read a failed service's status and journal.","Identify the first error and fix its cause.","Verify the fix and clean up."] %}
- {% task id="task-ac12e7a5c79b" title="Install the broken service" %}
- As root on servera (`sudo -i`), create the program and unit below. They represent the colleague's work; do not read them closely yet.
-
-```bash
-cat > /usr/local/bin/billing.sh <<'EOT'
-#!/bin/bash
-CONF=/etc/billing.conf
-if [ ! -r "$CONF" ]; then
- echo "billing: cannot read $CONF" >&2
- exit 3
-fi
-. "$CONF"
-echo "billing: started with rate=$RATE"
-exec sleep infinity
-EOT
-chmod 755 /usr/local/bin/billing.sh
-cat > /etc/systemd/system/billing.service <<'EOT'
-[Unit]
-Description=Billing job
-
-[Service]
-ExecStart=/usr/local/bin/billing.sh
-Restart=on-failure
-RestartSec=1
-
-[Install]
-WantedBy=multi-user.target
-EOT
-systemctl daemon-reload
-```
-
- {% reveal title="Show the commands in a session" %}
+ {% task id="task-1b2c47fa03e4" title="Start the exercise" %}
+ On workstation, start the exercise. It installs the broken billing service (a program and a unit, written by a "colleague") on servera and reloads systemd.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# cat > /usr/local/bin/billing.sh <<'EOT'
-...
-EOT
-[root@servera ~]# chmod 755 /usr/local/bin/billing.sh
-[root@servera ~]# cat > /etc/systemd/system/billing.service <<'EOT'
-...
-EOT
-[root@servera ~]# systemctl daemon-reload
+[student@workstation ~]$ lab start sa-diagnosing
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-e6518c1c69ad" title="State the symptom" %}
@@ -131,7 +97,7 @@ Oct 03 17:04:58 servera.lab.example.com systemd[1]: Started Billing job.
{% /task %}
{% task id="task-999070fbb650" title="Look wider, then clean up" %}
- Check that nothing else is failing, and look at the system-wide errors of this boot. Then remove everything you created.
+ Check that nothing else is failing, and look at the system-wide errors of this boot.
{% reveal title="Show solution" %}
@@ -140,12 +106,13 @@ Oct 03 17:04:58 servera.lab.example.com systemd[1]: Started Billing job.
UNIT LOAD ACTIVE SUB DESCRIPTION
0 loaded units listed.
[root@servera ~]# journalctl -p err -b --no-pager | tail -3
-[root@servera ~]# systemctl stop billing
-[root@servera ~]# rm -f /etc/systemd/system/billing.service /usr/local/bin/billing.sh /etc/billing.conf
-[root@servera ~]# systemctl daemon-reload
[root@servera ~]# exit
[student@servera ~]$ exit
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-dd17bafc70a7" title="Grade and finish" %}
+ {% lab-finish exercise="sa-diagnosing" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/09-lab.md
index 13e9d3c..ff681ee 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch11-logs-and-time/09-lab.md
@@ -103,6 +103,6 @@ EOT
{% task id="task-5d38583a50de" title="Answers and grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-logs-review" grade=true /%}
+ {% lab-finish exercise="sa-logs-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch12-networking/04-exercise-nmcli.md b/content/programs/rhel9-sysadmin/chapters/ch12-networking/04-exercise-nmcli.md
index fc5ca39..996fc7a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch12-networking/04-exercise-nmcli.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch12-networking/04-exercise-nmcli.md
@@ -12,9 +12,23 @@ Build a static network configuration from scratch on a virtual interface: create
objectives=["ch12.networkmanager"]
id="nmcli"
title="Configure a static connection"
+ exercise="sa-nmcli"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create a static dummy connection with nmcli.","Add and replace addresses, applying them with connection up.","Verify persistence across a reboot and clean up."] %}
+ {% task id="task-35822c99c20e" title="Start the exercise" %}
+ On workstation, start the exercise. It removes a dummy0 connection left by an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-nmcli
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-fcdedbe17877" title="Look before you touch" %}
On servera as root (`sudo -i`), list the devices and the connections.
@@ -157,4 +171,8 @@ nameserver 172.25.250.254
The virtual device and the extra DNS server disappear a moment after the profile is deleted.
{% /task %}
+
+ {% task id="task-cac4d7d77de1" title="Grade and finish" %}
+ {% lab-finish exercise="sa-nmcli" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch12-networking/06-exercise-names.md b/content/programs/rhel9-sysadmin/chapters/ch12-networking/06-exercise-names.md
index 3df0e4b..8f7ded7 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch12-networking/06-exercise-names.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch12-networking/06-exercise-names.md
@@ -12,9 +12,23 @@ Look at how servera knows its own name and how it finds serverb, then plant a wr
objectives=["ch12.names"]
id="names"
title="Names, hosts and DNS"
+ exercise="sa-names"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Show and change the host name with hostnamectl.","Compare getent with dig.","Use /etc/hosts and see that it wins over DNS."] %}
+ {% task id="task-538f7e88aa10" title="Start the exercise" %}
+ On workstation, start the exercise. It removes test entries from `/etc/hosts` of servera and checks its host name.
+
+```console
+[student@workstation ~]$ lab start sa-names
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-ba878b5cb585" title="Read the name configuration" %}
On servera as root (`sudo -i`), show the short and fully qualified names, the file that stores the name, the DNS servers and the search domain, and the order of name sources.
@@ -125,4 +139,8 @@ servera.lab.example.com
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-e2b9d263c480" title="Grade and finish" %}
+ {% lab-finish exercise="sa-names" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch12-networking/08-exercise-troubleshooting.md b/content/programs/rhel9-sysadmin/chapters/ch12-networking/08-exercise-troubleshooting.md
index 640f09f..d0fcc3e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch12-networking/08-exercise-troubleshooting.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch12-networking/08-exercise-troubleshooting.md
@@ -12,28 +12,27 @@ A small web service on serverb answers on its own machine and from nowhere else.
objectives=["ch12.troubleshooting"]
id="troubleshooting"
title="The service nobody can reach"
+ exercise="sa-reach"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Use the error message to pick the next layer to check.","Find a loopback-only listener with ss.","Open a port and fix the bind address."] %}
- {% task id="task-968f7f9436c8" title="Start the broken service on serverb" %}
- On serverb as root (`sudo -i`), create a page and start a web server on port 8080 that is bound to the loopback address only. (Use `nohup` and `&` so it keeps running.)
+ {% task id="task-aeb68e6f8fc8" title="Start the exercise" %}
+ On workstation, start the exercise. It starts a small web server on serverb that listens on port 8080 of the loopback address only, with the page `hello from serverb`.
- {% reveal title="Show solution" %}
+```console
+[student@workstation ~]$ lab start sa-reach
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+
+ Then, as root on serverb, change to the exercise directory:
```console
-[student@workstation ~]$ ssh student@serverb
-[student@serverb ~]$ sudo -i
-[root@serverb ~]# mkdir -p /srv/web
-[root@serverb ~]# echo "hello from serverb" > /srv/web/index.html
[root@serverb ~]# cd /srv/web
-[root@serverb web]# nohup python3 -m http.server 8080 --bind 127.0.0.1 > /tmp/http.log 2>&1 &
-[1] 679
-[root@serverb web]# curl -s http://127.0.0.1:8080/
-hello from serverb
```
-
- It works locally, as expected. Keep this session open.
- {% /reveal %}
{% /task %}
{% task id="task-f3c90437d725" title="Rung 1 to 5: is the machine reachable?" %}
@@ -105,19 +104,7 @@ hello from serverb
{% /reveal %}
{% /task %}
- {% task id="task-d032740aa9df" title="Clean up" %}
- Stop the server, remove the page and the temporary firewall rule (a reload restores the permanent configuration).
-
- {% reveal title="Show solution" %}
-
-```console
-[root@serverb web]# pkill -f 'http.server 8080'
-[root@serverb web]# firewall-cmd --reload
-success
-[root@serverb web]# cd; rm -rf /srv/web /tmp/http.log
-[root@serverb ~]# exit
-[student@serverb ~]$ exit
-```
- {% /reveal %}
+ {% task id="task-e77fdca4e4c7" title="Grade and finish" %}
+ {% lab-finish exercise="sa-reach" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch12-networking/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch12-networking/09-lab.md
index e9fb649..814372e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch12-networking/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch12-networking/09-lab.md
@@ -93,6 +93,6 @@ hosts: files dns myhostname
{% task id="task-cc0c66b2e1ec" title="Grade" %}
On workstation, with `answers.txt` filled in:
- {% lab-finish exercise="sa-network-review" grade=true /%}
+ {% lab-finish exercise="sa-network-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch13-software/02-exercise-rpm.md b/content/programs/rhel9-sysadmin/chapters/ch13-software/02-exercise-rpm.md
index 5af5a40..82d24dc 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch13-software/02-exercise-rpm.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch13-software/02-exercise-rpm.md
@@ -12,9 +12,23 @@ Use `rpm` to learn where files come from, what a package contains, and whether a
objectives=["ch13.rpm"]
id="rpm"
title="Interrogate the installed software"
+ exercise="sa-rpm"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Query packages with rpm -q, -qi, -ql, -qc, -qd and -qf.","Find the owner of a file.","Detect a modified file with rpm -V and repair it."] %}
+ {% task id="task-9955f107ff28" title="Start the exercise" %}
+ On workstation, start the exercise. It makes sure the package `tree` is installed and intact on servera.
+
+```console
+[student@workstation ~]$ lab start sa-rpm
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-50e42e415190" title="Ask about a package" %}
On servera as root (`sudo -i`), show the exact version of the `tree` package, its summary and licence, and the files it installed.
@@ -111,4 +125,8 @@ Complete!
No output: the file is back to what the package contains.
{% /reveal %}
{% /task %}
+
+ {% task id="task-b1a6c98506c8" title="Grade and finish" %}
+ {% lab-finish exercise="sa-rpm" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch13-software/04-exercise-dnf.md b/content/programs/rhel9-sysadmin/chapters/ch13-software/04-exercise-dnf.md
index 3a2ddb6..5a6e9ea 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch13-software/04-exercise-dnf.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch13-software/04-exercise-dnf.md
@@ -12,9 +12,23 @@ A colleague asks for the `zip` command. Find the package, check what it needs, i
objectives=["ch13.dnf"]
id="dnf"
title="Install, inspect and undo"
+ exercise="sa-dnf"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Search, inspect and install a package with dnf.","Read the transaction summary and the dependencies.","Review and undo a transaction."] %}
+ {% task id="task-140a1b6e4799" title="Start the exercise" %}
+ On workstation, start the exercise. It makes sure zip and unzip are not installed on servera.
+
+```console
+[student@workstation ~]$ lab start sa-dnf
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-f7ebcb15fdf9" title="Find the package" %}
On servera as root (`sudo -i`), confirm that `zip` is not installed. Search the repositories for it, and show its description.
@@ -153,4 +167,8 @@ package unzip is not installed
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-a2dc88f87d6a" title="Grade and finish" %}
+ {% lab-finish exercise="sa-dnf" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch13-software/06-exercise-local-repo.md b/content/programs/rhel9-sysadmin/chapters/ch13-software/06-exercise-local-repo.md
index 1ded582..e323c7b 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch13-software/06-exercise-local-repo.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch13-software/06-exercise-local-repo.md
@@ -12,9 +12,23 @@ Build a small repository from downloaded packages, define it for dnf with signat
objectives=["ch13.repositories"]
id="local-repo"
title="Serve your own repository"
+ exercise="sa-local-repo"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Download packages with their dependencies.","Create repository metadata with createrepo_c.","Define a repository, keep gpgcheck on, and install from it only."] %}
+ {% task id="task-10d8a144bf5a" title="Start the exercise" %}
+ On workstation, start the exercise. It installs `dnf-plugins-core` and `createrepo_c` on servera, and removes a repository left by an earlier run.
+
+```console
+[student@workstation ~]$ lab start sa-local-repo
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-feece255f9d1" title="Look at the existing repositories" %}
On servera as root (`sudo -i`), list the enabled repositories and find the `gpgkey` line that the distribution's repositories use.
@@ -36,19 +50,6 @@ gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Rocky-9
{% /reveal %}
{% /task %}
- {% task id="task-6f286b1c5260" title="Install the tools" %}
- Install `dnf-plugins-core` (for `dnf download`) and `createrepo_c`.
-
- {% reveal title="Show solution" %}
-
-```console
-[root@servera ~]# dnf install -y dnf-plugins-core createrepo_c
-...output omitted...
-Complete!
-```
- {% /reveal %}
- {% /task %}
-
{% task id="task-10e2bfc26016" title="Download the packages" %}
Create `/srv/localrepo` and download `zip` with its dependencies into it. Check one signature with `rpm -K`.
@@ -122,14 +123,7 @@ From repo : localrepo
{% /reveal %}
{% /task %}
- {% task id="task-ccc490605f79" title="Clean up" %}
-
-```console
-[root@servera ~]# dnf remove -y zip
-[root@servera ~]# rm -rf /srv/localrepo /etc/yum.repos.d/local.repo
-[root@servera ~]# dnf clean all > /dev/null
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-d85d24fd5360" title="Grade and finish" %}
+ {% lab-finish exercise="sa-local-repo" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch13-software/08-exercise-modules.md b/content/programs/rhel9-sysadmin/chapters/ch13-software/08-exercise-modules.md
index 3a401b2..291be41 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch13-software/08-exercise-modules.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch13-software/08-exercise-modules.md
@@ -12,9 +12,23 @@ Pick a version of a program from the module streams, see what that does to the p
objectives=["ch13.updates"]
id="modules"
title="Choose a version and check for updates"
+ exercise="sa-modules"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["List module streams and enable one.","See the effect of a stream on the offered version, and reset it.","Review pending updates and decide about a reboot."] %}
+ {% task id="task-6270123b483f" title="Start the exercise" %}
+ On workstation, start the exercise. It resets the nginx module on servera, so no stream is enabled.
+
+```console
+[student@workstation ~]$ lab start sa-modules
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-f0eefe80162a" title="List the streams" %}
On servera as root (`sudo -i`), list the streams of the `nginx` module, and show which version of the `nginx` package dnf offers before any stream is enabled.
@@ -127,4 +141,8 @@ Reboot should not be necessary.
A kernel update is waiting, but it has not been installed yet; once it is, `uname -r` will still show the old version until a reboot. Do not apply the updates in this exercise.
{% /reveal %}
{% /task %}
+
+ {% task id="task-a6c18ad03688" title="Grade and finish" %}
+ {% lab-finish exercise="sa-modules" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch13-software/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch13-software/09-lab.md
index 565bb93..d50dadd 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch13-software/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch13-software/09-lab.md
@@ -110,6 +110,6 @@ passwd-0.80-12.el9.x86_64
{% task id="task-49c5be688098" title="Grade" %}
On workstation, with `answers.txt` filled in:
- {% lab-finish exercise="sa-software-review" grade=true /%}
+ {% lab-finish exercise="sa-software-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/02-exercise-tar.md b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/02-exercise-tar.md
index ac0f348..6c6d453 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/02-exercise-tar.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/02-exercise-tar.md
@@ -12,9 +12,23 @@ Build a small project, archive it three ways, restore it into a clean directory,
objectives=["ch14.tar"]
id="tar"
title="Archive a project"
+ exercise="sa-tar"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create, list and extract tar archives.","Extract into a chosen directory and extract a single member.","Compare the restored tree with the original."] %}
+ {% task id="task-61f650ffb6d4" title="Start the exercise" %}
+ On workstation, start the exercise. It removes `/root/arch` of an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-tar
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-cb1b078db942" title="Build the project" %}
On servera as root (`sudo -i`), create `/root/arch/project` with `docs/notes.md`, `src/data1.txt` (the numbers 1 to 20000, from `seq`), and an executable script `run.sh`.
@@ -128,12 +142,7 @@ root/arch/project/docs/notes.md
{% /reveal %}
{% /task %}
- {% task id="task-fba6ad76cade" title="Clean up" %}
-
-```console
-[root@servera arch]# cd; rm -rf /root/arch
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-13eba9e5f961" title="Grade and finish" %}
+ {% lab-finish exercise="sa-tar" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/04-exercise-checksums.md b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/04-exercise-checksums.md
index df0c9b4..242d193 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/04-exercise-checksums.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/04-exercise-checksums.md
@@ -12,9 +12,23 @@ Compress the same data with gzip, bzip2 and xz and compare the cost and the gain
objectives=["ch14.checksums"]
id="checksums"
title="Compress and verify"
+ exercise="sa-checksums"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Compare compression tools by size and time.","Create and verify a SHA-256 checksum list.","Detect a modified file."] %}
+ {% task id="task-40ad2a7e9796" title="Start the exercise" %}
+ On workstation, start the exercise. It installs bzip2, zip and unzip on servera and removes `/root/arch` of an earlier run.
+
+```console
+[student@workstation ~]$ lab start sa-checksums
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-768cf4925687" title="Make the data" %}
On servera as root (`sudo -i`), install `bzip2` and `zip` if needed. Make an uncompressed archive `etc.tar` of `/etc` in `/root/arch` and note its size.
@@ -133,12 +147,7 @@ status=1
{% /reveal %}
{% /task %}
- {% task id="task-4b503a339821" title="Clean up" %}
-
-```console
-[root@servera arch]# cd; rm -rf /root/arch
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-4fecbba60ac0" title="Grade and finish" %}
+ {% lab-finish exercise="sa-checksums" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/06-exercise-backups.md b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/06-exercise-backups.md
index 169cdfc..99ae0a4 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/06-exercise-backups.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/06-exercise-backups.md
@@ -12,9 +12,23 @@ Make a directory with special properties (a script, a private file with an ACL),
objectives=["ch14.backups"]
id="backups"
title="Back up with ACLs, then restore"
+ exercise="sa-backups"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Show that a plain tar loses ACLs and that --acls keeps them.","Make and restore a full and an incremental backup.","Create hard-linked rsync snapshots."] %}
+ {% task id="task-b1493b0d7765" title="Start the exercise" %}
+ On workstation, start the exercise. It removes `/root/arch` of an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-backups
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-8303f5800ecd" title="Build a directory with an ACL" %}
On servera as root (`sudo -i`), create `/root/arch/project/docs/notes.md`, give the user `student` read access to it with an ACL, and show the ACL.
@@ -146,12 +160,7 @@ a.txt c.txt d.txt
{% /reveal %}
{% /task %}
- {% task id="task-20ae7ae8d8f9" title="Clean up" %}
-
-```console
-[root@servera arch]# cd; rm -rf /root/arch
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-28fe69e0b6ab" title="Grade and finish" %}
+ {% lab-finish exercise="sa-backups" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/08-exercise-moving.md b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/08-exercise-moving.md
index 229626a..f13bc09 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/08-exercise-moving.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/08-exercise-moving.md
@@ -12,21 +12,21 @@ Create data on servera, send it to serverb through a pipe, prove it arrived inta
objectives=["ch14.transfer"]
id="moving"
title="Move a tree between servers"
+ exercise="sa-moving"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Stream a tar archive between hosts through ssh.","Verify a transfer with sha256sum.","Mirror with rsync and confirm identity with a dry run."] %}
- {% task id="task-13e5da3c7ecd" title="Create data on servera" %}
- On workstation, run the commands via `ssh root@servera`: create `/tmp/arch/work/docs` with three small files `a.txt`, `c.txt` and `d.txt`.
-
- {% reveal title="Show solution" %}
+ {% task id="task-ed923932f474" title="Start the exercise" %}
+ On workstation, start the exercise. It creates `/tmp/arch/work/docs` with three small files on servera, and removes leftovers of an earlier run.
```console
-[student@workstation ~]$ ssh root@servera 'mkdir -p /tmp/arch/work/docs && cd /tmp/arch/work/docs && echo one > a.txt && echo three > c.txt && echo four > d.txt && ls'
-a.txt
-c.txt
-d.txt
+[student@workstation ~]$ lab start sa-moving
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-9a52dcc13d7d" title="Stream it to serverb" %}
@@ -92,12 +92,7 @@ total size is 23 speedup is 0.11 (DRY RUN)
{% /reveal %}
{% /task %}
- {% task id="task-34d24f741acb" title="Clean up" %}
-
-```console
-[student@workstation ~]$ rm -rf ~/xfer sums.txt
-[student@workstation ~]$ ssh root@serverb 'rm -rf /tmp/recv'
-[student@workstation ~]$ ssh root@servera 'rm -rf /tmp/arch'
-```
+ {% task id="task-4031fbe4f4fd" title="Grade and finish" %}
+ {% lab-finish exercise="sa-moving" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/09-lab.md
index 5f44690..d0c8612 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch14-archives-and-transfer/09-lab.md
@@ -107,6 +107,6 @@ other::---
{% task id="task-c73a6f33acc0" title="Grade" %}
- {% lab-finish exercise="sa-archives-review" grade=true /%}
+ {% lab-finish exercise="sa-archives-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/02-exercise-first-scripts.md b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/02-exercise-first-scripts.md
index 88ba6c8..c8e7ee9 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/02-exercise-first-scripts.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/02-exercise-first-scripts.md
@@ -12,9 +12,23 @@ Write a greeting script, run it every way that works, then pass it arguments and
objectives=["ch15.scripts"]
id="scripts"
title="Write and run your first scripts"
+ exercise="sa-first-scripts"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a script with a shebang and make it executable.","Run it with ./, bash and through PATH.","Use arguments, quoting and exit statuses."] %}
+ {% task id="task-c3e3d33935eb" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the scripts of an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-first-scripts
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-fa4171b2c43e" title="Write hello.sh" %}
On servera as `student`, create `~/scripts/hello.sh` (use `vim`) with the content below. Try to run it before doing anything else.
@@ -136,11 +150,7 @@ not found
{% /reveal %}
{% /task %}
- {% task id="task-64ceed0d2588" title="Clean up" %}
-
-```console
-[student@servera scripts]$ cd; rm -rf ~/scripts ~/bin/hello.sh
-[student@servera ~]$ exit
-```
+ {% task id="task-0fafec186b58" title="Grade and finish" %}
+ {% lab-finish exercise="sa-first-scripts" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/04-exercise-conditions.md b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/04-exercise-conditions.md
index 94977fc..f3fbc65 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/04-exercise-conditions.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/04-exercise-conditions.md
@@ -12,9 +12,23 @@ Write a script that checks its input and chooses what to do, one that loops over
objectives=["ch15.logic"]
id="logic"
title="Scripts that decide and repeat"
+ exercise="sa-conditions"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Validate arguments and exit with distinct statuses.","Use if/elif/else, case, for and while.","Parse /etc/passwd line by line."] %}
+ {% task id="task-769f4bc2a0b2" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the scripts of an earlier run from student's home on servera.
+
+```console
+[student@workstation ~]$ lab start sa-conditions
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-eeb0172f1a47" title="check.sh: validate and decide" %}
On servera as `student`, create `~/scripts/check.sh`: with exactly one argument, it reports whether it is a directory (with the number of entries), a file (with its size in bytes) or missing. With no argument or too many it prints `Usage: SCRIPT PATH` to standard error and exits with status 2; a missing path exits with status 1.
@@ -167,11 +181,14 @@ student /bin/bash
++ stat -c %s /etc/hostname
+ echo '/etc/hostname is a file of 24 bytes'
/etc/hostname is a file of 24 bytes
-[student@servera scripts]$ cd; rm -rf ~/scripts
[student@servera ~]$ exit
```
Each `+` line is a command as bash ran it, after variables were expanded. This is the first tool to reach for when a script misbehaves.
{% /reveal %}
{% /task %}
+
+ {% task id="task-07b6845fc0ce" title="Grade and finish" %}
+ {% lab-finish exercise="sa-conditions" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/06-exercise-cron.md b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/06-exercise-cron.md
index 14ea8fa..ef4ebe5 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/06-exercise-cron.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/06-exercise-cron.md
@@ -12,9 +12,23 @@ Put a job in a personal crontab, watch it fail because of an unescaped `%`, fix
objectives=["ch15.cron"]
id="cron"
title="Schedule a job"
+ exercise="sa-cron"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create a personal crontab entry and read the cron log.","Diagnose and fix the percent-sign problem.","Create a system job in /etc/cron.d."] %}
+ {% task id="task-e01c5e932d9e" title="Start the exercise" %}
+ On workstation, start the exercise. It removes cron jobs and output files of an earlier run from servera and makes sure crond runs.
+
+```console
+[student@workstation ~]$ lab start sa-cron
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-d99ea3db39cb" title="Look at what is scheduled" %}
On servera as root (`sudo -i`), list the system cron files, show `0hourly`, and check whether `student` has a crontab.
@@ -93,19 +107,7 @@ Oct 3 17:54:01 servera CROND[1010]: (root) CMD (/usr/bin/logger -t cron-demo he
{% /reveal %}
{% /task %}
- {% task id="task-972ba6bc7dbc" title="Clean up" %}
- Remove student's crontab and the demo job, and the output file.
-
- {% reveal title="Show solution" %}
-
-```console
-[root@servera ~]# crontab -u student -r
-[root@servera ~]# rm -f /etc/cron.d/demo /home/student/cron.out /tmp/ct
-[root@servera ~]# crontab -u student -l
-no crontab for student
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
- {% /reveal %}
+ {% task id="task-c4f416e79bc4" title="Grade and finish" %}
+ {% lab-finish exercise="sa-cron" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/08-exercise-timers.md b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/08-exercise-timers.md
index bb4b07e..fe55063 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/08-exercise-timers.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/08-exercise-timers.md
@@ -12,9 +12,23 @@ Build a service and timer pair, test the service by hand, watch the timer fire a
objectives=["ch15.timers"]
id="timers"
title="Timers and one-off jobs"
+ exercise="sa-timers"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a oneshot service and a timer and enable the timer.","Check calendar expressions with systemd-analyze.","Schedule and remove an at job."] %}
+ {% task id="task-1912509ccaac" title="Start the exercise" %}
+ On workstation, start the exercise. It removes the hello units and the at package of an earlier run from servera.
+
+```console
+[student@workstation ~]$ lab start sa-timers
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-a8c581357ef5" title="The service" %}
On servera as root (`sudo -i`), create `/etc/systemd/system/hello.service`: a oneshot service that logs `hello from the timer` with the tag `hello-timer` (use `logger`). Start it by hand and look in the journal.
@@ -134,16 +148,7 @@ job 2 at Sat Oct 3 23:59:00 2026
{% /reveal %}
{% /task %}
- {% task id="task-30bce7bfd9f5" title="Clean up" %}
-
-```console
-[root@servera ~]# systemctl disable --now hello.timer
-Removed "/etc/systemd/system/timers.target.wants/hello.timer".
-[root@servera ~]# rm -f /etc/systemd/system/hello.service /etc/systemd/system/hello.timer /home/student/at.out
-[root@servera ~]# systemctl daemon-reload
-[root@servera ~]# dnf remove -y at > /dev/null
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-b5cbf9f9e6ea" title="Grade and finish" %}
+ {% lab-finish exercise="sa-timers" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/09-lab.md
index 7ac4a2d..f526983 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch15-scripts-and-scheduling/09-lab.md
@@ -141,6 +141,6 @@ EOT
{% task id="task-ba78cc1ba75b" title="Grade" %}
Leave servera, then on workstation:
- {% lab-finish exercise="sa-scripts-review" grade=true /%}
+ {% lab-finish exercise="sa-scripts-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/04-exercise-fix-labels.md b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/04-exercise-fix-labels.md
index b1649e3..1d931ef 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/04-exercise-fix-labels.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/04-exercise-fix-labels.md
@@ -12,31 +12,21 @@ Serve a page from a new directory with Apache, watch it fail, prove that SELinux
objectives=["ch16.contexts"]
id="fix-labels"
title="Fix a 403 caused by a label"
+ exercise="sa-fix-labels"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Find that a wrong type blocks httpd.","Use semanage fcontext and restorecon for a permanent fix.","Compare cp, mv and chcon."] %}
- {% task id="task-89e066f27a14" title="Install and configure httpd" %}
- On servera as root (`sudo -i`), install `httpd` and the audit tools (`audit`, `setroubleshoot-server`), start `auditd`, create `/srv/web/index.html` containing `Hello from /srv/web`, and tell Apache to serve `/srv/web` with a file `/etc/httpd/conf.d/web.conf`. Start httpd.
-
- {% reveal title="Show solution" %}
+ {% task id="task-66a6edd47ff0" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd and the audit tools on servera, creates `/srv/web/index.html` and the Apache file `/etc/httpd/conf.d/web.conf` that serves it, and starts httpd.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# dnf install -y httpd audit setroubleshoot-server > /dev/null
-[root@servera ~]# systemctl enable --now auditd
-[root@servera ~]# mkdir -p /srv/web
-[root@servera ~]# echo "Hello from /srv/web" > /srv/web/index.html
-[root@servera ~]# cat > /etc/httpd/conf.d/web.conf <<'EOT'
-DocumentRoot "/srv/web"
-
- Require all granted
-
-EOT
-[root@servera ~]# systemctl enable --now httpd
-Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
+[student@workstation ~]$ lab start sa-fix-labels
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-108264cdfa5b" title="See it fail" %}
@@ -134,14 +124,7 @@ Relabeled /srv/web/copy.html from unconfined_u:object_r:user_home_t:s0 to unconf
{% /reveal %}
{% /task %}
- {% task id="task-e5747f243852" title="Clean up" %}
-
-```console
-[root@servera ~]# semanage fcontext -d "/srv/web(/.*)?"
-[root@servera ~]# systemctl disable --now httpd
-[root@servera ~]# rm -rf /srv/web /etc/httpd/conf.d/web.conf /tmp/second.html
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-0534637ecf5b" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fix-labels" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/06-exercise-booleans-ports.md b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/06-exercise-booleans-ports.md
index e0e9ae8..ca3a492 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/06-exercise-booleans-ports.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/06-exercise-booleans-ports.md
@@ -12,29 +12,21 @@ Two more reasons for a service to be refused by SELinux, and their proper fixes:
objectives=["ch16.booleans"]
id="booleans-ports"
title="Flip a switch and label a port"
+ exercise="sa-booleans-ports"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Find and change an httpd boolean permanently.","Label a port for httpd and start the service on it.","Review local SELinux customisations."] %}
- {% task id="task-338fabc53204" title="Prepare httpd and a user page" %}
- On servera as root (`sudo -i`), install `httpd`, `audit` and `setroubleshoot-server` if they are not there (`dnf install -y …`), start `auditd`, create the user `bob` with a page `/home/bob/public_html/index.html` (home mode 711, directory 755, file 644), and enable user directories by editing `/etc/httpd/conf.d/userdir.conf`: comment out `UserDir disabled` and add `UserDir public_html`. Start httpd.
-
- {% reveal title="Show solution" %}
+ {% task id="task-713375a67e2c" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd and the audit tools on servera, creates the user bob with a page in `~/public_html`, turns on Apache's UserDir feature and starts httpd.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# dnf install -y httpd audit setroubleshoot-server > /dev/null
-[root@servera ~]# systemctl enable --now auditd
-[root@servera ~]# useradd -m bob
-[root@servera ~]# mkdir -p /home/bob/public_html
-[root@servera ~]# echo "bob page" > /home/bob/public_html/index.html
-[root@servera ~]# chmod 711 /home/bob; chmod 755 /home/bob/public_html; chmod 644 /home/bob/public_html/index.html
-[root@servera ~]# sed -i 's/^\s*UserDir disabled/#UserDir disabled/; s/^\s*#\s*UserDir public_html/ UserDir public_html/' /etc/httpd/conf.d/userdir.conf
-[root@servera ~]# grep -n "UserDir public_html" /etc/httpd/conf.d/userdir.conf
-24: UserDir public_html
-[root@servera ~]# systemctl enable --now httpd
+[student@workstation ~]$ lab start sa-booleans-ports
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-4860cc481235" title="See it fail" %}
@@ -117,16 +109,7 @@ http_port_t tcp 82
{% /reveal %}
{% /task %}
- {% task id="task-2fccc677c5a9" title="Put everything back" %}
-
-```console
-[root@servera ~]# semanage port -d -t http_port_t -p tcp 82
-[root@servera ~]# setsebool -P httpd_enable_homedirs off
-[root@servera ~]# sed -i 's/^Listen 82$/Listen 80/' /etc/httpd/conf/httpd.conf
-[root@servera ~]# systemctl disable --now httpd
-[root@servera ~]# userdel -r bob
-[root@servera ~]# exit
-[student@servera ~]$ exit
-```
+ {% task id="task-dcc7eb5690cd" title="Grade and finish" %}
+ {% lab-finish exercise="sa-booleans-ports" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/08-exercise-troubleshooting.md b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/08-exercise-troubleshooting.md
index b487b4e..8df4e22 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/08-exercise-troubleshooting.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/08-exercise-troubleshooting.md
@@ -12,42 +12,21 @@ A colleague has set up a web site on port 8085 serving `/srv/site`, and it does
objectives=["ch16.troubleshooting"]
id="troubleshooting"
title="Two faults, one symptom"
+ exercise="sa-two-faults"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Use the journal and the audit log to find a name_bind denial.","Fix the port and then the file label.","Verify the result in enforcing mode."] %}
- {% task id="task-d7d349765459" title="Create the broken site" %}
- On servera as root (`sudo -i`), install `httpd`, `audit` and `setroubleshoot-server`, start `auditd`, and run the commands below. They build the site the way a hurried colleague might have: the page is moved in from `/tmp`, and Apache is set to a new port.
-
-```bash
-mkdir -p /srv/site
-echo "
Site OK
" > /tmp/index.html
-mv /tmp/index.html /srv/site/index.html
-cat > /etc/httpd/conf.d/site.conf <<'EOT'
-Listen 8085
-
- DocumentRoot "/srv/site"
-
-
- Require all granted
-
-EOT
-systemctl restart httpd
-```
-
- {% reveal title="Show solution" %}
+ {% task id="task-ca0b7c73b438" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd and the audit tools on servera and builds the "hurried colleague's" site: a page moved in from `/tmp`, and Apache set to port 8085. httpd fails to start.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# dnf install -y httpd audit setroubleshoot-server > /dev/null
-[root@servera ~]# systemctl enable --now auditd
-[root@servera ~]# mkdir -p /srv/site
-...output omitted...
-[root@servera ~]# systemctl restart httpd
-Job for httpd.service failed because the control process exited with error code.
-See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
+[student@workstation ~]$ lab start sa-two-faults
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-796cb6ad962f" title="Symptom, then the service log" %}
@@ -154,8 +133,6 @@ http_port_t tcp 8085
SELinux fcontext type Context
/srv/site(/.*)? all files system_u:object_r:httpd_sys_content_t:s0
-[root@servera ~]# semanage fcontext -d "/srv/site(/.*)?"; semanage port -d -t http_port_t -p tcp 8085
-[root@servera ~]# systemctl disable --now httpd; rm -rf /srv/site /etc/httpd/conf.d/site.conf
[root@servera ~]# exit
[student@servera ~]$ exit
```
@@ -163,4 +140,8 @@ SELinux fcontext type Context
(The lab image may list one more rule, for its own management agent: leave that one alone.) The review is the habit that matters: every customisation should be one you can name and explain.
{% /reveal %}
{% /task %}
+
+ {% task id="task-6c87f915ab2c" title="Grade and finish" %}
+ {% lab-finish exercise="sa-two-faults" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/09-lab.md
index 29ab37d..42428e4 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch16-selinux/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch16-selinux/09-lab.md
@@ -95,6 +95,6 @@ Enforcing
{% task id="task-b6866acf4bc6" title="Answers and grade" %}
On workstation, fill in `answers.txt` (`ps -eZ | grep httpd` shows the process type), and grade:
- {% lab-finish exercise="sa-selinux-review" grade=true /%}
+ {% lab-finish exercise="sa-selinux-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/02-exercise-partitions.md b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/02-exercise-partitions.md
index b38dcd2..b79489a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/02-exercise-partitions.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/02-exercise-partitions.md
@@ -12,9 +12,23 @@ servera has an empty 5 GiB disk, `/dev/sdb`. Look at the disks, give it a GPT pa
objectives=["ch17.partitions"]
id="partitions"
title="Partition a new disk"
+ exercise="sa-partitions"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Identify the system disk and the spare disk.","Create a GPT table and three partitions with parted.","Check the result with lsblk and parted."] %}
+ {% task id="task-992656087c6b" title="Start the exercise" %}
+ On workstation, start the exercise. It clears the spare disk `/dev/sdb` of servera (5 GiB).
+
+```console
+[student@workstation ~]$ lab start sa-partitions
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-a28017581855" title="Which disk is which?" %}
On servera as root (`sudo -i`), list the block devices with their file systems. Which disk is the system disk, and which one is empty?
@@ -117,4 +131,8 @@ Device Start End Sectors Size Type
`Disklabel type: gpt`: fdisk and parted read the same table. Sector 2048 is exactly 1 MiB, the aligned start.
{% /reveal %}
{% /task %}
+
+ {% task id="task-d8a604eca4d7" title="Grade and finish" %}
+ {% lab-finish exercise="sa-partitions" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/04-exercise-filesystems.md b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/04-exercise-filesystems.md
index b3d00e7..2e9411e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/04-exercise-filesystems.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/04-exercise-filesystems.md
@@ -12,27 +12,21 @@ Put XFS on the data partition, ext4 on the logs partition, prepare the swap part
objectives=["ch17.filesystems"]
id="filesystems"
title="Create file systems and mount them"
+ exercise="sa-filesystems"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create xfs and ext4 file systems and swap space.","Mount, use and unmount file systems.","Read UUIDs and diagnose a busy mount."] %}
- {% task id="task-bb548a7edb8d" title="Check that the partitions exist" %}
- On servera as root, show the partitions of `/dev/sdb`. If you reset the servers since the last exercise, recreate them first.
-
- {% reveal title="Show solution" %}
+ {% task id="task-69432ff18268" title="Start the exercise" %}
+ On workstation, start the exercise. It partitions the spare disk `/dev/sdb` of servera into the three partitions of the previous exercise (data 1 GiB, logs 1 GiB, swap 512 MiB).
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# lsblk /dev/sdb
-NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
-sdb 8:16 0 5G 0 disk
-├─sdb1 8:17 0 1G 0 part
-├─sdb2 8:18 0 1G 0 part
-└─sdb3 8:19 0 512M 0 part
+[student@workstation ~]$ lab start sa-filesystems
```
- To recreate them after a reset: `parted -s /dev/sdb mklabel gpt mkpart data xfs 1MiB 1025MiB mkpart logs ext4 1025MiB 2049MiB mkpart swap linux-swap 2049MiB 2561MiB`.
- {% /reveal %}
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-80688e69edf7" title="Create the file systems and the swap area" %}
@@ -129,4 +123,8 @@ Swap: 511Mi 0B 511Mi
Leave the file systems mounted for the next exercise, or unmount them with `umount /data /logs` and start from the partitions again.
{% /reveal %}
{% /task %}
+
+ {% task id="task-94d037b1ee6c" title="Grade and finish" %}
+ {% lab-finish exercise="sa-filesystems" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/06-exercise-fstab.md b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/06-exercise-fstab.md
index 4e775c1..24e8fa9 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/06-exercise-fstab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/06-exercise-fstab.md
@@ -12,9 +12,23 @@ Mount the two file systems and the swap area at boot, using UUIDs and sensible o
objectives=["ch17.fstab"]
id="fstab"
title="Make the storage permanent"
+ exercise="sa-fstab-storage"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write fstab entries by UUID for xfs, ext4 and swap.","Test with findmnt --verify, mount -a and swapon -a.","Verify the entries after a reboot."] %}
+ {% task id="task-21fde289a53b" title="Start the exercise" %}
+ On workstation, start the exercise. It partitions `/dev/sdb` of servera and creates the xfs file system, the ext4 file system and the swap area of the previous exercise.
+
+```console
+[student@workstation ~]$ lab start sa-fstab-storage
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-d7a6ee2b8619" title="Unmount and collect the UUIDs" %}
On servera as root, unmount `/data` and `/logs` if they are still mounted from the previous exercise, create the mount points `/srv/data` and `/srv/archive`, and read the UUIDs of the three partitions.
@@ -127,4 +141,8 @@ sdb
Everything came back without any manual `mount`: that is a permanent configuration. Keep it for the next exercise.
{% /reveal %}
{% /task %}
+
+ {% task id="task-b33c9410fa0a" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fstab-storage" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/08-exercise-space.md b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/08-exercise-space.md
index f7541d1..d3b8b58 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/08-exercise-space.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/08-exercise-space.md
@@ -12,9 +12,23 @@ Use up the space of one file system, find who is responsible, run out of inodes
objectives=["ch17.maintenance"]
id="space"
title="Hunt the missing space"
+ exercise="sa-space"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Find large files with df and du.","Recognise and demonstrate inode exhaustion.","Find a deleted-but-open file with lsof +L1."] %}
+ {% task id="task-21b5a96407c1" title="Start the exercise" %}
+ On workstation, start the exercise. It partitions `/dev/sdb` of servera, creates the file systems and the swap area, adds them to `/etc/fstab` and mounts them on `/srv/data` and `/srv/archive`.
+
+```console
+[student@workstation ~]$ lab start sa-space
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-ad866e16ee50" title="Check the starting point" %}
On servera as root, make sure `/srv/data` (xfs) and `/srv/archive` (ext4) are mounted, and show their blocks and inodes.
@@ -124,7 +138,7 @@ tail 1072 root 3r REG 8,17 209715200 0 132 /srv/data/log.big (d
{% /task %}
{% task id="task-83cf3efe9e5d" title="Maintenance and clean up" %}
- Unmount `/srv/data`, check it with `xfs_repair -n`, set the label `datavol` with `xfs_admin -L`, and mount it again. Then undo everything from this chapter: remove the three fstab lines (restore your backup), unmount, turn off swap, wipe the signatures, and check that `sdb` is empty.
+ Unmount `/srv/data`, check it with `xfs_repair -n`, set the label `datavol` with `xfs_admin -L`, and mount it again. `lab finish` undoes the fstab lines, the mounts, the swap and the partitions for you.
{% reveal title="Show solution" %}
@@ -138,20 +152,12 @@ writing all SBs
new label = "datavol"
[root@servera ~]# mount /srv/data && blkid /dev/sdb1 | cut -c1-60
/dev/sdb1: LABEL="datavol" UUID="c64946eb-fc27-4485-8020-db4119b686eb"
-[root@servera ~]# umount /srv/data /srv/archive; swapoff -a
-[root@servera ~]# cp /root/fstab.bak /etc/fstab; systemctl daemon-reload
-[root@servera ~]# wipefs -a /dev/sdb1 /dev/sdb2 /dev/sdb3 /dev/sdb > /dev/null
-[root@servera ~]# rmdir /srv/data /srv/archive
-[root@servera ~]# lsblk /dev/sdb
-NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
-sdb 8:16 0 5G 0 disk
-[root@servera ~]# findmnt --verify | tail -1
-0 parse errors, 0 errors, 1 warning
-[root@servera ~]# exit
-[student@servera ~]$ exit
```
- `wipefs -a` erases the file system and partition-table signatures. You can also simply reset the servers with `rht-vmctl reset servers`.
{% /reveal %}
{% /task %}
+
+ {% task id="task-00691b48290b" title="Grade and finish" %}
+ {% lab-finish exercise="sa-space" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/09-lab.md
index 7a47bf5..c7df267 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch17-storage-and-file-systems/09-lab.md
@@ -88,6 +88,6 @@ EOT
{% task id="task-028f1b0ef4f9" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-storage-review" grade=true /%}
+ {% lab-finish exercise="sa-storage-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/02-exercise-build.md b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/02-exercise-build.md
index 4b82df0..127e454 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/02-exercise-build.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/02-exercise-build.md
@@ -12,30 +12,21 @@ Prepare two partitions of the spare disk for LVM, build a volume group on the fi
objectives=["ch18.concepts"]
id="build"
title="Build your first volume group"
+ exercise="sa-lvm-build"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Create PV, VG and LVs with the lvm tools.","Make and mount file systems on logical volumes.","Read pvs, vgs, lvs and lsblk -f."] %}
- {% task id="task-1f0b6cee1e45" title="Prepare the disk" %}
- On servera as root (`sudo -i`), check that `/dev/sdb` is empty. If it still has partitions from the previous chapter, clear it with `wipefs -a`. Then create two 1.5 GiB partitions flagged for LVM.
-
- {% reveal title="Show solution" %}
+ {% task id="task-934bbfeee5c4" title="Start the exercise" %}
+ On workstation, start the exercise. It clears the spare disk `/dev/sdb` of servera and creates two 1.5 GiB LVM partitions on it (`sdb1` and `sdb2`).
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# lsblk /dev/sdb
-NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
-sdb 8:16 0 5G 0 disk
-[root@servera ~]# parted -s /dev/sdb mklabel gpt mkpart pv1 1MiB 1537MiB mkpart pv2 1537MiB 3073MiB set 1 lvm on set 2 lvm on
-[root@servera ~]# udevadm settle; lsblk /dev/sdb
-NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
-sdb 8:16 0 5G 0 disk
-├─sdb1 8:17 0 1.5G 0 part
-└─sdb2 8:18 0 1.5G 0 part
+[student@workstation ~]$ lab start sa-lvm-build
```
- If `sdb` has leftovers: `wipefs -a /dev/sdb1 /dev/sdb2 /dev/sdb3 /dev/sdb` (this erases them) and look again, or reset the servers.
- {% /reveal %}
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-d6d2124a23eb" title="Physical volume and volume group" %}
@@ -130,4 +121,8 @@ lrwxrwxrwx. 1 root root 7 Oct 3 18:33 /dev/vgdata/lvapp -> ../dm-0
Two names, one device (`dm-0`, a "device mapper" volume). Leave everything mounted for the next exercise.
{% /reveal %}
{% /task %}
+
+ {% task id="task-74c98e500cd0" title="Grade and finish" %}
+ {% lab-finish exercise="sa-lvm-build" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/04-exercise-resize.md b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/04-exercise-resize.md
index 2be3393..ac1c076 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/04-exercise-resize.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/04-exercise-resize.md
@@ -12,9 +12,23 @@ Run out of space on purpose, add a disk to the volume group, grow a mounted XFS
objectives=["ch18.resize"]
id="resize"
title="Grow and shrink volumes"
+ exercise="sa-lvm-resize"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Extend an LV and its file system online with lvextend -r.","Add a physical volume to a volume group.","Shrink ext4 and understand why XFS cannot shrink."] %}
+ {% task id="task-554715ab948f" title="Start the exercise" %}
+ On workstation, start the exercise. It builds the volume group `vgdata` on `/dev/sdb1` of servera with the volumes `lvapp` (1 GiB, XFS, `/srv/app`) and `lvlog` (ext4, `/srv/log`), the result of the previous exercise.
+
+```console
+[student@workstation ~]$ lab start sa-lvm-resize
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-576f1da0ffde" title="Start from the previous exercise" %}
On servera as root, make sure `vgdata` with `lvapp` (xfs, `/srv/app`) and `lvlog` (ext4, `/srv/log`) exist and are mounted. Write a recognisable file into `/srv/app`.
@@ -142,4 +156,8 @@ resize2fs done
ext4 shrank (offline); XFS refused. Your exact sizes differ slightly.
{% /reveal %}
{% /task %}
+
+ {% task id="task-1d1ba5c9c5c5" title="Grade and finish" %}
+ {% lab-finish exercise="sa-lvm-resize" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/06-exercise-maintenance.md b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/06-exercise-maintenance.md
index ef072ff..b83c13e 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/06-exercise-maintenance.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/06-exercise-maintenance.md
@@ -12,9 +12,23 @@ A volume holds your data on an old disk. Move it to a new disk while it stays mo
objectives=["ch18.maintenance"]
id="maintenance"
title="Move data to a new disk"
+ exercise="sa-lvm-maintenance"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Replace a PV with pvmove, vgreduce and pvremove.","Rename a volume and update fstab.","Add LVM swap and verify after a reboot."] %}
+ {% task id="task-817671257cef" title="Start the exercise" %}
+ On workstation, start the exercise. It builds the volume group `vgdata` from the two partitions of `/dev/sdb` on servera, with the volumes `lvapp` and `lvlog` mounted on `/srv/app` and `/srv/log`.
+
+```console
+[student@workstation ~]$ lab start sa-lvm-maintenance
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-a249cb7e09bd" title="A fresh start" %}
On servera as root, remove the volumes from the previous exercises (unmount them first), keep the volume group `vgdata` with both PVs, and create one volume `lvdata` of 800 MiB that lives only on `/dev/sdb1`. Put XFS on it, mount it on `/srv/data`, and write a file. Note its checksum. Check on which PV it lives.
@@ -145,4 +159,8 @@ b2bc7d3f8b652d2e
Volumes activate by themselves at boot, and the data is exactly what you wrote. Keep this layout for the troubleshooting exercise.
{% /reveal %}
{% /task %}
+
+ {% task id="task-04e5d8e441d2" title="Grade and finish" %}
+ {% lab-finish exercise="sa-lvm-maintenance" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/08-exercise-troubleshooting.md b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/08-exercise-troubleshooting.md
index c4ae6d6..4f67e7a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/08-exercise-troubleshooting.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/08-exercise-troubleshooting.md
@@ -12,9 +12,23 @@ Three problems, each with a different message. Diagnose each from its message, f
objectives=["ch18.troubleshooting"]
id="troubleshooting"
title="Fix three LVM problems"
+ exercise="sa-lvm-troubleshooting"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Activate an inactive volume.","Respond to Insufficient free space.","Resolve a group that cannot be deactivated."] %}
+ {% task id="task-1c291d41ff72" title="Start the exercise" %}
+ On workstation, start the exercise. It builds the volume group `vgdata` on `/dev/sdb2` of servera with the XFS volume `lvstore` (800 MiB, mounted on `/srv/data`) and the swap volume `lvswap`, both in `/etc/fstab`.
+
+```console
+[student@workstation ~]$ lab start sa-lvm-troubleshooting
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-733227e1e67f" title="The starting point" %}
On servera as root, make sure `vgdata` has the mounted volume `lvstore` on `/srv/data` (as left by the previous exercise).
@@ -112,19 +126,12 @@ lvswap
```console
[root@servera ~]# vgcfgbackup vgdata
Volume group "vgdata" successfully backed up.
-[root@servera ~]# swapoff -a; umount /srv/data
-[root@servera ~]# cp /root/fstab.bak /etc/fstab; systemctl daemon-reload
-[root@servera ~]# lvremove -y vgdata/lvstore vgdata/lvswap
Logical volume "lvstore" successfully removed.
Logical volume "lvswap" successfully removed.
-[root@servera ~]# vgremove vgdata
Volume group "vgdata" successfully removed
-[root@servera ~]# pvremove /dev/sdb2
Labels on physical volume "/dev/sdb2" successfully wiped.
-[root@servera ~]# wipefs -a /dev/sdb1 /dev/sdb2 /dev/sdb > /dev/null 2>&1; lsblk /dev/sdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdb 8:16 0 5G 0 disk
-[root@servera ~]# rmdir /srv/data /srv/app /srv/log 2> /dev/null
[root@servera ~]# exit
[student@servera ~]$ exit
```
@@ -132,4 +139,8 @@ sdb 8:16 0 5G 0 disk
Order matters: volumes, then the group, then the PVs. `rht-vmctl reset servers` is the quick alternative.
{% /reveal %}
{% /task %}
+
+ {% task id="task-95b4e17972cb" title="Grade and finish" %}
+ {% lab-finish exercise="sa-lvm-troubleshooting" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/09-lab.md
index a7dbd30..950e521 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch18-lvm/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch18-lvm/09-lab.md
@@ -114,6 +114,6 @@ EOT
{% task id="task-543d852bb172" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-lvm-review" grade=true /%}
+ {% lab-finish exercise="sa-lvm-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/02-exercise-share.md b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/02-exercise-share.md
index 083d23b..59b9e81 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/02-exercise-share.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/02-exercise-share.md
@@ -12,28 +12,21 @@ Make serverb an NFS server for one directory, open the firewall for it, and moun
objectives=["ch19.nfs"]
id="share"
title="Export and mount a directory"
+ exercise="sa-nfs-share"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Export a directory with exportfs.","Open NFS in the firewall.","Mount it on a client and check the result."] %}
- {% task id="task-6d627eeb3294" title="Prepare the server" %}
- On serverb as root (`sudo -i`), make sure `nfs-utils` is installed, create `/srv/shared` with a file `hello.txt` containing `shared file`, and find your lab network (the network of `ip -br addr show enp5s0`).
-
- {% reveal title="Show solution" %}
+ {% task id="task-baeb0ca16eaa" title="Start the exercise" %}
+ On workstation, start the exercise. It installs nfs-utils on servera and serverb and creates `/srv/shared/hello.txt` on serverb.
```console
-[student@workstation ~]$ ssh student@serverb
-[student@serverb ~]$ sudo -i
-[root@serverb ~]# dnf install -y nfs-utils
-...output omitted...
-Complete!
-[root@serverb ~]# mkdir -p /srv/shared
-[root@serverb ~]# echo "shared file" > /srv/shared/hello.txt
-[root@serverb ~]# ip -br addr show enp5s0
-enp5s0 UP 172.25.250.11/24 fe80::216:3eff:fe04:a5a7/64
+[student@workstation ~]$ lab start sa-nfs-share
```
- The network here is `172.25.250.0/24` (the address with the host part zeroed). Use the network of your own lab in the next task.
- {% /reveal %}
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-043134ef0721" title="Export it" %}
@@ -142,4 +135,8 @@ exporting 172.25.250.0/24:/srv/shared
"Read-only file system" is the message in both cases; `mount` shows which side decided. Leave the server exporting read-write, and the share unmounted, for the next exercises.
{% /reveal %}
{% /task %}
+
+ {% task id="task-5e32b427ad3d" title="Grade and finish" %}
+ {% lab-finish exercise="sa-nfs-share" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/04-exercise-permissions.md b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/04-exercise-permissions.md
index c30a3aa..929bfa1 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/04-exercise-permissions.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/04-exercise-permissions.md
@@ -12,9 +12,23 @@ See user IDs cross the network: a file created on the client, a user that exists
objectives=["ch19.access"]
id="permissions"
title="Get the permissions right"
+ exercise="sa-nfs-permissions"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Show that NFS carries numeric IDs.","Make a user work on both sides with a matching UID.","Build a setgid group directory on a share."] %}
+ {% task id="task-ee7f3e44c540" title="Start the exercise" %}
+ On workstation, start the exercise. It installs nfs-utils, exports `/srv/shared` read-write from serverb to the lab network, starts the NFS server and opens the `nfs` firewall service.
+
+```console
+[student@workstation ~]$ lab start sa-nfs-permissions
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-49cd65115f6e" title="Make the share writable for student" %}
The share must be exported read-write and mounted from the previous exercise (export `/srv/shared` read-write, firewall open, mounted on servera in `/mnt/shared`). On serverb, give the directory to `student`, who has UID 1000 on both machines. On servera, create a file as `student` in the share and list it by number and by name.
@@ -125,15 +139,7 @@ total 8
{% /reveal %}
{% /task %}
- {% task id="task-718654323cf3" title="Clean up" %}
-
-```console
-[root@servera ~]# umount /mnt/shared
-[root@servera ~]# userdel -r alice; groupdel team 2> /dev/null
-[root@serverb ~]# rm -rf /srv/shared/alice /srv/shared/team /srv/shared/st.txt
-[root@serverb ~]# userdel -r alice; groupdel team 2> /dev/null
-```
-
- Keep the export and the firewall rule: the next exercises use them.
+ {% task id="task-d0c4c5654cc5" title="Grade and finish" %}
+ {% lab-finish exercise="sa-nfs-permissions" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/06-exercise-persistent.md b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/06-exercise-persistent.md
index 44b1cbb..d202828 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/06-exercise-persistent.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/06-exercise-persistent.md
@@ -12,21 +12,21 @@ Make servera mount the share at boot with an fstab entry and check it after a re
objectives=["ch19.persistent"]
id="persistent"
title="Mount at boot, then on demand"
+ exercise="sa-nfs-persistent"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Write and test an NFS fstab entry with _netdev.","Create an autofs master entry and an indirect map.","Observe on-demand mounting and idle unmounting."] %}
- {% task id="task-89a46b9e1103" title="Check the server" %}
- On serverb, check that `/srv/shared` is exported read-write, the `nfs-server` service runs and the firewall allows `nfs`.
-
- {% reveal title="Show solution" %}
+ {% task id="task-342c7a774f4c" title="Start the exercise" %}
+ On workstation, start the exercise. It installs nfs-utils, exports `/srv/shared` from serverb to the lab network (NFS server running, firewall open) and creates the mount point `/mnt/shared` on servera.
```console
-[student@workstation ~]$ ssh root@serverb 'exportfs -v | cut -c1-70; systemctl is-active nfs-server; firewall-cmd --list-services'
-/srv/shared 172.25.250.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure
-active
-cockpit dhcpv6-client nfs ssh
+[student@workstation ~]$ lab start sa-nfs-persistent
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-3a1b64fd9b4a" title="An fstab entry" %}
@@ -120,4 +120,8 @@ ls: cannot access '/mnt/auto/broken': No such file or directory
The map entry exists, but the server has no such export, so the mount fails and the path does not appear. The error is "No such file or directory", not an NFS message, which is why `showmount`, `exportfs -v` on the server and `journalctl -u autofs` are the next stops.
{% /reveal %}
{% /task %}
+
+ {% task id="task-1bbe047db674" title="Grade and finish" %}
+ {% lab-finish exercise="sa-nfs-persistent" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/08-exercise-maps.md b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/08-exercise-maps.md
index ad105c1..8f6d4ba 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/08-exercise-maps.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/08-exercise-maps.md
@@ -12,9 +12,23 @@ Export a directory of home directories from serverb, and make servera mount each
objectives=["ch19.maps"]
id="maps"
title="Serve home directories on demand"
+ exercise="sa-nfs-maps"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Create a wildcard map with * and &.","Create a direct map with the /- master entry.","Use automount -m and journalctl -u autofs to inspect maps."] %}
+ {% task id="task-7e46fbdf7674" title="Start the exercise" %}
+ On workstation, start the exercise. It installs nfs-utils and autofs, exports `/srv/shared` from serverb to the lab network (NFS server running, firewall open), and starts autofs on servera.
+
+```console
+[student@workstation ~]$ lab start sa-nfs-maps
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-4cdf0b470d48" title="The homes on the server" %}
On serverb as root, create `/srv/homes/alice` and `/srv/homes/student` with a file `readme` in each, owned by the matching user (UID 2001 for alice; create the user if needed). Export `/srv/homes` to your lab network read-write and publish.
@@ -121,12 +135,7 @@ total 4
{% /reveal %}
{% /task %}
- {% task id="task-79dcd78cb700" title="Clean up" %}
-
-```console
-[root@servera ~]# rm -f /etc/auto.master.d/{homes,direct,shared}.autofs /etc/auto.homes /etc/auto.direct /etc/auto.shared
-[root@servera ~]# systemctl reload autofs; userdel -r alice
-[root@serverb ~]# sed -i '/srv\/homes/d' /etc/exports; exportfs -ra; rm -rf /srv/homes; userdel -r alice
-```
+ {% task id="task-9d2d61f64c25" title="Grade and finish" %}
+ {% lab-finish exercise="sa-nfs-maps" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/09-lab.md
index 20fdb26..7e16a9a 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch19-nfs-and-autofs/09-lab.md
@@ -110,6 +110,6 @@ touch: cannot touch '/remote/archive/2025/x': Read-only file system
{% task id="task-df9cf7463e73" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-nfs-review" grade=true /%}
+ {% lab-finish exercise="sa-nfs-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/02-exercise-web.md b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/02-exercise-web.md
index 6bb50e6..104a2c0 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/02-exercise-web.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/02-exercise-web.md
@@ -12,25 +12,21 @@ Start a web server on servera and find out why serverb cannot reach it. Then ope
objectives=["ch20.zones"]
id="web"
title="Publish a web server through the firewall"
+ exercise="sa-fw-web"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Read the active zone and its services.","Allow a service permanently and verify it from another host.","Tell a closed firewall from a missing service."] %}
- {% task id="task-d13fed178054" title="Start a web server" %}
- On servera as root, install `httpd`, put the line `hello from servera` in `/var/www/html/index.html`, and start and enable the service. Test it locally with `curl`.
-
- {% reveal title="Show solution" %}
+ {% task id="task-5c70a2201d6d" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd on servera with the page `hello from servera` and starts it.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ sudo -i
-[root@servera ~]# dnf install -y httpd > /dev/null
-[root@servera ~]# echo "hello from servera" > /var/www/html/index.html
-[root@servera ~]# systemctl enable --now httpd
-Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
-[root@servera ~]# curl -s http://localhost/
-hello from servera
+[student@workstation ~]$ lab start sa-fw-web
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-78d0e2258f6f" title="Try it from serverb" %}
@@ -110,15 +106,7 @@ hello from servera
{% /reveal %}
{% /task %}
- {% task id="task-dd89882e76b1" title="Clean up" %}
-
-```console
-[root@servera ~]# firewall-cmd --permanent --remove-service=http; firewall-cmd --permanent --remove-port=8080/tcp; firewall-cmd --reload
-success
-success
-success
-[root@servera ~]# rm /etc/httpd/conf.d/extra.conf; systemctl restart httpd
-[root@servera ~]# exit
-```
+ {% task id="task-f31577139d55" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fw-web" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/04-exercise-stores.md b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/04-exercise-stores.md
index 602d0a3..30582ec 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/04-exercise-stores.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/04-exercise-stores.md
@@ -12,21 +12,21 @@ Watch the two stores part company: a runtime rule that a reload erases, a perman
objectives=["ch20.stores"]
id="stores"
title="Runtime, permanent and temporary rules"
+ exercise="sa-fw-stores"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Show that a runtime rule is lost on reload.","Use --timeout for a temporary rule.","Explain what --runtime-to-permanent saves."] %}
- {% task id="task-2acb0d9cc583" title="Set up" %}
- On servera as root, make sure `httpd` is running with the page from the previous exercise (install it and create `/var/www/html/index.html` if you reset the servers). Check that the firewall allows no `http` at the moment.
-
- {% reveal title="Show solution" %}
+ {% task id="task-ed0f6cf56a1f" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd on servera with the page `hello from servera` and starts it.
```console
-[root@servera ~]# dnf install -y httpd > /dev/null; systemctl enable --now httpd 2>&1 | tail -1
-[root@servera ~]# echo "hello from servera" > /var/www/html/index.html
-[root@servera ~]# firewall-cmd --query-service=http
-no
+[student@workstation ~]$ lab start sa-fw-stores
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-2a976c4335b2" title="A runtime rule" %}
@@ -124,12 +124,7 @@ hello from servera
{% /reveal %}
{% /task %}
- {% task id="task-84dbe770d77f" title="Clean up" %}
-
-```console
-[student@servera ~]$ sudo firewall-cmd --permanent --remove-service=http; sudo firewall-cmd --reload
-success
-success
-```
+ {% task id="task-b5a458a4e2ba" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fw-stores" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/06-exercise-special.md b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/06-exercise-special.md
index 3532a3d..15e0846 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/06-exercise-special.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/06-exercise-special.md
@@ -12,25 +12,21 @@ servera runs a web server on ports 80, 8080 and 9000. You will let only serverb
objectives=["ch20.special"]
id="special"
title="One trusted host, a custom port and a forward"
+ exercise="sa-fw-special"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Build a source-based zone and test it from two hosts.","Create a custom service and a forward port.","Read rejected packets in the kernel log."] %}
- {% task id="task-246bdca80164" title="Prepare the server" %}
- On servera as root: install `httpd`, write `hello from servera` as the home page, add `Listen 8080` and `Listen 9000` in `/etc/httpd/conf.d/extra.conf` (one per line), then start and enable the service. Check with `ss -tlnp` that it listens on the three ports. Port 8080 and 9000 are already web ports for SELinux.
-
- {% reveal title="Show solution" %}
+ {% task id="task-1fe0885ae68c" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd on servera, serves the page `hello from servera` on ports 80, 8080 and 9000, and starts it.
```console
-[root@servera ~]# dnf install -y httpd > /dev/null
-[root@servera ~]# echo "hello from servera" > /var/www/html/index.html
-[root@servera ~]# printf 'Listen 8080\nListen 9000\n' > /etc/httpd/conf.d/extra.conf
-[root@servera ~]# systemctl enable --now httpd 2>&1 | tail -1
-[root@servera ~]# ss -tln | grep -E ':(80|8080|9000) '
-LISTEN 0 511 *:80 *:*
-LISTEN 0 511 *:9000 *:*
-LISTEN 0 511 *:8080 *:*
+[student@workstation ~]$ lab start sa-fw-special
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-21c0703eb160" title="A zone for serverb" %}
@@ -162,14 +158,7 @@ port 9000 -> 000
{% /reveal %}
{% /task %}
- {% task id="task-3fdf015029c8" title="Clean up" %}
-
-```console
-[root@servera ~]# firewall-cmd --permanent --delete-zone=lanonly; firewall-cmd --permanent --delete-service=myapp; firewall-cmd --reload
-success
-success
-success
-[root@servera ~]# rm /etc/httpd/conf.d/extra.conf; systemctl restart httpd
-```
+ {% task id="task-8e819d93d3fc" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fw-special" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/08-exercise-diagnosis.md b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/08-exercise-diagnosis.md
index 5013529..4b68c83 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/08-exercise-diagnosis.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/08-exercise-diagnosis.md
@@ -12,25 +12,21 @@ A web server on servera answers on port 80. You will break it four different way
objectives=["ch20.diagnosis"]
id="diagnosis"
title="Find four faults"
+ exercise="sa-fw-diagnosis"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Match each client symptom to a layer.","Use ss, firewall-cmd and the journal to confirm the cause.","Fix each fault at the right layer."] %}
- {% task id="task-687b089c6a69" title="A working baseline" %}
- On servera: httpd installed and running with the page `hello from servera`, and `http` allowed permanently. Confirm from serverb.
-
- {% reveal title="Show solution" %}
+ {% task id="task-13eced3e5d26" title="Start the exercise" %}
+ On workstation, start the exercise. It installs httpd on servera with the page `hello from servera`, starts it and allows http in the firewall.
```console
-[root@servera ~]# dnf install -y httpd > /dev/null
-[root@servera ~]# echo "hello from servera" > /var/www/html/index.html
-[root@servera ~]# systemctl enable --now httpd 2>&1 | tail -1
-[root@servera ~]# firewall-cmd --permanent --add-service=http; firewall-cmd --reload
-success
-success
-[student@serverb ~]$ curl -sS -m 3 http://servera/
-hello from servera
+[student@workstation ~]$ lab start sa-fw-diagnosis
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-e270a5c4b302" title="Fault 1: the service is stopped" %}
@@ -124,12 +120,7 @@ hello from servera
{% /reveal %}
{% /task %}
- {% task id="task-862e182fbf9a" title="Clean up" %}
-
-```console
-[root@servera ~]# rm /etc/httpd/conf.d/extra.conf; semanage port -d -t http_port_t -p tcp 82
-[root@servera ~]# firewall-cmd --permanent --remove-port=82/tcp; firewall-cmd --permanent --remove-service=http; firewall-cmd --reload
-[root@servera ~]# systemctl restart httpd
-```
+ {% task id="task-da8afc057516" title="Grade and finish" %}
+ {% lab-finish exercise="sa-fw-diagnosis" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/09-lab.md
index 2142918..e6c3e43 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch20-firewalld/09-lab.md
@@ -118,6 +118,6 @@ curl: (7) Failed to connect to servera port 80: No route to host
{% task id="task-686e1f06ad46" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-firewalld-review" grade=true /%}
+ {% lab-finish exercise="sa-firewalld-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch21-containers/02-exercise-run.md b/content/programs/rhel9-sysadmin/chapters/ch21-containers/02-exercise-run.md
index 682ef01..ac19eb3 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch21-containers/02-exercise-run.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch21-containers/02-exercise-run.md
@@ -12,9 +12,23 @@ Pull an image, run a one-off container and a background one, look inside, read i
objectives=["ch21.containers"]
id="run"
title="Run and manage containers"
+ exercise="sa-containers-run"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Pull an image by its full name.","Run, inspect, stop and remove containers.","Use logs and ps -a to understand a container that exited."] %}
+ {% task id="task-366d25c96e0e" title="Start the exercise" %}
+ On workstation, start the exercise. It installs podman on servera and removes containers of an earlier run.
+
+```console
+[student@workstation ~]$ lab start sa-containers-run
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-35a22b8c74d5" title="Install Podman and pull an image" %}
On servera, install `podman` with sudo if it is not there. As student, pull `registry.access.redhat.com/ubi9/ubi-minimal:latest` and list your images. Then try to pull the short name `httpd`: what does Podman say?
@@ -113,4 +127,8 @@ REPOSITORY TAG IMAGE ID CREATED SIZE
root has its own, empty storage. Images and containers belong to the user who ran Podman.
{% /reveal %}
{% /task %}
+
+ {% task id="task-c08c7862d8b4" title="Grade and finish" %}
+ {% lab-finish exercise="sa-containers-run" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch21-containers/04-exercise-storage.md b/content/programs/rhel9-sysadmin/chapters/ch21-containers/04-exercise-storage.md
index 17cae32..4ae9f74 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch21-containers/04-exercise-storage.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch21-containers/04-exercise-storage.md
@@ -12,20 +12,21 @@ Publish a web container, meet the SELinux 403 and fix it with the right mount op
objectives=["ch21.storage"]
id="storage"
title="Ports, mounts and volumes"
+ exercise="sa-containers-storage"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Publish ports and recognise the two port errors.","Fix an SELinux-blocked bind mount with :Z.","Keep data in a volume and understand the user mapping."] %}
- {% task id="task-3f8b9cd220d7" title="Prepare a page" %}
- As student on servera pull `registry.access.redhat.com/ubi9/httpd-24:latest` (it is a 300 MB download), and create `~/web/index.html` with the line `hello from a container`.
-
- {% reveal title="Show solution" %}
+ {% task id="task-6b26c2956c7c" title="Start the exercise" %}
+ On workstation, start the exercise. It installs podman, pulls the web and minimal images for student on servera and creates `~/web/index.html` with the line `hello from a container`.
```console
-[student@servera ~]$ podman pull registry.access.redhat.com/ubi9/httpd-24:latest | tail -1
-4e74cc90c2a4ab4fcc97976b9e382cd2b8e0711222a30206ae8089fc39b01bb9
-[student@servera ~]$ mkdir -p ~/web; echo "hello from a container" > ~/web/index.html
+[student@workstation ~]$ lab start sa-containers-storage
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-72cbd79c173b" title="A bind mount that fails" %}
@@ -127,12 +128,7 @@ total 4
{% /reveal %}
{% /task %}
- {% task id="task-226a288c35e2" title="Clean up" %}
-
-```console
-[student@servera ~]$ podman rm -f -t 1 web
-web
-[student@servera ~]$ rm -rf ~/data
-```
+ {% task id="task-b8085e8906ea" title="Grade and finish" %}
+ {% lab-finish exercise="sa-containers-storage" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch21-containers/06-exercise-build.md b/content/programs/rhel9-sysadmin/chapters/ch21-containers/06-exercise-build.md
index 41f56aa..2fdf2c2 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch21-containers/06-exercise-build.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch21-containers/06-exercise-build.md
@@ -12,9 +12,23 @@ Build a small web image from a Containerfile, read a build error, run the image,
objectives=["ch21.build"]
id="build"
title="Build and version an image"
+ exercise="sa-containers-build"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera"]
outcomes=["Write a Containerfile and build a tagged image.","Read a failed COPY step.","Run and compare two versions of an image."] %}
+ {% task id="task-ac932c86ca1b" title="Start the exercise" %}
+ On workstation, start the exercise. It installs podman on servera and pulls the base image `httpd-24` for student.
+
+```console
+[student@workstation ~]$ lab start sa-containers-build
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-94520ca5e1dd" title="The project directory" %}
As student on servera, create `~/portal` with `index.html` containing `Portal is up`. Make sure the base image `registry.access.redhat.com/ubi9/httpd-24:latest` is pulled.
@@ -119,18 +133,7 @@ sha256:ea005876daf0c5ee8024c9334503c742a37ed5391ee0e32a9051540425298d76
{% /reveal %}
{% /task %}
- {% task id="task-dbe804b78a04" title="Clean up" %}
-
-```console
-[student@servera portal]$ podman rm -f -t 1 portal
-portal
-[student@servera portal]$ podman rmi localhost/portal:latest localhost/portal:2 localhost/portal:1
-Untagged: localhost/portal:latest
-Untagged: localhost/portal:2
-Untagged: localhost/portal:1
-Deleted: 65755b3c59904919657cad23be6f3e8e87d42d648de4c04cb3cc43b15efc1f88
-Deleted: 8783f7652eb1f58df91e9c935a51986cbb9e3de30809eb517a08e5e4f883b136
-[student@servera portal]$ cd; rm -rf ~/portal
-```
+ {% task id="task-44921bebd9b6" title="Grade and finish" %}
+ {% lab-finish exercise="sa-containers-build" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch21-containers/08-exercise-service.md b/content/programs/rhel9-sysadmin/chapters/ch21-containers/08-exercise-service.md
index fa22121..5ba215d 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch21-containers/08-exercise-service.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch21-containers/08-exercise-service.md
@@ -12,22 +12,21 @@ Turn the web container into a Quadlet service, watch systemd restart it, break a
objectives=["ch21.services"]
id="service"
title="Run a container as a service"
+ exercise="sa-containers-service"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Write a Quadlet file and start the generated service.","Diagnose a wrong image name and a missing SELinux label.","Enable lingering and open the firewall so the service works after a reboot."] %}
- {% task id="task-579aa32f3398" title="Prepare" %}
- Log in to servera as student. Pull `registry.access.redhat.com/ubi9/httpd-24:latest`, create `~/web/index.html` with `hello from a container`, and make sure no container of the earlier exercises is running.
-
- {% reveal title="Show solution" %}
+ {% task id="task-c2bda658caa9" title="Start the exercise" %}
+ On workstation, start the exercise. It installs podman, pulls the web image for student on servera and creates `~/web/index.html` with the line `hello from a container`.
```console
-[student@workstation ~]$ ssh student@servera
-[student@servera ~]$ podman pull registry.access.redhat.com/ubi9/httpd-24:latest | tail -1
-4e74cc90c2a4ab4fcc97976b9e382cd2b8e0711222a30206ae8089fc39b01bb9
-[student@servera ~]$ mkdir -p ~/web; echo "hello from a container" > ~/web/index.html
-[student@servera ~]$ podman ps -a --format "{{.Names}}"
+[student@workstation ~]$ lab start sa-containers-service
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-04344c24e45c" title="The Quadlet file" %}
@@ -103,7 +102,6 @@ NRestarts=1
{% reveal title="Show solution" %}
```console
-[student@servera ~]$ systemctl --user stop web.service
[student@servera ~]$ sed -i 's#httpd-24:latest#httpd-99:latest#' ~/.config/containers/systemd/web.container
[student@servera ~]$ systemctl --user daemon-reload; systemctl --user start web.service
Job for web.service failed because the control process exited with error code.
@@ -165,15 +163,7 @@ hello from a container
{% /reveal %}
{% /task %}
- {% task id="task-f73b4f5b322d" title="Clean up" %}
-
-```console
-[student@servera ~]$ systemctl --user stop web.service
-[student@servera ~]$ rm ~/.config/containers/systemd/web.container; systemctl --user daemon-reload
-[student@servera ~]$ sudo firewall-cmd --permanent --remove-port=8080/tcp; sudo firewall-cmd --reload
-success
-success
-[student@servera ~]$ sudo loginctl disable-linger student
-```
+ {% task id="task-64109f68f491" title="Grade and finish" %}
+ {% lab-finish exercise="sa-containers-service" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch21-containers/09-lab.md b/content/programs/rhel9-sysadmin/chapters/ch21-containers/09-lab.md
index d9661f0..82e7716 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch21-containers/09-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch21-containers/09-lab.md
@@ -112,6 +112,6 @@ Portal is up
{% task id="task-b9e0a99c36c8" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-containers-review" grade=true /%}
+ {% lab-finish exercise="sa-containers-review" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch22-review/02-exercise-portal.md b/content/programs/rhel9-sysadmin/chapters/ch22-review/02-exercise-portal.md
index 31f0cb0..74cebaa 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch22-review/02-exercise-portal.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch22-review/02-exercise-portal.md
@@ -12,9 +12,23 @@ Deliver the status portal on servera, one layer at a time, and prove each layer
objectives=["ch22.evidence"]
id="portal"
title="Deliver the portal"
+ exercise="sa-portal"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Create the account, content, ACL, labels and port label.","Limit access to one caller with a firewall zone.","Add a persistent journal and a scheduled backup, then prove it after a reboot."] %}
+ {% task id="task-adcd1b489fa1" title="Start the exercise" %}
+ On workstation, start the exercise. It removes a portal of an earlier run from servera, so you start from a plain system.
+
+```console
+[student@workstation ~]$ lab start sa-portal
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-9e657e0b66c2" title="Identity and software" %}
On servera as root create the group `portal` (GID 4800) and the account `portaladm` (UID 3001, primary group `portal`, shell `/sbin/nologin`). Install `httpd` and check both.
@@ -184,4 +198,8 @@ Enforcing
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-90e051ce5e15" title="Grade and finish" %}
+ {% lab-finish exercise="sa-portal" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch22-review/04-exercise-faults.md b/content/programs/rhel9-sysadmin/chapters/ch22-review/04-exercise-faults.md
index bb9213c..bd73dfe 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch22-review/04-exercise-faults.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch22-review/04-exercise-faults.md
@@ -12,9 +12,23 @@ The portal from the previous exercise works. You will break it six ways, one at
objectives=["ch22.faults"]
id="faults"
title="Find six faults"
+ exercise="sa-portal-faults"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Map each symptom to one layer.","Repair with one change and repeat the failing request.","Keep the other requirements intact."] %}
+ {% task id="task-956de9dc6ae5" title="Start the exercise" %}
+ On workstation, start the exercise. It builds the finished portal of the previous exercise on servera: account, content with ACL, labels, httpd on port 8090 and the firewall zone for serverb.
+
+```console
+[student@workstation ~]$ lab start sa-portal-faults
+```
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
+ {% /task %}
+
{% task id="task-671ba214e616" title="The test request" %}
Make the portal from the previous exercise work again if needed. Then define your test: on serverb, `curl -sS -m 3 -o /dev/null -w "HTTP %{http_code}\n" http://servera:8090/` must print `HTTP 200`.
@@ -151,4 +165,8 @@ HTTP 200
Every layer works and the status code is 200. Only a check of the content (or a comparison with the backup) notices. The next exercise restores it.
{% /reveal %}
{% /task %}
+
+ {% task id="task-4ee76f1fbd21" title="Grade and finish" %}
+ {% lab-finish exercise="sa-portal-faults" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch22-review/06-exercise-recovery.md b/content/programs/rhel9-sysadmin/chapters/ch22-review/06-exercise-recovery.md
index 41453b9..41f5409 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch22-review/06-exercise-recovery.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch22-review/06-exercise-recovery.md
@@ -12,24 +12,21 @@ Use the backup from the build to repair the wrong-content fault, see what a care
objectives=["ch22.handover"]
id="recovery"
title="Restore and hand over"
+ exercise="sa-portal-recovery"
+ ownExercise=true
+ guided=true
+ starter=false
hosts=["workstation","servera","serverb"]
outcomes=["Restore into a staging directory and replace only the bad file.","Show what a restore without ACLs and labels loses.","Write the handover note."] %}
- {% task id="task-616e2ce1f293" title="A fresh backup" %}
- The portal must be healthy again (repair the last fault of the previous exercise if you stopped there). Run the backup service and note the archive checksum.
-
- {% reveal title="Show solution" %}
+ {% task id="task-1c1640dedc53" title="Start the exercise" %}
+ On workstation, start the exercise. It builds the finished portal on servera, with the daily backup service and timer from the build exercise, and runs the backup once.
```console
-[root@servera ~]# echo "Portal status: OK" > /srv/portal/index.html
-[root@servera ~]# systemctl start portal-backup.service
-[root@servera ~]# sha256sum /srv/backups/portal.tar.gz | cut -c1-20
-2e46d2621750dc98c8fd
-[root@servera ~]# tar -tzf /srv/backups/portal.tar.gz
-portal/
-portal/index.html
+[student@workstation ~]$ lab start sa-portal-recovery
```
- {% /reveal %}
+
+ Later tasks assume that you are logged in to the server they name: `ssh student@servera` from workstation, then `sudo -i` for a root shell.
{% /task %}
{% task id="task-ed6fdaccaf4f" title="Damage the content" %}
@@ -105,4 +102,8 @@ EOT
```
{% /reveal %}
{% /task %}
+
+ {% task id="task-74facf7bb787" title="Grade and finish" %}
+ {% lab-finish exercise="sa-portal-recovery" grade=true servers=true /%}
+ {% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/chapters/ch22-review/07-lab.md b/content/programs/rhel9-sysadmin/chapters/ch22-review/07-lab.md
index 4370210..d232e9c 100644
--- a/content/programs/rhel9-sysadmin/chapters/ch22-review/07-lab.md
+++ b/content/programs/rhel9-sysadmin/chapters/ch22-review/07-lab.md
@@ -155,6 +155,6 @@ Status page: ready
{% task id="task-324ae14d4e05" title="Grade" %}
On workstation, fill in `answers.txt` and grade:
- {% lab-finish exercise="sa-review-final" grade=true /%}
+ {% lab-finish exercise="sa-review-final" grade=true servers=true /%}
{% /task %}
{% /lab %}
diff --git a/content/programs/rhel9-sysadmin/lab/sa-accounts.yml b/content/programs/rhel9-sysadmin/lab/sa-accounts.yml
new file mode 100644
index 0000000..32510e4
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-accounts.yml
@@ -0,0 +1,84 @@
+name: sa-accounts
+title: Set up a web team
+page: ch06/exercise-accounts
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: webapp
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: webteam
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: group
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the group webteam has GID 40000
+ paths:
+ - /etc/group
+ contains:
+ - 'webteam:x:40000:'
+ - id: members
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: alice and bob are members of webteam
+ names:
+ - alice
+ - bob
+ groups:
+ - webteam
+ - id: service
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: webapp is a login-less service account in webteam
+ names:
+ - webapp
+ uid: 4000
+ primaryGroup: webteam
+ shell: /sbin/nologin
+finish:
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: webapp
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: webteam
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-acls.yml b/content/programs/rhel9-sysadmin/lab/sa-acls.yml
new file mode 100644
index 0000000..c345cbb
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-acls.yml
@@ -0,0 +1,125 @@
+name: sa-acls
+title: Give the auditors read access
+page: ch07/exercise-acls
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: group
+ hosts:
+ - servera
+ name: auditors
+ state: present
+ gid: 4800
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: present
+ groups:
+ - auditors
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: present
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/reports
+ state: present
+ owner: maria
+ group: maria
+ mode: '700'
+- action: directory
+ hosts:
+ - servera
+ path: /srv/reports/q3
+ state: present
+ owner: maria
+ group: maria
+ mode: '700'
+- action: file
+ hosts:
+ - servera
+ path: /srv/reports/q3/summary.txt
+ state: present
+ content: 'figures
+
+ '
+ owner: maria
+ group: maria
+ mode: '600'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: summary
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the auditors group can read summary.txt through an ACL
+ paths:
+ - /srv/reports/q3/summary.txt
+ acl:
+ - group:auditors:r--
+ - id: reports
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: auditors can traverse /srv/reports
+ paths:
+ - /srv/reports
+ acl:
+ - group:auditors:r-x
+ - id: after
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: after.txt was created by maria
+ paths:
+ - /srv/reports/q3/after.txt
+ owner: maria
+ - id: before
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: before.txt was created by maria
+ paths:
+ - /srv/reports/q3/before.txt
+ owner: maria
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /srv/reports
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: auditors
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-archives-review.yml b/content/programs/rhel9-sysadmin/lab/sa-archives-review.yml
index e1f1201..cfaec6f 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-archives-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-archives-review.yml
@@ -80,3 +80,19 @@ checkpoints:
paths:
- /srv/restore/project/cache
exists: false
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backup
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/restore
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/backup
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-backups.yml b/content/programs/rhel9-sysadmin/lab/sa-backups.yml
new file mode 100644
index 0000000..2f599c0
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-backups.yml
@@ -0,0 +1,90 @@
+name: sa-backups
+title: Back up with ACLs, then restore
+page: ch14/exercise-backups
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: acl-tar
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: acl.tar.gz holds the project
+ path: /root/arch/acl.tar.gz
+ format: tar.gz
+ members:
+ - project/docs/notes.md
+ - id: acl
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the careful restore kept the ACL entry for student
+ paths:
+ - /root/arch/out2/project/docs/notes.md
+ acl:
+ - user:student:r--
+ - id: full
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the full backup holds both files
+ path: /root/arch/bk/full.tar.gz
+ format: tar.gz
+ members:
+ - work/docs/a.txt
+ - work/docs/b.txt
+ - id: incr
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the incremental backup holds the new file
+ path: /root/arch/bk/incr1.tar.gz
+ format: tar.gz
+ members:
+ - work/docs/c.txt
+ - id: restored
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the restore in order has c.txt
+ paths:
+ - /root/arch/restore/work/docs/c.txt
+ fileType: regular
+ - id: b-gone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the restore has no b.txt (deleted before the increment)
+ paths:
+ - /root/arch/restore/work/docs/b.txt
+ exists: false
+ - id: snap
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: unchanged files in snap2 are hard links to snap1
+ paths:
+ - /root/arch/bk/snap2/docs/a.txt
+ sameFileAs: /root/arch/bk/snap1/docs/a.txt
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-booleans-ports.yml b/content/programs/rhel9-sysadmin/lab/sa-booleans-ports.yml
new file mode 100644
index 0000000..a149fec
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-booleans-ports.yml
@@ -0,0 +1,153 @@
+name: sa-booleans-ports
+title: Flip a switch and label a port
+page: ch16/exercise-booleans-ports
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ - audit
+ - setroubleshoot-server
+ state: present
+- action: service
+ hosts:
+ - servera
+ names:
+ - auditd
+ state: started
+ enabled: true
+ tolerant: true
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /home/bob/public_html
+ state: present
+ owner: bob
+ group: bob
+ mode: '755'
+- action: file
+ hosts:
+ - servera
+ path: /home/bob/public_html/index.html
+ state: present
+ content: 'bob page
+
+ '
+ owner: bob
+ group: bob
+ mode: '644'
+- action: directory
+ hosts:
+ - servera
+ path: /home/bob
+ mode: '711'
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/userdir.conf
+ state: present
+ content: "\n UserDir public_html\n\n\n\n AllowOverride FileInfo AuthConfig Limit Indexes\n Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec\n Require method GET POST OPTIONS\n\n"
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: boolean
+ kind: selinux-boolean
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd_enable_homedirs is on, now and permanently
+ name: httpd_enable_homedirs
+ value: true
+ - id: port
+ kind: selinux-port
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: TCP port 82 is labelled http_port_t
+ type: http_port_t
+ proto: tcp
+ port: 82
+ - id: listen
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd listens on port 82
+ paths:
+ - /etc/httpd/conf/httpd.conf
+ line: Listen 82
+ - id: svc
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd is running
+ names:
+ - httpd.service
+ active: true
+ - id: page82
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the page of bob is served on port 82
+ url: http://localhost:82/~bob/
+ containsAny:
+ - bob page
+finish:
+- action: selinux
+ hosts:
+ - servera
+ kind: boolean
+ state: present
+ name: httpd_enable_homedirs
+ value: false
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 82
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/userdir.conf
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-boot.yml b/content/programs/rhel9-sysadmin/lab/sa-boot.yml
new file mode 100644
index 0000000..ea35392
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-boot.yml
@@ -0,0 +1,31 @@
+name: sa-boot
+title: Change how the server boots
+page: ch09/exercise-boot
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: boot
+ hosts:
+ - servera
+ target: graphical.target
+ removeKernelArgs:
+ - loglevel=5
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: target
+ kind: boot-target
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the default target is graphical.target again
+ target: graphical.target
+finish:
+- action: boot
+ hosts:
+ - servera
+ target: graphical.target
+ removeKernelArgs:
+ - loglevel=5
diff --git a/content/programs/rhel9-sysadmin/lab/sa-changing.yml b/content/programs/rhel9-sysadmin/lab/sa-changing.yml
new file mode 100644
index 0000000..5a7a711
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-changing.yml
@@ -0,0 +1,132 @@
+name: sa-changing
+title: Secure a web folder
+page: ch07/exercise-changing
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: group
+ hosts:
+ - servera
+ name: webteam
+ state: present
+ gid: 4600
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: present
+ groups:
+ - webteam
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/site/html
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/site/bin
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /srv/site/html/index.html
+ state: present
+ content: ''
+- action: file
+ hosts:
+ - servera
+ path: /srv/site/bin/deploy.sh
+ state: present
+ content: ''
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: owner
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the tree belongs to maria:webteam
+ paths:
+ - /srv/site/html/index.html
+ owner: maria
+ group: webteam
+ - id: page
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: 'index.html is 664: the group may edit, others read'
+ paths:
+ - /srv/site/html/index.html
+ mode: '0664'
+ - id: script
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: deploy.sh is mode 750 and holds the command
+ paths:
+ - /srv/site/bin/deploy.sh
+ mode: '0750'
+ contains:
+ - echo deployed
+ - id: dirs
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: site and html are traversable by others (755)
+ paths:
+ - /srv/site
+ fileType: directory
+ mode: '0755'
+ - id: html
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: html is traversable by others (755)
+ paths:
+ - /srv/site/html
+ fileType: directory
+ mode: '0755'
+ - id: bin
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: bin stays closed to others (750)
+ paths:
+ - /srv/site/bin
+ fileType: directory
+ mode: '0750'
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /srv/site
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: webteam
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-checksums.yml b/content/programs/rhel9-sysadmin/lab/sa-checksums.yml
new file mode 100644
index 0000000..ebe9fc9
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-checksums.yml
@@ -0,0 +1,84 @@
+name: sa-checksums
+title: Compress and verify
+page: ch14/exercise-checksums
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - bzip2
+ - zip
+ - unzip
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: gz
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: etc.tar.gz exists
+ paths:
+ - /root/arch/etc.tar.gz
+ fileType: regular
+ - id: bz2
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: etc.tar.bz2 exists
+ paths:
+ - /root/arch/etc.tar.bz2
+ fileType: regular
+ - id: xz
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: etc.tar.xz exists
+ paths:
+ - /root/arch/etc.tar.xz
+ fileType: regular
+ - id: zip
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ssh.zip is a readable zip file
+ path: /root/arch/ssh.zip
+ format: zip
+ - id: sums
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: SHA256SUMS lists the three archives
+ paths:
+ - /root/arch/SHA256SUMS
+ contains:
+ - etc.tar.gz
+ - etc.tar.bz2
+ - etc.tar.xz
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-conditions.yml b/content/programs/rhel9-sysadmin/lab/sa-conditions.yml
new file mode 100644
index 0000000..34c7c5d
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-conditions.yml
@@ -0,0 +1,67 @@
+name: sa-conditions
+title: Scripts that decide and repeat
+page: ch15/exercise-conditions
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/scripts
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: check
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: check.sh is executable
+ paths:
+ - /home/student/scripts/check.sh
+ executable: true
+ contains:
+ - exit 2
+ - exit 1
+ - id: loops
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: loops.sh is executable
+ paths:
+ - /home/student/scripts/loops.sh
+ executable: true
+ contains:
+ - 'for '
+ - id: svc
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: svc.sh uses case
+ paths:
+ - /home/student/scripts/svc.sh
+ executable: true
+ contains:
+ - 'case '
+ - id: users
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: users.sh reads a file line by line
+ paths:
+ - /home/student/scripts/users.sh
+ executable: true
+ contains:
+ - while
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/scripts
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-containers-build.yml b/content/programs/rhel9-sysadmin/lab/sa-containers-build.yml
new file mode 100644
index 0000000..0106a0b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-containers-build.yml
@@ -0,0 +1,116 @@
+name: sa-containers-build
+title: Build and version an image
+page: ch21/exercise-build
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - podman
+ state: present
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/portal
+ state: absent
+- action: run-as
+ hosts:
+ - servera
+ user: student
+ tool: podman-pull
+ image: registry.access.redhat.com/ubi9/httpd-24:latest
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: v1
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the image localhost/portal:1 exists
+ user: student
+ image: localhost/portal:1
+ - id: v2
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the image localhost/portal:2 exists
+ user: student
+ image: localhost/portal:2
+ - id: latest
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the image localhost/portal:latest exists
+ user: student
+ image: localhost/portal:latest
+ - id: running
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the portal container runs from the latest image
+ user: student
+ running: portal
+ - id: containerfile
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the Containerfile copies the page
+ paths:
+ - /home/student/portal/Containerfile
+ contains:
+ - FROM registry.access.redhat.com/ubi9/httpd-24
+ - COPY index.html
+ - id: page
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: version 2 is served on port 8081
+ url: http://localhost:8081/
+ containsAny:
+ - Portal v2
+finish:
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: linger
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/portal
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-containers-review.yml b/content/programs/rhel9-sysadmin/lab/sa-containers-review.yml
index 504032b..b0b13b9 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-containers-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-containers-review.yml
@@ -123,3 +123,24 @@ checkpoints:
paths:
- answers.txt
matches: ^LABEL_OPTION=Z[ \t]*$
+finish:
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: linger
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/portal
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-containers-run.yml b/content/programs/rhel9-sysadmin/lab/sa-containers-run.yml
new file mode 100644
index 0000000..4d07ede
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-containers-run.yml
@@ -0,0 +1,36 @@
+name: sa-containers-run
+title: Run and manage containers
+page: ch21/exercise-run
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - podman
+ state: present
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: image
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: student has the ubi-minimal image
+ user: student
+ image: registry.access.redhat.com/ubi9/ubi-minimal:latest
+finish:
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-containers-service.yml b/content/programs/rhel9-sysadmin/lab/sa-containers-service.yml
new file mode 100644
index 0000000..3eb9b6e
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-containers-service.yml
@@ -0,0 +1,135 @@
+name: sa-containers-service
+title: Run a container as a service
+page: ch21/exercise-service
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - podman
+ state: present
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: linger
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: absent
+- action: run-as
+ hosts:
+ - servera
+ user: student
+ tool: podman-pull
+ image: registry.access.redhat.com/ubi9/httpd-24:latest
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: present
+ owner: student
+ group: student
+ mode: '755'
+- action: file
+ hosts:
+ - servera
+ path: /home/student/web/index.html
+ state: present
+ content: 'hello from a container
+
+ '
+ owner: student
+ group: student
+ mode: '644'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: quadlet
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the Quadlet file defines the container and its restart policy
+ paths:
+ - /home/student/.config/containers/systemd/web.container
+ contains:
+ - Image=registry.access.redhat.com/ubi9/httpd-24
+ - PublishPort=8080:8080
+ - Restart=always
+ - WantedBy=default.target
+ - ',Z'
+ - id: running
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: web.service is active and systemd-web runs
+ user: student
+ unit: web.service
+ running: systemd-web
+ - id: linger
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lingering is enabled for student
+ user: student
+ linger: true
+ - id: firewall
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: port 8080/tcp is open permanently
+ port: 8080/tcp
+ - id: remote
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches the container on servera
+ url: http://servera.lab.example.com:8080/
+ containsAny:
+ - hello from a container
+finish:
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: linger
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/portal
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-containers-storage.yml b/content/programs/rhel9-sysadmin/lab/sa-containers-storage.yml
new file mode 100644
index 0000000..336ab9f
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-containers-storage.yml
@@ -0,0 +1,130 @@
+name: sa-containers-storage
+title: Ports, mounts and volumes
+page: ch21/exercise-storage
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - podman
+ state: present
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/data
+ state: absent
+- action: run-as
+ hosts:
+ - servera
+ user: student
+ tool: podman-pull
+ image: registry.access.redhat.com/ubi9/httpd-24:latest
+- action: run-as
+ hosts:
+ - servera
+ user: student
+ tool: podman-pull
+ image: registry.access.redhat.com/ubi9/ubi-minimal:latest
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: present
+ owner: student
+ group: student
+ mode: '755'
+- action: file
+ hosts:
+ - servera
+ path: /home/student/web/index.html
+ state: present
+ content: 'hello from a container
+
+ '
+ owner: student
+ group: student
+ mode: '644'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: web
+ kind: container
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the web container is running
+ user: student
+ running: web
+ - id: label
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/web carries the private container label
+ paths:
+ - /home/student/web
+ selinuxType: container_file_t
+ - id: page
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the page is served on port 8080
+ url: http://localhost:8080/
+ containsAny:
+ - hello from a container
+ - id: data
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the container wrote ~/data/a.txt
+ paths:
+ - /home/student/data/a.txt
+ contains:
+ - made
+finish:
+- action: container-reset
+ hosts:
+ - servera
+ user: student
+ keepImages: true
+- action: linger
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/web
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/portal
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-cron.yml b/content/programs/rhel9-sysadmin/lab/sa-cron.yml
new file mode 100644
index 0000000..96ed91a
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-cron.yml
@@ -0,0 +1,95 @@
+name: sa-cron
+title: Schedule a job
+page: ch15/exercise-cron
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: crontab
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/cron.d/demo
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/cron.out
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/ct
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - crond
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: crontab
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: student has a job with an escaped percent sign
+ paths:
+ - /var/spool/cron/student
+ matches: date \+\\%T
+ - id: out
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the job has written its output file
+ paths:
+ - /home/student/cron.out
+ contains:
+ - cron ran at
+ - id: demo
+ kind: cron
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a system job in /etc/cron.d/demo runs logger every two minutes
+ file: demo
+ matches: ^\*/2 \* \* \* \* root /usr/bin/logger -t cron-demo hello
+ - id: crond
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: crond is running
+ names:
+ - crond.service
+ active: true
+finish:
+- action: crontab
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/cron.d/demo
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/cron.out
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/ct
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-diagnosing.yml b/content/programs/rhel9-sysadmin/lab/sa-diagnosing.yml
new file mode 100644
index 0000000..577a4d0
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-diagnosing.yml
@@ -0,0 +1,117 @@
+name: sa-diagnosing
+title: Find and fix a failing service
+page: ch11/exercise-diagnosing
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - billing
+ state: stopped
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/billing.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/billing.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/billing.conf
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/billing.sh
+ state: present
+ content: "#!/bin/bash\nCONF=/etc/billing.conf\nif [ ! -r \"$CONF\" ]; then\n echo \"billing: cannot read $CONF\" >&2\n exit 3\nfi\n. \"$CONF\"\necho \"billing: started with rate=$RATE\"\nexec sleep infinity\n"
+ owner: root
+ group: root
+ mode: '755'
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/billing.service
+ state: present
+ content: '[Unit]
+
+ Description=Billing job
+
+
+ [Service]
+
+ ExecStart=/usr/local/bin/billing.sh
+
+ Restart=on-failure
+
+ RestartSec=1
+
+
+ [Install]
+
+ WantedBy=multi-user.target
+
+ '
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: service
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: billing is running
+ names:
+ - billing.service
+ active: true
+ - id: conf
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /etc/billing.conf holds RATE=10
+ paths:
+ - /etc/billing.conf
+ line: RATE=10
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - billing
+ state: stopped
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/billing.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/billing.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/billing.conf
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-dnf.yml b/content/programs/rhel9-sysadmin/lab/sa-dnf.yml
new file mode 100644
index 0000000..1bc698b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-dnf.yml
@@ -0,0 +1,73 @@
+name: sa-dnf
+title: Install, inspect and undo
+page: ch13/exercise-dnf
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/a.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/a.zip
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: zip
+ kind: package
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: zip is gone again after the undo
+ names:
+ - zip
+ installed: false
+ - id: unzip
+ kind: package
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: unzip is gone again after the undo
+ names:
+ - unzip
+ installed: false
+ - id: files
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the practice files in /tmp were removed
+ paths:
+ - /tmp/a.zip
+ exists: false
+finish:
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/a.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/a.zip
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-environment.yml b/content/programs/rhel9-sysadmin/lab/sa-environment.yml
new file mode 100644
index 0000000..e36997d
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-environment.yml
@@ -0,0 +1,61 @@
+name: sa-environment
+title: Variables, PATH and start-up files
+page: ch05/exercise-environment
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/bin
+ state: absent
+- action: restore-skel
+ hosts:
+ - servera
+ user: student
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: script
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/bin/up is an executable script
+ paths:
+ - /home/student/bin/up
+ fileType: regular
+ contains:
+ - uptime -p
+ - id: alias
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/.bashrc defines the alias now and the history time format
+ paths:
+ - /home/student/.bashrc
+ contains:
+ - alias now='date +%T'
+ - HISTTIMEFORMAT
+ - id: editor
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/.bash_profile exports EDITOR=vim
+ paths:
+ - /home/student/.bash_profile
+ line: export EDITOR=vim
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/bin
+ state: absent
+- action: restore-skel
+ hosts:
+ - servera
+ user: student
diff --git a/content/programs/rhel9-sysadmin/lab/sa-files-review.yml b/content/programs/rhel9-sysadmin/lab/sa-files-review.yml
index 0e050f5..8d769bc 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-files-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-files-review.yml
@@ -131,3 +131,29 @@ checkpoints:
- /home/student/latest
fileType: symlink
resolvesTo: /home/student/Podcasts/season2
+finish:
+- action: directory
+ hosts:
+ - serverb
+ path: /home/student/Podcasts
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /home/student/Documents
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /home/student/podcast_s1_e1.mp3
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /home/student/podcast_s2_e6.mp3
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /home/student/latest
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-filesystems.yml b/content/programs/rhel9-sysadmin/lab/sa-filesystems.yml
new file mode 100644
index 0000000..6e6fd57
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-filesystems.yml
@@ -0,0 +1,109 @@
+name: sa-filesystems
+title: Create file systems and mount them
+page: ch17/exercise-filesystems
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: data
+ fs: xfs
+ startMiB: 1
+ endMiB: 1025
+ - name: logs
+ fs: ext4
+ startMiB: 1025
+ endMiB: 2049
+ - name: swap
+ fs: linux-swap
+ startMiB: 2049
+ endMiB: 2561
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /logs
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: xfs
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /data is /dev/sdb1 formatted as xfs
+ path: /data
+ fstype: xfs
+ persistent: false
+ - id: ext4
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /logs is /dev/sdb2 formatted as ext4
+ path: /logs
+ fstype: ext4
+ persistent: false
+ - id: label
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the ext4 file system is labelled logs
+ device: /dev/sdb2
+ fsLabel: logs
+ - id: swap
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /dev/sdb3 holds a swap area
+ device: /dev/sdb3
+ fsType: swap
+ - id: a
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /data/a.txt exists
+ paths:
+ - /data/a.txt
+ line: hello
+ - id: b
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /logs/b.txt exists
+ paths:
+ - /logs/b.txt
+ line: world
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /logs
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-firewalld-review.yml b/content/programs/rhel9-sysadmin/lab/sa-firewalld-review.yml
index 8316769..aad28f5 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-firewalld-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-firewalld-review.yml
@@ -163,3 +163,39 @@ checkpoints:
paths:
- answers.txt
matches: ^DROP_SYMPTOM=timeout[ \t]*$
+finish:
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: partners
+ deleteZone: true
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ service: portal
+ deleteService: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-first-scripts.yml b/content/programs/rhel9-sysadmin/lab/sa-first-scripts.yml
new file mode 100644
index 0000000..6d6a4cb
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-first-scripts.yml
@@ -0,0 +1,64 @@
+name: sa-first-scripts
+title: Write and run your first scripts
+page: ch15/exercise-first-scripts
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/scripts
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/bin/hello.sh
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: hello
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: hello.sh is an executable script with a default name
+ paths:
+ - /home/student/scripts/hello.sh
+ executable: true
+ owner: student
+ contains:
+ - name=${1:-$USER}
+ - id: args
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: args.sh loops over "$@"
+ paths:
+ - /home/student/scripts/args.sh
+ executable: true
+ contains:
+ - for a in "$@"
+ - id: path
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a copy of hello.sh is in ~/bin (on the PATH)
+ paths:
+ - /home/student/bin/hello.sh
+ executable: true
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/scripts
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/bin/hello.sh
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fix-labels.yml b/content/programs/rhel9-sysadmin/lab/sa-fix-labels.yml
new file mode 100644
index 0000000..861e489
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fix-labels.yml
@@ -0,0 +1,144 @@
+name: sa-fix-labels
+title: Fix a 403 caused by a label
+page: ch16/exercise-fix-labels
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ - audit
+ - setroubleshoot-server
+ state: present
+- action: service
+ hosts:
+ - servera
+ names:
+ - auditd
+ state: started
+ enabled: true
+ tolerant: true
+- action: directory
+ hosts:
+ - servera
+ path: /srv/web
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /srv/web/index.html
+ state: present
+ content: 'Hello from /srv/web
+
+ '
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/web.conf
+ state: present
+ content: "DocumentRoot \"/srv/web\"\n\n Require all granted\n\n"
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: fcontext
+ kind: selinux-fcontext
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a stored mapping gives /srv/web and below the web content type
+ path: /srv/web(/.*)?
+ type: httpd_sys_content_t
+ - id: index
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: index.html carries the web content type
+ paths:
+ - /srv/web/index.html
+ selinuxType: httpd_sys_content_t
+ - id: copy
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: copy.html (restored after chcon) carries the web content type
+ paths:
+ - /srv/web/copy.html
+ selinuxType: httpd_sys_content_t
+ - id: moved
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: moved.html (relabelled after the move) carries the web content type
+ paths:
+ - /srv/web/moved.html
+ selinuxType: httpd_sys_content_t
+ - id: enforcing
+ kind: selinux
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: SELinux is enforcing, now and in the configuration
+ mode: enforcing
+ - id: page
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the page is served
+ url: http://localhost/
+ containsAny:
+ - Hello from /srv/web
+finish:
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/web(/.*)?
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/web
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/web.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/second.html
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/moved.html
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fstab-storage.yml b/content/programs/rhel9-sysadmin/lab/sa-fstab-storage.yml
new file mode 100644
index 0000000..a94571b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fstab-storage.yml
@@ -0,0 +1,155 @@
+name: sa-fstab-storage
+title: Make the storage permanent
+page: ch17/exercise-fstab
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/sdb[123]|^UUID=[0-9a-f-]+[[:space:]]+(/srv/data|/srv/archive|none[[:space:]]+swap)
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /logs
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/archive
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: data
+ fs: xfs
+ startMiB: 1
+ endMiB: 1025
+ - name: logs
+ fs: ext4
+ startMiB: 1025
+ endMiB: 2049
+ - name: swap
+ fs: linux-swap
+ startMiB: 2049
+ endMiB: 2561
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb1
+ type: xfs
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb2
+ type: ext4
+ label: logs
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb3
+ type: swap
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: data
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/data is xfs, mounted now and at boot
+ path: /srv/data
+ fstype: xfs
+ persistent: true
+ - id: archive
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/archive is ext4, mounted now and at boot
+ path: /srv/archive
+ fstype: ext4
+ persistent: true
+ - id: noexec
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the archive entry has noexec
+ paths:
+ - /etc/fstab
+ matches: /srv/archive[[:space:]]+ext4[[:space:]]+defaults,noexec
+ - id: swap
+ kind: swap
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the swap partition is active and in fstab
+ minSizeMiB: 400
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/sdb[123]|^UUID=[0-9a-f-]+[[:space:]]+(/srv/data|/srv/archive|none[[:space:]]+swap)
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /logs
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/archive
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fstab.yml b/content/programs/rhel9-sysadmin/lab/sa-fstab.yml
new file mode 100644
index 0000000..42b9af3
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fstab.yml
@@ -0,0 +1,78 @@
+name: sa-fstab
+title: Check /etc/fstab before you reboot
+page: ch09/exercise-fstab
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: ^/dev/sdb1
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: fstab
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /etc/fstab has no /dev/sdb1 line any more
+ paths:
+ - /etc/fstab
+ lacks:
+ - /dev/sdb1
+ - id: mountpoint
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /data is gone again
+ paths:
+ - /data
+ exists: false
+ - id: backup
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the backup /root/fstab.bak was removed
+ paths:
+ - /root/fstab.bak
+ exists: false
+finish:
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: ^/dev/sdb1
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fw-diagnosis.yml b/content/programs/rhel9-sysadmin/lab/sa-fw-diagnosis.yml
new file mode 100644
index 0000000..499c78e
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fw-diagnosis.yml
@@ -0,0 +1,134 @@
+name: sa-fw-diagnosis
+title: Find four faults
+page: ch20/exercise-diagnosis
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: present
+ content: 'hello from servera
+
+ '
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ service: http
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: svc
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd is running
+ names:
+ - httpd.service
+ active: true
+ - id: port-label
+ kind: selinux-port
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: TCP port 82 is labelled http_port_t
+ type: http_port_t
+ proto: tcp
+ port: 82
+ - id: fw-82
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: 82/tcp is allowed
+ port: 82/tcp
+ - id: http
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: http is allowed
+ service: http
+ - id: from-80
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches port 80
+ url: http://servera.lab.example.com/
+ containsAny:
+ - hello from servera
+ - id: from-82
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches port 82
+ url: http://servera.lab.example.com:82/
+ containsAny:
+ - hello from servera
+finish:
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 82
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ service: http
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 82/tcp
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fw-special.yml b/content/programs/rhel9-sysadmin/lab/sa-fw-special.yml
new file mode 100644
index 0000000..cba41f2
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fw-special.yml
@@ -0,0 +1,163 @@
+name: sa-fw-special
+title: One trusted host, a custom port and a forward
+page: ch20/exercise-special
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: present
+ content: 'hello from servera
+
+ '
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: present
+ content: 'Listen 8080
+
+ Listen 9000
+
+ '
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: zone-file
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The permanent zone lanonly has a source address
+ paths:
+ - /etc/firewalld/zones/lanonly.xml
+ matches:
+ - id: zone-http
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The zone lanonly allows http
+ zone: lanonly
+ service: http
+ - id: zone-myapp
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The zone lanonly allows the service myapp
+ zone: lanonly
+ service: myapp
+ - id: myapp-file
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The service myapp is defined for 9000/tcp
+ paths:
+ - /etc/firewalld/services/myapp.xml
+ matches: port="9000"
+ - id: forward
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The zone lanonly forwards 8081 to 8080
+ zone: lanonly
+ forwardPort: port=8081:proto=tcp:toport=8080
+ - id: rich
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The zone lanonly has a rich rule that rejects serverb on 9000
+ paths:
+ - /etc/firewalld/zones/lanonly.xml
+ matches: port="9000"
+ contains:
+ - reject
+ - id: log
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: Logging of rejected packets is off again
+ paths:
+ - /etc/firewalld/firewalld.conf
+ matches: ^LogDenied=off[[:space:]]*$
+ - id: public-closed
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The default zone does not allow http
+ service: http
+ allowed: false
+ - id: from-80
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches port 80
+ url: http://servera.lab.example.com/
+ containsAny:
+ - hello from servera
+finish:
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: lanonly
+ deleteZone: true
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: drop
+ source: 172.25.251.9
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ service: myapp
+ deleteService: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fw-stores.yml b/content/programs/rhel9-sysadmin/lab/sa-fw-stores.yml
new file mode 100644
index 0000000..0559316
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fw-stores.yml
@@ -0,0 +1,95 @@
+name: sa-fw-stores
+title: Runtime, permanent and temporary rules
+page: ch20/exercise-stores
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: present
+ content: 'hello from servera
+
+ '
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: http-both
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: http is allowed in the running firewall and in the saved one
+ service: http
+ - id: port-closed
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: 8080/tcp is not left open anywhere
+ port: 8080/tcp
+ allowed: false
+ - id: from-80
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches the page on port 80
+ url: http://servera.lab.example.com/
+ containsAny:
+ - hello from servera
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ service: http
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 82/tcp
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-fw-web.yml b/content/programs/rhel9-sysadmin/lab/sa-fw-web.yml
new file mode 100644
index 0000000..3655204
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-fw-web.yml
@@ -0,0 +1,122 @@
+name: sa-fw-web
+title: Publish a web server through the firewall
+page: ch20/exercise-web
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: present
+ content: 'hello from servera
+
+ '
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: http-service
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The service http is allowed, now and permanently
+ service: http
+ - id: port-8080
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: 8080/tcp is allowed, now and permanently
+ port: 8080/tcp
+ - id: listen
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd listens on 8080
+ paths:
+ - /etc/httpd/conf.d/extra.conf
+ contains:
+ - Listen 8080
+ - id: local-8080
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: The page answers on port 8080
+ url: http://localhost:8080/
+ containsAny:
+ - hello from servera
+ - id: from-80
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches port 80
+ url: http://servera.lab.example.com/
+ containsAny:
+ - hello from servera
+ - id: from-8080
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches port 8080
+ url: http://servera.lab.example.com:8080/
+ containsAny:
+ - hello from servera
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ service: http
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 8080/tcp
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ port: 82/tcp
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/extra.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/www/html/index.html
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-hardening.yml b/content/programs/rhel9-sysadmin/lab/sa-hardening.yml
new file mode 100644
index 0000000..0b568fb
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-hardening.yml
@@ -0,0 +1,121 @@
+name: sa-hardening
+title: Harden sshd, safely
+page: ch10/exercise-hardening
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: present
+ password: Ops-Pass-2026
+- action: home-remove
+ paths:
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+- action: ssh-identity
+ name: id_ops
+ hosts:
+ - servera
+ user: ops
+- action: file
+ hosts:
+ - servera
+ path: /etc/ssh/sshd_config.d/99-hardening.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/ssh/sshd_config.d/10-hardening.conf
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: file
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the drop-in 10-hardening.conf has the three options
+ paths:
+ - /etc/ssh/sshd_config.d/10-hardening.conf
+ contains:
+ - PasswordAuthentication no
+ - X11Forwarding no
+ - MaxAuthTries 3
+ - id: no-wrong
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the wrongly named 99-hardening.conf is gone
+ paths:
+ - /etc/ssh/sshd_config.d/99-hardening.conf
+ exists: false
+ - id: password
+ kind: sshd
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: sshd refuses password authentication
+ setting: passwordauthentication
+ value: 'no'
+ - id: x11
+ kind: sshd
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: sshd refuses X11 forwarding
+ setting: x11forwarding
+ value: 'no'
+ - id: tries
+ kind: sshd
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: sshd allows three authentication tries
+ setting: maxauthtries
+ value: '3'
+ - id: sshd
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: sshd is running
+ names:
+ - sshd.service
+ active: true
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /etc/ssh/sshd_config.d/99-hardening.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/ssh/sshd_config.d/10-hardening.conf
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - sshd
+ state: restarted
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: home-remove
+ paths:
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
diff --git a/content/programs/rhel9-sysadmin/lab/sa-keys.yml b/content/programs/rhel9-sysadmin/lab/sa-keys.yml
new file mode 100644
index 0000000..b66538f
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-keys.yml
@@ -0,0 +1,72 @@
+name: sa-keys
+title: Passwordless access for an account
+page: ch10/exercise-keys
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: present
+ password: Ops-Pass-2026
+- action: home-remove
+ paths:
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+ - .ssh/id_pp
+ - .ssh/id_pp.pub
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: key
+ kind: file
+ 'on': control
+ message: the private key ~/.ssh/id_ops exists with mode 600
+ paths:
+ - /home/student/.ssh/id_ops
+ mode: '0600'
+ - id: pub
+ kind: file
+ 'on': control
+ message: the public key ~/.ssh/id_ops.pub exists
+ paths:
+ - /home/student/.ssh/id_ops.pub
+ contains:
+ - student@workstation (ops)
+ - id: installed
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ops has an authorized_keys file with the right owner and mode
+ names:
+ - ops
+ authorizedKeys: true
+ - id: pp-gone
+ kind: file
+ 'on': control
+ message: the temporary passphrase copy id_pp was removed
+ paths:
+ - /home/student/.ssh/id_pp
+ - /home/student/.ssh/id_pp.pub
+ exists: false
+finish:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: home-remove
+ paths:
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+ - .ssh/id_pp
+ - .ssh/id_pp.pub
diff --git a/content/programs/rhel9-sysadmin/lab/sa-links.yml b/content/programs/rhel9-sysadmin/lab/sa-links.yml
new file mode 100644
index 0000000..5abf1ba
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-links.yml
@@ -0,0 +1,88 @@
+name: sa-links
+title: Releases with symbolic and hard links
+page: ch03/exercise-links
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/release
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/current
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/app.conf.saved
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: current
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: current is a symbolic link to release/v2
+ paths:
+ - /home/student/current
+ symlinkTo: release/v2
+ - id: v1
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: release/v1/app.conf still says version 1
+ paths:
+ - /home/student/release/v1/app.conf
+ line: version 1
+ - id: v2-gone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the release/v2 directory was deleted
+ paths:
+ - /home/student/release/v2
+ exists: false
+ - id: saved
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: app.conf.saved kept the data of version 2
+ paths:
+ - /home/student/app.conf.saved
+ line: version 2
+ hardLinks: 1
+ - id: no-stray
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: no stray link v2 is left inside release/v1
+ paths:
+ - /home/student/release/v1/v2
+ exists: false
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/release
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/current
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/app.conf.saved
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-local-repo.yml b/content/programs/rhel9-sysadmin/lab/sa-local-repo.yml
new file mode 100644
index 0000000..1fa7422
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-local-repo.yml
@@ -0,0 +1,82 @@
+name: sa-local-repo
+title: Serve your own repository
+page: ch13/exercise-local-repo
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - dnf-plugins-core
+ - createrepo_c
+ state: present
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/localrepo
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/yum.repos.d/local.repo
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: rpm
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/localrepo holds the downloaded zip package
+ paths:
+ - /srv/localrepo/repodata/repomd.xml
+ fileType: regular
+ - id: repo
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a repository definition points at /srv/localrepo
+ paths:
+ - /etc/yum.repos.d/local.repo
+ contains:
+ - '[localrepo]'
+ - file:///srv/localrepo
+ - id: zip
+ kind: package
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: zip was installed from the local repository
+ names:
+ - zip
+finish:
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ - createrepo_c
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/localrepo
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/yum.repos.d/local.repo
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-logs-review.yml b/content/programs/rhel9-sysadmin/lab/sa-logs-review.yml
index 5dbb6fb..28a2678 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-logs-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-logs-review.yml
@@ -113,3 +113,56 @@ checkpoints:
paths:
- answers.txt
matches: ^SELECTED_MARK=\*[ \t]*$
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-retention.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/rsyslog.d/10-appserver.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/appserver.log
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/logrotate.d/appserver
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - rsyslog
+ state: restarted
+- action: timezone
+ hosts:
+ - servera
+ zone: UTC
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/chrony.conf
+ matching: ^server 0[.]pool[.]ntp[.]org
+- action: service
+ hosts:
+ - servera
+ names:
+ - chronyd
+ state: restarted
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-lvm-build.yml b/content/programs/rhel9-sysadmin/lab/sa-lvm-build.yml
new file mode 100644
index 0000000..404c25b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-lvm-build.yml
@@ -0,0 +1,152 @@
+name: sa-lvm-build
+title: Build your first volume group
+page: ch18/exercise-build
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: pv1
+ fs: lvm
+ startMiB: 1
+ endMiB: 1537
+ - name: pv2
+ fs: lvm
+ startMiB: 1537
+ endMiB: 3073
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: vg
+ kind: volume-group
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: vgdata exists on /dev/sdb1
+ vg: vgdata
+ - id: lvapp
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvapp is at least 1 GiB
+ vg: vgdata
+ lv: lvapp
+ minSizeMiB: 1000
+ - id: lvlog
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvlog exists (half of the free space)
+ vg: vgdata
+ lv: lvlog
+ minSizeMiB: 200
+ - id: app
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/app is XFS and mounted
+ path: /srv/app
+ fstype: xfs
+ persistent: false
+ - id: log
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/log is ext4 and mounted
+ path: /srv/log
+ fstype: ext4
+ persistent: false
+ - id: a
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/app/a.txt holds data
+ paths:
+ - /srv/app/a.txt
+ line: hello
+ - id: b
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/log/b.txt holds data
+ paths:
+ - /srv/log/b.txt
+ line: world
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-lvm-maintenance.yml b/content/programs/rhel9-sysadmin/lab/sa-lvm-maintenance.yml
new file mode 100644
index 0000000..1478dac
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-lvm-maintenance.yml
@@ -0,0 +1,158 @@
+name: sa-lvm-maintenance
+title: Move data to a new disk
+page: ch18/exercise-maintenance
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: pv1
+ fs: lvm
+ startMiB: 1
+ endMiB: 1537
+ - name: pv2
+ fs: lvm
+ startMiB: 1537
+ endMiB: 3073
+- action: lvm-build
+ hosts:
+ - servera
+ vg: vgdata
+ pvs:
+ - /dev/sdb1
+ - /dev/sdb2
+ volumes:
+ - name: lvapp
+ size: 1G
+ fs: xfs
+ mount: /srv/app
+ - name: lvlog
+ size: 256M
+ fs: ext4
+ mount: /srv/log
+ label: lvlog
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: vg
+ kind: volume-group
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: vgdata exists
+ vg: vgdata
+ - id: lv
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the renamed volume lvstore is at least 800 MiB
+ vg: vgdata
+ lv: lvstore
+ minSizeMiB: 800
+ - id: swaplv
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvswap is at least 256 MiB
+ vg: vgdata
+ lv: lvswap
+ minSizeMiB: 256
+ - id: mount
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/data is XFS, mounted now and at boot
+ path: /srv/data
+ fstype: xfs
+ persistent: true
+ - id: swap
+ kind: swap
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the swap volume is active and in fstab
+ minSizeMiB: 200
+ - id: numbers
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the data survived the move
+ paths:
+ - /srv/data/numbers.txt
+ line: '100000'
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-lvm-resize.yml b/content/programs/rhel9-sysadmin/lab/sa-lvm-resize.yml
new file mode 100644
index 0000000..9ad0969
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-lvm-resize.yml
@@ -0,0 +1,151 @@
+name: sa-lvm-resize
+title: Grow and shrink volumes
+page: ch18/exercise-resize
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: pv1
+ fs: lvm
+ startMiB: 1
+ endMiB: 1537
+ - name: pv2
+ fs: lvm
+ startMiB: 1537
+ endMiB: 3073
+- action: lvm-build
+ hosts:
+ - servera
+ vg: vgdata
+ pvs:
+ - /dev/sdb1
+ volumes:
+ - name: lvapp
+ size: 1G
+ fs: xfs
+ mount: /srv/app
+ - name: lvlog
+ size: 50%FREE
+ fs: ext4
+ mount: /srv/log
+ label: lvlog
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: vg
+ kind: volume-group
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: vgdata spans two physical volumes
+ vg: vgdata
+ minPhysicalVolumes: 2
+ - id: lvapp
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvapp took all the free space (over 2 GiB)
+ vg: vgdata
+ lv: lvapp
+ minSizeMiB: 1900
+ - id: lvlog
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvlog exists
+ vg: vgdata
+ lv: lvlog
+ minSizeMiB: 190
+ - id: numbers
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the data in /srv/app survived the growing
+ paths:
+ - /srv/app/numbers.txt
+ line: '100000'
+ - id: log
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/log is mounted again
+ path: /srv/log
+ fstype: ext4
+ persistent: false
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-lvm-review.yml b/content/programs/rhel9-sysadmin/lab/sa-lvm-review.yml
index cd407e2..abcae91 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-lvm-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-lvm-review.yml
@@ -103,3 +103,32 @@ checkpoints:
paths:
- answers.txt
matches: ^SHRINKABLE_FS=ext4[ \t]*$
+finish:
+- action: wipe-disk
+ hosts:
+ - serverb
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/fstab
+ matching: /dev/vgapp/
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/web
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/db
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - serverb
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-lvm-troubleshooting.yml b/content/programs/rhel9-sysadmin/lab/sa-lvm-troubleshooting.yml
new file mode 100644
index 0000000..92f4e40
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-lvm-troubleshooting.yml
@@ -0,0 +1,144 @@
+name: sa-lvm-troubleshooting
+title: Fix three LVM problems
+page: ch18/exercise-troubleshooting
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: pv1
+ fs: lvm
+ startMiB: 1
+ endMiB: 1537
+ - name: pv2
+ fs: lvm
+ startMiB: 1537
+ endMiB: 3073
+- action: lvm-build
+ hosts:
+ - servera
+ vg: vgdata
+ pvs:
+ - /dev/sdb2
+ volumes:
+ - name: lvstore
+ size: 800M
+ fs: xfs
+ mount: /srv/data
+ - name: lvswap
+ size: 256M
+ fs: swap
+- action: append-line
+ hosts:
+ - servera
+ path: /etc/fstab
+ line: /dev/vgdata/lvstore /srv/data xfs defaults 0 0
+- action: append-line
+ hosts:
+ - servera
+ path: /etc/fstab
+ line: /dev/vgdata/lvswap none swap defaults 0 0
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: lv
+ kind: logical-volume
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvstore grew into the free space (over 1100 MiB)
+ vg: vgdata
+ lv: lvstore
+ minSizeMiB: 1100
+ - id: mount
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/data is XFS and mounted at boot
+ path: /srv/data
+ fstype: xfs
+ persistent: true
+ - id: swap
+ kind: swap
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: lvswap is active again and in fstab
+ minSizeMiB: 200
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/vgdata/
+- action: directory
+ hosts:
+ - servera
+ path: /srv/app
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-manage-files.yml b/content/programs/rhel9-sysadmin/lab/sa-manage-files.yml
new file mode 100644
index 0000000..b0d0124
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-manage-files.yml
@@ -0,0 +1,124 @@
+name: sa-manage-files
+title: Build and tidy a small website
+page: ch03/exercise-manage-files
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/site
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/archive
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: dirs
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: site has the directories images, css and pages
+ paths:
+ - /home/student/site/images
+ - /home/student/site/css
+ - /home/student/site/pages
+ fileType: directory
+ - id: images
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: logo.png and banner.png are in site/images
+ paths:
+ - /home/student/site/images/logo.png
+ fileType: regular
+ - id: banner
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: banner.png is in site/images
+ paths:
+ - /home/student/site/images/banner.png
+ fileType: regular
+ - id: css
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: style.css is in site/css
+ paths:
+ - /home/student/site/css/style.css
+ fileType: regular
+ - id: pages
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: home.html and about.html are in site/pages
+ paths:
+ - /home/student/site/pages/home.html
+ fileType: regular
+ - id: about
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: about.html is in site/pages
+ paths:
+ - /home/student/site/pages/about.html
+ fileType: regular
+ - id: renamed
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the contact page was renamed to contact-us.html
+ paths:
+ - /home/student/site/pages/contact-us.html
+ fileType: regular
+ - id: old-name
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: pages/contact.html no longer exists
+ paths:
+ - /home/student/site/pages/contact.html
+ exists: false
+ - id: backup-gone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the pages-backup directory was removed
+ paths:
+ - /home/student/site/pages-backup
+ exists: false
+ - id: archive-gone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/archive was removed
+ paths:
+ - /home/student/archive
+ exists: false
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/site
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/archive
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-modules.yml b/content/programs/rhel9-sysadmin/lab/sa-modules.yml
new file mode 100644
index 0000000..b364444
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-modules.yml
@@ -0,0 +1,32 @@
+name: sa-modules
+title: Choose a version and check for updates
+page: ch13/exercise-modules
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: dnf-module
+ hosts:
+ - servera
+ name: nginx
+ state: reset
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: no-stream
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: no nginx stream is left enabled
+ paths:
+ - /etc/dnf/modules.d/nginx.module
+ lacks:
+ - state=enabled
+finish:
+- action: dnf-module
+ hosts:
+ - servera
+ name: nginx
+ state: reset
diff --git a/content/programs/rhel9-sysadmin/lab/sa-monitoring.yml b/content/programs/rhel9-sysadmin/lab/sa-monitoring.yml
new file mode 100644
index 0000000..7c357f6
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-monitoring.yml
@@ -0,0 +1,66 @@
+name: sa-monitoring
+title: Find the slowdown and tune the server
+page: ch08/exercise-monitoring
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - tuned
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: tuned
+ kind: package
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: tuned is installed
+ names:
+ - tuned
+ - id: service
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: tuned is running and enabled
+ names:
+ - tuned.service
+ active: true
+ enabled: true
+ - id: profile
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a tuned profile is active
+ paths:
+ - /etc/tuned/active_profile
+ matches: ^[a-z-]+$
+ - id: no-hog
+ kind: process
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the sha256sum hogs were ended
+ pattern: ^sha256sum /dev/zero
+ running: false
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - tuned
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - tuned
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-moving.yml b/content/programs/rhel9-sysadmin/lab/sa-moving.yml
new file mode 100644
index 0000000..8cf31ea
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-moving.yml
@@ -0,0 +1,103 @@
+name: sa-moving
+title: Move a tree between servers
+page: ch14/exercise-moving
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /tmp/arch
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /tmp/recv
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /tmp/arch/work/docs
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /tmp/arch/work/docs/a.txt
+ state: present
+ content: 'one
+
+ '
+- action: file
+ hosts:
+ - servera
+ path: /tmp/arch/work/docs/c.txt
+ state: present
+ content: 'three
+
+ '
+- action: file
+ hosts:
+ - servera
+ path: /tmp/arch/work/docs/d.txt
+ state: present
+ content: 'four
+
+ '
+- action: home-remove
+ paths:
+ - xfer
+ - sums.txt
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: received
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: the tree arrived on serverb
+ paths:
+ - /tmp/recv/work/docs/a.txt
+ line: one
+ - id: damaged
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: c.txt on serverb was damaged on purpose
+ paths:
+ - /tmp/recv/work/docs/c.txt
+ contains:
+ - oops
+ - id: sums
+ kind: file
+ 'on': control
+ message: the checksum list is on workstation
+ paths:
+ - /home/student/sums.txt
+ contains:
+ - docs/a.txt
+ - id: pulled
+ kind: file
+ 'on': control
+ message: the rsync copy is in ~/xfer/work
+ paths:
+ - /home/student/xfer/work/docs/d.txt
+ line: four
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /tmp/arch
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /tmp/recv
+ state: absent
+- action: home-remove
+ paths:
+ - xfer
+ - sums.txt
diff --git a/content/programs/rhel9-sysadmin/lab/sa-names.yml b/content/programs/rhel9-sysadmin/lab/sa-names.yml
new file mode 100644
index 0000000..6c88dd4
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-names.yml
@@ -0,0 +1,67 @@
+name: sa-names
+title: Names, hosts and DNS
+page: ch12/exercise-names
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/hosts
+ matching: ^10[.]1[.]1[.]1 serverb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/hosts
+ matching: ' app[.]lab[.]example[.]com app'
+- action: hostname
+ hosts:
+ - servera
+ name: servera.lab.example.com
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: hostname
+ kind: hostname
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the host name is servera again
+ short: servera
+ - id: no-wrong
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /etc/hosts has no wrong entry for serverb any more
+ paths:
+ - /etc/hosts
+ lacks:
+ - 10.1.1.1 serverb
+ - id: no-app
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the temporary entry for app was removed
+ paths:
+ - /etc/hosts
+ lacks:
+ - app.lab.example.com app
+finish:
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/hosts
+ matching: ^10[.]1[.]1[.]1 serverb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/hosts
+ matching: ' app[.]lab[.]example[.]com app'
+- action: hostname
+ hosts:
+ - servera
+ name: servera.lab.example.com
diff --git a/content/programs/rhel9-sysadmin/lab/sa-navigate.yml b/content/programs/rhel9-sysadmin/lab/sa-navigate.yml
new file mode 100644
index 0000000..e297429
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-navigate.yml
@@ -0,0 +1,52 @@
+name: sa-navigate
+title: Log in and move around the file system
+page: ch03/exercise-navigate
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/plan.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/team notes.txt
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: plan
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/plan.txt exists and belongs to student
+ paths:
+ - /home/student/plan.txt
+ fileType: regular
+ owner: student
+ - id: notes
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the file with a space in its name exists (the quotes keep it one name)
+ paths:
+ - /home/student/team notes.txt
+ fileType: regular
+ owner: student
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/plan.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/team notes.txt
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-network-review.yml b/content/programs/rhel9-sysadmin/lab/sa-network-review.yml
index 93cd5a7..fbfe484 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-network-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-network-review.yml
@@ -76,3 +76,14 @@ checkpoints:
paths:
- answers.txt
matches: ^NAME_ORDER=files dns[ \t]*$
+finish:
+- action: nm-connection
+ hosts:
+ - servera
+ name: dummy1
+ state: absent
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/hosts
+ matching: appnet
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nfs-maps.yml b/content/programs/rhel9-sysadmin/lab/sa-nfs-maps.yml
new file mode 100644
index 0000000..3064e55
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-nfs-maps.yml
@@ -0,0 +1,384 @@
+name: sa-nfs-maps
+title: Serve home directories on demand
+page: ch19/exercise-maps
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
+- action: package
+ hosts:
+ - serverb
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: present
+- action: file
+ hosts:
+ - serverb
+ path: /srv/shared/hello.txt
+ state: present
+ content: 'shared file
+
+ '
+- action: append-line
+ hosts:
+ - serverb
+ path: /etc/exports
+ line: /srv/shared 172.25.0.0/16(rw,sync)
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: restarted
+ enabled: true
+- action: firewall
+ hosts:
+ - serverb
+ state: present
+ service: nfs
+- action: package
+ hosts:
+ - servera
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: present
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: present
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: started
+ enabled: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: homes-master
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a master map serves /remote/homes
+ paths:
+ - /etc/auto.master.d/homes.autofs
+ matches: ^/remote/homes[[:space:]]+/etc/auto\.homes
+ - id: wildcard
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the map is a wildcard map
+ paths:
+ - /etc/auto.homes
+ matches: ^\*[[:space:]]+-rw,sync[[:space:]]+serverb:/srv/homes/&
+ - id: direct
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a direct map mounts /mnt/direct/data
+ paths:
+ - /etc/auto.direct
+ matches: ^/mnt/direct/data[[:space:]]+-rw[[:space:]]+serverb:/srv/shared
+ - id: exports
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb also exports /srv/homes
+ paths:
+ - /etc/exports
+ contains:
+ - /srv/homes
+ - id: edited
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: alice edited her home from servera
+ paths:
+ - /srv/homes/alice/readme
+ owner: alice
+ line: edited from servera
+ - id: autofs
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: autofs is running
+ names:
+ - autofs.service
+ active: true
+finish:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nfs-permissions.yml b/content/programs/rhel9-sysadmin/lab/sa-nfs-permissions.yml
new file mode 100644
index 0000000..865856e
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-nfs-permissions.yml
@@ -0,0 +1,384 @@
+name: sa-nfs-permissions
+title: Get the permissions right
+page: ch19/exercise-permissions
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
+- action: package
+ hosts:
+ - serverb
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: present
+- action: file
+ hosts:
+ - serverb
+ path: /srv/shared/hello.txt
+ state: present
+ content: 'shared file
+
+ '
+- action: append-line
+ hosts:
+ - serverb
+ path: /etc/exports
+ line: /srv/shared 172.25.0.0/16(rw,sync)
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: restarted
+ enabled: true
+- action: firewall
+ hosts:
+ - serverb
+ state: present
+ service: nfs
+- action: package
+ hosts:
+ - servera
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: present
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: student
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: student wrote st.txt over NFS (UID 1000 on both sides)
+ paths:
+ - /srv/shared/st.txt
+ owner: student
+ line: from servera as student
+ - id: alice
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: alice wrote from servera, stored with her UID
+ paths:
+ - /srv/shared/alice/from-servera.txt
+ owner: alice
+ line: written from servera
+ - id: alice-a
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: alice has UID 2001 on the client
+ names:
+ - alice
+ uid: 2001
+ - id: alice-b
+ kind: user
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: alice has UID 2001 on the server
+ names:
+ - alice
+ uid: 2001
+ - id: team
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: /srv/shared/team is group team with setgid
+ paths:
+ - /srv/shared/team
+ fileType: directory
+ group: team
+ mode: '2775'
+ - id: by-alice
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: alice created a file in the team directory
+ paths:
+ - /srv/shared/team/by-alice.txt
+ owner: alice
+ group: team
+ - id: by-student
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: student created a file in the team directory
+ paths:
+ - /srv/shared/team/by-student.txt
+ owner: student
+ group: team
+finish:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nfs-persistent.yml b/content/programs/rhel9-sysadmin/lab/sa-nfs-persistent.yml
new file mode 100644
index 0000000..eeaf6f6
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-nfs-persistent.yml
@@ -0,0 +1,355 @@
+name: sa-nfs-persistent
+title: Mount at boot, then on demand
+page: ch19/exercise-persistent
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
+- action: package
+ hosts:
+ - serverb
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: present
+- action: file
+ hosts:
+ - serverb
+ path: /srv/shared/hello.txt
+ state: present
+ content: 'shared file
+
+ '
+- action: append-line
+ hosts:
+ - serverb
+ path: /etc/exports
+ line: /srv/shared 172.25.0.0/16(rw,sync)
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: restarted
+ enabled: true
+- action: firewall
+ hosts:
+ - serverb
+ state: present
+ service: nfs
+- action: package
+ hosts:
+ - servera
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: present
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: master
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: autofs has a master map for /mnt/auto
+ paths:
+ - /etc/auto.master.d/shared.autofs
+ matches: ^/mnt/auto[[:space:]]+/etc/auto\.shared
+ - id: map
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the map holds the data key and no broken line
+ paths:
+ - /etc/auto.shared
+ matches: ^data[[:space:]]+-rw,sync[[:space:]]+serverb:/srv/shared
+ lacks:
+ - broken
+ - id: autofs
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: autofs is running and enabled
+ names:
+ - autofs.service
+ active: true
+ enabled: true
+ - id: fstab
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the permanent NFS line was removed from fstab again
+ paths:
+ - /etc/fstab
+ lacks:
+ - serverb:/srv/shared
+finish:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nfs-review.yml b/content/programs/rhel9-sysadmin/lab/sa-nfs-review.yml
index 3c671c5..3385c61 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-nfs-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-nfs-review.yml
@@ -135,3 +135,77 @@ checkpoints:
paths:
- answers.txt
matches: ^FIREWALL_SERVICE=nfs[[:space:]]*$
+finish:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/projects
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/projects
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/projects
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/archive.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.archive
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/projects
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/archive
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nfs-share.yml b/content/programs/rhel9-sysadmin/lab/sa-nfs-share.yml
new file mode 100644
index 0000000..57d538c
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-nfs-share.yml
@@ -0,0 +1,337 @@
+name: sa-nfs-share
+title: Export and mount a directory
+page: ch19/exercise-share
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
+- action: package
+ hosts:
+ - serverb
+ names:
+ - nfs-utils
+ state: present
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: present
+- action: file
+ hosts:
+ - serverb
+ path: /srv/shared/hello.txt
+ state: present
+ content: 'shared file
+
+ '
+- action: package
+ hosts:
+ - servera
+ names:
+ - nfs-utils
+ state: present
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: exports
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: /etc/exports shares /srv/shared read-write with the lab network
+ paths:
+ - /etc/exports
+ matches: ^/srv/shared[[:space:]]+[^[:space:]]+\(rw,sync\)[[:space:]]*$
+ - id: server
+ kind: service
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: nfs-server is running and enabled
+ names:
+ - nfs-server.service
+ active: true
+ enabled: true
+ - id: firewall
+ kind: firewall
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: the nfs firewall service is open permanently
+ service: nfs
+ - id: mountpoint
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /mnt/shared exists on the client
+ paths:
+ - /mnt/shared
+ fileType: directory
+ - id: no-rootfile
+ kind: file
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: root could not create a file (root squash)
+ paths:
+ - /srv/shared/rootfile
+ exists: false
+finish:
+- action: unmount
+ hosts:
+ - servera
+ path: /mnt/shared
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: serverb:/srv/shared
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/shared
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/auto
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /remote
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /mnt/direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/shared.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/homes.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.master.d/direct.autofs
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.shared
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.homes
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/auto.direct
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - autofs
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/exports
+ matching: /srv/
+- action: service
+ hosts:
+ - serverb
+ names:
+ - nfs-server
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ service: nfs
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/shared
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/homes
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: alice
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: team
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-nmcli.yml b/content/programs/rhel9-sysadmin/lab/sa-nmcli.yml
new file mode 100644
index 0000000..536e06c
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-nmcli.yml
@@ -0,0 +1,40 @@
+name: sa-nmcli
+title: Configure a static connection
+page: ch12/exercise-nmcli
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: nm-connection
+ hosts:
+ - servera
+ name: dummy0
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: profile
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the dummy0 profile was deleted at the end
+ paths:
+ - /etc/NetworkManager/system-connections/dummy0.nmconnection
+ exists: false
+ - id: device
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the dummy0 interface is gone
+ paths:
+ - /sys/class/net/dummy0
+ exists: false
+finish:
+- action: nm-connection
+ hosts:
+ - servera
+ name: dummy0
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-partitions.yml b/content/programs/rhel9-sysadmin/lab/sa-partitions.yml
new file mode 100644
index 0000000..dc52c23
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-partitions.yml
@@ -0,0 +1,58 @@
+name: sa-partitions
+title: Partition a new disk
+page: ch17/exercise-partitions
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: table
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /dev/sdb1 is on a GPT disk
+ device: /dev/sdb1
+ tableType: gpt
+ - id: p1
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: partition 1 is named data and is about 1 GiB
+ device: /dev/sdb1
+ partLabel: data
+ minSizeMiB: 1000
+ maxSizeMiB: 1030
+ - id: p2
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: partition 2 is named logs and is about 1 GiB
+ device: /dev/sdb2
+ partLabel: logs
+ minSizeMiB: 1000
+ maxSizeMiB: 1030
+ - id: p3
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: partition 3 is named swap and is about 512 MiB
+ device: /dev/sdb3
+ partLabel: swap
+ minSizeMiB: 500
+ maxSizeMiB: 520
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
diff --git a/content/programs/rhel9-sysadmin/lab/sa-passwords.yml b/content/programs/rhel9-sysadmin/lab/sa-passwords.yml
new file mode 100644
index 0000000..2e9e85f
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-passwords.yml
@@ -0,0 +1,42 @@
+name: sa-passwords
+title: Onboard and offboard an intern
+page: ch06/exercise-passwords
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: intern
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: intern
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: intern exists with the aging policy 90/1/7
+ names:
+ - intern
+ maxDays: 90
+ minDays: 1
+ warnDays: 7
+ - id: expired
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the account is expired
+ names:
+ - intern
+ expires: '1970-01-01'
+finish:
+- action: user
+ hosts:
+ - servera
+ name: intern
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-perms-review.yml b/content/programs/rhel9-sysadmin/lab/sa-perms-review.yml
index d8cb5dc..bd24ff1 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-perms-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-perms-review.yml
@@ -108,3 +108,34 @@ checkpoints:
acl:
- user:visitor:--x
- default:user:visitor:r--
+finish:
+- action: user
+ hosts:
+ - servera
+ name: dana
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: evan
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: visitor
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: analysts
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/analysis
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/dropbox
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-persistence.yml b/content/programs/rhel9-sysadmin/lab/sa-persistence.yml
new file mode 100644
index 0000000..b85fedf
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-persistence.yml
@@ -0,0 +1,138 @@
+name: sa-persistence
+title: Keep and rotate your logs
+page: ch11/exercise-persistence
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: file
+ hosts:
+ - servera
+ path: /etc/rsyslog.d/10-myapp.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/logrotate.d/myapp
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/myapp.log
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/myapp.log.1.gz
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-retention.conf
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - rsyslog
+ state: restarted
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: journal-dir
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the journal directory exists
+ paths:
+ - /var/log/journal
+ fileType: directory
+ - id: journal-conf
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: journald is persistent and limited to 200M
+ paths:
+ - /etc/systemd/journald.conf.d/10-retention.conf
+ contains:
+ - Storage=persistent
+ - SystemMaxUse=200M
+ - id: rsyslog
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: rsyslog sends facility local5 to /var/log/myapp.log
+ paths:
+ - /etc/rsyslog.d/10-myapp.conf
+ matches: ^local5\.\*[[:space:]]+/var/log/myapp\.log[[:space:]]*$
+ - id: rotate
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: logrotate rotates weekly, keeps 4, compresses
+ paths:
+ - /etc/logrotate.d/myapp
+ contains:
+ - weekly
+ - rotate 4
+ - compress
+ - missingok
+ - notifempty
+ - create 0640 root root
+ - id: rotated
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the first rotation produced myapp.log.1.gz
+ paths:
+ - /var/log/myapp.log.1.gz
+ fileType: regular
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /etc/rsyslog.d/10-myapp.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/logrotate.d/myapp
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/myapp.log
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/myapp.log.1.gz
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-retention.conf
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - rsyslog
+ state: restarted
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-portal-faults.yml b/content/programs/rhel9-sysadmin/lab/sa-portal-faults.yml
new file mode 100644
index 0000000..df61cd7
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-portal-faults.yml
@@ -0,0 +1,384 @@
+name: sa-portal-faults
+title: Find six faults
+page: ch22/exercise-faults
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: present
+ gid: 4800
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: present
+ uid: 3001
+ group: portal
+ shell: /sbin/nologin
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: present
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: present
+ type: httpd_sys_content_t
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: present
+ owner: portaladm
+ group: portal
+ mode: '2750'
+- action: file
+ hosts:
+ - servera
+ path: /srv/portal/index.html
+ state: present
+ content: 'Portal status: OK
+
+ '
+ owner: portaladm
+ group: portal
+ mode: '640'
+- action: acl
+ hosts:
+ - servera
+ path: /srv/portal
+ entries:
+ - u:apache:rX
+ - d:u:apache:rX
+- action: acl
+ hosts:
+ - servera
+ path: /srv/portal/index.html
+ entries:
+ - u:apache:r
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: present
+ content: "Listen 8090\n\n DocumentRoot /srv/portal\n \n Require all granted\n \n\n"
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ deleteZone: true
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ source: '@serverb'
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ port: 8090/tcp
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: svc
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: httpd is running
+ names:
+ - httpd.service
+ active: true
+ - id: label
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the page carries the web content type again
+ paths:
+ - /srv/portal/index.html
+ selinuxType: httpd_sys_content_t
+ - id: acl
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the ACL for apache is back
+ paths:
+ - /srv/portal/index.html
+ acl:
+ - user:apache:r--
+ - id: port
+ kind: selinux-port
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: TCP port 8090 is labelled http_port_t again
+ type: http_port_t
+ proto: tcp
+ port: 8090
+ - id: zone
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the zone portal allows 8090/tcp again
+ zone: portal
+ port: 8090/tcp
+ - id: content
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the page says DOWN (fault 6, repaired in the next exercise)
+ paths:
+ - /srv/portal/index.html
+ line: 'Portal status: DOWN'
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-portal-recovery.yml b/content/programs/rhel9-sysadmin/lab/sa-portal-recovery.yml
new file mode 100644
index 0000000..1ea5c1e
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-portal-recovery.yml
@@ -0,0 +1,439 @@
+name: sa-portal-recovery
+title: Restore and hand over
+page: ch22/exercise-recovery
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: present
+ gid: 4800
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: present
+ uid: 3001
+ group: portal
+ shell: /sbin/nologin
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: present
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: present
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: present
+ type: httpd_sys_content_t
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: present
+ owner: portaladm
+ group: portal
+ mode: '2750'
+- action: file
+ hosts:
+ - servera
+ path: /srv/portal/index.html
+ state: present
+ content: 'Portal status: OK
+
+ '
+ owner: portaladm
+ group: portal
+ mode: '640'
+- action: acl
+ hosts:
+ - servera
+ path: /srv/portal
+ entries:
+ - u:apache:rX
+ - d:u:apache:rX
+- action: acl
+ hosts:
+ - servera
+ path: /srv/portal/index.html
+ entries:
+ - u:apache:r
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: present
+ content: "Listen 8090\n\n DocumentRoot /srv/portal\n \n Require all granted\n \n\n"
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: started
+ enabled: true
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ deleteZone: true
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ source: '@serverb'
+- action: firewall
+ hosts:
+ - servera
+ state: present
+ zone: portal
+ port: 8090/tcp
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: present
+ mode: '750'
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: present
+ content: '[Unit]
+
+ Description=Back up the portal content
+
+
+ [Service]
+
+ Type=oneshot
+
+ ExecStart=/usr/bin/tar --acls --selinux -czf /srv/backups/portal.tar.gz -C /srv portal
+
+ '
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: present
+ content: '[Unit]
+
+ Description=Daily portal backup
+
+
+ [Timer]
+
+ OnCalendar=daily
+
+ Persistent=true
+
+
+ [Install]
+
+ WantedBy=timers.target
+
+ '
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: started
+ enabled: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.service
+ state: started
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: page
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the live page holds the restored content
+ paths:
+ - /srv/portal/index.html
+ line: 'Portal status: OK'
+ owner: portaladm
+ acl:
+ - user:apache:r--
+ selinuxType: httpd_sys_content_t
+ - id: stage
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the staging copy keeps its ACL
+ paths:
+ - /root/stage/portal/index.html
+ acl:
+ - user:apache:r--
+ - id: handover
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the handover note has its sections
+ paths:
+ - /root/portal-handover.txt
+ contains:
+ - Purpose
+ - Changes
+ - Operate
+ - Recover
+ - Known failures
+ - Limits
+ - id: remote
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb gets the restored page
+ url: http://servera.lab.example.com:8090/
+ containsAny:
+ - 'Portal status: OK'
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-portal.yml b/content/programs/rhel9-sysadmin/lab/sa-portal.yml
new file mode 100644
index 0000000..de8db84
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-portal.yml
@@ -0,0 +1,354 @@
+name: sa-portal
+title: Deliver the portal
+page: ch22/exercise-portal
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: user
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: portaladm (UID 3001) is a nologin account in the group portal
+ names:
+ - portaladm
+ uid: 3001
+ primaryGroup: portal
+ shell: /sbin/nologin
+ - id: dir
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/portal belongs to portaladm:portal with mode 2750
+ paths:
+ - /srv/portal
+ fileType: directory
+ owner: portaladm
+ group: portal
+ mode: '2750'
+ - id: acl
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: apache may read the page through an ACL
+ paths:
+ - /srv/portal/index.html
+ acl:
+ - user:apache:r--
+ selinuxType: httpd_sys_content_t
+ - id: fcontext
+ kind: selinux-fcontext
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a stored mapping labels /srv/portal for the web server
+ path: /srv/portal(/.*)?
+ type: httpd_sys_content_t
+ - id: port
+ kind: selinux-port
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: TCP port 8090 is labelled http_port_t
+ type: http_port_t
+ proto: tcp
+ port: 8090
+ - id: enforcing
+ kind: selinux
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: SELinux is enforcing
+ mode: enforcing
+ - id: zone
+ kind: firewall
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the zone portal allows 8090/tcp
+ zone: portal
+ port: 8090/tcp
+ - id: zone-file
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the zone portal has a source address
+ paths:
+ - /etc/firewalld/zones/portal.xml
+ matches:
+ - id: journal
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the journal is persistent
+ paths:
+ - /var/log/journal
+ fileType: directory
+ - id: timer
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: portal-backup.timer is enabled and running
+ names:
+ - portal-backup.timer
+ active: true
+ enabled: true
+ - id: archive
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the backup holds the portal page
+ path: /srv/backups/portal.tar.gz
+ format: tar.gz
+ members:
+ - portal/index.html
+ - id: local
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the portal answers on servera
+ url: http://localhost:8090/
+ containsAny:
+ - 'Portal status: OK'
+ - id: remote
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: serverb reaches the portal
+ url: http://servera.lab.example.com:8090/
+ containsAny:
+ - 'Portal status: OK'
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - portal-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: portal
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/stage
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /root/bad
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/portal-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/portal-handover.txt
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: portaladm
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: portal
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-proc-review.yml b/content/programs/rhel9-sysadmin/lab/sa-proc-review.yml
index 1b78304..69ddd60 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-proc-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-proc-review.yml
@@ -102,3 +102,22 @@ checkpoints:
paths:
- answers.txt
matches: ^cont=18[ \t]*$
+finish:
+- action: user
+ hosts:
+ - servera
+ name: worker
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - tuned
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - tuned
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-reach.yml b/content/programs/rhel9-sysadmin/lab/sa-reach.yml
new file mode 100644
index 0000000..9b254d9
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-reach.yml
@@ -0,0 +1,85 @@
+name: sa-reach
+title: The service nobody can reach
+page: ch12/exercise-troubleshooting
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/web
+ state: present
+- action: file
+ hosts:
+ - serverb
+ path: /srv/web/index.html
+ state: present
+ content: 'hello from serverb
+
+ '
+- action: http-server
+ hosts:
+ - serverb
+ port: 8080
+ bind: 127.0.0.1
+ directory: /srv/web
+ state: started
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ port: 8080/tcp
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: listen
+ kind: http
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: the web server on serverb answers locally on 8080
+ url: http://localhost:8080/
+ containsAny:
+ - hello from serverb
+ - id: firewall
+ kind: firewall
+ 'on': serverb.lab.example.com
+ targets:
+ - serverb.lab.example.com
+ message: 8080/tcp is allowed in the running firewall
+ port: 8080/tcp
+ permanent: false
+ - id: remote
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: servera reaches serverb on port 8080
+ url: http://serverb.lab.example.com:8080/
+ containsAny:
+ - hello from serverb
+finish:
+- action: http-server
+ hosts:
+ - serverb
+ port: 8080
+ bind: 127.0.0.1
+ directory: /srv/web
+ state: stopped
+- action: firewall
+ hosts:
+ - serverb
+ state: absent
+ port: 8080/tcp
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/web
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /tmp/http.log
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-reading.yml b/content/programs/rhel9-sysadmin/lab/sa-reading.yml
new file mode 100644
index 0000000..7fafcf0
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-reading.yml
@@ -0,0 +1,118 @@
+name: sa-reading
+title: Who can read this?
+page: ch07/exercise-reading
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: group
+ hosts:
+ - servera
+ name: project
+ state: present
+ gid: 4500
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: present
+ groups:
+ - project
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: present
+ groups:
+ - project
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/lab7
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /srv/lab7/report.txt
+ state: present
+ content: 'Q3 figures
+
+ '
+ owner: maria
+ group: project
+ mode: '640'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: report
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: report.txt is locked down to mode 070
+ paths:
+ - /srv/lab7/report.txt
+ owner: maria
+ group: project
+ mode: '0070'
+ - id: box
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: box is owned by maria with mode 555
+ paths:
+ - /srv/lab7/box
+ fileType: directory
+ owner: maria
+ mode: '0555'
+ - id: one
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: maria deleted box/one
+ paths:
+ - /srv/lab7/box/one
+ exists: false
+ - id: two
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: box/two is still there
+ paths:
+ - /srv/lab7/box/two
+ fileType: regular
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /srv/lab7
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: project
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-redirect.yml b/content/programs/rhel9-sysadmin/lab/sa-redirect.yml
new file mode 100644
index 0000000..883e6ef
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-redirect.yml
@@ -0,0 +1,92 @@
+name: sa-redirect
+title: Redirection and pipes
+page: ch05/exercise-redirection
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/conf-files.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/found.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/errors.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/disk-report.txt
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: list
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: conf-files.txt holds the list and the footer line
+ paths:
+ - /home/student/conf-files.txt
+ matches: ^/etc/chrony\.conf$
+ contains:
+ - '--- listed on'
+ - id: found
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: found.txt holds the results of find
+ paths:
+ - /home/student/found.txt
+ matches: ^/etc/.*\.conf$
+ - id: errors
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: errors.txt holds the permission errors
+ paths:
+ - /home/student/errors.txt
+ contains:
+ - Permission denied
+ - id: report
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: disk-report.txt was saved with tee
+ paths:
+ - /home/student/disk-report.txt
+ contains:
+ - Filesystem
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/conf-files.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/found.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/errors.txt
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/disk-report.txt
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-review-final.yml b/content/programs/rhel9-sysadmin/lab/sa-review-final.yml
index a06172b..fd113de 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-review-final.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-review-final.yml
@@ -234,3 +234,104 @@ checkpoints:
paths:
- answers.txt
matches: ^TAR_LABEL_OPTION=--selinux[ \t]*$
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - status-backup.timer
+ state: stopped
+ enabled: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: firewall
+ hosts:
+ - servera
+ state: absent
+ zone: statuspartners
+ deleteZone: true
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8095
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/status(/.*)?
+- action: directory
+ hosts:
+ - servera
+ path: /srv/status
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/backups
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/status.conf
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/status-backup.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/status-backup.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/journald.conf.d/10-persistent.conf
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: user
+ hosts:
+ - servera
+ name: statusadm
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: auditor
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /var/log/journal
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - systemd-journald
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-rpm.yml b/content/programs/rhel9-sysadmin/lab/sa-rpm.yml
new file mode 100644
index 0000000..af90b4a
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-rpm.yml
@@ -0,0 +1,33 @@
+name: sa-rpm
+title: Interrogate the installed software
+page: ch13/exercise-rpm
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - tree
+ state: reinstalled
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: tree
+ kind: package
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: tree is installed and every file matches the RPM database again
+ names:
+ - tree
+ verify: true
+finish:
+- action: package
+ hosts:
+ - servera
+ names:
+ - tree
+ state: reinstalled
diff --git a/content/programs/rhel9-sysadmin/lab/sa-scripts-review.yml b/content/programs/rhel9-sysadmin/lab/sa-scripts-review.yml
index ca4f2c6..da66b1a 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-scripts-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-scripts-review.yml
@@ -120,3 +120,40 @@ checkpoints:
- sa-report.timer
active: true
enabled: true
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/sa-report.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/cron.d/sa-report
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/sa-report.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/sa-report.timer
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - sa-report.timer
+ state: stopped
+ enabled: false
+- action: crontab
+ hosts:
+ - servera
+ user: student
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-selinux-review.yml b/content/programs/rhel9-sysadmin/lab/sa-selinux-review.yml
index 3a1a8a0..37ed8e2 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-selinux-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-selinux-review.yml
@@ -101,3 +101,55 @@ checkpoints:
paths:
- answers.txt
matches: ^AUDIT_LOG=/var/log/audit/audit\.log[ \t]*$
+finish:
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/portal(/.*)?
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8090
+- action: selinux
+ hosts:
+ - servera
+ kind: boolean
+ state: present
+ name: httpd_can_network_connect
+ value: false
+- action: selinux
+ hosts:
+ - servera
+ kind: boolean
+ state: present
+ name: httpd_enable_homedirs
+ value: false
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: directory
+ hosts:
+ - servera
+ path: /srv/portal
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/portal.conf
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-services.yml b/content/programs/rhel9-sysadmin/lab/sa-services.yml
new file mode 100644
index 0000000..e8e7567
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-services.yml
@@ -0,0 +1,57 @@
+name: sa-services
+title: Control a service
+page: ch09/exercise-services
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - crond
+ state: started
+ enabled: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - bluetooth
+ masked: false
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: crond
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: crond is running and enabled again
+ names:
+ - crond.service
+ active: true
+ enabled: true
+ - id: bluetooth
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: bluetooth is not masked any more
+ names:
+ - bluetooth.service
+ masked: false
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - crond
+ state: started
+ enabled: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - bluetooth
+ masked: false
diff --git a/content/programs/rhel9-sysadmin/lab/sa-shared.yml b/content/programs/rhel9-sysadmin/lab/sa-shared.yml
new file mode 100644
index 0000000..27f6177
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-shared.yml
@@ -0,0 +1,140 @@
+name: sa-shared
+title: Build a team directory and a drop box
+page: ch07/exercise-shared
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: present
+ gid: 4700
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: present
+ groups:
+ - team
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: present
+ groups:
+ - team
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/team
+ state: present
+ owner: root
+ group: team
+ mode: '2770'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: team
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/team is root:team with setgid (2770)
+ paths:
+ - /srv/team
+ fileType: directory
+ owner: root
+ group: team
+ mode: '2770'
+ - id: plan
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: plan.txt is group-writable and holds both lines
+ paths:
+ - /srv/team/plan.txt
+ owner: maria
+ group: team
+ mode: '0660'
+ lines:
+ - plan
+ - john was here
+ - id: docs
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: docs inherited group and setgid
+ paths:
+ - /srv/team/docs
+ fileType: directory
+ group: team
+ mode: '2770'
+ - id: dropbox
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/dropbox exists without the sticky bit now (777)
+ paths:
+ - /srv/dropbox
+ fileType: directory
+ mode: '0777'
+ - id: priya
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: priya.txt is in the drop box
+ paths:
+ - /srv/dropbox/priya.txt
+ owner: priya
+ - id: maria-gone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: maria.txt was deleted once the sticky bit was off
+ paths:
+ - /srv/dropbox/maria.txt
+ exists: false
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /srv/team
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/dropbox
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: maria
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: john
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: priya
+ state: absent
+- action: group
+ hosts:
+ - servera
+ name: team
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-signals.yml b/content/programs/rhel9-sysadmin/lab/sa-signals.yml
new file mode 100644
index 0000000..b2410ec
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-signals.yml
@@ -0,0 +1,100 @@
+name: sa-signals
+title: Stop a runaway process the right way
+page: ch08/exercise-signals
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/work.lock
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/cleanup.sh
+ state: present
+ content: '#!/bin/bash
+
+ trap ''echo "cleaning up"; rm -f /tmp/work.lock; exit 0'' TERM
+
+ touch /tmp/work.lock
+
+ while true; do sleep 1; done
+
+ '
+ owner: student
+ group: student
+ mode: '755'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: script
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: ~/cleanup.sh is an executable script with a TERM trap
+ paths:
+ - /home/student/cleanup.sh
+ mode: '0755'
+ owner: student
+ contains:
+ - trap
+ - id: lock
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the lock file was removed (the polite kill ran the trap, and you removed the rest)
+ paths:
+ - /tmp/work.lock
+ exists: false
+ - id: no-hog
+ kind: process
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: no sha256sum hog is left running
+ pattern: ^sha256sum /dev/zero
+ running: false
+ - id: no-bob
+ kind: process
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: no sleep of user bob is left
+ pattern: ^sleep 90[01]$
+ user: bob
+ running: false
+ - id: bob
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: bob exists (the exercise removes him in the clean-up step)
+ names:
+ - bob
+finish:
+- action: user
+ hosts:
+ - servera
+ name: bob
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/cleanup.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /tmp/work.lock
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-software-review.yml b/content/programs/rhel9-sysadmin/lab/sa-software-review.yml
index 2b86499..0af1a22 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-software-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-software-review.yml
@@ -91,3 +91,33 @@ checkpoints:
paths:
- answers.txt
matches: ^REPO_DIR=/etc/yum\.repos\.d/?[ \t]*$
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /etc/yum.repos.d/local.repo
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/localrepo
+ state: absent
+- action: package
+ hosts:
+ - servera
+ names:
+ - zip
+ - unzip
+ - wget
+ state: absent
+- action: dnf-module
+ hosts:
+ - servera
+ name: nginx
+ state: reset
+- action: package
+ hosts:
+ - servera
+ names:
+ - tree
+ state: reinstalled
diff --git a/content/programs/rhel9-sysadmin/lab/sa-space.yml b/content/programs/rhel9-sysadmin/lab/sa-space.yml
new file mode 100644
index 0000000..8383d48
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-space.yml
@@ -0,0 +1,159 @@
+name: sa-space
+title: Hunt the missing space
+page: ch17/exercise-space
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: partition-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+ partitions:
+ - name: data
+ fs: xfs
+ startMiB: 1
+ endMiB: 1025
+ - name: logs
+ fs: ext4
+ startMiB: 1025
+ endMiB: 2049
+ - name: swap
+ fs: linux-swap
+ startMiB: 2049
+ endMiB: 2561
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb1
+ type: xfs
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb2
+ type: ext4
+ label: logs
+- action: format
+ hosts:
+ - servera
+ device: /dev/sdb3
+ type: swap
+- action: append-line
+ hosts:
+ - servera
+ path: /etc/fstab
+ line: /dev/sdb1 /srv/data xfs defaults 0 0
+- action: append-line
+ hosts:
+ - servera
+ path: /etc/fstab
+ line: /dev/sdb2 /srv/archive ext4 defaults,noexec 0 0
+- action: append-line
+ hosts:
+ - servera
+ path: /etc/fstab
+ line: /dev/sdb3 none swap defaults 0 0
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: present
+- action: directory
+ hosts:
+ - servera
+ path: /srv/archive
+ state: present
+- action: mount-all
+ hosts:
+ - servera
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: data
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/data is mounted from /dev/sdb1
+ path: /srv/data
+ fstype: xfs
+ persistent: true
+ - id: label
+ kind: partition
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the xfs file system carries the label datavol
+ device: /dev/sdb1
+ fsLabel: datavol
+ - id: archive
+ kind: mount
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: /srv/archive is mounted
+ path: /srv/archive
+ fstype: ext4
+ persistent: true
+ - id: many
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the directory with 65,600 files was removed again
+ paths:
+ - /srv/archive/many
+ exists: false
+ - id: big
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the big log file is gone
+ paths:
+ - /srv/data/log.big
+ exists: false
+finish:
+- action: wipe-disk
+ hosts:
+ - servera
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/fstab
+ matching: /dev/sdb[123]|^UUID=[0-9a-f-]+[[:space:]]+(/srv/data|/srv/archive|none[[:space:]]+swap)
+- action: directory
+ hosts:
+ - servera
+ path: /data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /logs
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/data
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/archive
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-ssh-review.yml b/content/programs/rhel9-sysadmin/lab/sa-ssh-review.yml
index 287d3d6..e947847 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-ssh-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-ssh-review.yml
@@ -119,3 +119,27 @@ checkpoints:
paths:
- answers.txt
matches: ^HOSTKEY_TYPE=ED25519[ \t]*$
+finish:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/ssh/sshd_config.d/10-hardening.conf
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - sshd
+ state: restarted
+- action: home-remove
+ paths:
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+- action: ssh-config-block
+ host: opsa
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-storage-review.yml b/content/programs/rhel9-sysadmin/lab/sa-storage-review.yml
index 5a88ae9..0353e9d 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-storage-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-storage-review.yml
@@ -92,3 +92,32 @@ checkpoints:
paths:
- answers.txt
matches: ^INODE_COMMAND=df -i[ \t]*$
+finish:
+- action: wipe-disk
+ hosts:
+ - serverb
+ device: /dev/sdb
+- action: remove-lines
+ hosts:
+ - serverb
+ path: /etc/fstab
+ matching: /srv/projects|/srv/backups|none[[:space:]]+swap
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/projects
+ state: absent
+- action: directory
+ hosts:
+ - serverb
+ path: /srv/backups
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /root/fstab.bak
+ state: absent
+- action: systemd
+ hosts:
+ - serverb
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-sudo.yml b/content/programs/rhel9-sysadmin/lab/sa-sudo.yml
new file mode 100644
index 0000000..02dcff6
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-sudo.yml
@@ -0,0 +1,52 @@
+name: sa-sudo
+title: Grant limited root access
+page: ch06/exercise-superuser
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: tester
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/sudoers.d/tester
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: tester
+ kind: user
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the account tester exists
+ names:
+ - tester
+ - id: rule
+ kind: sudoers
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: tester may only run systemctl status
+ path: /etc/sudoers.d/tester
+ mode: '0440'
+ owner: root
+ valid: true
+ rules:
+ - tester ALL=(ALL) /usr/bin/systemctl status *
+finish:
+- action: user
+ hosts:
+ - servera
+ name: tester
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/sudoers.d/tester
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-systemd-review.yml b/content/programs/rhel9-sysadmin/lab/sa-systemd-review.yml
index f7708b8..d8e081b 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-systemd-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-systemd-review.yml
@@ -87,3 +87,45 @@ checkpoints:
- servera.lab.example.com
message: The default target is multi-user.target
target: multi-user.target
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - reporter
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/reporter.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/reporter.log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service.d
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - bluetooth
+ masked: false
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: boot
+ hosts:
+ - servera
+ target: graphical.target
diff --git a/content/programs/rhel9-sysadmin/lab/sa-tar.yml b/content/programs/rhel9-sysadmin/lab/sa-tar.yml
new file mode 100644
index 0000000..c201b9b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-tar.yml
@@ -0,0 +1,84 @@
+name: sa-tar
+title: Archive a project
+page: ch14/exercise-tar
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: tar
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: project.tar lists the project
+ path: /root/arch/project.tar
+ format: tar
+ members:
+ - project/run.sh
+ - project/docs/notes.md
+ - id: tgz
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: project.tar.gz is a readable archive of the project
+ path: /root/arch/project.tar.gz
+ format: tar.gz
+ members:
+ - project/run.sh
+ - project/src/data1.txt
+ - id: notxt
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: notxt.tar.gz leaves out the .txt files
+ path: /root/arch/notxt.tar.gz
+ format: tar.gz
+ members:
+ - project/docs/notes.md
+ absentMembers:
+ - project/src/data1.txt
+ - id: abs
+ kind: archive
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: abs.tar stores the path without the leading slash
+ path: /root/arch/abs.tar
+ format: tar
+ members:
+ - root/arch/project/docs/notes.md
+ - id: restore
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the full restore holds the script with its mode
+ paths:
+ - /root/arch/restore/project/run.sh
+ mode: '0750'
+ - id: one
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: only run.sh was extracted into one/
+ paths:
+ - /root/arch/one/project/run.sh
+ fileType: regular
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /root/arch
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-text-review.yml b/content/programs/rhel9-sysadmin/lab/sa-text-review.yml
index 1fa3248..8936693 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-text-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-text-review.yml
@@ -98,3 +98,18 @@ checkpoints:
paths:
- /home/student/.bash_profile
matches: ^export EDITOR=vim
+finish:
+- action: directory
+ hosts:
+ - servera
+ path: /home/student/reports
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/notice.txt
+ state: absent
+- action: restore-skel
+ hosts:
+ - servera
+ user: student
diff --git a/content/programs/rhel9-sysadmin/lab/sa-time.yml b/content/programs/rhel9-sysadmin/lab/sa-time.yml
new file mode 100644
index 0000000..50b117f
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-time.yml
@@ -0,0 +1,84 @@
+name: sa-time
+title: Time zone and time sources
+page: ch11/exercise-time
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: timezone
+ hosts:
+ - servera
+ zone: UTC
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/chrony.conf
+ matching: ^server 0[.]pool[.]ntp[.]org
+- action: file
+ hosts:
+ - servera
+ path: /root/chrony.conf.bak
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: zone
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the time zone is UTC again
+ paths:
+ - /etc/localtime
+ resolvesTo: /usr/share/zoneinfo/UTC
+ - id: conf
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: chrony.conf has no extra pool source any more
+ paths:
+ - /etc/chrony.conf
+ lacks:
+ - server 0.pool.ntp.org iburst
+ - id: backup
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the backup /root/chrony.conf.bak was removed
+ paths:
+ - /root/chrony.conf.bak
+ exists: false
+ - id: chronyd
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: chronyd is running and enabled
+ names:
+ - chronyd.service
+ active: true
+ enabled: true
+finish:
+- action: timezone
+ hosts:
+ - servera
+ zone: UTC
+- action: remove-lines
+ hosts:
+ - servera
+ path: /etc/chrony.conf
+ matching: ^server 0[.]pool[.]ntp[.]org
+- action: file
+ hosts:
+ - servera
+ path: /root/chrony.conf.bak
+ state: absent
+- action: service
+ hosts:
+ - servera
+ names:
+ - chronyd
+ state: restarted
diff --git a/content/programs/rhel9-sysadmin/lab/sa-timers.yml b/content/programs/rhel9-sysadmin/lab/sa-timers.yml
new file mode 100644
index 0000000..6eaa30b
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-timers.yml
@@ -0,0 +1,133 @@
+name: sa-timers
+title: Timers and one-off jobs
+page: ch15/exercise-timers
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - hello.timer
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/hello.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/hello.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/at.out
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: package
+ hosts:
+ - servera
+ names:
+ - at
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: service
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: hello.service is a oneshot service that logs with the tag hello-timer
+ paths:
+ - /etc/systemd/system/hello.service
+ contains:
+ - Type=oneshot
+ - hello-timer
+ - id: timer-file
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: hello.timer fires every minute and catches up
+ paths:
+ - /etc/systemd/system/hello.timer
+ contains:
+ - Persistent=true
+ - WantedBy=timers.target
+ - id: timer
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: hello.timer is enabled and running
+ names:
+ - hello.timer
+ active: true
+ enabled: true
+ - id: atd
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: atd is running
+ names:
+ - atd.service
+ active: true
+ - id: at-out
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the at job ran
+ paths:
+ - /home/student/at.out
+ line: at job ran
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - hello.timer
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/hello.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/hello.timer
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /home/student/at.out
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: service
+ hosts:
+ - servera
+ names:
+ - atd
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - at
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-transfer.yml b/content/programs/rhel9-sysadmin/lab/sa-transfer.yml
new file mode 100644
index 0000000..1baabf4
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-transfer.yml
@@ -0,0 +1,138 @@
+name: sa-transfer
+title: Move files between machines
+page: ch10/exercise-transfer
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: present
+ password: Ops-Pass-2026
+- action: home-remove
+ paths:
+ - sshlab
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+- action: ssh-config-block
+ host: opsa
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /tmp/reports
+ state: absent
+- action: ssh-identity
+ name: id_ops
+ hosts:
+ - servera
+ user: ops
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: inventory-local
+ kind: file
+ 'on': control
+ message: ~/sshlab/inventory.txt exists on workstation
+ paths:
+ - /home/student/sshlab/inventory.txt
+ contains:
+ - web1
+ - id: alias
+ kind: file
+ 'on': control
+ message: ~/.ssh/config defines the alias opsa
+ paths:
+ - /home/student/.ssh/config
+ contains:
+ - Host opsa
+ - User ops
+ - id: downloaded
+ kind: file
+ 'on': control
+ message: servera's host name was downloaded
+ paths:
+ - /home/student/sshlab/servera-hostname.txt
+ contains:
+ - servera
+ - id: sftp
+ kind: file
+ 'on': control
+ message: sftp fetched downloaded-a.txt
+ paths:
+ - /home/student/sshlab/downloaded-a.txt
+ contains:
+ - r1
+ - id: uploaded
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: inventory.txt is in the home of ops
+ paths:
+ - /home/ops/inventory.txt
+ owner: ops
+ - id: scp-dir
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the reports directory was copied to /tmp
+ paths:
+ - /tmp/reports/a.txt
+ contains:
+ - r1
+ - id: rsync-a
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: rsyncdir holds a.txt
+ paths:
+ - /home/ops/rsyncdir/a.txt
+ owner: ops
+ - id: rsync-c
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: rsyncdir holds c.txt
+ paths:
+ - /home/ops/rsyncdir/c.txt
+ owner: ops
+ - id: rsync-b
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the mirror deleted b.txt
+ paths:
+ - /home/ops/rsyncdir/b.txt
+ exists: false
+finish:
+- action: user
+ hosts:
+ - servera
+ name: ops
+ state: absent
+- action: home-remove
+ paths:
+ - sshlab
+ - .ssh/id_ops
+ - .ssh/id_ops.pub
+- action: ssh-config-block
+ host: opsa
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /tmp/reports
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-two-faults.yml b/content/programs/rhel9-sysadmin/lab/sa-two-faults.yml
new file mode 100644
index 0000000..720a2ea
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-two-faults.yml
@@ -0,0 +1,135 @@
+name: sa-two-faults
+title: Two faults, one symptom
+page: ch16/exercise-troubleshooting
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ - audit
+ - setroubleshoot-server
+ state: present
+- action: service
+ hosts:
+ - servera
+ names:
+ - auditd
+ state: started
+ enabled: true
+ tolerant: true
+- action: directory
+ hosts:
+ - servera
+ path: /srv/site
+ state: present
+- action: file
+ hosts:
+ - servera
+ path: /srv/site/index.html
+ state: present
+ content: '
Site OK
+
+ '
+ selinuxType: user_tmp_t
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/site.conf
+ state: present
+ content: "Listen 8085\n\n DocumentRoot \"/srv/site\"\n\n\n Require all granted\n\n"
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: restarted
+ enabled: false
+ tolerant: true
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: port
+ kind: selinux-port
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: TCP port 8085 is labelled http_port_t
+ type: http_port_t
+ proto: tcp
+ port: 8085
+ - id: fcontext
+ kind: selinux-fcontext
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a stored mapping gives /srv/site and below the web content type
+ path: /srv/site(/.*)?
+ type: httpd_sys_content_t
+ - id: label
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: index.html carries the web content type
+ paths:
+ - /srv/site/index.html
+ selinuxType: httpd_sys_content_t
+ - id: enforcing
+ kind: selinux
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: SELinux is still enforcing
+ mode: enforcing
+ - id: page
+ kind: http
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the site answers on port 8085
+ url: http://localhost:8085/
+ containsAny:
+ - Site OK
+finish:
+- action: selinux
+ hosts:
+ - servera
+ kind: fcontext
+ state: absent
+ path: /srv/site(/.*)?
+- action: selinux
+ hosts:
+ - servera
+ kind: port
+ state: absent
+ type: http_port_t
+ proto: tcp
+ port: 8085
+- action: service
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: stopped
+ enabled: false
+- action: package
+ hosts:
+ - servera
+ names:
+ - httpd
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /srv/site
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /etc/httpd/conf.d/site.conf
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-unit-files.yml b/content/programs/rhel9-sysadmin/lab/sa-unit-files.yml
new file mode 100644
index 0000000..cf9b620
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-unit-files.yml
@@ -0,0 +1,126 @@
+name: sa-unit-files
+title: Write your own service
+page: ch09/exercise-unit-files
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: service
+ hosts:
+ - servera
+ names:
+ - reporter
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/reporter.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/reporter.log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service.d
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/reporter.sh
+ state: present
+ content: "#!/bin/bash\n# Append a timestamp to the report every few seconds.\nwhile true; do\n echo \"$(date +%T) reporter alive\" >> /var/log/reporter.log\n sleep \"${INTERVAL:-10}\"\ndone\n"
+ owner: root
+ group: root
+ mode: '755'
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: unit
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the unit runs the script, restarts on failure after 2 s and is wanted by multi-user.target
+ paths:
+ - /etc/systemd/system/reporter.service
+ contains:
+ - ExecStart=/usr/local/bin/reporter.sh
+ - Restart=on-failure
+ - RestartSec=2
+ - WantedBy=multi-user.target
+ - id: dropin
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: a drop-in sets INTERVAL=2
+ paths:
+ - /etc/systemd/system/reporter.service.d/10-interval.conf
+ contains:
+ - Environment=INTERVAL=2
+ - id: service
+ kind: service
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: reporter is running and enabled
+ names:
+ - reporter.service
+ active: true
+ enabled: true
+ - id: log
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the report file gets lines
+ paths:
+ - /var/log/reporter.log
+ contains:
+ - reporter alive
+finish:
+- action: service
+ hosts:
+ - servera
+ names:
+ - reporter
+ state: stopped
+ enabled: false
+- action: file
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /usr/local/bin/reporter.sh
+ state: absent
+- action: file
+ hosts:
+ - servera
+ path: /var/log/reporter.log
+ state: absent
+- action: directory
+ hosts:
+ - servera
+ path: /etc/systemd/system/reporter.service.d
+ state: absent
+- action: systemd
+ hosts:
+ - servera
+ reload: true
diff --git a/content/programs/rhel9-sysadmin/lab/sa-users-review.yml b/content/programs/rhel9-sysadmin/lab/sa-users-review.yml
index 3f28b7e..6698092 100644
--- a/content/programs/rhel9-sysadmin/lab/sa-users-review.yml
+++ b/content/programs/rhel9-sysadmin/lab/sa-users-review.yml
@@ -119,3 +119,29 @@ checkpoints:
matches: ^%operators[[:space:]]+ALL[[:space:]]*=[[:space:]]*\(ALL(:ALL)?\)[[:space:]]+/usr/bin/systemctl([[:space:]]+\*)?[[:space:]]*$
lacks:
- NOPASSWD
+finish:
+- action: user
+ hosts:
+ - serverb
+ name: opsadmin
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: auditor
+ state: absent
+- action: user
+ hosts:
+ - serverb
+ name: backup
+ state: absent
+- action: group
+ hosts:
+ - serverb
+ name: operators
+ state: absent
+- action: file
+ hosts:
+ - serverb
+ path: /etc/sudoers.d/operators
+ state: absent
diff --git a/content/programs/rhel9-sysadmin/lab/sa-vim.yml b/content/programs/rhel9-sysadmin/lab/sa-vim.yml
new file mode 100644
index 0000000..3bb06c9
--- /dev/null
+++ b/content/programs/rhel9-sysadmin/lab/sa-vim.yml
@@ -0,0 +1,44 @@
+name: sa-vim
+title: Write and change a file with vim
+page: ch05/exercise-vim
+transport: ssh
+version: 1
+starter: []
+setup:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/welcome.txt
+ state: absent
+checkpoints:
+ final:
+ files: []
+ checks:
+ - id: welcome
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: welcome.txt has the three lines that remain
+ paths:
+ - /home/student/welcome.txt
+ lines:
+ - Welcome to servera.lab.example.com.
+ - 'Contact: admin@lab.example.com'
+ - 'Contact: oncall@lab.example.com'
+ - id: no-window
+ kind: file
+ 'on': servera.lab.example.com
+ targets:
+ - servera.lab.example.com
+ message: the cancelled maintenance window line is gone
+ paths:
+ - /home/student/welcome.txt
+ lacks:
+ - Maintenance window
+finish:
+- action: file
+ hosts:
+ - servera
+ path: /home/student/welcome.txt
+ state: absent
diff --git a/docs/AUTHORING.md b/docs/AUTHORING.md
index 6d62725..47de7bd 100644
--- a/docs/AUTHORING.md
+++ b/docs/AUTHORING.md
@@ -271,14 +271,15 @@ checkpoints:
| Check `kind` | Values it takes |
| --- | --- |
| `service` | `names`, `active`, `enabled` |
-| `firewall` | `service` or `port`, `allowed`, `runtime`, `permanent` |
-| `package` | `names`, `installed` |
-| `file` | `paths` (any one may satisfy), `exists`, `nonEmpty`, `contains`, `lacks`, `line`, `lines`, `matches`, `contentEquals`, `mode`, `owner`, `group`, `selinuxType`, `symlinkTo` |
+| `firewall` | `service`, `port`, `source` or `forwardPort` (optionally in a `zone`), `allowed`, `runtime`, `permanent` |
+| `package` | `names`, `installed`, `verify` |
+| `file` | `paths` (any one may satisfy), `exists`, `nonEmpty`, `contains`, `lacks`, `line`, `lines`, `matches`, `contentEquals`, `mode`, `owner`, `group`, `selinuxType`, `symlinkTo`, `executable`, `acl` |
| `file-compare` | `a`, `b` |
| `archive` | `path`, `format` |
| `user` | `names`, `exists`, `groups`, `passwordSet`, `authorizedKeys`, `homeFile` |
| `mount`, `logical-volume` | `path`, `fstype`, `persistent`; `vg`, `lv`, `minSizeMiB` |
| `http` | `url` (to the managed host itself), `insecure`, `resolveToLocalhost`, `containsAny` |
+| `partition`, `container` | `device`, `minSizeMiB`, `partLabel`, `tableType`, `fsType`, `fsLabel`; `user`, `image`, `running`, `unit`, `linger` |
| `selinux`, `sudoers`, `sshd`, `cron`, `boot-target`, `address`, `hostname`, `commands` | see the lab schema |
| `git`, `lint` | on `control` only: the project's Git state, and `ansible-lint` in a container |
@@ -287,6 +288,18 @@ checkpoints:
- Setup actions: `self-signed-cert`, `htpasswd`, `password-hash-var`, `vault-encrypt`, `ssh-keypairs`, `pack-installed-collection`, `build-collection`, `collection-requirements` and `git-seed-remote`.
- Starter and tree files are published with a `.lab` suffix so a browser never renders them, and a name part that starts with a dot gets a `_` in front (`files/.htaccess` is published as `files/_.htaccess.lab`), because static hosts leave dotfiles out. The lab command saves them under their real names. Do not name a file starting with `_.`; that form is reserved.
+#### Guided exercises: start, grade and finish on the servers
+
+A guided exercise (a step-by-step page, `{% lab exercise="NAME" guided=true ownExercise=true %}`) has no challenge brief, but it is still an exercise definition. The lab tool does three jobs for it:
+
+- **`lab start NAME`** copies the starter files and runs the `setup` actions, which prepare what the exercise needs but does not teach: install a package, create the users and files an earlier exercise left behind, start a broken service. Tasks that only did this preparation are removed from the page and replaced by a "Start the exercise" task.
+- **`lab grade NAME`** runs the typed `checks` against the state the tasks leave behind.
+- **`lab finish NAME`** runs the `finish` actions on the servers, then archives the project folder. The last task of the page is `{% lab-finish exercise="NAME" grade=true servers=true /%}`, which tells the reader that the servers are cleaned. Each finish action is best effort: one that fails is reported and the others still run. `rht-vmctl reset servers` remains the way to the exact clean baseline.
+
+`setup` and `finish` use the same typed actions. **Host actions** run as root on the named lab servers (`hosts: [servera]`) and are built by `prepare.py` from validated values: `package`, `service`, `group`, `user`, `directory`, `file`, `remove-lines`, `append-line`, `acl`, `firewall`, `selinux`, `systemd`, `linger`, `boot`, `timezone`, `hostname`, `nm-connection`, `crontab`, `dnf-module`, `http-server`, `partition-disk`, `format`, `lvm-build`, `mount-all`, `unmount`, `wipe-disk`, `restore-skel`, `container-reset` and `run-as`. Path values are limited to places meant for local data and drop-in configuration (`/srv`, `/opt`, `/mnt`, `/data`, `/home`, `/tmp`, `/var/log`, `/etc/*.d` and a few more), the top directories themselves and whole home directories can never be removed, and `remove-lines` and `append-line` only touch `/etc/fstab`, `/etc/exports`, `/etc/hosts` and `/etc/chrony.conf`. **Workstation actions** work in the learner's home and refuse shell start-up files and the lab's own keys: `home-remove`, `ssh-config-block` and `ssh-identity`.
+
+Write the checks for the state at the moment the page asks to grade, and make the setup produce the state the first remaining task assumes, and say in the Start task where the reader works (for example "change to `/srv/lab7`"). Run the whole page against the lab (`lab start`, the tasks, `lab grade`, `lab finish`, `lab grade` again) before you publish it.
+
The compiler publishes the lab tree (the `lab` command, the grader, starter files, `INDEX`, and a `MANIFEST` per exercise) with the rest of the site, and the build also places it at `/lab/`. Try an exercise end to end with `npm run build`, then `LAB_URL=file://$PWD/dist/lab bash dist/lab/lab start NAME`, and run its solution against the lab before you publish it.
## 6. Code font
diff --git a/docs/CATALOG.md b/docs/CATALOG.md
index b9637df..aefb907 100644
--- a/docs/CATALOG.md
+++ b/docs/CATALOG.md
@@ -391,6 +391,7 @@ A hands-on exercise with tracked tasks, notes and an optional challenge brief.
| `hosts` | string[] | | Labels for the machines the exercise uses, such as 'workstation' or 'Ubuntu host'. |
| `outcomes` | string[] | | What the learner will have done. |
| `objectives` | string[] | | Objectives the exercise practises. |
+| `guided` | boolean (default `false`) | | A guided exercise: its tasks say exactly what to do, so it needs no challenge brief. It still has an exercise definition whose start, grading and finish the lab tool performs. |
## lab-challenge
@@ -408,6 +409,7 @@ How to grade and finish an exercise with the lab tools.
| --- | --- | --- | --- |
| `exercise` | string (exerciseName) | yes | Exercise name. |
| `grade` | boolean (default `false`) | | Also show the grading command. |
+| `servers` | boolean (default `false`) | | The exercise's cleanup steps undo its changes on the lab servers, so the Finish text says so. |
## lab-network-map
diff --git a/packages/compiler/src/lab.js b/packages/compiler/src/lab.js
index 7e5e042..9ecd250 100644
--- a/packages/compiler/src/lab.js
+++ b/packages/compiler/src/lab.js
@@ -29,6 +29,38 @@ echo " Run: lab update then start the exercise again with --force."
exit 1
`;
const CONTROL_ONLY = new Set(['git', 'lint']);
+// Setup and cleanup actions that run as root on the lab servers; the rest work on the workstation.
+const HOST_ACTIONS = new Set([
+ 'package',
+ 'service',
+ 'group',
+ 'user',
+ 'directory',
+ 'file',
+ 'remove-lines',
+ 'firewall',
+ 'selinux',
+ 'wipe-disk',
+ 'systemd',
+ 'linger',
+ 'container-reset',
+ 'run-as',
+ 'restore-skel',
+ 'boot',
+ 'timezone',
+ 'nm-connection',
+ 'hostname',
+ 'http-server',
+ 'dnf-module',
+ 'crontab',
+ 'partition-disk',
+ 'format',
+ 'append-line',
+ 'mount-all',
+ 'lvm-build',
+ 'unmount',
+ 'acl',
+]);
/** Every file under `dir` as paths relative to it, sorted. */
function walk(dir) {
@@ -87,8 +119,9 @@ export function compileLabs({ labs, pages, references, seen, validator, diagnost
if (bytes) files.set(`lab/${name}/${publishedPath(relative)}`, bytes);
}
const listing = [`# ${def.title}${def.note ? ` (${def.note})` : ''}`];
- if ((def.setup ?? []).length) {
- listing.push('# generated: some files are created on your workstation');
+ if ((def.setup ?? []).length || (def.finish ?? []).length) {
+ if ((def.setup ?? []).some((action) => !HOST_ACTIONS.has(action.action)))
+ listing.push('# generated: some files are created on your workstation');
listing.push('@lab-update-required');
files.set(`lab/${name}/lab-update-required`, UPDATE_REQUIRED);
}
@@ -140,6 +173,7 @@ export function compileLabs({ labs, pages, references, seen, validator, diagnost
lesson: `#/${def.page}`,
...(def.transport === 'ssh' ? { transport: 'ssh' } : {}),
...(setup.length ? { setup } : {}),
+ ...(def.finish?.length ? { finish: def.finish } : {}),
checkpoints: def.checkpoints,
};
index.push({ name, page: `${page.number}`, title: def.title, note: def.note });
diff --git a/packages/engine/src/components/content/registry.jsx b/packages/engine/src/components/content/registry.jsx
index b459e51..10fd306 100644
--- a/packages/engine/src/components/content/registry.jsx
+++ b/packages/engine/src/components/content/registry.jsx
@@ -82,7 +82,7 @@ export const tags = {
'lab-notes': () => [LabNotes],
'lab-challenge': () => [LabChallenge],
'lab-setup': () => [HomeSetup],
- 'lab-finish': (a) => [Finish, { name: a.exercise, grade: a.grade }],
+ 'lab-finish': (a) => [Finish, { name: a.exercise, grade: a.grade, servers: a.servers }],
'starter-files': (a) => [StarterFiles, { name: a.exercise }],
'variant-group': () => [Env],
diff --git a/packages/engine/src/components/interactive/Env.jsx b/packages/engine/src/components/interactive/Env.jsx
index 6067297..9b633a1 100644
--- a/packages/engine/src/components/interactive/Env.jsx
+++ b/packages/engine/src/components/interactive/Env.jsx
@@ -165,7 +165,23 @@ export function StarterFiles({ name }) {
}
/** The "Finish" step of an exercise, for both environments. */
-export function Finish({ name, grade = false }) {
+export function Finish({ name, grade = false, servers = false }) {
+ if (singleEnvironment() && servers)
+ return (
+
+ {grade ? (
+ <>
+ On workstation, check your work with lab grade {name}. Fix anything that fails and grade again; grading only reads.
+ Then{' '}
+ >
+ ) : (
+ 'On workstation, '
+ )}
+ lab finish {name} undoes this exercise's changes on the servers and moves the exercise folder to{' '}
+ ~/lab-archive/. It cleans only what this exercise set up; rht-vmctl reset servers on the Ubuntu host
+ returns the servers to the exact clean baseline.
+