Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f28661e664 | |||
| a09712c142 | |||
| a7e2320b21 | |||
| 24c83029e9 | |||
| ac64b3c04e | |||
| 26d696f813 | |||
| 1096981ef2 | |||
| e1936b494d | |||
| 0afc841b69 | |||
| a39122eb4b | |||
| 0f4da0bb53 |
@@ -2,7 +2,16 @@
|
||||
|
||||
This homelab project implements single-node On-premise IaaS system. The homelab contains virtual machines which are divided by their roles, such as private firewall, DNS, PKI, LDAP and database, SSO(OIDC). The standard domain is used to implement this system without specific vendors. All components are defined as code and initiated by IaC (Ansible) except hypervisor initial configuration.
|
||||
|
||||
## RTO times
|
||||
## RTO and RPO
|
||||
|
||||
### RPO
|
||||
- Each backup guarantees 24 hours RPO
|
||||
- DB dumps are backed up at 12:00 AM
|
||||
- Stateful data in app vm is backed up at 03:00 AM
|
||||
- The maximum inconsistency window between DB dumps and stateful data can be 27 hours.
|
||||
- The different backup time.
|
||||
|
||||
### RTO
|
||||
- Feb/25/2026 - Reprovisioning Hypervisor and vms
|
||||
- RTO: 1 hour 30 min - verified
|
||||
- Manual install and set vmm: 20 min
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
tags:
|
||||
- "always"
|
||||
- "init"
|
||||
- "upgrade"
|
||||
- "update"
|
||||
|
||||
- "site"
|
||||
- "[service_name]"
|
||||
# when: "'tags' is not in ansible_run_tags"
|
||||
|
||||
@@ -142,8 +142,8 @@
|
||||
name: "common"
|
||||
tasks_from: "services/set_alloy"
|
||||
apply:
|
||||
tags: ["init", "update", "alloy"]
|
||||
tags: ["init", "update", "alloy"]
|
||||
tags: ["init", "alloy"]
|
||||
tags: ["init", "alloy"]
|
||||
|
||||
- name: Set kopia
|
||||
ansible.builtin.include_role:
|
||||
|
||||
@@ -162,8 +162,8 @@
|
||||
name: "fw"
|
||||
tasks_from: "services/set_bind"
|
||||
apply:
|
||||
tags: ["init", "update", "bind"]
|
||||
tags: ["init", "update", "bind"]
|
||||
tags: ["init", "bind"]
|
||||
tags: ["init", "bind"]
|
||||
|
||||
- name: Set blocky
|
||||
ansible.builtin.include_role:
|
||||
|
||||
@@ -57,8 +57,16 @@
|
||||
- "data/containers/paperless/consume"
|
||||
- "containers/paperless"
|
||||
- "containers/paperless/ssl"
|
||||
- "containers/paperless/build"
|
||||
become: true
|
||||
|
||||
- name: Deploy containerfile for build
|
||||
ansible.builtin.template:
|
||||
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/paperless/build/paperless.containerfile.j2"
|
||||
dest: "{{ node['home_path'] }}/containers/paperless/build/Containerfile"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "svadmins"
|
||||
mode: "0640"
|
||||
|
||||
- name: Deploy root certificate
|
||||
ansible.builtin.copy:
|
||||
@@ -72,6 +80,18 @@
|
||||
notify: "notification_restart_paperless"
|
||||
no_log: true
|
||||
|
||||
- name: Build paperless container image
|
||||
containers.podman.podman_image:
|
||||
name: "{{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx"
|
||||
# check tags from container file
|
||||
tag: "{{ version['containers']['paperless'] }}"
|
||||
state: "build"
|
||||
path: "{{ node['home_path'] }}/containers/paperless/build"
|
||||
|
||||
- name: Prune paperless dangling images
|
||||
containers.podman.podman_prune:
|
||||
image: true
|
||||
|
||||
- name: Register secret value to podman secret
|
||||
containers.podman.podman_secret:
|
||||
name: "{{ item.name }}"
|
||||
@@ -129,8 +149,8 @@
|
||||
loop:
|
||||
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
|
||||
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
|
||||
- image: "ghcr.io/paperless-ngx/paperless-ngx:{{ version['containers']['paperless'] }}"
|
||||
file: "ghcr.io_paperless-ngx_paperless-ngx_{{ version['containers']['paperless'] }}"
|
||||
- image: "{{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx:{{ version['containers']['paperless'] }}"
|
||||
file: "{{ domain['internal'] }}_{{ node['name'] }}_paperless-ngx_{{ version['containers']['paperless'] }}"
|
||||
loop_control:
|
||||
label: "{{ item.file }}"
|
||||
register: container_archive_images
|
||||
|
||||
@@ -74,3 +74,10 @@
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
become: true
|
||||
|
||||
- name: Fetch deb bin file
|
||||
ansible.builtin.fetch:
|
||||
src: "/var/cache/apt/archives/alloy-{{ version['packages']['alloy'] }}.deb"
|
||||
dest: "{{ hostvars['console']['node']['data_path'] }}/bin/"
|
||||
flat: true
|
||||
become: true
|
||||
|
||||
@@ -100,20 +100,20 @@
|
||||
|
||||
- name: Check container archive images
|
||||
ansible.builtin.stat:
|
||||
path: "{{ node['home_path'] }}/archives/containers/ilnmors.internal_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
path: "{{ node['home_path'] }}/archives/containers/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
register: container_archive_images
|
||||
|
||||
- name: Save container archive images
|
||||
containers.podman.podman_save:
|
||||
image:
|
||||
- "ilnmors.internal/{{ node['name'] }}/caddy:{{ version['containers']['caddy'] }}"
|
||||
dest: "{{ node['home_path'] }}/archives/containers/ilnmors.internal_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
- "{{ domain['internal'] }}/{{ node['name'] }}/caddy:{{ version['containers']['caddy'] }}"
|
||||
dest: "{{ node['home_path'] }}/archives/containers/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
format: "oci-archive"
|
||||
force: false
|
||||
when: not container_archive_images.stat.exists
|
||||
|
||||
- name: Fetch container archive images
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ node['home_path'] }}/archives/containers/ilnmors.internal_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
src: "{{ node['home_path'] }}/archives/containers/{{ domain['internal'] }}_{{ node['name'] }}_caddy_{{ version['containers']['caddy'] }}.tar"
|
||||
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
|
||||
flat: true
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
---
|
||||
- name: Register font
|
||||
ansible.builtin.shell: |
|
||||
fc-cache -f -v
|
||||
become: true
|
||||
changed_when: false
|
||||
listen: "notification_update_font"
|
||||
ignore_errors: true # noqa: ignore-errors
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/0xERR0R/blocky/releases/download/v{{ version['packages']['blocky'] }}/\
|
||||
blocky_v{{ version['packages']['blocky'] }}_Linux_x86_64.tar.gz"
|
||||
dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}-x86_64.tar.gz"
|
||||
dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
|
||||
owner: "blocky"
|
||||
group: "blocky"
|
||||
mode: "0600"
|
||||
@@ -52,16 +52,16 @@
|
||||
ansible.builtin.get_url:
|
||||
url: "https://github.com/0xERR0R/blocky/releases/download/v{{ version['packages']['blocky'] }}/\
|
||||
blocky_v{{ version['packages']['blocky'] }}_Linux_arm64.tar.gz"
|
||||
dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}-arm64.tar.gz"
|
||||
dest: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
|
||||
owner: "blocky"
|
||||
group: "blocky"
|
||||
mode: "0600"
|
||||
become: true
|
||||
when: ansible_facts['architecture'] == "aarch64"
|
||||
|
||||
- name: Deploy blocky binary file (x86_64)
|
||||
- name: Deploy blocky binary file
|
||||
ansible.builtin.unarchive:
|
||||
src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}-x86_64.tar.gz"
|
||||
src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
|
||||
remote_src: true
|
||||
dest: "/usr/local/bin/"
|
||||
owner: "root"
|
||||
@@ -72,23 +72,6 @@
|
||||
- "--wildcards"
|
||||
- "blocky"
|
||||
become: true
|
||||
when: ansible_facts['architecture'] == "x86_64"
|
||||
notify: "notification_restart_blocky"
|
||||
|
||||
- name: Deploy blocky binary file (aarch64)
|
||||
ansible.builtin.unarchive:
|
||||
src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}-arm64.tar.gz"
|
||||
remote_src: true
|
||||
dest: "/usr/local/bin/"
|
||||
owner: "root"
|
||||
group: "root"
|
||||
mode: "0755"
|
||||
extra_opts:
|
||||
- "--strip-components=0"
|
||||
- "--wildcards"
|
||||
- "blocky"
|
||||
become: true
|
||||
when: ansible_facts['architecture'] == "aarch64"
|
||||
notify: "notification_restart_blocky"
|
||||
|
||||
- name: Deploy blocky config
|
||||
@@ -141,3 +124,10 @@
|
||||
enabled: true
|
||||
daemon_reload: true
|
||||
become: true
|
||||
|
||||
- name: Fetch deb bin file
|
||||
ansible.builtin.fetch:
|
||||
src: "/home/blocky/bin/blocky-{{ version['packages']['blocky'] }}.tar.gz"
|
||||
dest: "{{ hostvars['console']['node']['data_path'] }}/bin/"
|
||||
flat: true
|
||||
become: true
|
||||
|
||||
@@ -176,15 +176,17 @@
|
||||
- name: Check container archive images
|
||||
ansible.builtin.stat:
|
||||
path: "{{ node['home_path'] }}/archives/containers/\
|
||||
ilnmors.internal_{{ node['name'] }}_postgres_pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
|
||||
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
register: container_archive_images
|
||||
|
||||
- name: Save container archive images
|
||||
containers.podman.podman_save:
|
||||
image:
|
||||
- "ilnmors.internal/{{ node['name'] }}/postgres:pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}"
|
||||
- "{{ domain['internal'] }}/{{ node['name'] }}/postgres:pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}"
|
||||
dest: "{{ node['home_path'] }}/archives/containers/\
|
||||
ilnmors.internal_{{ node['name'] }}_postgres_pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
|
||||
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
format: "oci-archive"
|
||||
force: false
|
||||
when: not container_archive_images.stat.exists
|
||||
@@ -192,6 +194,7 @@
|
||||
- name: Fetch container archive images
|
||||
ansible.builtin.fetch:
|
||||
src: "{{ node['home_path'] }}/archives/containers/\
|
||||
ilnmors.internal_{{ node['name'] }}_postgres_pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
{{ domain['internal'] }}_{{ node['name'] }}_postgres_\
|
||||
pg{{ version['containers']['postgresql'] }}-vectorchord{{ version['containers']['vectorchord'] }}.tar"
|
||||
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
|
||||
flat: true
|
||||
|
||||
@@ -19,7 +19,11 @@ log() {
|
||||
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
local level="$1"
|
||||
local msg="$2"
|
||||
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&2
|
||||
if [ "$level" == "error" ]; then
|
||||
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&2
|
||||
else
|
||||
echo "time=\"$timestamp\" level=\"$level\" msg=\"$msg\" source=\"edit_secret.sh\"">&1
|
||||
fi
|
||||
}
|
||||
|
||||
# Secret file check
|
||||
@@ -58,9 +62,9 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
# Get GPG password from prompt
|
||||
echo -n "Enter GPG passphrase: " >&2
|
||||
echo -n "Enter GPG passphrase: " >&1
|
||||
read -s GPG_PASSPHRASE
|
||||
echo "" >&2
|
||||
echo "" >&1
|
||||
|
||||
# Decrypt age-key on the tmpfs (memory)
|
||||
echo "$GPG_PASSPHRASE" | gpg --batch --yes --passphrase-fd 0 \
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM ghcr.io/paperless-ngx/paperless-ngx:{{ version['containers']['paperless'] }}
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends curl ca-certificates \
|
||||
&& curl -fsSL https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/main/kor.traineddata \
|
||||
-o /usr/share/tesseract-ocr/5/tessdata/kor.traineddata \
|
||||
&& curl -fsSL https://raw.githubusercontent.com/tesseract-ocr/tessdata_best/main/eng.traineddata \
|
||||
-o /usr/share/tesseract-ocr/5/tessdata/eng.traineddata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER paperless
|
||||
@@ -8,7 +8,7 @@ After=redis_paperless.service
|
||||
Wants=redis_paperless.service
|
||||
|
||||
[Container]
|
||||
Image=ghcr.io/paperless-ngx/paperless-ngx:{{ version['containers']['paperless'] }}
|
||||
Image={{ domain['internal'] }}/{{ node['name'] }}/paperless-ngx:{{ version['containers']['paperless'] }}
|
||||
ContainerName=paperless
|
||||
HostName=paperless
|
||||
PublishPort={{ services['paperless']['ports']['http'] }}:8000/tcp
|
||||
|
||||
@@ -16,4 +16,6 @@ whitelist:
|
||||
- "evt.Meta.target_fqdn == '{{ services['immich']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains '/api/assets/' && evt.Meta.http_path contains '/thumbnail'"
|
||||
# nextcloud thumbnail/preview request error false positive
|
||||
- "evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path startsWith '/index.php/core/preview?'"
|
||||
# nextcloud chunks.mjs request false positive
|
||||
- "evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status in ['200', '304'] && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains 'chunk.mjs'"
|
||||
{% endif %}
|
||||
|
||||
+1
-1
@@ -148,4 +148,4 @@ if [ "$TYPE" == "ENV" ]; then
|
||||
log "error" "SOPS extract error"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -23,11 +23,14 @@
|
||||
- 2026-05-03: Make previous expressions annotation
|
||||
- 2026-05-07: Find the false positive case, which is not on `crowdsecurity/nextcloud-whitelist`
|
||||
- 2026-05-07: Set whitelist expression
|
||||
- 2026-05-11: Find the false positive case, which is not on `crowdsec/nextcloud-whitelist`
|
||||
- 2026-05-11: Set whitelist expression
|
||||
|
||||
## Solution
|
||||
- Install crowdsecurity/nextcloud-whitelist on auth node
|
||||
- Add expression on whitelist
|
||||
- evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path startsWith '/index.php/core/preview?'
|
||||
- evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status == '404' && evt.Meta.http_verb == 'GET' && evt.Meta.http_path startsWith '/index.php/core/preview?'
|
||||
- evt.Meta.target_fqdn == '{{ services['nextcloud']['domain']['public'] }}.{{ domain['public'] }}' && evt.Meta.http_status in ['200', '304'] && evt.Meta.http_verb == 'GET' && evt.Meta.http_path contains 'chunk.mjs'
|
||||
|
||||
### Deprecated solution
|
||||
- Access to fw
|
||||
|
||||
@@ -45,6 +45,11 @@ ALTER DATABASE paperless_db OWNER TO paperless;
|
||||
- "paperless"
|
||||
```
|
||||
|
||||
### Paperless custom build
|
||||
|
||||
- paperless-ngx uses 'tesseract_fast' model
|
||||
- building custom container to use 'tesseract_best' model to improve OCR accuracy.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Access to paperless
|
||||
|
||||
Reference in New Issue
Block a user