mirror of
https://codeberg.org/Mo8it/How_To_Linux.git
synced 2024-12-05 01:40:32 +00:00
Compare commits
6 commits
8b90df6fe7
...
bb03656c61
Author | SHA1 | Date | |
---|---|---|---|
bb03656c61 | |||
c30081a00d | |||
55bf9ed17b | |||
7d8e66b722 | |||
5e8f175bae | |||
6ba629913b |
29 changed files with 1420 additions and 991 deletions
|
@ -7,9 +7,9 @@ mod system_update;
|
|||
mod zombie;
|
||||
mod zombie_nuked;
|
||||
|
||||
use collective_score_client::check::RunnableCheck;
|
||||
use collective_score_client::check::Task;
|
||||
|
||||
pub fn tasks() -> [(&'static str, Box<dyn RunnableCheck>); 8] {
|
||||
pub fn tasks() -> [Task; 8] {
|
||||
[
|
||||
collective_score_intro::task(),
|
||||
initial_house::task(),
|
||||
|
|
74
cs/src/day2/cargo.rs
Normal file
74
cs/src/day2/cargo.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{
|
||||
command::{Command, CommandStatus, StdioVariant},
|
||||
file::FileContent,
|
||||
string_content::StringContent,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn task() -> Task {
|
||||
let fish_history = dirs::home_dir()
|
||||
.expect("Failed to get the home directory!")
|
||||
.join(".local/share/fish/fish_history");
|
||||
|
||||
[
|
||||
Check::builder()
|
||||
.description("Checking that `cargo-update` is installed")
|
||||
.validator(
|
||||
CommandStatus::builder()
|
||||
.command(
|
||||
Command::builder()
|
||||
.program("which")
|
||||
.args(vec!["cargo-install-update".into()])
|
||||
.stdout(StdioVariant::Null)
|
||||
.stderr(StdioVariant::Null)
|
||||
.stdin(StdioVariant::Null)
|
||||
.build(),
|
||||
)
|
||||
.status_code(0)
|
||||
.build(),
|
||||
)
|
||||
.hint("Did you install `cargo-update` using `cargo`?")
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking that `tldr` is installed")
|
||||
.validator(
|
||||
CommandStatus::builder()
|
||||
.command(
|
||||
Command::builder()
|
||||
.program("which")
|
||||
.args(vec!["tldr".into()])
|
||||
.stdout(StdioVariant::Null)
|
||||
.stderr(StdioVariant::Null)
|
||||
.stdin(StdioVariant::Null)
|
||||
.build(),
|
||||
)
|
||||
.status_code(0)
|
||||
.build(),
|
||||
)
|
||||
.hint("Did you install `tealdeer` using `cargo`?")
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking that you ran tldr")
|
||||
.validator(FileContent {
|
||||
file: fish_history.clone(),
|
||||
expected: StringContent::Part("\n- cmd: tldr dnf\n"),
|
||||
})
|
||||
.hint("Did check the command `dnf` using `tldr`?")
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking that you ran tldr not only once")
|
||||
.validator(FileContent {
|
||||
file: fish_history.clone(),
|
||||
expected: StringContent::Part("\n- cmd: tldr apt\n"),
|
||||
})
|
||||
.hint("Did check the command `apt` using `tldr`?")
|
||||
.build()
|
||||
.into_box(),
|
||||
]
|
||||
.into_task("cargo")
|
||||
}
|
15
cs/src/day2/curly_wc.rs
Normal file
15
cs/src/day2/curly_wc.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{stdin::Stdin, string_content::StringContent},
|
||||
};
|
||||
|
||||
pub fn task() -> Task {
|
||||
Check::builder()
|
||||
.description("Checking the number of characters")
|
||||
.validator(Stdin {
|
||||
expected: StringContent::Full("756\n"),
|
||||
})
|
||||
.hint("curl … | … | cs task curly-wc")
|
||||
.build()
|
||||
.into_task("curly-wc")
|
||||
}
|
17
cs/src/day2/fish_config.rs
Normal file
17
cs/src/day2/fish_config.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{file::FileContent, string_content::StringContent},
|
||||
};
|
||||
|
||||
pub fn task() -> Task {
|
||||
Check::builder()
|
||||
.description("Checking you disabled the Fish greeting")
|
||||
.validator(FileContent {
|
||||
file: dirs::home_dir()
|
||||
.expect("Failed to get the home directory!")
|
||||
.join(".config/fish/config.fish"),
|
||||
expected: StringContent::Part("set -g fish_greeting"),
|
||||
})
|
||||
.build()
|
||||
.into_task("fish-config")
|
||||
}
|
20
cs/src/day2/fish_in_zellij.rs
Normal file
20
cs/src/day2/fish_in_zellij.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{file::FileContent, string_content::StringContent},
|
||||
};
|
||||
|
||||
pub fn task() -> Task {
|
||||
Check::builder()
|
||||
.description("Checking you made Fish the default Zellij shell")
|
||||
.validator(FileContent {
|
||||
file: dirs::home_dir()
|
||||
.expect("Failed to get the home directory!")
|
||||
.join(".config/zellij/config.kdl"),
|
||||
expected: StringContent::Part("default_shell \"/usr/bin/fish\""),
|
||||
})
|
||||
.hint(
|
||||
"Did you set the option `default_shell`? Did you check the path of fish using `which`?",
|
||||
)
|
||||
.build()
|
||||
.into_task("fish-in-zellij")
|
||||
}
|
19
cs/src/day2/mod.rs
Normal file
19
cs/src/day2/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
mod cargo;
|
||||
mod curly_wc;
|
||||
mod fish_config;
|
||||
mod fish_in_zellij;
|
||||
mod pdf;
|
||||
mod redirections;
|
||||
|
||||
use collective_score_client::check::Task;
|
||||
|
||||
pub fn tasks() -> [Task; 6] {
|
||||
[
|
||||
cargo::task(),
|
||||
fish_config::task(),
|
||||
fish_in_zellij::task(),
|
||||
curly_wc::task(),
|
||||
redirections::task(),
|
||||
pdf::task(),
|
||||
]
|
||||
}
|
17
cs/src/day2/pdf.rs
Normal file
17
cs/src/day2/pdf.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{file::FileContent, string_content::StringContent},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn task() -> Task {
|
||||
Check::builder()
|
||||
.description("Checking the downloaded PDF file")
|
||||
.validator(FileContent {
|
||||
file: PathBuf::from("knowunity.pdf"),
|
||||
expected: StringContent::Part("Build 19E258"),
|
||||
})
|
||||
.hint("Did you download the PDF file?")
|
||||
.build()
|
||||
.into_task("pdf")
|
||||
}
|
43
cs/src/day2/redirections.rs
Normal file
43
cs/src/day2/redirections.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use collective_score_client::{
|
||||
check::{Check, IntoTask, Task},
|
||||
validator::{file::FileContent, string_content::StringContent},
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn task() -> Task {
|
||||
[
|
||||
Check::builder()
|
||||
.description("Checking the stdout file")
|
||||
.validator(FileContent {
|
||||
file: PathBuf::from("normal_output.log"),
|
||||
expected: StringContent::Full("OK\nThis is some random output\nunshipped hardly lip cactus appetite petticoat\n"),
|
||||
})
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking the stderr file")
|
||||
.validator(FileContent {
|
||||
file: PathBuf::from("errors.log"),
|
||||
expected: StringContent::Full("Just do what the tasks tells you\nbundle favored sierra ungraded uneaten passage\ncrummy worrisome nearness level stays handmade\n"),
|
||||
})
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking the mixed file")
|
||||
.validator(FileContent {
|
||||
file: PathBuf::from("verbose.log"),
|
||||
expected: StringContent::Full("OK\nThis is some random output\nJust do what the tasks tells you\nbundle favored sierra ungraded uneaten passage\nunshipped hardly lip cactus appetite petticoat\ncrummy worrisome nearness level stays handmade\n"),
|
||||
})
|
||||
.build()
|
||||
.into_box(),
|
||||
Check::builder()
|
||||
.description("Checking the appended stdout")
|
||||
.validator(FileContent {
|
||||
file: PathBuf::from("dont_overwrite.txt"),
|
||||
expected: StringContent::Full("spherical survey capillary relatable tameness fame\nOK\nThis is some random output\nunshipped hardly lip cactus appetite petticoat\n"),
|
||||
})
|
||||
.build()
|
||||
.into_box(),
|
||||
]
|
||||
.into_task("redirections")
|
||||
}
|
|
@ -1,10 +1,18 @@
|
|||
mod day1;
|
||||
mod day2;
|
||||
|
||||
use collective_score_client::run;
|
||||
use collective_score_client::{run, Tasks};
|
||||
use std::process;
|
||||
|
||||
fn main() {
|
||||
let tasks = day1::tasks().into_iter().collect();
|
||||
let day1_tasks = day1::tasks();
|
||||
let day2_tasks = day2::tasks();
|
||||
|
||||
let n_tasks = day1_tasks.len() + day2_tasks.len();
|
||||
|
||||
let tasks: Tasks = day1_tasks.into_iter().chain(day2_tasks).collect();
|
||||
|
||||
assert_eq!(tasks.len(), n_tasks, "Task name conflict!");
|
||||
|
||||
if let Err(e) = run(
|
||||
tasks,
|
||||
|
|
|
@ -7,19 +7,26 @@
|
|||
- [Terminal basics](day_1/terminal_basics.md)
|
||||
- [Packages](day_1/packages.md)
|
||||
- [Tasks](day_1/tasks.md)
|
||||
- [Day 2](day_2/README.md)
|
||||
- [Terminal upgrade](day_2/terminal_upgrade.md)
|
||||
- [Shell glue](day_2/glue.md)
|
||||
- [Shell tricks](day_2/shell_tricks.md)
|
||||
- [Regex](day_2/regex.md)
|
||||
- [CLIs of the day](day_2/clis_of_the_day.md)
|
||||
- [Tasks](day_2/tasks.md)
|
||||
|
||||
<!--
|
||||
- [Day 2](day_2/README.md)
|
||||
- [Shell glue](day_2/glue.md)
|
||||
- [Shell scripting](day_2/shell_scripting.md)
|
||||
- [Tasks](day_2/tasks.md)
|
||||
- [Day 3](day_3/README.md)
|
||||
- [Notes](day_3/notes.md)
|
||||
- [CLIs of the day](day_3/clis_of_the_day.md)
|
||||
- [Tasks](day_3/tasks.md)
|
||||
- [Day 4](day_4/README.md)
|
||||
- [Shell scripting](day_4/shell_scripting.md)
|
||||
- [Notes](day_4/notes.md)
|
||||
- [CLIs of the day](day_4/clis_of_the_day.md)
|
||||
- [Tasks](day_4/tasks.md)
|
||||
- [Day 5](day_5/README.md)
|
||||
- [Notes](day_5/notes.md)
|
||||
- [CLIs of the day](day_5/clis_of_the_day.md)
|
||||
- [Tasks](day_5/tasks.md)
|
||||
-->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Tasks
|
||||
|
||||
## Task 0: Collective-Score
|
||||
## Task: Collective-Score
|
||||
|
||||
This course uses [collective-score](https://codeberg.org/mo8it/collective-score) for interactive tasks.
|
||||
I spent many days programming it for you 😇
|
||||
|
@ -42,7 +42,7 @@ Congratulations, you have done your first task 🎉
|
|||
|
||||
To see your progress at any time, run the command **`cs progress show`**.
|
||||
|
||||
## Task 1: Building houses 🏠️
|
||||
## Task: Building houses 🏠️
|
||||
|
||||
In this task, you will build a house with different rooms using (nested) directories.
|
||||
|
||||
|
@ -87,12 +87,12 @@ Now, destroy the whole living room with the zombie in it.
|
|||
|
||||
🟢 Run `cs task zombie-nuked` in the house directory.
|
||||
|
||||
Tipps:
|
||||
### Hints
|
||||
|
||||
- If you get lost, use `pwd`.
|
||||
- If you are looking for an option but you can not remember it, use `--help`.
|
||||
|
||||
## Task 2: Reset your password
|
||||
## Task: Password 🔑
|
||||
|
||||
⚠️ Launch the [fish shell](https://fishshell.com/) by running the command `fish` and stay in it for all following tasks.
|
||||
If you close the terminal and lauch it later again, you have to run `fish` again.
|
||||
|
@ -102,7 +102,7 @@ Use the command `passwd` to reset the password of your user. It is important to
|
|||
|
||||
🟢 Run `cs task passwd`.
|
||||
|
||||
## Task 3: Update your system
|
||||
## Task: System updates
|
||||
|
||||
Find out how to update your system with `dnf` and run the updates.
|
||||
|
||||
|
@ -112,7 +112,7 @@ It is possible that you don't find any updates. In this case, you can try it aga
|
|||
|
||||
🟢 Run `cs task system-update`.
|
||||
|
||||
## Task 4: Package installation and usage
|
||||
## Task: Package installation and usage
|
||||
|
||||
Install the package `cowsay` and find out how to use it!
|
||||
|
||||
|
|
|
@ -164,7 +164,16 @@ friend1.txt friend2.txt
|
|||
The loud friend is removed!
|
||||
`rm` stand for _remove_.
|
||||
|
||||
> **Warning** ⚠️ : `rm` deletes a file directly! The file is **not** moved to a trash! It is gone! You can't restore it anymore! Think twice before using `rm`.
|
||||
> **Warning** ⚠️ : `rm` deletes a file directly!
|
||||
> The file is **not** moved to a trash!
|
||||
> It is gone!
|
||||
> You can't just restore it anymore!
|
||||
> Think twice before using `rm`.
|
||||
|
||||
> **Note**: Although `rm` deletes the file from the file system, there is still some chance that the file content still exists on the disk.
|
||||
> One could try to recover it with some tools, but it is hard and nothing is guaranteed.
|
||||
> Nevertheless, if you want to delete something senstive for sure, then you have to use a tool that overwrites the file content before deleting it.
|
||||
> Physically destroying the whole disk is also an option 🔥😂
|
||||
|
||||
Does `rm` also work with directories?
|
||||
Let's test it:
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# Day 2
|
||||
|
||||
In this day, we will learn how to glue commands together to benefit from their modularity. Later, we will write our own shell scripts to achieve some automation.
|
||||
In this day, we will learn more about the terminal and how to glue commands together to benefit from their modularity.
|
||||
|
||||
But we will start by making our terminal more comfortable!
|
||||
Let's make an upgrade away from the terminal of the 90s ✨
|
||||
|
|
75
src/day_2/clis_of_the_day.md
Normal file
75
src/day_2/clis_of_the_day.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
# CLIs of the day
|
||||
|
||||
## cargo
|
||||
|
||||
Cargo is the package manager of the programming language Rust.
|
||||
|
||||
You don't have program in Rust to use it.
|
||||
It is listed here because you can use it to install many of the fancy command line programs written in Rust (like `zellij`, `ripgrep`, `fd-find`, `bat`, `tealdeer`, etc.).
|
||||
|
||||
You can install a program with cargo using the command:
|
||||
|
||||
```bash
|
||||
cargo install PROGRAMNAME
|
||||
```
|
||||
|
||||
You don't need to run it with `sudo` since it installs programs for the current user only.
|
||||
It doesn't modify files not belonging to the user.
|
||||
|
||||
To update programs installed using `cargo`, you need to have `cargo-update` installed:
|
||||
|
||||
```bash
|
||||
# The package `openssl-devel` is needed to compile `cargo-update`
|
||||
sudo dnf install openssl-devel
|
||||
|
||||
# To be able to run cargo install-update -a
|
||||
cargo install cargo-update
|
||||
|
||||
# Update installed crates
|
||||
cargo install-update -a
|
||||
```
|
||||
|
||||
## curl
|
||||
|
||||
We did already use `curl`, but not yet for downloading.
|
||||
|
||||
```bash
|
||||
# Download a file into the current directory while keeping the default name of the file.
|
||||
curl -L LINK_TO_FILE -O
|
||||
|
||||
# Download a file while giving the path to save the file into
|
||||
# (notice that we are using small o now, not O)
|
||||
curl -L LINK_TO_FILE -o PATH
|
||||
```
|
||||
|
||||
`-L` tells `curl` to follow redirections (for example from `http` to `https`).
|
||||
|
||||
## xargs
|
||||
|
||||
`xargs` uses each line from the standard input (stdin) as an argument to the command specified after it.
|
||||
|
||||
Here is an example that shows the content of all files with the extension `.txt` in the current directory.
|
||||
|
||||
```bash
|
||||
ls *.txt | xargs cat
|
||||
```
|
||||
|
||||
If you have the files `fiel1.txt` and `file2.txt` in the current directory, then the command above is equivalent to just running `cat file1.txt file2.txt`.
|
||||
|
||||
## ripgrep
|
||||
|
||||
ripgrep is like `grep`, but it offers [many additional features](https://github.com/BurntSushi/ripgrep#why-should-i-use-ripgrep) and has much better performance (+ it is written in Rust 🦀).
|
||||
|
||||
Here is an example of how you can use it with regex to catch a group:
|
||||
|
||||
```console
|
||||
$ cat demo.csv
|
||||
a,b,c
|
||||
x,y,z
|
||||
1,2,3
|
||||
|
||||
$ rg '.*,(.*),.*' -r '$1' demo.csv
|
||||
b
|
||||
y
|
||||
2
|
||||
```
|
|
@ -1,14 +1,12 @@
|
|||
# Shell glue
|
||||
|
||||
When you run something in the terminal, then you are interacting with the so called _shell_.
|
||||
|
||||
The default shell on almost all Linux systems is `bash`. (We will learn about the `fish` shell later 🐠)
|
||||
|
||||
The shell has the power to glue commands together to make the impossible possible! Let's use some gluing magic!
|
||||
The shell has the power to glue commands together to make the impossible possible!
|
||||
Let's use some gluing magic!
|
||||
|
||||
## Piping
|
||||
|
||||
We will start with pipes. In the last task of the last day, you did see the usage of the symbol `|` between two separate commands.
|
||||
We will start with pipes.
|
||||
In the last task of the last day, you did see the usage of the symbol `|` between two separate commands.
|
||||
|
||||
By entering `cowsay "Hello" | lolcat`, the output of the first command `cowsay` is sent to the second command `lolcat`.
|
||||
|
||||
|
@ -16,110 +14,195 @@ By entering `cowsay "Hello" | lolcat`, the output of the first command `cowsay`
|
|||
|
||||
Many Linux commands support handling input of another command.
|
||||
|
||||
You might have seen in the manual of `wc` in day 1 that the file as an argument is only optional. How could you use `wc` without arguments?
|
||||
You might have seen in the manual of `wc` in day 1 that the file as an argument is only optional.
|
||||
How could you use `wc` without arguments?
|
||||
|
||||
You might have guessed it now, make some `wc` pipes.
|
||||
OK, I admit that the naming is not the best 🚾😂
|
||||
|
||||
OK, I admit that the naming is not the best 😂
|
||||
Let's get some data to work with.c
|
||||
To do so, we will use the command `curl` which graps content from the internet.
|
||||
|
||||
Let's get some data to work with. To do so, we will use the command `curl` which graps content from the internet.
|
||||
|
||||
Let's count the number of lines of the html file of the homepage of this book:
|
||||
Let's count the number of lines of the HTML file of the homepage of this book:
|
||||
|
||||
```console
|
||||
$ curl -s https://how-to-linux.mo8it.com | wc -l
|
||||
220
|
||||
$ curl -s https://dev-tools.mo8it.com | wc -l
|
||||
254
|
||||
```
|
||||
|
||||
The option `-s` tells `curl` to be silent and not show progress information.
|
||||
|
||||
You can see that `wc` did count the number of lines. We did just combine two completely different tools with some pipes glue!
|
||||
You can see that `wc` did count the number of lines.
|
||||
We did just combine two completely different tools using a pipe!
|
||||
|
||||
How about counting the number of lines that contain the word "Linux" on the homepage?
|
||||
|
||||
To do so, we will add a new pipe inbetween!
|
||||
|
||||
`grep` is a command that searches for matches of a specified pattern. Each line with a match is printed in a new line.
|
||||
`grep` is a command that searches for matches of a specified pattern.
|
||||
Each line with a match is printed in a new line.
|
||||
|
||||
To demonstrate `grep`, here is an usage example:
|
||||
To demonstrate `grep`, here is one usage example:
|
||||
|
||||
```
|
||||
$ curl --help | grep "silent"
|
||||
-f, --fail Fail silently (no output at all) on HTTP errors
|
||||
-s, --silent Silent mode
|
||||
```
|
||||
|
||||
We did just filter the output of the help of a command. This way, you can also search quickly for command options!
|
||||
We did just filter the output of the help message of a command.
|
||||
This is one way to search quickly for command options!
|
||||
|
||||
Back to the main example:
|
||||
|
||||
```console
|
||||
$ curl -s https://how-to-linux.mo8it.com | grep "Linux" | wc -l
|
||||
7
|
||||
$ curl -s https://dev-tools.mo8it.com | grep "Linux" | wc -l
|
||||
6
|
||||
```
|
||||
|
||||
You can see that you can use multiple pipes. This allows for almost infinite combinations!
|
||||
You can see that you can use multiple pipes.
|
||||
This allows for almost infinite combinations!
|
||||
|
||||
Being able to combine commands is the reason why many commands are simple. They do one thing and do it well! To do more, combine them!
|
||||
Being able to combine commands is the reason why many commands are simple.
|
||||
They do one thing and do it well!
|
||||
To do more, combine them!
|
||||
|
||||
This is much more flexible and powerful than a program that tries to do a lot of things.
|
||||
This is much more flexible and powerful than a program that tries to do many things at once.
|
||||
|
||||
## Input, output
|
||||
|
||||
Before going any further, we need to understand an important concept in Linux.
|
||||
|
||||
A command accepts input and generates two types of output. The input is called _standard input_. The output is split to _standard output_ and _standard error_.
|
||||
A command accepts input and generates two types of output.
|
||||
The input is called _standard input_ (**stdin**).
|
||||
The output is split to _standard output_ (**stdout**) and _standard error_ (**stderr**).
|
||||
They actually have numbers that will be relevant later:
|
||||
|
||||
The standard output has the number 1 while the standard error has the number 2.
|
||||
- 0: stdin
|
||||
- 1: stdout
|
||||
- 2: stderr
|
||||
|
||||
Normal output is sent to the standard output. Errors (and sometimes output that is not very important) are sent to the standard error.
|
||||
|
||||
You can redirect the standard output or the standard error to a file!
|
||||
Normal output is sent to the standard output.
|
||||
Errors (and sometimes output that is not important) are sent to the standard error.
|
||||
|
||||
## Redirections
|
||||
|
||||
Let's see how you can redirect the output of commands to a file.
|
||||
You can redirect the standard output or the standard error of a command to a file!
|
||||
|
||||
If you just run `curl -s https://how-to-linux.mo8it.com`, you will see the html file printed to the terminal. Let's redirect the output to a html file on your disk:
|
||||
If you just run `curl -s https://dev-tools.mo8it.com`, you will see the HTML file printed to the terminal.
|
||||
Let's redirect the output to an HTML file on the disk:
|
||||
|
||||
```console
|
||||
$ curl -s https://how-to-linux.mo8it.com > how-to-linux.html
|
||||
$ curl -s https://dev-tools.mo8it.com >dev-tools.html
|
||||
```
|
||||
|
||||
Now view the content of the new file `how-to-linux.html`. You will see the same output from the terminal without redirection.
|
||||
Now, view the content of the new file `dev-tools.html`.
|
||||
You will see the same output from the terminal without redirection.
|
||||
|
||||
Now try this command:
|
||||
Now, try this command:
|
||||
|
||||
```console
|
||||
$ curl https://non-existent-site.mo8it.com > test.html
|
||||
$ curl https://non-existent-site.mo8it.com >test.html
|
||||
(…)
|
||||
curl: (60) SSL certificate problem: self-signed certificate
|
||||
More details here: https://curl.se/docs/sslcerts.html
|
||||
|
||||
curl failed to verify the legitimacy of the server and therefore could not
|
||||
establish a secure connection to it. To learn more about this situation and
|
||||
how to fix it, please visit the web page mentioned above.
|
||||
|
||||
$ cat test.html
|
||||
```
|
||||
|
||||
You will see that the file is empty since `curl` did not find a page to show as normal output.
|
||||
The error message was displayed using `stderr`.
|
||||
|
||||
If you are using this command in a script, then it might be wise to redirect the error to a log file:
|
||||
|
||||
```console
|
||||
$ curl https://non-existent-site.mo8it.com 2> curl.log
|
||||
$ curl https://non-existent-site.mo8it.com 2>curl.log
|
||||
|
||||
$ cat curl.log
|
||||
curl: (60) SSL certificate problem: self-signed certificate
|
||||
More details here: https://curl.se/docs/sslcerts.html
|
||||
|
||||
curl failed to verify the legitimacy of the server and therefore could not
|
||||
establish a secure connection to it. To learn more about this situation and
|
||||
how to fix it, please visit the web page mentioned above.
|
||||
(…)
|
||||
```
|
||||
|
||||
You can see that the error is now not shown after running the `curl` command. It was redirected to the file `curl.log`.
|
||||
You can see that the error is not shown anymore after running the `curl` command.
|
||||
It was redirected to the file `curl.log`.
|
||||
|
||||
Did you notice the number 2 before the redirection symbol `2>`?
|
||||
|
||||
The last section did mention that the number of the standard error is 2. Therefore, 2 has to be specified to redirect the errors.
|
||||
The last section did mention that the number of the standard error is 2.
|
||||
Therefore, 2 has to be specified to redirect it.
|
||||
|
||||
If you don't specify a number, then it is equivalent to 1 which stands for the standard output. This means that `>` is equivalent to `1>`.
|
||||
If you don't specify a number, then it is equivalent to 1 which stands for the standard output.
|
||||
This means that `>` is equivalent to `1>`.
|
||||
|
||||
What if a command produces output using stdout _and_ stderr?
|
||||
Take a look at the following example:
|
||||
|
||||
```console
|
||||
$ touch some_file.txt
|
||||
|
||||
$ # Produces output to stdout and stderr.
|
||||
$ ls some_file.txt does_not_exist.txt
|
||||
ls: cannot access 'does_not_exist.txt': No such file or directory
|
||||
some_file.txt
|
||||
|
||||
$ # Redirect only stdout to a file. stderr is shown.
|
||||
$ ls some_file.txt does_not_exist.txt >stdout.txt
|
||||
ls: cannot access 'does_not_exist.txt': No such file or directory
|
||||
|
||||
$ cat stdout.txt
|
||||
some_file.txt
|
||||
|
||||
$ # Redirect only stderr to a file. stdout is shown.
|
||||
$ ls some_file.txt does_not_exist.txt 2>stderr.txt
|
||||
some_file.txt
|
||||
|
||||
$ cat stderr.txt
|
||||
ls: cannot access 'does_not_exist.txt': No such file or directory
|
||||
|
||||
$ # Redirect both stdout and stderr to different files.
|
||||
$ ls some_file.txt does_not_exist.txt >stdout.txt 2>stderr.txt
|
||||
|
||||
$ cat stdout.txt
|
||||
some_file.txt
|
||||
|
||||
$ cat stderr.txt
|
||||
ls: cannot access 'does_not_exist.txt': No such file or directory
|
||||
|
||||
$ # Redirect stdout and stderr to the same file with `&>`.
|
||||
$ ls some_file.txt does_not_exist.txt &>mixed.txt
|
||||
|
||||
$ cat mixed.txt
|
||||
ls: cannot access 'does_not_exist.txt': No such file or directory
|
||||
some_file.txt
|
||||
```
|
||||
|
||||
### Appending
|
||||
|
||||
So far, we did redirect output using the operators `>`, `1>`, `2>` and `&>`.
|
||||
But these operators overwrite the files they are redirected to if they already exist.
|
||||
If you want to append to a file instead, use the operators above but with double `>`, for example `&>>`.
|
||||
|
||||
### Discarding
|
||||
|
||||
There is a special file that you see some command redirect to: `/dev/null`.
|
||||
|
||||
This file is like a black hole.
|
||||
Everything redirected to that file is discarded.
|
||||
|
||||
For example, if you don't care about the errors that some command throughs, then you can redirected its stderr to `/dev/null` using `2>/dev/null`
|
||||
|
||||
### More details
|
||||
|
||||
We did discuss the most important cases for redirections.
|
||||
But there are some less important details like the following:
|
||||
|
||||
- `command &>filename` is equivalent to `command >filename 2>&1`, but not to `command 2>&1 >filename` because the order matters.
|
||||
- `0<filename command` or `command 0<filename` can be used to redirect a file to the stdin of a command.
|
||||
- A pipe `command1 | command2` redirects only the stdout of `command1` to the stdin of `command2`. But if you want to redirect stderr too, then you have to use `command1 2>&1 | command2`. To only redirect stderr, you have to use `command 2>&1 >/dev/null | command2` (not `command >/dev/null 2>&1 | command2` since the order matters).
|
||||
|
||||
You might have noticed how it can get complicated.
|
||||
Therefore, refer to the ["Redirections" section in the Bash reference manual](https://www.gnu.org/software/bash/manual/bash.html#Redirections) for more details.
|
||||
|
||||
Yes, the reference is about Bash, but Fish has the same behavior here.
|
||||
|
|
28
src/day_2/regex.md
Normal file
28
src/day_2/regex.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Regex
|
||||
|
||||
[**Reg**ular **ex**pressions](https://en.wikipedia.org/wiki/Regular_expression) specify a match patter in a text.
|
||||
|
||||
They can be used for example with `grep`, `rg`, `find`, `fd`, `vim`, etc.
|
||||
|
||||
Similar expressions are also used for Git (`.gitignore`) and containerization (`.containerignore`) which we will learn about later.
|
||||
|
||||
Here are some of the most important building blocks:
|
||||
|
||||
- `^`: Start of line
|
||||
- `$`: End of line
|
||||
- `()`: Group
|
||||
- `|`: Alternation
|
||||
- `[abcd]`: Character set, here `a` until `d`
|
||||
- `[a-z]`: Character range, here `a` until `z`
|
||||
- `[^b-h]`: Negated character range, here `b` to `h`
|
||||
- `.`: Any character
|
||||
- `.*`: 0 or more characters
|
||||
- `.+`: 1 or more characters
|
||||
- `\w`: Letter or number
|
||||
- `\W`: Neither letter nor number
|
||||
- `\d`: Digit
|
||||
- `\D`: Not digit
|
||||
- `\s`: Whitespace
|
||||
- `\S`: Not whitespace
|
||||
|
||||
Writing regular expressions is not easy, but there are many (online) tools that you can use to test and debug your regex.
|
30
src/day_2/shell_tricks.md
Normal file
30
src/day_2/shell_tricks.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Shell tricks
|
||||
|
||||
We will learn about two shell features that can save you a lot of typing.
|
||||
|
||||
## Expansion
|
||||
|
||||
`command PREFIX{aaa,bbb}POSTFIX` is expanded to `command PREFIXaaaPOSTFIX PREFIXbbbPOSTFIX`.
|
||||
It also works with more than two arguments inside the curly brackets `{}`.
|
||||
|
||||
This is especially useful when dealing with paths.
|
||||
Here are some examples:
|
||||
|
||||
- `mkdir -p dir/sub{1,2,3}` ➡️ `mkdir -p dir/sub1 dir/sub2 dir/sub3`
|
||||
- `touch dir/sub1/file{1,2}.txt` ➡️ `touch dir/sub1/file1.txt dir/sub1/file2.txt`
|
||||
- `cp dir/sub1/file1.txt{,.bak}` ➡️ `cp dir/sub1/file1.txt dir/sub1/file1.txt.bak`
|
||||
|
||||
> **Note**: The additional extension `.bak` is sometimes used for **ba**c**k**ups.
|
||||
|
||||
## Globbing
|
||||
|
||||
The globbing asterisk `*` is used for expanding to every possible path in a directory.
|
||||
|
||||
It is best explained using examples:
|
||||
|
||||
- `cat *.sh` prints the content of all files ending with `.sh` in the current directory.
|
||||
- `mv dir1/* dir2` moves all **visible** files and directories from `dir1` to `dir2`.
|
||||
- `mv dir1/.* dir2` moves all **hidden** files and directories from `dir1` to `dir2`.
|
||||
- `mv dir1/{,.}* dir2` expands to `mv dir1/* dir1/.* dir2` and therefore moves all _visible and hidden_ files and directories from `dir1` to `dir2`.
|
||||
|
||||
> **Note**: Fish can expand a globbing when pressing `Tab` after an asterisk `*` (but it is not always helpful).
|
|
@ -2,99 +2,93 @@
|
|||
|
||||
Organize the files and directories of your tasks in separate directories!
|
||||
|
||||
## Task: Job scheduler
|
||||
## Task: Cargo 📦️
|
||||
|
||||
> Warning ⚠️ : This task is not an easy task. Don't give up quickly and ask for help if you don't get further!
|
||||
Use `cargo` to install the following crates:
|
||||
|
||||
In this task, we want to write our own job scheduler.
|
||||
- `cargo-update`
|
||||
- `tealdeer`
|
||||
|
||||
Understanding how job schedulers work is important when you are working on a computer cluster.
|
||||
It might take a long time to compile everything.
|
||||
|
||||
Computer clusters are shared by many users. Therefore, running jobs on a cluster has to be scheduled to make sure that the resources are shared probably.
|
||||
`cargo-update` should be installed to be able to run `cargo install-update -a` to update all installed crates.
|
||||
Try running the command.
|
||||
But you should not find any updates since you did just install the crates.
|
||||
|
||||
In this task, we will keep it simple. No aspects of multiple users or any optimizations.
|
||||
The crate `tealdeer` provides you with the program `tldr`.
|
||||
|
||||
We want to be able to submit a job as a single script (without any dependencies). The submitted scripts should run one after the another to save CPU usage for example.
|
||||
|
||||
We will use the program `inotifywait`. This program can monitor a directory and notify on changes within this directory.
|
||||
|
||||
1. Find out which package installs `inotifywait` and install it.
|
||||
1. Read the manual of `inotifywait` for a better understanding of what it does.
|
||||
1. Find out how to tell `inotifywait` to keep monitoring a directory and not exit after the first event.
|
||||
1. Find out what events mean in the context of `inotifywait`.
|
||||
1. Create a new directory called `jobs` to be monitored.
|
||||
1. Create a new directory called `logs` that will be used later.
|
||||
1. Run `inotifywait` while telling it to monitor the directory `jobs`. Leave the command running in a terminal and open a second terminal (tab) to continue the work in.
|
||||
1. Create a file **outside** of the directory `jobs` and then copy it to the directory `jobs`.
|
||||
1. Go back to the first terminal and see the output of `inotifywait` was.
|
||||
1. Based on the output, choose an event that you want to listen to with `inotifywait` that tells you when a file is _completely_ added to the directory `jobs`. Use the manual to read more about specific events.
|
||||
1. Find an option that lets you tell `inotifywait` to only notify when the chosen event occurs.
|
||||
1. Find an option that lets you format the output of the notification of `inotifywait`. Since we only listen on one event and monitor only one directory, an output that shows only the name of the new file should be enough.
|
||||
1. Enter the command that you have until now in a script. Now extend it by using a `while` loop that continuously listens on the notifications of `inotifywait`. Use the following snippet while replacing the sections with `(...)`:
|
||||
```bash
|
||||
inotifywait (...) | while read FILENAME
|
||||
do
|
||||
(...)
|
||||
done
|
||||
```
|
||||
1. After a notification, the body of the `while` loop should first print the name of the script that was added. From now on, we only want to add scripts to the `jobs` directory.
|
||||
1. After printing the script name, run the script!
|
||||
1. Save the standard output and standard error of the script into two separate files in the `logs` directory. If the name of the script is `job.sh` for example, then the output should be in the files `logs/job.sh.out` and `logs/job.sh.err`.
|
||||
|
||||
##### Tipps:
|
||||
|
||||
- Take a look at the examples from the sections of this day.
|
||||
- Take care of permissions.
|
||||
|
||||
If you have extra time, read about the command `screen` in the internet. `screen` allows you to run commands in the background. This way, you don't need two terminals.
|
||||
|
||||
## Task: Job submitter
|
||||
|
||||
In this task we will write a small script that lets us submit a job script to the scheduler from the last task.
|
||||
|
||||
The script should take the path to the job script as a single required argument.
|
||||
|
||||
The script should then copy the job script to the directory `jobs` while adding the time and date to the beginning of the name of the job script in the `jobs` directory.
|
||||
|
||||
Read the manual of the command `date` to know how to get the time and date in the following format: `2022-08-22T20:00:00+00:00`.
|
||||
|
||||
If the name of the job script is `job.sh` for example, the job script should be named `2022-08-22T20:00:00+00:00_job.sh` in the `jobs` directory.
|
||||
|
||||
Use variables to write the script to make it more understandable.
|
||||
|
||||
#### Help
|
||||
|
||||
To save the output of a command into a variable, use you have to use the following syntax:
|
||||
Run `tldr --update`. Now run the following two commands:
|
||||
|
||||
```bash
|
||||
DATE=$(date ...)
|
||||
tldr dnf
|
||||
tldr apt
|
||||
```
|
||||
|
||||
Replace `...` with your code.
|
||||
It should be obvious to you what `tldr` does after you run the commands above and read their output.
|
||||
Try it with other programs than `dnf` and `apt`!
|
||||
|
||||
To read the `n`-th argument that is provided to a script you write, you have to use `$n`.
|
||||
🟢 Run `cs task cargo`.
|
||||
|
||||
Example script called `arg.sh`:
|
||||
## Task: Fish configuration
|
||||
|
||||
```bash
|
||||
#!/usr/bin/bash
|
||||
Disable the default greeting by Fish everytime you start it.
|
||||
|
||||
echo "The first argument is: $1"
|
||||
```
|
||||
The configuration file should be `~/.config/fish/config.fish`.
|
||||
|
||||
When you run this script with an argument:
|
||||
🟢 Run `cs task fish-config`.
|
||||
|
||||
```console
|
||||
$ ./arg.sh "Hello"
|
||||
The first argument is: Hello
|
||||
```
|
||||
## Task: Fish in Zellij
|
||||
|
||||
## Task: Submit a job
|
||||
Configure Fish as the default Zellij shell.
|
||||
|
||||
Write a small scripts of your choice that require a long time to run and submit them using the script from the last task. Make sure that the scheduler is running in the background.
|
||||
🟢 Run `cs task fish-in-zellij`.
|
||||
|
||||
You can use the command `sleep` to simulate a job that needs long time to run.
|
||||
## Task: Curly line count.
|
||||
|
||||
Submit your job script multiple times and take a look at the terminal that is running the scheduler to make sure that the job scripts are running one after the other.
|
||||
Use `curl` to fetch this file:
|
||||
[https://codeberg.org/mo8it/collective-score/raw/commit/4ff0cd6f871e4a17a7ecd36d7d01ca7713c11ca1/Cargo.toml](https://codeberg.org/mo8it/collective-score/raw/commit/4ff0cd6f871e4a17a7ecd36d7d01ca7713c11ca1/Cargo.toml)
|
||||
|
||||
Verify the redirection of the standard output and standard error in the directory `logs`.
|
||||
You don't have to save it to disk!
|
||||
|
||||
Now, pipe the output of `curl` into `wc` (with some option) to count the number of characters.
|
||||
|
||||
🟢 Pipe the number of characters into `cs task curly-wc`.
|
||||
|
||||
### Hints
|
||||
|
||||
- `curl … | … | cs task curly-wc`
|
||||
|
||||
## Task: IO redirections
|
||||
|
||||
I placed a program called `mixed-output-generator` on your system.
|
||||
|
||||
If you run it, it will create the file `dont_overwrite.txt` in your current directory and output random text to both stdout and stderr.
|
||||
|
||||
First, run it while writing its standard output to the file `normal_output.log` and its standard error to the file `errors.log`.
|
||||
|
||||
After that, run it again while writing both standard output and standard error to the same file `verbose.log`.
|
||||
|
||||
Now, run it for the last time while _appending_ the standard output to the file `dont_overwrite.txt` that it creates.
|
||||
|
||||
🟢 Run `cs task redirections`.
|
||||
|
||||
## Task: Gimme that PDF 😈
|
||||
|
||||
The following website uses a PDF file but it doesn't let you download it:
|
||||
[https://knowunity.de/knows/biologie-neurobiologie-1c6a4647-4707-4d1b-8ffb-e7a750582921](https://knowunity.de/knows/biologie-neurobiologie-1c6a4647-4707-4d1b-8ffb-e7a750582921)
|
||||
|
||||
Now that you are kind of a "hacker", you want to use a workaround 😈
|
||||
|
||||
Use `curl`, pipes `|`, `rg` (ripgrep) and `xargs` to parse the HTML of the web page, extract the link to the PDF file and download it.
|
||||
Save the PDF file using the name `knowunity.pdf`.
|
||||
|
||||
The link to the PDF file starts with `https://` and ends with `.pdf`.
|
||||
|
||||
After that it works, write a script that asks the user for the link to a document at [knowunity.de](https://knowunity.de) and downloads the PDF file.
|
||||
|
||||
🟢 Run `cs task pdf` in the same directory where the downloaded PDF file is.
|
||||
|
||||
### Hints
|
||||
|
||||
- To capture a regex group using ripgrep, you should use `rg '.*(GROUP_PATTERN).*' -r '$1'` after replacing `GROUP_PATTERN` with the pattern that you are looking for.
|
||||
- If you find the link of the file using `rg`, copy it into a browser to make sure that you got a correct link to a PDF file.
|
||||
|
|
132
src/day_2/terminal_upgrade.md
Normal file
132
src/day_2/terminal_upgrade.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
# Terminal upgrade
|
||||
|
||||
## Fish
|
||||
|
||||
When you run something in the terminal, then you are interacting with a so called _shell_.
|
||||
|
||||
The default shell on almost all Linux systems is `bash`.
|
||||
Therefore, you should be familiar with it.
|
||||
|
||||
But if you want a modern terminal experience instead of that of the 90s, then you should use the [Fish shell](https://fishshell.com/) 🐠
|
||||
The **f**riendly **i**nteractive **sh**ell".
|
||||
|
||||
Bash offers basic (auto)completion, but Fish takes it to the next level!
|
||||
|
||||
Type `ls ~/` (without hitting `Enter` yet) and press `Tab` twice in Bash.
|
||||
Bash will just show you all possible completion options.
|
||||
But Fish will let you cycle through these options with `Tab` and `Shift + Tab` to choose one!
|
||||
This doen't only work with paths, but also for commands and even command options!
|
||||
|
||||
In Bash, you you can cycle through your command history using the up and down arrow keys.
|
||||
But in Fish, you can also start a command and _then_ cycle through your history that has the same **prefix** with the up and down arrow keys!
|
||||
|
||||
It will also automatically give you suggestions to what you type based on history and completion!
|
||||
These autosuggestions are showed as dimmed text to the right of your input.
|
||||
To use that suggestion, you can just press the right arrow key (or `Ctrl + e`)!
|
||||
|
||||
Fish also supports true color synatx highlighting 🌈
|
||||
|
||||
Colors are not only fancy, but they can also be very helpful!
|
||||
If you start typing a command with a program that does not exit (like `echoo` instead of `echo` for example), Fish will highlight that program in red!
|
||||
Otherwise, the program is highlighted in blue!
|
||||
This gives you rapid feedback while typing 🤩
|
||||
|
||||
You can also configure Fish in the file `~/.config/fish/config.fish`.
|
||||
Here is any example
|
||||
|
||||
```bash
|
||||
if status is-interactive
|
||||
# Disable the greeting message.
|
||||
set -g fish_greeting
|
||||
|
||||
# Abbreviations
|
||||
|
||||
# Prevent overriding files/directories by accident when using `cp` and `mv`.
|
||||
abbr -ag cp "cp -i"
|
||||
abbr -ag mv "mv -i"
|
||||
# Use `trash` by default instead of `rm`.
|
||||
abbr -ag rm "trash"
|
||||
# Use another shortcut for when you really want to delete instead of moving into a trash.
|
||||
abbr -ag rmr "rm -r"
|
||||
# Set some default options.
|
||||
abbr -ag rsync "rsync -Lahz"
|
||||
|
||||
# Alias
|
||||
# Use `bat` instead of `cat` for colored output!
|
||||
alias cat "bat"
|
||||
|
||||
# Functions
|
||||
function demo
|
||||
echo "Hallo from the demo function!"
|
||||
echo "Arguments taken: $argv"
|
||||
echo "First argument: $argv[1]"
|
||||
end
|
||||
# Well will learn more about arguments later.
|
||||
end
|
||||
```
|
||||
|
||||
Use **abbreviations** when you want your input to be replaced while typing with the ability to modify the replacement.
|
||||
**Aliases** should only be used if you are sure that the two commands are equivalent for you.
|
||||
|
||||
## Zellij
|
||||
|
||||
Now that we have a fancy shell, what can be even better than a fancy terminal?
|
||||
|
||||
Multiple fancy terminals!
|
||||
|
||||
Let me introduce you to [Zellij](https://zellij.dev/about/), a modern [terminal multiplexer](https://en.wikipedia.org/wiki/Terminal_multiplexer) written in Rust 🦀
|
||||
|
||||
It offers panes, tabs and even floating panes!
|
||||
|
||||
Start Zellij by running `zellij` and take a look at the shortcuts at the bottom.
|
||||
It is pretty user friendly!
|
||||
|
||||
Press `Ctrl + p` to go into the _pane mode_ where you can select one of the shortcuts that newly appeared at the bottom.
|
||||
Press `n` in the _pane mode_ to create a **new pane**!
|
||||
|
||||
You can change the focused pane by clicking on another pane.
|
||||
But there are also keyboard shortcuts for that.
|
||||
|
||||
Normally, you can press `Ctrl + t` to go into the _tab mode_ where you can press `n` to create a new tab.
|
||||
But if you are using the terminal in the browser provided during the course, then you might not be able to press `Ctrl + t` without opening a _browser_ new tab.
|
||||
|
||||
Therefore, we have to change that keybinding to something that does not conflict with the browser.
|
||||
To do so, create the directory `~/.config/zellij` and place the file `config.kdl` in it with the following content:
|
||||
|
||||
```kdl
|
||||
keybinds {
|
||||
normal {
|
||||
bind "Ctrl b" { SwitchToMode "tab"; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration uses the shortcut `Ctrl + b` instead of `Ctrl + t` to enter the _tab mode_.
|
||||
|
||||
Now, use that new shortcut followed by `n` to create a new tab.
|
||||
The easiest way to change the focused tab is by clicking on the tab label at the top.
|
||||
But there are also keyboard shortcuts for that.
|
||||
|
||||
Try detaching the session by pressing `Ctrl + o` followed by `d`.
|
||||
This will lead to exiting Zellij, but your session (open panes and tabs) is not gone!
|
||||
|
||||
To attach back to that session, run `zellij attach` or `zellij a` for short.
|
||||
Fancy, right?
|
||||
You can just detach a session you working in, do something else and attach back to continue from where you have left it 😃
|
||||
|
||||
There is still something annoying:
|
||||
Everytime you start zellij, you have to start fish afterwards.
|
||||
Wouldn't it be awesome to have Fish as the default shell in Zellij?
|
||||
|
||||
Let's make it the default one!
|
||||
Add the following line to the beinning of the configuration file `~/.config/zellij/config.kdl`:
|
||||
|
||||
```kdl
|
||||
default_shell "/usr/bin/fish"
|
||||
```
|
||||
|
||||
You can check that this is the path of the program `fish` on your system by running `which fish`.
|
||||
|
||||
We are not done with upgrading our terminal workspace.
|
||||
We will introduce more modern tools later.
|
||||
But this should be enough for now 🙂
|
3
src/day_3/clis_of_the_day.md
Normal file
3
src/day_3/clis_of_the_day.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# CLIs of the day
|
||||
|
||||
## typos
|
|
@ -1,276 +1 @@
|
|||
# Notes
|
||||
|
||||
## Shell tricks
|
||||
|
||||
### Expansion
|
||||
|
||||
```bash
|
||||
# mkdir -p dir/sub1 dir/sub2
|
||||
mkdir -p dir/sub{1,2}
|
||||
|
||||
# touch dir/sub1/file1.txt dir/sub1/file2.txt
|
||||
touch dir/sub1/file{1,2}.txt
|
||||
|
||||
# cp dir/sub1/file1.txt dir/sub1/file1.txt
|
||||
cp dir/sub1/file1.txt{,.bak}
|
||||
```
|
||||
|
||||
### Globbing
|
||||
|
||||
```bash
|
||||
# Print content of all files ending with `.sh`
|
||||
cat *.sh
|
||||
|
||||
# Move all files visible files and directories from dir1 to dir2
|
||||
mv dir1/* dir2
|
||||
|
||||
# Move all hidden files and directories from dir1 to dir2
|
||||
mv dir1/.* dir2
|
||||
|
||||
# Move all visible and hidden files and directories from dir1 to dir2
|
||||
# mv dir1/* dir1/.* dir2
|
||||
mv dir1/{,.}* dir2
|
||||
```
|
||||
|
||||
## Cargo
|
||||
|
||||
```bash
|
||||
# Install. openssl-devel needed for cargo-update
|
||||
sudo dnf install cargo openssl-devel
|
||||
|
||||
# To be able to run cargo install-update -a
|
||||
cargo install cargo-update
|
||||
|
||||
# Install crate (package)
|
||||
cargo install CRATENAME
|
||||
|
||||
# Update installed crates
|
||||
cargo install-update -a
|
||||
```
|
||||
|
||||
## bashrc
|
||||
|
||||
You write at the end of `~/.bashrc`.
|
||||
|
||||
### PATH
|
||||
|
||||
Add Cargo binaries to `PATH`:
|
||||
|
||||
```bash
|
||||
export PATH="$PATH:$HOME/.cargo/bin"
|
||||
```
|
||||
|
||||
### Alias
|
||||
|
||||
```bash
|
||||
alias rm="trash"
|
||||
```
|
||||
|
||||
## Fish
|
||||
|
||||
`~/.config/fish/config.fish`
|
||||
|
||||
```bash
|
||||
if status is-interactive
|
||||
# Disable greeting message
|
||||
set -g fish_greeting
|
||||
|
||||
# Abbreviations
|
||||
abbr -ag cp "cp -i"
|
||||
abbr -ag mv "mv -i"
|
||||
abbr -ag rmr "rm -r"
|
||||
abbr -ag rm "trash"
|
||||
abbr -ag rsync "rsync -Lahz"
|
||||
|
||||
# Aliases
|
||||
alias cat "bat"
|
||||
alias lg "lazygit"
|
||||
|
||||
# Functions
|
||||
function demo
|
||||
echo "Hallo from demo function!"
|
||||
echo "Arguments taken: $argv"
|
||||
echo "First argument: $argv[1]"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Add to path:
|
||||
|
||||
```bash
|
||||
fish_add_path ~/.cargo/bin
|
||||
fish_add_path ~/.local/bin
|
||||
```
|
||||
|
||||
## SSH
|
||||
|
||||
### Setup host
|
||||
|
||||
In `~/.ssh/config`
|
||||
|
||||
```
|
||||
Host HOST
|
||||
HostName SERVERIP
|
||||
User SERVERUSER
|
||||
```
|
||||
|
||||
### Generate key pair
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "COMMENT"
|
||||
```
|
||||
|
||||
Leave blank to take default for the prompt `Enter file in which to save the key (/home/USERNAME/.ssh/id_ed25519)`.
|
||||
|
||||
Then enter a passphrase for your key. **You should not leave it blank!**
|
||||
|
||||
### Add public key to server
|
||||
|
||||
```bash
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub HOST
|
||||
```
|
||||
|
||||
### Connect
|
||||
|
||||
```bash
|
||||
ssh HOST
|
||||
```
|
||||
|
||||
### Config on server
|
||||
|
||||
**Very important for security!** Only after adding the public key to the server!
|
||||
|
||||
> WARNING ⚠️ :
|
||||
>
|
||||
> Verify that you are only asked for the passphrase of the SSH key before continuing in this section!
|
||||
>
|
||||
> If you are asked for the password of the user on the server when connecting, then the authentication with a key did not work. Therefore, don't set `PasswordAuthentication no`! Fix the issue with the key authentication first. **Otherwise, you will be locked out of the server!** ⚠️
|
||||
|
||||
In `/etc/ssh/sshd_config` on the server:
|
||||
|
||||
Uncomment line with `PasswordAuthentication` and set it to `PasswordAuthentication no`
|
||||
|
||||
Save and exit, then run:
|
||||
|
||||
```bash
|
||||
sudo systemctl restart sshd
|
||||
```
|
||||
|
||||
If you are locked out after running this command, then you did not take the warning above seriously!
|
||||
|
||||
### Copy files
|
||||
|
||||
From server:
|
||||
|
||||
```bash
|
||||
scp HOST:SRC_PATH DEST_PATH
|
||||
```
|
||||
|
||||
To server:
|
||||
|
||||
```bash
|
||||
scp SRC_PATH HOST:DEST_PATH
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `-r`, `--recursive`: For directories.
|
||||
|
||||
## Rsync
|
||||
|
||||
From server:
|
||||
|
||||
```bash
|
||||
rsync -Lahz HOST:SRC_PATH DEST_PATH
|
||||
```
|
||||
|
||||
To server:
|
||||
|
||||
```bash
|
||||
rsync -Lahz SRC_PATH HOST:DEST_PATH
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `-a`, `--archive`: Set of useful options to preserve permissions, use recursive mode, etc.
|
||||
- `-h`, `--human-readable`: Output number in a human-readable format.
|
||||
- `-z`, `--compress`: Use compression.
|
||||
- `--partial`: Continue after interruption.
|
||||
- `-L`, `--copy-links`: Copy links.
|
||||
- `-v`, `--verbose`: Show more infos.
|
||||
- `--delete`: Delete files from `DEST_PATH` if they don't exist on `SRC_PATH` anymore. **Use with caution!!!**
|
||||
|
||||
## Systemd
|
||||
|
||||
Check status of a service:
|
||||
|
||||
```bash
|
||||
sudo systemctl status SERVICENAME
|
||||
```
|
||||
|
||||
Enable service:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable SERVICENAME
|
||||
```
|
||||
|
||||
Start service:
|
||||
|
||||
```bash
|
||||
sudo systemctl start SERVICENAME
|
||||
```
|
||||
|
||||
Enable and start service at the same time:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now SERVICENAME
|
||||
```
|
||||
|
||||
Disable service:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable SERVICENAME
|
||||
```
|
||||
|
||||
Stop service:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop SERVICENAME
|
||||
```
|
||||
|
||||
Disable and stop service at the same time:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now SERVICENAME
|
||||
```
|
||||
|
||||
## Firewalld
|
||||
|
||||
Install and enable firewalld:
|
||||
|
||||
```bash
|
||||
sudo dnf install firewalld
|
||||
sudo systemctl enable --now firewalld
|
||||
```
|
||||
|
||||
View open ports and services:
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --list-all
|
||||
```
|
||||
|
||||
Open ports 80 (http) and 443 (https):
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --add-service http
|
||||
sudo firewall-cmd --add-service https
|
||||
sudo firewall-cmd --runtime-to-permanent
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --add-port 80/tcp
|
||||
sudo firewall-cmd --add-port 443/tcp
|
||||
sudo firewall-cmd --runtime-to-permanent
|
||||
```
|
||||
|
|
|
@ -2,108 +2,22 @@
|
|||
|
||||
Do the tasks in the given order! They depend on each other.
|
||||
|
||||
## Task 1: Cargo
|
||||
## Task: SSH key
|
||||
|
||||
Use `cargo` to install the following crates:
|
||||
## Task: GitUI
|
||||
|
||||
- cargo-update
|
||||
- tealdeer
|
||||
## Task: Lazygit
|
||||
|
||||
It might take a long time to compile everything.
|
||||
In this task, you will learn using Lazygit.
|
||||
|
||||
Add `$HOME/.cargo/bin` to your `PATH`.
|
||||
|
||||
`cargo-update` should be installed to be able to run `cargo install-update -a` to update all installed crates. Try running the command. But you should not find any updates since you did just install the crates.
|
||||
|
||||
The crate `tealdeer` provides you with the program `tldr`.
|
||||
|
||||
Run `tldr --update`. Now run the following two commands:
|
||||
|
||||
```bash
|
||||
tldr dnf
|
||||
tldr apt
|
||||
```
|
||||
|
||||
It should be obvious to you what `tldr` does after you run the commands. Try it with other programs than `dnf` and `apt`!
|
||||
|
||||
## Task 2: SSH
|
||||
|
||||
Generate a SSH key pair and send me the public key per email: mo8it@proton.me
|
||||
|
||||
Enter a passphrase while generating the key pair!
|
||||
|
||||
Don't send me the private key!!! **You should never send your private SSH keys to anyone!**
|
||||
|
||||
The public key ends with `.pub`.
|
||||
|
||||
I will then append your public key to `~/.ssh/authorized_keys` on the server that we will use in the next tasks. After I add your public key, you will be able to login to the server and do the next tasks.
|
||||
|
||||
Create the file `~/.ssh/config` and add the server as a host with the name `linux-lab`.
|
||||
|
||||
Enter this IP: 45.94.58.19
|
||||
Enter this user: admin
|
||||
|
||||
After that I add you public key, connect to the server using the host name that you did enter in `~/.ssh/config` which should be `linux-lab`.
|
||||
|
||||
## Task 3: User creation
|
||||
|
||||
1. Create a user for you on the server after connecting with SSH. To do so, run:
|
||||
```bash
|
||||
sudo useradd USERNAME
|
||||
```
|
||||
|
||||
Replace `USERNAME` with your name.
|
||||
|
||||
1. Now set a password for the new user:
|
||||
|
||||
```bash
|
||||
sudo passwd USERNAME
|
||||
```
|
||||
1. For the new user to be able to use `sudo`, it has to be added to the `wheel` group:
|
||||
|
||||
```bash
|
||||
sudo usermod -aG wheel USERNAME
|
||||
```
|
||||
|
||||
`-aG` stands for _append to group(s)_.
|
||||
|
||||
(On Debian based distros, the user should be added to the `sudo` group instead of `wheel`.)
|
||||
|
||||
1. Now, change your user to the new user:
|
||||
|
||||
```bash
|
||||
sudo su USERNAME
|
||||
```
|
||||
|
||||
You will see that the user name did change in the prompt.
|
||||
|
||||
1. Run the following command for verification:
|
||||
|
||||
```bash
|
||||
whoami
|
||||
```
|
||||
|
||||
It should not output "admin"!
|
||||
|
||||
Yes, the command is called `whoami`. Linux is kind of philosophical 🤔
|
||||
|
||||
1. Now, verify that you can run `sudo` as the new user:
|
||||
|
||||
```bash
|
||||
sudo whoami
|
||||
```
|
||||
|
||||
You should see "root" as output because `sudo` runs a command as the `root` user.
|
||||
|
||||
1. `cd` to the home directory of the new user.
|
||||
1. Make sure that you are in the home directory of the new user! Run `pwd` to verify that you are NOT in `/home/admin`. **`PLEASE DON'T TOUCH /home/admin/.ssh`** ⚠️ . Now, create the directory `~/.ssh` in the home directory of the new user. Change the permissions of `~/.ssh` such that only the user has read, write and execution permissions. _group_ and _others_ should have no permissions for `~/.ssh`!
|
||||
1. Create the file `authorized_keys` inside `~/.ssh`. Only the user should have read and write permissions for the file. _group_ and _others_ should have no permissions for the file!
|
||||
1. Copy the content of your public key file (with `.pub` as extension) to this file. It should be one line! Then save the file.
|
||||
1. Logout from the server. Go to `~/.ssh/config` that you did write at the beginning of this task. Change the user for the host `linux-lab` from `admin` to `USERNAME` where `USERNAME` is the name of the new user that you did create on the server.
|
||||
1. Try to connect using the host name again. If you did everything right, you should be connected and be the user that you did create. Run `whoami` to verify that the output is not "admin".
|
||||
|
||||
## Task 4: File transfer
|
||||
|
||||
Use `scp` and then `rsync` to transfer the files that you did create during the course to the server `linux-lab`.
|
||||
|
||||
Do you notice any differences between the two commands?
|
||||
1. Install Lazygit. Follow the instructions for Fedora on [github.com/jesseduffield/lazygit](https://github.com/jesseduffield/lazygit). You might need to install `dnf-plugins-core` first to be able to add something via _COPR_ (Cool Other Package Repo).
|
||||
1. Put the files and directories that you did create during the course into a directory.
|
||||
1. Go into the directory.
|
||||
1. Run `lazygit`. It will initialize a git repository in this directory if none already exists. Confirm the initialization. Starting with now, every git operation should be done in Lazygit.
|
||||
1. Add everything as staged.
|
||||
1. Commit.
|
||||
1. Create a repository on [git.mo8it.com](https://git.mo8it.com).
|
||||
1. Add the new remote.
|
||||
1. Push to the new remote.
|
||||
1. Modify a file (or multiple files).
|
||||
1. Verify your changes in Lazygit, stage, commit and push.
|
||||
|
|
64
src/day_4/clis_of_the_day.md
Normal file
64
src/day_4/clis_of_the_day.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# CLIs of the day
|
||||
|
||||
## cut
|
||||
|
||||
Demo file `demo.txt`:
|
||||
|
||||
```
|
||||
here,are,some
|
||||
comma,separated,values
|
||||
de mo,file,t x t
|
||||
```
|
||||
|
||||
```bash
|
||||
# Get the N-th column by using SEP as separator
|
||||
cut -d SEP -f N FILE
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ cut -d "," -f 1 demo.txt
|
||||
here
|
||||
comma
|
||||
de mo
|
||||
```
|
||||
|
||||
You can also pipe into `cut` instead of specifying `FILE`.
|
||||
|
||||
## sed
|
||||
|
||||
```bash
|
||||
# Substitute
|
||||
sed 's/OLD/NEW/g' FILE
|
||||
|
||||
# Delete line that contains PATTERN
|
||||
sed '/PATTERN/d' FILE
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ sed 's/values/strings/g' demo.txt
|
||||
here,are,some
|
||||
comma,separated,strings
|
||||
de mo,file,t x t
|
||||
$ sed '/separated/d' demo.txt
|
||||
here,are,some
|
||||
de mo,file,t x t
|
||||
```
|
||||
|
||||
You can also pipe into `sed` instead of specifying `FILE`.
|
||||
|
||||
When you specify `FILE`, you can use the option `-i` to operate _inplace_. This means that the file is modified directly.
|
||||
|
||||
## find
|
||||
|
||||
```bash
|
||||
# Find everything ending with `.sh` of type file `f` using globbing
|
||||
find . -type f -name '*.sh'
|
||||
|
||||
|
||||
# Using regex
|
||||
find . -type f -regex '.+\.sh'
|
||||
```
|
|
@ -1,61 +1,120 @@
|
|||
# Notes
|
||||
|
||||
## Podman
|
||||
## Vim
|
||||
|
||||
- `:q`: Quit (**very important!**)
|
||||
- `:q!`: Quit without saving (**important!**)
|
||||
- `j`: Down
|
||||
- `k`: Up
|
||||
- `h`: Left
|
||||
- `l`: Right
|
||||
- `i`: Insert at left of cursor
|
||||
- `a`: Insert at right of cursor (append)
|
||||
- `I`: Insert at beginning of line
|
||||
- `A`: Append to end of line
|
||||
- `Esc`: Normal mode
|
||||
- `w`: Go to beginning of next word
|
||||
- `b`: Go to beginning of last word
|
||||
- `e`: Go to end of word
|
||||
- `gg`: Go to beginning of file
|
||||
- `G`: Go to end of file
|
||||
- `0`: Go to beginning of line
|
||||
- `$`: Go to end of line
|
||||
- `%`: Go to the other bracket
|
||||
- `u`: Undo
|
||||
- `Ctrl+r`: Redo
|
||||
- `:h`: Help
|
||||
- `:w`: Write buffer
|
||||
- `:wq`: Write buffer and exit
|
||||
- `/PATTERN`: Search
|
||||
- `n`: Next match
|
||||
- `N`: Previous match
|
||||
- `*`: Next match of the word under cursor
|
||||
- `o`: Add line below and enter insert mode
|
||||
- `O`: Add line above and enter insert mode
|
||||
- `v`: Start selection
|
||||
- `V`: Block selection
|
||||
- `y`: Yank (copy)
|
||||
- `p`: Paste
|
||||
- `x`: Delete one character
|
||||
- `dw`: Delete word
|
||||
- `dd`: Delete line
|
||||
- `D`: Delete util end of line
|
||||
- `cw`: Change word
|
||||
- `cc`: Change line
|
||||
- `C`: Change until end of line
|
||||
- `di(`: Delete inside bracket `(`. Can be used with other brackets and quotation marks.
|
||||
- `da(`: Same as above but delete around, not inside.
|
||||
- `ci(`: Change inside bracket `(`. Can be used with other brackets and quotation marks.
|
||||
- `ca(`: Same as above but delete around, not inside.
|
||||
- `:%s/OLD/NEW/g`: Substitute `OLD` with `NEW` in the whole file (with regex)
|
||||
- `:%s/OLD/NEW/gc`: Same as above but ask for confirmation for every substitution
|
||||
- `:N`: Go line number `N`
|
||||
- `.`: Repeat last action
|
||||
- `<` and `>`: Indentation
|
||||
- `q`: Start recording a macro (followed by macro character)
|
||||
|
||||
## Symlinks
|
||||
|
||||
```bash
|
||||
# Search for image
|
||||
podman search python
|
||||
# Soft link
|
||||
ln -s SRC_PATH DEST_PATH
|
||||
|
||||
# Pull image
|
||||
podman pull docker.io/library/python:latest
|
||||
|
||||
# See pulled images
|
||||
podman images
|
||||
|
||||
# Run container and remove it afterwards
|
||||
podman run -it --rm docker.io/library/python:latest bash
|
||||
|
||||
# Create network
|
||||
podman network create NETWORKNAME
|
||||
|
||||
# Create container
|
||||
podman create \
|
||||
--name CONTAINERNAME \
|
||||
--network NETWORKNAME \
|
||||
-e ENVVAR="Some value for the demo environment variable" \
|
||||
--tz local \
|
||||
docker.io/library/python:latest
|
||||
|
||||
# Start container
|
||||
podman start CONTAINERNAME
|
||||
|
||||
# Enter a running container
|
||||
podman exec -it CONTAINERNAME bash
|
||||
|
||||
# Stop container
|
||||
podman stop CONTAINERNAME
|
||||
|
||||
# Generate systemd files
|
||||
podman generate systemd --new --files --name CONTAINERNAME
|
||||
|
||||
# Create directory for user's systemd services
|
||||
mkdir -p ~/.config/systemd/user
|
||||
|
||||
# Place service file
|
||||
mv container-CONTAINERNAME.service ~/.config/systemd/user
|
||||
|
||||
# Activate user's service (container)
|
||||
systemctl --user enable --now container-CONTAINERNAME
|
||||
# Hard link
|
||||
ln SRC_PATH DEST_PATH
|
||||
```
|
||||
|
||||
Keep user's systemd services live after logging out:
|
||||
## Python scripting
|
||||
|
||||
```bash
|
||||
sudo loginctl enable-linger USERNAME
|
||||
```python-repl
|
||||
>>> import subprocess
|
||||
>>> proc = subprocess.run(["ls", "student"])
|
||||
(...)
|
||||
CompletedProcess(args=['ls', '/home/student'], returncode=0)
|
||||
>>> proc.returncode
|
||||
0
|
||||
>>> proc = subprocess.run("ls /home/student", shell=True)
|
||||
(...)
|
||||
CompletedProcess(args='ls /home/student', returncode=0)
|
||||
>>> proc = subprocess.run("ls /home/student", shell=True, capture_output=True, text=True)
|
||||
CompletedProcess(args='ls /home/student', returncode=0, stdout='(...)', stderr='')
|
||||
>>> proc.stdout
|
||||
(...)
|
||||
>>> proc.stderr
|
||||
>>> proc = subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True)
|
||||
CompletedProcess(args='ls /home/nonexistent', returncode=2, stdout='', stderr="ls: cannot access '/home/nonexistent': No such file or directory\n")
|
||||
>>> proc = subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True, check=True)
|
||||
---------------------------------------------------------------------------
|
||||
CalledProcessError Traceback (most recent call last)
|
||||
Input In [8], in <cell line: 1>()
|
||||
----> 1 subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True, check=True)
|
||||
|
||||
File /usr/lib64/python3.10/subprocess.py:524, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
|
||||
522 retcode = process.poll()
|
||||
523 if check and retcode:
|
||||
--> 524 raise CalledProcessError(retcode, process.args,
|
||||
525 output=stdout, stderr=stderr)
|
||||
526 return CompletedProcess(process.args, retcode, stdout, stderr)
|
||||
|
||||
CalledProcessError: Command 'ls /home/nonexistent' returned non-zero exit status 2.
|
||||
```
|
||||
|
||||
Options:
|
||||
Most used modules when writing system scripts with Python: `pathlib`, `os`, `shutil`.
|
||||
|
||||
- `-v`, `--volume`: `SRC_PATH:DEST_PATH:L`. Label should be one of `z`, `z,ro`, `Z` or `Z,ro`.
|
||||
- `--label "io.containers.autoupdate=registry"` for `podman auto-update`
|
||||
- `-p`, `--publish`: `SERVER_PORT:CONTAINER_PORT`
|
||||
```python-repl
|
||||
>>> from pathlib import Path
|
||||
>>> p = Path.home() / "day_5"
|
||||
PosixPath('/home/student/day_5')
|
||||
>>> p.is_dir()
|
||||
(...)
|
||||
>>> p.is_file()
|
||||
(...)
|
||||
>>> p.exists()
|
||||
(...)
|
||||
>>> p.chmod(0o700)
|
||||
(...)
|
||||
>>> rel = Path(".")
|
||||
PosixPath('.')
|
||||
>>> rel.resolve()
|
||||
PosixPath('/home/student/(...)')
|
||||
```
|
||||
|
|
|
@ -41,11 +41,11 @@ If you don't choose Linux, you get the output "Nah, that can't be right! (...)".
|
|||
|
||||
We did learn that we can redirect to a file using `>`. The syntax `1>&2` redirects the standard output to the standard error. On the other hand. `2>&1` redirects the standard error to the standard output.
|
||||
|
||||
If you want to redirect both the standard output and error to a file, you can use this syntax: `COMMAND > FILE 2>&1`.
|
||||
If you want to redirect both the standard output and error to a file, you can use this syntax: `COMMAND >FILE 2>&1`.
|
||||
|
||||
The redirection **order** is important in this case! `COMMAND 2>&1 > FILE` will redirect the standard output **only** to the file `FILE`!
|
||||
The redirection **order** is important in this case! `COMMAND 2>&1 >FILE` will redirect the standard output **only** to the file `FILE`!
|
||||
|
||||
`COMMAND > FILE 2>&1` has a useful shortcut: `COMMAND &> FILE`. You can also use `&>>` to append.
|
||||
`COMMAND >FILE 2>&1` has a useful shortcut: `COMMAND &>FILE`. You can also use `&>>` to append.
|
||||
|
||||
All other aspects of the script above will be explained in the next sections.
|
||||
|
|
@ -1,195 +1,158 @@
|
|||
# Tasks
|
||||
|
||||
## Compilation in containers
|
||||
## Task: Vim macros
|
||||
|
||||
> 📍 : This task should be done on the Contabo server after connecting with SSH to the user that you did create yesterday on the server (not admin).
|
||||
In this task, we will learn about the power of macros in Vim.
|
||||
|
||||
We want to practice compilation and containers, so let's compile in a container!
|
||||
1. Visit [this URL](https://www.randomlists.com/random-names?qty=500), select the generated 500 names with the mouse and copy them.
|
||||
1. Create a new text file and open it with `vim`.
|
||||
1. Paste the names into the file. You should see 500 lines with a name in each line.
|
||||
1. Record a macro that changes a line in the form `FIRST_NAME LAST_NAME` to `("FIRST_NAME", "LAST_NAME"),`.
|
||||
1. Run the macro on all lines.
|
||||
1. Now go to the beginning of the file (`gg`) and add this line as a first line: `names = [`
|
||||
1. Go to the end of the file (`G`) and add this line as last line: `]`
|
||||
|
||||
In this task, we want to compile the program `tmate`.
|
||||
Congratulations, you did just convert the names into a form that could be directly used by a Python program! It is a list of tuples now.
|
||||
|
||||
1. Start an Ubuntu container with `podman run -it --rm --name tmate-compiler ubuntu:latest bash`.
|
||||
1. Go to the [website of `tmate`](https://tmate.io/) and find out how to compile from source (there are instructions for compiling on Ubuntu).
|
||||
1. Follow the compilation instructions in the container.
|
||||
1. After compilation, you will find the binary `tmate` in the directory of the git repository.
|
||||
1. Don't exit the container yet, otherwise you will lose what you have done in it. Now open a new terminal (tab) and copy the binary `tmate` from the container to the directory `bin` in your home directory. Use the command `podman cp CONTAINERNAME:SRC_PATH DESTINATION_PATH`.
|
||||
1. Verify that the binary `tmate` was copied to `DESTINATION_PATH` and then exit the container in the first terminal (tab).
|
||||
## Task: Job scheduler
|
||||
|
||||
Now write a script called `compile_tmate.sh` that automates what you have done in the container to compile `tmate`. Just copy all the commands that you used in the container to a script.
|
||||
> Warning ⚠️ : This task is not an easy task. Don't give up quickly and ask for help if you don't get further!
|
||||
|
||||
Add to the end of the script `mv PATH_TO_TMATE_BINARY_IN_CONTAINER /volumes/bin` to copy the binary to the directory `/volumes/bin` after compilation.
|
||||
In this task, we want to write our own job scheduler.
|
||||
|
||||
Create a directory called `scripts` and put the script in it.
|
||||
Understanding how job schedulers work is important when you are working on a computer cluster.
|
||||
|
||||
Now write a second script in the parent directory of the directory `scripts`. The second script should automate creating the container that runs the first script.
|
||||
Computer clusters are shared by many users. Therefore, running jobs on a cluster has to be scheduled to make sure that the resources are shared probably.
|
||||
|
||||
Do the following in the second script:
|
||||
In this task, we will keep it simple. No aspects of multiple users or any optimizations.
|
||||
|
||||
1. Check if `scripts/compile_tmate.sh` does NOT exist. In this case you should print a useful message that explains why the script terminates and then exit with error code 1.
|
||||
1. Make sure that `scripts/compile_tmate.sh` is executable for the user.
|
||||
1. Create a directory called `bin` (next to the directory `scripts`) if it does not already exist.
|
||||
1. Use the following snippet:
|
||||
We want to be able to submit a job as a single script (without any dependencies). The submitted scripts should run one after the another to save CPU usage for example.
|
||||
|
||||
We will use the program `inotifywait`. This program can monitor a directory and notify on changes within this directory.
|
||||
|
||||
1. Find out which package installs `inotifywait` and install it.
|
||||
1. Read the manual of `inotifywait` for a better understanding of what it does.
|
||||
1. Find out how to tell `inotifywait` to keep monitoring a directory and not exit after the first event.
|
||||
1. Find out what events mean in the context of `inotifywait`.
|
||||
1. Create a new directory called `jobs` to be monitored.
|
||||
1. Create a new directory called `logs` that will be used later.
|
||||
1. Run `inotifywait` while telling it to monitor the directory `jobs`. Leave the command running in a terminal and open a second terminal (tab) to continue the work in.
|
||||
1. Create a file **outside** of the directory `jobs` and then copy it to the directory `jobs`.
|
||||
1. Go back to the first terminal and see the output of `inotifywait` was.
|
||||
1. Based on the output, choose an event that you want to listen to with `inotifywait` that tells you when a file is _completely_ added to the directory `jobs`. Use the manual to read more about specific events.
|
||||
1. Find an option that lets you tell `inotifywait` to only notify when the chosen event occurs.
|
||||
1. Find an option that lets you format the output of the notification of `inotifywait`. Since we only listen on one event and monitor only one directory, an output that shows only the name of the new file should be enough.
|
||||
1. Enter the command that you have until now in a script. Now extend it by using a `while` loop that continuously listens on the notifications of `inotifywait`. Use the following snippet while replacing the sections with `(...)`:
|
||||
```bash
|
||||
podman run -it --rm \
|
||||
--name tmate-compiler \
|
||||
-v ./scripts:/volumes/scripts:Z,ro \
|
||||
-v ./bin:/volumes/bin:Z \
|
||||
docker.io/library/ubuntu:latest \
|
||||
/volumes/scripts/compile_tmate.sh
|
||||
inotifywait (...) | while read FILENAME
|
||||
do
|
||||
(...)
|
||||
done
|
||||
```
|
||||
1. After a notification, the body of the `while` loop should first print the name of the script that was added. From now on, we only want to add scripts to the `jobs` directory.
|
||||
1. After printing the script name, run the script!
|
||||
1. Save the standard output and standard error of the script into two separate files in the `logs` directory. If the name of the script is `job.sh` for example, then the output should be in the files `logs/job.sh.out` and `logs/job.sh.err`.
|
||||
|
||||
It creates a container that runs the script `compile_tmate.sh` and is removed afterwards (because of `--rm`).
|
||||
### Hints
|
||||
|
||||
The `scripts` directory is mounted to be able to give the container access to the script `compile_tmate.sh`. The directory is mounted as _read only_ (`ro`) because it will not be modified.
|
||||
- Take a look at the examples from the sections of this day.
|
||||
- Take care of permissions.
|
||||
|
||||
The `bin` directory is mounted to be able to transfer the binary into it before the container exits.
|
||||
If you have extra time, read about the command `screen` in the internet. `screen` allows you to run commands in the background. This way, you don't need two terminals.
|
||||
|
||||
After running the second script, you should see the container compiling and then exiting. At the end, you should find the binary `tmate` in the `bin` directory.
|
||||
## Task: Job submitter
|
||||
|
||||
Now that you have the program `tmate`, find out what it does! Try it with a second person.
|
||||
In this task we will write a small script that lets us submit a job script to the scheduler from the last task.
|
||||
|
||||
##### Tipps:
|
||||
The script should take the path to the job script as a single required argument.
|
||||
|
||||
- On Debian based distributions like Ubuntu, the package manager is `apt`. Before that you can install any packages with `apt`, you have to run `apt update`. This does not run system updates like `dnf upgrade`. `apt update` does only synchronize the repositories which is needed before installations.
|
||||
- Test if a file exists in bash:
|
||||
The script should then copy the job script to the directory `jobs` while adding the time and date to the beginning of the name of the job script in the `jobs` directory.
|
||||
|
||||
```bash
|
||||
if [ -f FILE_PATH ]
|
||||
then
|
||||
(...)
|
||||
fi
|
||||
```
|
||||
Read the manual of the command `date` to know how to get the time and date in the following format: `2022-08-22T20:00:00+00:00`.
|
||||
|
||||
Replace `(...)` with your code. For more information on the option `-f` and other useful options for bash conditions, read the man page of the program `test`: `man test`.
|
||||
If the name of the job script is `job.sh` for example, the job script should be named `2022-08-22T20:00:00+00:00_job.sh` in the `jobs` directory.
|
||||
|
||||
To test if a file does NOT exist, replace `-f` with `! -f`.
|
||||
- Exit a bash script with error code 1:
|
||||
```bash
|
||||
exit 1
|
||||
```
|
||||
Use variables to write the script to make it more understandable.
|
||||
|
||||
## Task: Static website
|
||||
#### Help
|
||||
|
||||
> 📍 : In this task, you should connect as the user `admin` to the Contabo server. **Don't do this task as the user that you did create on the server!** ⚠️
|
||||
To save the output of a command into a variable, use you have to use the following syntax:
|
||||
|
||||
> 📍 : Starting with this task: Asking you to replace `N` means to enter the number that you are using in the URL `ttydN.mo8it.com`.
|
||||
```bash
|
||||
DATE=$(date ...)
|
||||
```
|
||||
|
||||
In this task, you will host a static website which is a website that does not have a backend. A static website is just a set of HTML, CSS (and optionally JavaScript) files.
|
||||
Replace `...` with your code.
|
||||
|
||||
To host the website, we need a web server. In this task, we will use the Nginx web server.
|
||||
To read the `n`-th argument that is provided to a script you write, you have to use `$n`.
|
||||
|
||||
Create a directory `~/nginxN` (replace `N`) with two directories in it: `website` and `conf`.
|
||||
Example script called `arg.sh`:
|
||||
|
||||
Place these two files:
|
||||
```bash
|
||||
#!/usr/bin/bash
|
||||
|
||||
1. `~/nginxN/conf/nginxN.jinext.xyz.conf` (replace `N`):
|
||||
echo "The first argument is: $1"
|
||||
```
|
||||
|
||||
When you run this script with an argument:
|
||||
|
||||
```console
|
||||
$ ./arg.sh "Hello"
|
||||
The first argument is: Hello
|
||||
```
|
||||
|
||||
## Task: Submit a job
|
||||
|
||||
Write a small scripts of your choice that require a long time to run and submit them using the script from the last task. Make sure that the scheduler is running in the background.
|
||||
|
||||
You can use the command `sleep` to simulate a job that needs long time to run.
|
||||
|
||||
Submit your job script multiple times and take a look at the terminal that is running the scheduler to make sure that the job scripts are running one after the other.
|
||||
|
||||
Verify the redirection of the standard output and standard error in the directory `logs`.
|
||||
|
||||
## Task: Parsing a CSV file
|
||||
|
||||
1. Use `curl` to take a look at the file with the following link: [https://gitlab.rlp.net/mobitar/julia\_course/-/raw/main/Day\_3/resources/fitting\_task\_data.csv](https://gitlab.rlp.net/mobitar/julia_course/-/raw/main/Day_3/resources/fitting_task_data.csv). The file contains measurement of a (fake) free fall experiment.
|
||||
1. Now that you know what the file contains, pipe the output to tools that let you remove the first 6 and last 2 lines. Afterwards, extract the first (measured height) and third column (measured time).
|
||||
1. Write a small Python that processes the output of the command from the last step. Since this book is not about programming in Python or plotting, the simple code to process the output of the variable `h_t` is given below:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name nginxN.jinext.xyz;
|
||||
#!/usr/bin/env python3
|
||||
|
||||
location / {
|
||||
root /volumes/website;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
(...)
|
||||
|
||||
h_values = []
|
||||
t_values = []
|
||||
|
||||
for line in h_t.strip().split("\n"):
|
||||
h, t = line.strip().split(",")
|
||||
h_values.append(h)
|
||||
t_values.append(t)
|
||||
|
||||
plt.plot(h_values, t_values)
|
||||
plt.xlabel("h")
|
||||
plt.ylabel("t")
|
||||
plt.savefig("h_t.pdf")
|
||||
```
|
||||
|
||||
Replace `N` also in `server_name`!
|
||||
|
||||
1. `~/nginxN/website/index.html` (replace `N`):
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Create a Nginx container with the following options:
|
||||
|
||||
- Name: `nginxN`. Replace `N`!
|
||||
- Timezone `tz`: `local`.
|
||||
- Network: `traefik`.
|
||||
- Volumes:
|
||||
- `~/nginxN/website:/volumes/website` with labels `Z,ro`.
|
||||
- `~/nginxN/conf:/etc/nginx/conf.d` with labels `Z,ro`.
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/nginx:alpine`
|
||||
|
||||
Create the systemd file for the container above.
|
||||
|
||||
Move the systemd file to `~/.config/systemd/user`.
|
||||
|
||||
Enable and start the container as user services with `systemctl --user enable --now container-nginxN`. Replace `N`!
|
||||
|
||||
Visit [https://nginxN.jinext.xyz](https://nginxN.jinext.xyz) to see if everything did work! Replace `N`!
|
||||
|
||||
Now, you can edit `index.html` and add your own HTML content.
|
||||
|
||||
You can also add more files to the directory `website`. If you add a file `test.html` for example, then you should see it under [https://nginxN.jinext.xyz/test](https://nginxN.jinext.xyz/test).
|
||||
|
||||
## Task: Nextcloud
|
||||
|
||||
> 📍 : In this task, you should connect as the user `admin` to the Contabo server. **Don't do this task as the user that you did create on the server!** ⚠️
|
||||
|
||||
In this task you will deploy your own cloud on the server: Nextcloud!
|
||||
|
||||
To do so, we will install Nextcloud as a container using `podman`.
|
||||
|
||||
To connect as `admin` again, change the user for the host `linux-lab` in `~/.ssh/config` back to `admin` or use `ssh admin@linux-lab` instead of only `ssh linux-lab`.
|
||||
|
||||
You can find more information about the Nextcloud container here: https://hub.docker.com/\_/nextcloud
|
||||
|
||||
Create a directory called `nextcloudN` (replace `N`) in the home directory of the user `admin`.
|
||||
|
||||
Create a directory called `nextcloudN-db` (replace `N`) for the database container.
|
||||
|
||||
Create a container for the database with the following options:
|
||||
|
||||
- Container name: `nextcloudN-db`. Replace `N`!
|
||||
- Timezone `tz`: `local`
|
||||
- Network: `traefik`
|
||||
- Volume: Mount the directory `nextcloudN-db` (replace `N`) that you did create into `/var/lib/postgresql/data` in the container. Use the label `Z`!
|
||||
- The following environment variables:
|
||||
- `POSTGRES_DB=nextcloud`
|
||||
- `POSTGRES_USER=nextcloud`
|
||||
- `POSTGRES_PASSWORD=DB_PASSWORD`. Replace `DB_PASSWORD` with a good password!
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/postgres:alpine`
|
||||
|
||||
Create the actual Nextcloud container with the following options:
|
||||
|
||||
- Container name: `nextcloudN`. `N` at the end stands for the number that you are using in the url to connect to the browser terminal `ttydN.mo8it.com`.
|
||||
- Timezone `tz`: `local`
|
||||
- Network: `traefik`
|
||||
- Volume: Mount the directory `nextcloudN` that you did create into `/var/www/html` in the container. Use the label `Z`!
|
||||
- The same environment variables as for the other container! Use the same `DB_PASSWORD`. Add one more environment variable:
|
||||
- `POSTGRES_HOST=nextcloudN-db`. Replace `N`!
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/nextcloud:24-apache`
|
||||
|
||||
Create the systemd files for the two containers above.
|
||||
|
||||
Move the systemd files to `~/.config/systemd/user`.
|
||||
|
||||
Enable and start the two containers as user services with `systemctl --user enable --now container-nextcloudN-db` and `systemctl --user enable --now container-nextcloudN`. Replace `N`!
|
||||
|
||||
Visit [https://nextcloudN.jinext.xyz](https://nextcloudN.jinext.xyz) to see if everything did work! Replace `N`!
|
||||
Use the command that you did write in the last step to extract the two columns and save the output in a variable called `h_t` in the `(...)` block.
|
||||
1. Save the Python script into a file in an empty directory called `bash_python_harmony`.
|
||||
1. Use `poetry` to initialize an environment in the directory `bash_python_harmony`.
|
||||
1. Use `poetry` to add the package `matplotlib` to the environment.
|
||||
1. Use `poetry shell` to enter the environment.
|
||||
1. Run the script and check the generated PDF file `h_t.pdf`.
|
||||
|
||||
## Task: Vim game
|
||||
|
||||
In this task, we are going to play a game! 🎮️
|
||||
|
||||
The game is an educational game that lets you learn the very basics of the navigation in Vim/Neovim.
|
||||
The game is an educational game that lets you learn the very basics of the navigation in Vim.
|
||||
|
||||
Play the game on this website: [https://vim-adventures.com](https://vim-adventures.com/)
|
||||
|
||||
## Task: Python rewrite
|
||||
|
||||
Choose one of the scripts that you have written during the course and rewrite it in Python!
|
||||
|
|
1
src/day_5/clis_of_the_day.md
Normal file
1
src/day_5/clis_of_the_day.md
Normal file
|
@ -0,0 +1 @@
|
|||
# CLIs of the day
|
|
@ -1,254 +1,216 @@
|
|||
# Notes
|
||||
|
||||
## Neovim
|
||||
## SSH
|
||||
|
||||
- `:q`: Quit (**very important!**)
|
||||
- `:q!`: Quit without saving (**important!**)
|
||||
- `j`: Down
|
||||
- `k`: Up
|
||||
- `h`: Left
|
||||
- `l`: Right
|
||||
- `i`: Insert at left of cursor
|
||||
- `a`: Insert at right of cursor (append)
|
||||
- `I`: Insert at beginning of line
|
||||
- `A`: Append to end of line
|
||||
- `Esc`: Normal mode
|
||||
- `w`: Go to beginning of next word
|
||||
- `b`: Go to beginning of last word
|
||||
- `e`: Go to end of word
|
||||
- `gg`: Go to beginning of file
|
||||
- `G`: Go to end of file
|
||||
- `0`: Go to beginning of line
|
||||
- `$`: Go to end of line
|
||||
- `%`: Go to the other bracket
|
||||
- `u`: Undo
|
||||
- `Ctrl+r`: Redo
|
||||
- `:h`: Help
|
||||
- `:w`: Write buffer
|
||||
- `:wq`: Write buffer and exit
|
||||
- `/PATTERN`: Search
|
||||
- `n`: Next match
|
||||
- `N`: Previous match
|
||||
- `*`: Next match of the word under cursor
|
||||
- `o`: Add line below and enter insert mode
|
||||
- `O`: Add line above and enter insert mode
|
||||
- `v`: Start selection
|
||||
- `V`: Block selection
|
||||
- `y`: Yank (copy)
|
||||
- `p`: Paste
|
||||
- `x`: Delete one character
|
||||
- `dw`: Delete word
|
||||
- `dd`: Delete line
|
||||
- `D`: Delete util end of line
|
||||
- `cw`: Change word
|
||||
- `cc`: Change line
|
||||
- `C`: Change until end of line
|
||||
- `di(`: Delete inside bracket `(`. Can be used with other brackets and quotation marks.
|
||||
- `da(`: Same as above but delete around, not inside.
|
||||
- `ci(`: Change inside bracket `(`. Can be used with other brackets and quotation marks.
|
||||
- `ca(`: Same as above but delete around, not inside.
|
||||
- `:%s/OLD/NEW/g`: Substitute `OLD` with `NEW` in the whole file (with regex)
|
||||
- `:%s/OLD/NEW/gc`: Same as above but ask for confirmation for every substitution
|
||||
- `:N`: Go line number `N`
|
||||
- `.`: Repeat last action
|
||||
- `<` and `>`: Indentation
|
||||
- `q`: Start recording a macro (followed by macro character)
|
||||
### Setup host
|
||||
|
||||
## Regular expressions (Regex)
|
||||
In `~/.ssh/config`
|
||||
|
||||
Can be used for example with `grep`, `rg`, `find`, `fd`, `nvim`, etc.
|
||||
```
|
||||
Host HOST
|
||||
HostName SERVERIP
|
||||
User SERVERUSER
|
||||
```
|
||||
|
||||
- `^`: Start of line
|
||||
- `$`: End of line
|
||||
- `()`: Group
|
||||
- `[abcd]`: Character set, here `a` until `d`
|
||||
- `[a-z]`: Character range, here `a` until `z`
|
||||
- `[^b-h]`: Negated character range, here `b` to `h`
|
||||
- `.`: Any character
|
||||
- `.*`: 0 or more characters
|
||||
- `.+`: 1 or more characters
|
||||
- `\w`: Letter or number
|
||||
- `\W`: Not letter nor number
|
||||
- `\d`: Digit
|
||||
- `\D`: Not digit
|
||||
- `\s`: Whitespace
|
||||
- `\S`: Not whitespace
|
||||
|
||||
## More tools
|
||||
|
||||
### Curl
|
||||
|
||||
We did use `curl`, but not yet for downloading.
|
||||
### Generate key pair
|
||||
|
||||
```bash
|
||||
# Download file into current directory while using the default name
|
||||
curl -L LINK_TO_FILE -O
|
||||
|
||||
# Download file while giving the path to save the file into
|
||||
# (notice that we are using small o now, not O)
|
||||
curl -L LINK_TO_FILE -o PATH
|
||||
ssh-keygen -t ed25519 -C "COMMENT"
|
||||
```
|
||||
|
||||
`-L` tells `curl` to follow redirections (for example from `http` to `https`).
|
||||
Leave blank to take default for the prompt `Enter file in which to save the key (/home/USERNAME/.ssh/id_ed25519)`.
|
||||
|
||||
### cut
|
||||
Then enter a passphrase for your key. **You should not leave it blank!**
|
||||
|
||||
Demo file `demo.txt`:
|
||||
|
||||
```
|
||||
here,are,some
|
||||
comma,separated,values
|
||||
de mo,file,t x t
|
||||
```
|
||||
### Add public key to server
|
||||
|
||||
```bash
|
||||
# Get the N-th column by using SEP as separator
|
||||
cut -d SEP -f N FILE
|
||||
ssh-copy-id -i ~/.ssh/id_ed25519.pub HOST
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ cut -d "," -f 1 demo.txt
|
||||
here
|
||||
comma
|
||||
de mo
|
||||
```
|
||||
|
||||
You can also pipe into `cut` instead of specifying `FILE`.
|
||||
|
||||
### sed
|
||||
### Connect
|
||||
|
||||
```bash
|
||||
# Substitute
|
||||
sed 's/OLD/NEW/g' FILE
|
||||
|
||||
# Delete line that contains PATTERN
|
||||
sed '/PATTERN/d' FILE
|
||||
ssh HOST
|
||||
```
|
||||
|
||||
Example:
|
||||
### Config on server
|
||||
|
||||
```console
|
||||
$ sed 's/values/strings/g' demo.txt
|
||||
here,are,some
|
||||
comma,separated,strings
|
||||
de mo,file,t x t
|
||||
$ sed '/separated/d' demo.txt
|
||||
here,are,some
|
||||
de mo,file,t x t
|
||||
```
|
||||
**Very important for security!** Only after adding the public key to the server!
|
||||
|
||||
You can also pipe into `sed` instead of specifying `FILE`.
|
||||
> WARNING ⚠️ :
|
||||
>
|
||||
> Verify that you are only asked for the passphrase of the SSH key before continuing in this section!
|
||||
>
|
||||
> If you are asked for the password of the user on the server when connecting, then the authentication with a key did not work. Therefore, don't set `PasswordAuthentication no`! Fix the issue with the key authentication first. **Otherwise, you will be locked out of the server!** ⚠️
|
||||
|
||||
When you specify `FILE`, you can use the option `-i` to operate _inplace_. This means that the file is modified directly.
|
||||
In `/etc/ssh/sshd_config` on the server:
|
||||
|
||||
### find
|
||||
Uncomment line with `PasswordAuthentication` and set it to `PasswordAuthentication no`
|
||||
|
||||
Save and exit, then run:
|
||||
|
||||
```bash
|
||||
# Find everything ending with `.sh` of type file `f` using globbing
|
||||
find . -type f -name '*.sh'
|
||||
|
||||
|
||||
# Using regex
|
||||
find . -type f -regex '.+\.sh'
|
||||
sudo systemctl restart sshd
|
||||
```
|
||||
|
||||
### xargs
|
||||
If you are locked out after running this command, then you did not take the warning above seriously!
|
||||
|
||||
## Rsync
|
||||
|
||||
From server:
|
||||
|
||||
```bash
|
||||
# Show the content of all files starting with '*.sh'
|
||||
find . -type f -name '*.sh' | xargs cat
|
||||
rsync -Lahz HOST:SRC_PATH DEST_PATH
|
||||
```
|
||||
|
||||
### ripgrep
|
||||
|
||||
```console
|
||||
$ rg '.*,(.*),.*' -r '$1' demo.txt
|
||||
```
|
||||
|
||||
## Symlinks
|
||||
To server:
|
||||
|
||||
```bash
|
||||
# Soft link
|
||||
ln -s SRC_PATH DEST_PATH
|
||||
|
||||
# Hard link
|
||||
ln SRC_PATH DEST_PATH
|
||||
rsync -Lahz SRC_PATH HOST:DEST_PATH
|
||||
```
|
||||
|
||||
## Poetry
|
||||
Options:
|
||||
|
||||
- `-a`, `--archive`: Set of useful options to preserve permissions, use recursive mode, etc.
|
||||
- `-h`, `--human-readable`: Output number in a human-readable format.
|
||||
- `-z`, `--compress`: Use compression.
|
||||
- `--partial`: Continue after interruption.
|
||||
- `-L`, `--copy-links`: Copy links.
|
||||
- `-v`, `--verbose`: Show more infos.
|
||||
- `--delete`: Delete files from `DEST_PATH` if they don't exist on `SRC_PATH` anymore. **Use with caution!!!**
|
||||
|
||||
## Systemd
|
||||
|
||||
Check status of a service:
|
||||
|
||||
```bash
|
||||
# Initialize environment
|
||||
poetry init
|
||||
|
||||
# Add package to the environment
|
||||
poetry add PACKAGENAME
|
||||
|
||||
# Remove package from environment
|
||||
poetry remove PACKAGENAME
|
||||
|
||||
# Update
|
||||
poetry update
|
||||
|
||||
# Enter environment
|
||||
poetry shell
|
||||
sudo systemctl status SERVICENAME
|
||||
```
|
||||
|
||||
It is important to write `#!/usr/bin/env python3` instead of `#!/usr/bin/python3` in your scripts to be able to use them in an environment.
|
||||
Enable service:
|
||||
|
||||
## Python scripting
|
||||
|
||||
```python-repl
|
||||
>>> import subprocess
|
||||
>>> proc = subprocess.run(["ls", "student"])
|
||||
(...)
|
||||
CompletedProcess(args=['ls', '/home/student'], returncode=0)
|
||||
>>> proc.returncode
|
||||
0
|
||||
>>> proc = subprocess.run("ls /home/student", shell=True)
|
||||
(...)
|
||||
CompletedProcess(args='ls /home/student', returncode=0)
|
||||
>>> proc = subprocess.run("ls /home/student", shell=True, capture_output=True, text=True)
|
||||
CompletedProcess(args='ls /home/student', returncode=0, stdout='(...)', stderr='')
|
||||
>>> proc.stdout
|
||||
(...)
|
||||
>>> proc.stderr
|
||||
>>> proc = subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True)
|
||||
CompletedProcess(args='ls /home/nonexistent', returncode=2, stdout='', stderr="ls: cannot access '/home/nonexistent': No such file or directory\n")
|
||||
>>> proc = subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True, check=True)
|
||||
---------------------------------------------------------------------------
|
||||
CalledProcessError Traceback (most recent call last)
|
||||
Input In [8], in <cell line: 1>()
|
||||
----> 1 subprocess.run("ls /home/nonexistent", shell=True, capture_output=True, text=True, check=True)
|
||||
|
||||
File /usr/lib64/python3.10/subprocess.py:524, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
|
||||
522 retcode = process.poll()
|
||||
523 if check and retcode:
|
||||
--> 524 raise CalledProcessError(retcode, process.args,
|
||||
525 output=stdout, stderr=stderr)
|
||||
526 return CompletedProcess(process.args, retcode, stdout, stderr)
|
||||
|
||||
CalledProcessError: Command 'ls /home/nonexistent' returned non-zero exit status 2.
|
||||
```bash
|
||||
sudo systemctl enable SERVICENAME
|
||||
```
|
||||
|
||||
Most used modules when writing system scripts with Python: `pathlib`, `os`, `shutil`.
|
||||
Start service:
|
||||
|
||||
```python-repl
|
||||
>>> from pathlib import Path
|
||||
>>> p = Path.home() / "day_5"
|
||||
PosixPath('/home/student/day_5')
|
||||
>>> p.is_dir()
|
||||
(...)
|
||||
>>> p.is_file()
|
||||
(...)
|
||||
>>> p.exists()
|
||||
(...)
|
||||
>>> p.chmod(0o700)
|
||||
(...)
|
||||
>>> rel = Path(".")
|
||||
PosixPath('.')
|
||||
>>> rel.resolve()
|
||||
PosixPath('/home/student/(...)')
|
||||
```bash
|
||||
sudo systemctl start SERVICENAME
|
||||
```
|
||||
|
||||
Enable and start service at the same time:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now SERVICENAME
|
||||
```
|
||||
|
||||
Disable service:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable SERVICENAME
|
||||
```
|
||||
|
||||
Stop service:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop SERVICENAME
|
||||
```
|
||||
|
||||
Disable and stop service at the same time:
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now SERVICENAME
|
||||
```
|
||||
|
||||
## Firewalld
|
||||
|
||||
Install and enable firewalld:
|
||||
|
||||
```bash
|
||||
sudo dnf install firewalld
|
||||
sudo systemctl enable --now firewalld
|
||||
```
|
||||
|
||||
View open ports and services:
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --list-all
|
||||
```
|
||||
|
||||
Open ports 80 (http) and 443 (https):
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --add-service http
|
||||
sudo firewall-cmd --add-service https
|
||||
sudo firewall-cmd --runtime-to-permanent
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
sudo firewall-cmd --add-port 80/tcp
|
||||
sudo firewall-cmd --add-port 443/tcp
|
||||
sudo firewall-cmd --runtime-to-permanent
|
||||
```
|
||||
|
||||
## Podman
|
||||
|
||||
```bash
|
||||
# Search for image
|
||||
podman search python
|
||||
|
||||
# Pull image
|
||||
podman pull docker.io/library/python:latest
|
||||
|
||||
# See pulled images
|
||||
podman images
|
||||
|
||||
# Run container and remove it afterwards
|
||||
podman run -it --rm docker.io/library/python:latest bash
|
||||
|
||||
# Create network
|
||||
podman network create NETWORKNAME
|
||||
|
||||
# Create container
|
||||
podman create \
|
||||
--name CONTAINERNAME \
|
||||
--network NETWORKNAME \
|
||||
-e ENVVAR="Some value for the demo environment variable" \
|
||||
--tz local \
|
||||
docker.io/library/python:latest
|
||||
|
||||
# Start container
|
||||
podman start CONTAINERNAME
|
||||
|
||||
# Enter a running container
|
||||
podman exec -it CONTAINERNAME bash
|
||||
|
||||
# Stop container
|
||||
podman stop CONTAINERNAME
|
||||
|
||||
# Generate systemd files
|
||||
podman generate systemd --new --files --name CONTAINERNAME
|
||||
|
||||
# Create directory for user's systemd services
|
||||
mkdir -p ~/.config/systemd/user
|
||||
|
||||
# Place service file
|
||||
mv container-CONTAINERNAME.service ~/.config/systemd/user
|
||||
|
||||
# Activate user's service (container)
|
||||
systemctl --user enable --now container-CONTAINERNAME
|
||||
```
|
||||
|
||||
Keep user's systemd services live after logging out:
|
||||
|
||||
```bash
|
||||
sudo loginctl enable-linger USERNAME
|
||||
```
|
||||
|
||||
Options:
|
||||
|
||||
- `-v`, `--volume`: `SRC_PATH:DEST_PATH:L`. Label should be one of `z`, `z,ro`, `Z` or `Z,ro`.
|
||||
- `--label "io.containers.autoupdate=registry"` for `podman auto-update`
|
||||
- `-p`, `--publish`: `SERVER_PORT:CONTAINER_PORT`
|
||||
|
|
|
@ -1,105 +1,269 @@
|
|||
# Tasks
|
||||
|
||||
## Task: Neovim macros
|
||||
## Task: SSH
|
||||
|
||||
In this task, we will learn about the power of macros in Neovim.
|
||||
Generate a SSH key pair and send me the public key per email: mo8it@proton.me
|
||||
|
||||
1. Visit [this URL](https://www.randomlists.com/random-names?qty=500), select the generated 500 names with the mouse and copy them.
|
||||
1. Create a new text file and open it with `nvim`.
|
||||
1. Paste the names into the file. You should see 500 lines with a name in each line.
|
||||
1. Record a macro that changes a line in the form `FIRST_NAME LAST_NAME` to `("FIRST_NAME", "LAST_NAME"),`.
|
||||
1. Run the macro on all lines.
|
||||
1. Now go to the beginning of the file (`gg`) and add this line as a first line: `names = [`
|
||||
1. Go to the end of the file (`G`) and add this line as last line: `]`
|
||||
Enter a passphrase while generating the key pair!
|
||||
|
||||
Congratulations, you did just convert the names into a form that could be directly used by a Python program! It is a list of tuples now.
|
||||
Don't send me the private key!!! **You should never send your private SSH keys to anyone!**
|
||||
|
||||
## Task: Parsing a CSV file
|
||||
The public key ends with `.pub`.
|
||||
|
||||
1. Use `curl` to take a look at the file with the following link: [https://gitlab.rlp.net/mobitar/julia\_course/-/raw/main/Day\_3/resources/fitting\_task\_data.csv](https://gitlab.rlp.net/mobitar/julia_course/-/raw/main/Day_3/resources/fitting_task_data.csv). The file contains measurement of a (fake) free fall experiment.
|
||||
1. Now that you know what the file contains, pipe the output to tools that let you remove the first 6 and last 2 lines. Afterwards, extract the first (measured height) and third column (measured time).
|
||||
1. Write a small Python that processes the output of the command from the last step. Since this book is not about programming in Python or plotting, the simple code to process the output of the variable `h_t` is given below:
|
||||
I will then append your public key to `~/.ssh/authorized_keys` on the server that we will use in the next tasks. After I add your public key, you will be able to login to the server and do the next tasks.
|
||||
|
||||
```
|
||||
#!/usr/bin/env python3
|
||||
Create the file `~/.ssh/config` and add the server as a host with the name `linux-lab`.
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
Enter this IP: 45.94.58.19
|
||||
Enter this user: admin
|
||||
|
||||
(...)
|
||||
After that I add you public key, connect to the server using the host name that you did enter in `~/.ssh/config` which should be `linux-lab`.
|
||||
|
||||
h_values = []
|
||||
t_values = []
|
||||
## Task: User creation
|
||||
|
||||
for line in h_t.strip().split("\n"):
|
||||
h, t = line.strip().split(",")
|
||||
h_values.append(h)
|
||||
t_values.append(t)
|
||||
|
||||
plt.plot(h_values, t_values)
|
||||
plt.xlabel("h")
|
||||
plt.ylabel("t")
|
||||
plt.savefig("h_t.pdf")
|
||||
1. Create a user for you on the server after connecting with SSH. To do so, run:
|
||||
```bash
|
||||
sudo useradd USERNAME
|
||||
```
|
||||
|
||||
Use the command that you did write in the last step to extract the two columns and save the output in a variable called `h_t` in the `(...)` block.
|
||||
1. Save the Python script into a file in an empty directory called `bash_python_harmony`.
|
||||
1. Use `poetry` to initialize an environment in the directory `bash_python_harmony`.
|
||||
1. Use `poetry` to add the package `matplotlib` to the environment.
|
||||
1. Use `poetry shell` to enter the environment.
|
||||
1. Run the script and check the generated PDF file `h_t.pdf`.
|
||||
Replace `USERNAME` with your name.
|
||||
|
||||
## Task: Python rewrite
|
||||
1. Now set a password for the new user:
|
||||
|
||||
Choose one of the scripts that you have written during the course and rewrite it in Python!
|
||||
```bash
|
||||
sudo passwd USERNAME
|
||||
```
|
||||
1. For the new user to be able to use `sudo`, it has to be added to the `wheel` group:
|
||||
|
||||
## Task: Ripgrep
|
||||
```bash
|
||||
sudo usermod -aG wheel USERNAME
|
||||
```
|
||||
|
||||
The following website uses a PDF file but it does not let you see download it: [https://knowunity.de/knows/biologie-neurobiologie-1c6a4647-4707-4d1b-8ffb-e7a750582921](https://knowunity.de/knows/biologie-neurobiologie-1c6a4647-4707-4d1b-8ffb-e7a750582921)
|
||||
`-aG` stands for _append to group(s)_.
|
||||
|
||||
Know that you are kind of a "hacker", you want to use a workaround.
|
||||
(On Debian based distros, the user should be added to the `sudo` group instead of `wheel`.)
|
||||
|
||||
Use pipes `|`, `curl`, `rg` (ripgrep) and `xargs` to parse the HTML of the website, extract the link to the PDF file and download the file.
|
||||
1. Now, change your user to the new user:
|
||||
|
||||
The link to the PDF file starts with `https://` and ends with `.pdf`.
|
||||
```bash
|
||||
sudo su USERNAME
|
||||
```
|
||||
|
||||
After that it works, write a script that asks the user for the link to a document at [knowunity.de](https://knowunity.de) and downloads the PDF file.
|
||||
You will see that the user name did change in the prompt.
|
||||
|
||||
## Task: Cows everywhere!
|
||||
1. Run the following command for verification:
|
||||
|
||||
[![](https://imgs.xkcd.com/comics/tar_2x.png)](https://xkcd.com/1168/)
|
||||
```bash
|
||||
whoami
|
||||
```
|
||||
|
||||
Download the source code of this book using `curl` as a `tar.gz` archive: [https://codeberg.org/Mo8it/How\_To\_Linux/archive/main.tar.gz](https://codeberg.org/Mo8it/How_To_Linux/archive/main.tar.gz)
|
||||
It should not output "admin"!
|
||||
|
||||
We are not using Git at this point to practice dealing with archives.
|
||||
Yes, the command is called `whoami`. Linux is kind of philosophical 🤔
|
||||
|
||||
Extract the files from the archive! (_Don't worry, you have more than 10 seconds_)
|
||||
1. Now, verify that you can run `sudo` as the new user:
|
||||
|
||||
Use `find` to find all Markdown files (ending with `.md`). Then use `sed` to replace every match of `echo` with `cowsay` in the files found.
|
||||
```bash
|
||||
sudo whoami
|
||||
```
|
||||
|
||||
Why? Easy: Why not?
|
||||
You should see "root" as output because `sudo` runs a command as the `root` user.
|
||||
|
||||
## Task: Lazygit
|
||||
1. `cd` to the home directory of the new user.
|
||||
1. Make sure that you are in the home directory of the new user! Run `pwd` to verify that you are NOT in `/home/admin`. **`PLEASE DON'T TOUCH /home/admin/.ssh`** ⚠️ . Now, create the directory `~/.ssh` in the home directory of the new user. Change the permissions of `~/.ssh` such that only the user has read, write and execution permissions. _group_ and _others_ should have no permissions for `~/.ssh`!
|
||||
1. Create the file `authorized_keys` inside `~/.ssh`. Only the user should have read and write permissions for the file. _group_ and _others_ should have no permissions for the file!
|
||||
1. Copy the content of your public key file (with `.pub` as extension) to this file. It should be one line! Then save the file.
|
||||
1. Logout from the server. Go to `~/.ssh/config` that you did write at the beginning of this task. Change the user for the host `linux-lab` from `admin` to `USERNAME` where `USERNAME` is the name of the new user that you did create on the server.
|
||||
1. Try to connect using the host name again. If you did everything right, you should be connected and be the user that you did create. Run `whoami` to verify that the output is not "admin".
|
||||
|
||||
In this task, you will learn using Lazygit.
|
||||
## Task: File transfer
|
||||
|
||||
1. Install Lazygit. Follow the instructions for Fedora on [github.com/jesseduffield/lazygit](https://github.com/jesseduffield/lazygit). You might need to install `dnf-plugins-core` first to be able to add something via _COPR_ (Cool Other Package Repo).
|
||||
1. Put the files and directories that you did create during the course into a directory.
|
||||
1. Go into the directory.
|
||||
1. Run `lazygit`. It will initialize a git repository in this directory if none already exists. Confirm the initialization. Starting with now, every git operation should be done in Lazygit.
|
||||
1. Add everything as staged.
|
||||
1. Commit.
|
||||
1. Create a repository on [git.mo8it.com](https://git.mo8it.com).
|
||||
1. Add the new remote.
|
||||
1. Push to the new remote.
|
||||
1. Modify a file (or multiple files).
|
||||
1. Verify your changes in Lazygit, stage, commit and push.
|
||||
Use `scp` and then `rsync` to transfer the files that you did create during the course to the server `linux-lab`.
|
||||
|
||||
## Task: Click
|
||||
Do you notice any differences between the two commands?
|
||||
|
||||
If you are done with all tasks and still have time, read about the Python package `click` that enables you to write your own CLI programs!
|
||||
## Task: Compilation in containers
|
||||
|
||||
Click is not installed by default. You need to install it with `poetry`.
|
||||
> 📍 : This task should be done on the Contabo server after connecting with SSH to the user that you did create yesterday on the server (not admin).
|
||||
|
||||
Write a CLI with some commands and subcommands.
|
||||
We want to practice compilation and containers, so let's compile in a container!
|
||||
|
||||
Don't forget to write useful help messages!
|
||||
In this task, we want to compile the program `tmate`.
|
||||
|
||||
1. Start an Ubuntu container with `podman run -it --rm --name tmate-compiler ubuntu:latest bash`.
|
||||
1. Go to the [website of `tmate`](https://tmate.io/) and find out how to compile from source (there are instructions for compiling on Ubuntu).
|
||||
1. Follow the compilation instructions in the container.
|
||||
1. After compilation, you will find the binary `tmate` in the directory of the git repository.
|
||||
1. Don't exit the container yet, otherwise you will lose what you have done in it. Now open a new terminal (tab) and copy the binary `tmate` from the container to the directory `bin` in your home directory. Use the command `podman cp CONTAINERNAME:SRC_PATH DESTINATION_PATH`.
|
||||
1. Verify that the binary `tmate` was copied to `DESTINATION_PATH` and then exit the container in the first terminal (tab).
|
||||
|
||||
Now write a script called `compile_tmate.sh` that automates what you have done in the container to compile `tmate`. Just copy all the commands that you used in the container to a script.
|
||||
|
||||
Add to the end of the script `mv PATH_TO_TMATE_BINARY_IN_CONTAINER /volumes/bin` to copy the binary to the directory `/volumes/bin` after compilation.
|
||||
|
||||
Create a directory called `scripts` and put the script in it.
|
||||
|
||||
Now write a second script in the parent directory of the directory `scripts`. The second script should automate creating the container that runs the first script.
|
||||
|
||||
Do the following in the second script:
|
||||
|
||||
1. Check if `scripts/compile_tmate.sh` does NOT exist. In this case you should print a useful message that explains why the script terminates and then exit with error code 1.
|
||||
1. Make sure that `scripts/compile_tmate.sh` is executable for the user.
|
||||
1. Create a directory called `bin` (next to the directory `scripts`) if it does not already exist.
|
||||
1. Use the following snippet:
|
||||
|
||||
```bash
|
||||
podman run -it --rm \
|
||||
--name tmate-compiler \
|
||||
-v ./scripts:/volumes/scripts:Z,ro \
|
||||
-v ./bin:/volumes/bin:Z \
|
||||
docker.io/library/ubuntu:latest \
|
||||
/volumes/scripts/compile_tmate.sh
|
||||
```
|
||||
|
||||
It creates a container that runs the script `compile_tmate.sh` and is removed afterwards (because of `--rm`).
|
||||
|
||||
The `scripts` directory is mounted to be able to give the container access to the script `compile_tmate.sh`. The directory is mounted as _read only_ (`ro`) because it will not be modified.
|
||||
|
||||
The `bin` directory is mounted to be able to transfer the binary into it before the container exits.
|
||||
|
||||
After running the second script, you should see the container compiling and then exiting. At the end, you should find the binary `tmate` in the `bin` directory.
|
||||
|
||||
Now that you have the program `tmate`, find out what it does! Try it with a second person.
|
||||
|
||||
### Hints
|
||||
|
||||
- On Debian based distributions like Ubuntu, the package manager is `apt`. Before that you can install any packages with `apt`, you have to run `apt update`. This does not run system updates like `dnf upgrade`. `apt update` does only synchronize the repositories which is needed before installations.
|
||||
- Test if a file exists in bash:
|
||||
|
||||
```bash
|
||||
if [ -f FILE_PATH ]
|
||||
then
|
||||
(...)
|
||||
fi
|
||||
```
|
||||
|
||||
Replace `(...)` with your code. For more information on the option `-f` and other useful options for bash conditions, read the man page of the program `test`: `man test`.
|
||||
|
||||
To test if a file does NOT exist, replace `-f` with `! -f`.
|
||||
- Exit a bash script with error code 1:
|
||||
```bash
|
||||
exit 1
|
||||
```
|
||||
|
||||
## Task: Static website
|
||||
|
||||
> 📍 : In this task, you should connect as the user `admin` to the Contabo server. **Don't do this task as the user that you did create on the server!** ⚠️
|
||||
|
||||
> 📍 : Starting with this task: Asking you to replace `N` means to enter the number that you are using in the URL `ttydN.mo8it.com`.
|
||||
|
||||
In this task, you will host a static website which is a website that does not have a backend. A static website is just a set of HTML, CSS (and optionally JavaScript) files.
|
||||
|
||||
To host the website, we need a web server. In this task, we will use the Nginx web server.
|
||||
|
||||
Create a directory `~/nginxN` (replace `N`) with two directories in it: `website` and `conf`.
|
||||
|
||||
Place these two files:
|
||||
|
||||
1. `~/nginxN/conf/nginxN.jinext.xyz.conf` (replace `N`):
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name nginxN.jinext.xyz;
|
||||
|
||||
location / {
|
||||
root /volumes/website;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Replace `N` also in `server_name`!
|
||||
|
||||
1. `~/nginxN/website/index.html` (replace `N`):
|
||||
|
||||
```
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Create a Nginx container with the following options:
|
||||
|
||||
- Name: `nginxN`. Replace `N`!
|
||||
- Timezone `tz`: `local`.
|
||||
- Network: `traefik`.
|
||||
- Volumes:
|
||||
- `~/nginxN/website:/volumes/website` with labels `Z,ro`.
|
||||
- `~/nginxN/conf:/etc/nginx/conf.d` with labels `Z,ro`.
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/nginx:alpine`
|
||||
|
||||
Create the systemd file for the container above.
|
||||
|
||||
Move the systemd file to `~/.config/systemd/user`.
|
||||
|
||||
Enable and start the container as user services with `systemctl --user enable --now container-nginxN`. Replace `N`!
|
||||
|
||||
Visit [https://nginxN.jinext.xyz](https://nginxN.jinext.xyz) to see if everything did work! Replace `N`!
|
||||
|
||||
Now, you can edit `index.html` and add your own HTML content.
|
||||
|
||||
You can also add more files to the directory `website`. If you add a file `test.html` for example, then you should see it under [https://nginxN.jinext.xyz/test](https://nginxN.jinext.xyz/test).
|
||||
|
||||
## Task: Nextcloud
|
||||
|
||||
> 📍 : In this task, you should connect as the user `admin` to the Contabo server. **Don't do this task as the user that you did create on the server!** ⚠️
|
||||
|
||||
In this task you will deploy your own cloud on the server: Nextcloud!
|
||||
|
||||
To do so, we will install Nextcloud as a container using `podman`.
|
||||
|
||||
To connect as `admin` again, change the user for the host `linux-lab` in `~/.ssh/config` back to `admin` or use `ssh admin@linux-lab` instead of only `ssh linux-lab`.
|
||||
|
||||
You can find more information about the Nextcloud container here: https://hub.docker.com/\_/nextcloud
|
||||
|
||||
Create a directory called `nextcloudN` (replace `N`) in the home directory of the user `admin`.
|
||||
|
||||
Create a directory called `nextcloudN-db` (replace `N`) for the database container.
|
||||
|
||||
Create a container for the database with the following options:
|
||||
|
||||
- Container name: `nextcloudN-db`. Replace `N`!
|
||||
- Timezone `tz`: `local`
|
||||
- Network: `traefik`
|
||||
- Volume: Mount the directory `nextcloudN-db` (replace `N`) that you did create into `/var/lib/postgresql/data` in the container. Use the label `Z`!
|
||||
- The following environment variables:
|
||||
- `POSTGRES_DB=nextcloud`
|
||||
- `POSTGRES_USER=nextcloud`
|
||||
- `POSTGRES_PASSWORD=DB_PASSWORD`. Replace `DB_PASSWORD` with a good password!
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/postgres:alpine`
|
||||
|
||||
Create the actual Nextcloud container with the following options:
|
||||
|
||||
- Container name: `nextcloudN`. `N` at the end stands for the number that you are using in the url to connect to the browser terminal `ttydN.mo8it.com`.
|
||||
- Timezone `tz`: `local`
|
||||
- Network: `traefik`
|
||||
- Volume: Mount the directory `nextcloudN` that you did create into `/var/www/html` in the container. Use the label `Z`!
|
||||
- The same environment variables as for the other container! Use the same `DB_PASSWORD`. Add one more environment variable:
|
||||
- `POSTGRES_HOST=nextcloudN-db`. Replace `N`!
|
||||
- Label: `io.containers.autoupdate=registry`
|
||||
- Image: `docker.io/library/nextcloud:24-apache`
|
||||
|
||||
Create the systemd files for the two containers above.
|
||||
|
||||
Move the systemd files to `~/.config/systemd/user`.
|
||||
|
||||
Enable and start the two containers as user services with `systemctl --user enable --now container-nextcloudN-db` and `systemctl --user enable --now container-nextcloudN`. Replace `N`!
|
||||
|
||||
Visit [https://nextcloudN.jinext.xyz](https://nextcloudN.jinext.xyz) to see if everything did work! Replace `N`!
|
||||
|
|
Loading…
Reference in a new issue