diff --git a/README.md b/README.md
index ce46e03c3d430c6ceb2d846898cf4e34d052def5..7cb97ee7f5db3f4e896bf0e0e0535b30325e103c 100644
--- a/README.md
+++ b/README.md
@@ -9,45 +9,67 @@ This implementation has several features to make usage as simple as possible.
 * Rustus is robust, since it uses asynchronous Rust;
 * It can store information about files in databases;
 * You can specify directory structure to organize your uploads;
+* It has a lot of hooks options, and hooks can be combined.
 * Highly configurable;
 
-### Supported info storages
+## Installation
 
-* FileSystem
-* PostgresSQL
-* Mysql
-* SQLite
-* Redis
+You can download binaries from a [releases page](https://github.com/s3rius/rustus/releases).
 
-### Supported data storages
+If you want to use docker, you can use official images from [s3rius/rustus](https://hub.docker.com/r/s3rius/rustus/):
+```bash
+docker run --rm -it -p 1081:1081 s3rius/rustus:latest
+```
 
-* FileSystem
+If we don't have a binary file for your operating system you can build it with [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html).
 
-## Installation
+```bash
+git clone https://github.com/s3rius/rustus.git
+cd rustus
+cargo install --path . --features=all
+```
+
+### Supported data storages
 
-Since I haven't configured build automation yet, you can build it
-from source using `cargo`.
+Right now you can only use `file-storage` to store uploads data.
+The only two options you can adjust are:
+* uploads directory
+* directory structure
+
+To upload files in a custom directory other than `./data`
+you can provide a `--data-dir` parameter.
 
 ```bash
-cargo install --path .
+rustus --data-dir "./files"
 ```
 
-Or you can use a docker image.
+If you have a lot of uploads, you don't want to store all your files in
+a flat structure. So you can set a directory structure for your uploads.
 
 ```bash
-docker run --rm -it -p 1081:1081 s3rius/rustus:latest
+rustus --dir-structure="{env[HOSTNAME]}/{year}/{month}/{day}"
 ```
 
-Docker image and binaries will be available soon.
+```bash
+tree data
+data
+├── 0bd911d4054d41c6a3ad54be67ee3e66.info
+├── 5bc9c62384494c439e2a064b82a39cc6.info
+└── rtus-68cb5b8746-5mgw9
+    └── 2022
+        └── 1
+            └── 8
+                ├── 0bd911d4054d41c6a3ad54be67ee3e66
+                └── 5bc9c62384494c439e2a064b82a39cc6
 
-## Architecture
+```
 
-Files and info about them are separated from each other.
-In order to modify original file rustus searches for information about
-the file in information storage.
+**Important note:** if you use variable that doesn't exist or incorrect like invalid env variable, it
+results in an error and the directory structure will become flat again.
 
-However, automatic migration between different information
-storages is not supported yet.
+As you can see all info files are stored in a flat structure. It cannot be changed if
+you use file info storage. In order to get rid of those `.info` files use different
+info storages.
 
 ## Info storages
 
@@ -55,7 +77,7 @@ The info storage is a database or directory. The main goal is to keep track
 of uploads. Rustus stores information about download in json format inside
 database.
 
-File storage is a default one. You can customize the directory of an .info files
+File storage is used by default. You can customize the directory of an .info files
 by providing `--info-dir` parameter.
 
 ```bash
@@ -90,27 +112,225 @@ you have to use webhooks or File hooks.
 
 Hooks have priorities: file hooks are the most important, then goes webhooks and AMQP hooks have the least priority.
 If pre-create hook failed, the upload would not start.
-Of course, since AMQP is a protocol that doesn't allow you to track responses.
-We can't validate anything to stop uploading.
-
-
-### Roadmap
-
-* [x] Data storage interface;
-* [x] Info storage interface;
-* [x] Core TUS protocol;
-* [x] Extensions interface;
-* [x] Creation extension;
-* [x] Creation-defer-length extension;
-* [x] Creation-with-upload extension;
-* [x] Termination extension;
-* [x] Route to get uploaded files;
-* [x] Database support for info storage;
-* [x] Redis support for info storage;
-* [x] Notification interface;
-* [x] Notifications via http hooks;
-* [x] Notifications via RabbitMQ;
-* [X] Executable files notifications;
-* [ ] S3 as data storage store support;
-* [ ] Rustus helm chart;
-* [ ] Cloud native rustus operator.
+Of course, since AMQP is a protocol that doesn't allow you to track responses
+we can't validate anything to stop uploading.
+
+Hooks can have 2 formats
+
+default:
+```json
+{
+  "upload": {
+    "id": "",
+    "offset": 0,
+    "length": 39729945,
+    "path": null,
+    "created_at": 1641620821,
+    "deferred_size": false,
+    "metadata": {
+      "filename": "38MB_video.mp4",
+      "meme": "hehe2"
+    }
+  },
+  "request": {
+    "URI": "/files",
+    "method": "POST",
+    "remote_addr": "127.0.0.1",
+    "headers": {
+      "accept-encoding": "gzip, deflate",
+      "connection": "keep-alive",
+      "host": "localhost:1081",
+      "upload-metadata": "meme aGVoZTI=,filename MzhNQl92aWRlby5tcDQ=",
+      "tus-resumable": "1.0.0",
+      "content-length": "0",
+      "upload-length": "39729945",
+      "user-agent": "python-requests/2.26.0",
+      "accept": "*/*"
+    }
+  }
+}
+```
+
+tusd:
+```json
+{
+  "Upload": {
+    "ID": "",
+    "Offset": 0,
+    "Size": 39729945,
+    "IsFinal": true,
+    "IsPartial": false,
+    "PartialUploads": null,
+    "SizeIsDeferred": false,
+    "Metadata": {
+      "filename": "38MB_video.mp4",
+      "meme": "hehe2"
+    },
+    "Storage": {
+      "Type": "filestore",
+      "Path": null
+    }
+  },
+  "HTTPRequest": {
+    "URI": "/files",
+    "Method": "POST",
+    "RemoteAddr": "127.0.0.1",
+    "Header": {
+      "host": [
+        "localhost:1081"
+      ],
+      "user-agent": [
+        "python-requests/2.26.0"
+      ],
+      "accept": [
+        "*/*"
+      ],
+      "content-length": [
+        "0"
+      ],
+      "upload-metadata": [
+        "meme aGVoZTI=,filename MzhNQl92aWRlby5tcDQ="
+      ],
+      "connection": [
+        "keep-alive"
+      ],
+      "tus-resumable": [
+        "1.0.0"
+      ],
+      "upload-length": [
+        "39729945"
+      ],
+      "accept-encoding": [
+        "gzip, deflate"
+      ]
+    }
+  }
+}
+```
+
+### File hooks
+
+Rustus can work with two types of file hooks.
+
+1. Single file hook;
+2. Hooks directory.
+
+The main difference is that hook name is passed as a command line parameter to a
+single file hook, but if you use hooks directory then hook name is used to determine a
+file to call. Let's take a look at the examples
+
+Example of a single file hook:
+
+```bash
+#!/bin/bash
+
+# Hook name would be "pre-create", "post-create" and so on.
+HOOK_NAME="$1"
+MEME="$(cat /dev/stdin | jq ".upload .metadata .meme" | xargs)"
+
+# Here we check if name in metadata is equal to pepe.
+if [[ $MEME = "pepe" ]]; then
+  echo "This meme isn't allowed" 1>&2;
+  exit 1
+fi
+```
+
+As you can see it uses first CLI parameter as a hook name and all hook data is received from stdin.
+
+Let's make it executable
+```bash
+chmod +x "hooks/unified_hook"
+```
+
+To use it you can add parameter
+```bash
+rustus --hooks-file "hooks/unified_hook"
+```
+
+This hook is going to ignore any file that has "pepe" in metadata.
+
+Let's create a hook directory.
+
+```bash
+❯ tree hooks
+hooks
+├── post-create
+├── post-finish
+├── post-receive
+├── post-terminate
+└── pre-create
+```
+
+Every file in this directory has an executable flag.
+So you can specify a parameter to use hooks directory.
+
+```bash
+rustus --hooks-dir "hooks"
+```
+
+In this case rustus will append a hook name to the directory you pointed at and call it as
+an executable.
+
+Information about hook can be found in stdin.
+
+### Http Hooks
+
+Http hooks use http protocol to notify you about an upload.
+You can use HTTP hooks to verify Authorization.
+
+
+Let's create a FastAPI application that listens to hooks and checks the
+authorization header.
+
+```bash
+# Installing dependencies
+pip install fastapi uvicorn
+```
+
+```python
+# server.py
+from fastapi import FastAPI, Header, HTTPException
+from typing import Optional
+
+app = FastAPI()
+
+@app.post("/hooks")
+def hook(
+    authorization: Optional[str] = Header(None),
+    hook_name: Optional[str] = Header(None),
+):
+    print(f"Received: {hook_name}")
+    if authorization != "Bearer jwt":
+        raise HTTPException(401)
+    return None
+```
+
+Now we can start a server.
+```bash
+uvicorn server:app --port 8080
+```
+
+Now you can start rustus, and it will check if Authorization header has a correct value.
+```bash
+rustus --hooks-http-urls "http://localhost:8000/hooks" --hooks-http-proxy-headers "Authorization"
+```
+
+
+### AMQP hooks
+
+All hooks can be sent with an AMQP protocol.
+
+For example if you have a rabbitMQ you can use it.
+
+```bash
+rustus --hooks-amqp-url "amqp://guest:guest@localhost" --hooks-amqp-exchange "my_exchange"
+```
+
+This command will create an exchange called "rustus" and queues for every hook.
+
+Every hook is published with routing key "rustus.{hook_name}" like
+"rustus.post-create" or "rustus.pre-create" and so on.
+
+The problem with AMQP hooks is that you can't block the upload.
+To do this you have to use HTTP or File hooks. But with AMQP your
+uploads become non-blocking which is definitely a good thing.
\ No newline at end of file
diff --git a/src/config.rs b/src/config.rs
index d27bc42a93bb68b020fae394de91cca83d790f84..15c1ab7b567e88378086832fb1594725e39e4e24 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -4,6 +4,7 @@ use std::path::PathBuf;
 
 use chrono::{Datelike, Timelike};
 use lazy_static::lazy_static;
+use log::error;
 use structopt::StructOpt;
 
 use crate::info_storages::AvailableInfoStores;
@@ -36,11 +37,11 @@ pub struct StorageOptions {
     ///
     /// This directory is used to store files
     /// for all *file_storage storages.
-    #[structopt(long, default_value = "./data")]
+    #[structopt(long, env = "RUSTUS_DATA_DIR", default_value = "./data")]
     pub data_dir: PathBuf,
 
-    #[structopt(long, default_value = "")]
-    pub dis_structure: String,
+    #[structopt(long, env = "RUSTUS_DIR_STRUCTURE", default_value = "")]
+    pub dir_structure: String,
 }
 
 #[derive(StructOpt, Debug, Clone)]
@@ -91,8 +92,8 @@ pub struct NotificationsOptions {
     ///
     /// This format will be used in all
     /// messages about hooks.
-    #[structopt(long, default_value = "default", env = "RUSTUS_NOTIFICATION_FORMAT")]
-    pub notification_format: Format,
+    #[structopt(long, default_value = "default", env = "RUSTUS_HOOKS_FORMAT")]
+    pub hooks_format: Format,
 
     /// Enabled hooks for notifications.
     #[structopt(
@@ -236,8 +237,10 @@ impl RustusConf {
         vars.insert("year".into(), now.year().to_string());
         vars.insert("hour".into(), now.hour().to_string());
         vars.insert("minute".into(), now.minute().to_string());
-        strfmt::strfmt(self.storage_opts.dis_structure.as_str(), &vars)
-            .unwrap_or_else(|_| "".into())
+        strfmt::strfmt(self.storage_opts.dir_structure.as_str(), &vars).unwrap_or_else(|err| {
+            error!("{}", err);
+            "".into()
+        })
     }
 
     /// List of extensions.
diff --git a/src/protocol/core/routes.rs b/src/protocol/core/routes.rs
index c81e32a304006001f4c50cd9887bd545cea4e64a..7ba5919abe84b1595ac7c49de3ad2d2c0189692c 100644
--- a/src/protocol/core/routes.rs
+++ b/src/protocol/core/routes.rs
@@ -81,7 +81,7 @@ pub async fn write_bytes(
         if app_conf.hook_is_active(hook) {
             let message = app_conf
                 .notification_opts
-                .notification_format
+                .hooks_format
                 .format(&request, &file_info)?;
             let headers = request.headers().clone();
             tokio::spawn(async move {
diff --git a/src/protocol/creation/routes.rs b/src/protocol/creation/routes.rs
index f9ee9cca7410bc419890bdbc9a418f1307d9d5ec..b24ca73503b53b006860bce45ea9ae0d46130efd 100644
--- a/src/protocol/creation/routes.rs
+++ b/src/protocol/creation/routes.rs
@@ -79,7 +79,7 @@ pub async fn create_file(
         let initial_file_info = FileInfo::new("", length, None, meta.clone());
         let message = app_conf
             .notification_opts
-            .notification_format
+            .hooks_format
             .format(&request, &initial_file_info)?;
         let headers = request.headers();
         notification_manager
@@ -110,7 +110,7 @@ pub async fn create_file(
     if app_conf.hook_is_active(Hook::PostCreate) {
         let message = app_conf
             .notification_opts
-            .notification_format
+            .hooks_format
             .format(&request, &file_info)?;
         let headers = request.headers().clone();
         // Adding send_message task to tokio reactor.
diff --git a/src/protocol/termination/routes.rs b/src/protocol/termination/routes.rs
index 272fc2154a43c7fc4b383df1512246925cb3545d..696feb2d44a824e6bb04d18512ea92a8d2884f6f 100644
--- a/src/protocol/termination/routes.rs
+++ b/src/protocol/termination/routes.rs
@@ -20,7 +20,7 @@ pub async fn terminate(
         if app_conf.hook_is_active(Hook::PostTerminate) {
             let message = app_conf
                 .notification_opts
-                .notification_format
+                .hooks_format
                 .format(&request, &file_info)?;
             let headers = request.headers().clone();
             tokio::spawn(async move {