Compare commits

..

7 Commits

Author SHA1 Message Date
il 1256122081 feat(outline): release outline
deployment notes:
- compare to Affine (Affine is heavy and their whtieboard and canvas is not used enough)
- don't restart this when it initiates, redis lock causes failure
2026-05-16 20:56:43 +09:00
il f28661e664 refactor(convention): add existing 'site' tag to convention 2026-05-15 10:16:36 +09:00
il a09712c142 refactor(script): update edit_secret.sh to optimize log print logic 2026-05-15 10:12:42 +09:00
il a7e2320b21 chore(script): archive a extract_secret.sh script
archived stack: extract_secret.sh
2026-05-15 09:19:59 +09:00
il 24c83029e9 refactor(playbook): update convention and remove deprecated tag
update notes:
- remove tags 'update', 'upgrade' from convention.yaml
- remove tags 'update' from playbooks/app/site.yaml
2026-05-15 09:04:51 +09:00
il ac64b3c04e docs(readme): add RPO on readme 2026-05-13 17:12:59 +09:00
il 26d696f813 refactor(all): update hardcoded internal domain to ansible variable 2026-05-12 08:08:04 +09:00
20 changed files with 402 additions and 25 deletions
+10 -1
View File
@@ -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
+2 -3
View File
@@ -33,7 +33,6 @@
tags:
- "always"
- "init"
- "upgrade"
- "update"
- "site"
- "[service_name]"
# when: "'tags' is not in ansible_run_tags"
+9
View File
@@ -151,6 +151,14 @@ services:
http: "3001"
redis: "6383"
subuid: "100999"
outline:
domain:
public: "outline"
internal: "outline.app"
ports:
http: "3002"
redis: "6384"
subuid: "101000"
version:
packages:
@@ -187,3 +195,4 @@ version:
nextcloud: "33.0.3"
collabora: "25.04.9.4.1"
sure: "0.7.0-hotfix.2"
outline: "1.7.1"
+10 -2
View File
@@ -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:
@@ -225,6 +225,14 @@
tags: ["site", "sure"]
tags: ["site", "sure"]
- name: Set outline
ansible.builtin.include_role:
name: "app"
tasks_from: "services/set_outline"
apply:
tags: ["site", "outline"]
tags: ["site", "outline"]
- name: Flush handlers right now
ansible.builtin.meta: "flush_handlers"
+2 -2
View File
@@ -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:
+12
View File
@@ -102,3 +102,15 @@
changed_when: false
listen: "notification_restart_sure"
ignore_errors: true # noqa: ignore-errors
- name: Restart outline
ansible.builtin.systemd:
name: "outline.service"
state: "restarted"
enabled: true
scope: "user"
daemon_reload: true
when: is_outline_init.stat.exists
changed_when: false
listen: "notification_restart_outline"
ignore_errors: true # noqa: ignore-errors
@@ -0,0 +1,152 @@
---
- name: Set redis service name
ansible.builtin.set_fact:
redis_service: "outline"
- name: Create redis_outline directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['redis']['subuid'] }}"
group: "svadmins"
mode: "0770"
loop:
- "containers/redis"
- "containers/redis/{{ redis_service }}"
- "containers/redis/{{ redis_service }}/data"
become: true
- name: Deploy redis config file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.conf.j2"
dest: "{{ node['home_path'] }}/containers/redis/{{ redis_service }}/redis.conf"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0644"
register: "is_redis_conf"
- name: Deploy redis container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/redis/redis.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/redis_{{ redis_service }}.container"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0644"
register: "is_redis_containerfile"
- name: Enable (Restart) redis service
ansible.builtin.systemd:
name: "redis_{{ redis_service }}.service"
state: "restarted"
enabled: true
daemon_reload: true
scope: "user"
when: is_redis_conf.changed or is_redis_containerfile.changed # noqa: no-handler
- name: Create outline directory
ansible.builtin.file:
path: "{{ node['home_path'] }}/{{ item }}"
state: "directory"
owner: "{{ services['outline']['subuid'] }}"
group: "svadmins"
mode: "0770"
loop:
- "data/containers/outline"
- "data/containers/outline/data"
- "containers/outline"
- "containers/outline/ssl"
become: true
- name: Check data directory empty
ansible.builtin.stat:
path: "{{ node['home_path'] }}/data/containers/outline/.init"
register: "is_outline_init"
- name: Create .init file
ansible.builtin.file:
path: "{{ node['home_path'] }}/data/containers/outline/.init"
state: "touch"
mode: "0644"
owner: "{{ ansible_user }}"
group: "svadmins"
when: not is_outline_init.stat.exists
- name: Deploy root certificate
ansible.builtin.copy:
content: |
{{ hostvars['console']['ca']['root']['crt'] }}
dest: "{{ node['home_path'] }}/containers/outline/ssl/{{ root_cert_filename }}"
owner: "{{ services['outline']['subuid'] }}"
group: "svadmins"
mode: "0440"
become: true
notify: "notification_restart_outline"
no_log: true
- name: Register secret value to podman secret
containers.podman.podman_secret:
name: "{{ item.name }}"
data: "{{ item.value }}"
state: "present"
force: true
loop:
- name: "OUTLINE_SECRET_KEY"
value: "{{ hostvars['console']['outline']['session_secret'] }}"
- name: "OUTLINE_UTILS_SECRET"
value: "{{ hostvars['console']['outline']['utils_secret'] }}"
- name: "OUTLINE_DATABASE_PASSWORD"
value: "{{ hostvars['console']['postgresql']['password']['outline'] }}"
- name: "OUTLINE_OIDC_CLIENT_SECRET"
value: "{{ hostvars['console']['outline']['oidc']['secret'] }}"
notify: "notification_restart_outline"
no_log: true
- name: Deploy outline.container file
ansible.builtin.template:
src: "{{ hostvars['console']['node']['config_path'] }}/services/containers/app/outline/outline.container.j2"
dest: "{{ node['home_path'] }}/.config/containers/systemd/outline.container"
owner: "{{ ansible_user }}"
group: "svadmins"
mode: "0644"
notify: "notification_restart_outline"
- name: Enable outline.service
ansible.builtin.systemd:
name: "outline.service"
state: "started"
enabled: true
daemon_reload: true
scope: "user"
- name: Check container archive images
ansible.builtin.stat:
path: "{{ node['home_path'] }}/archives/containers/{{ item.file }}.tar"
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "docker.io/outlinewiki/outline:{{ version['containers']['outline'] }}"
file: "docker.io_outlinewiki_outline_{{ version['containers']['outline'] }}"
loop_control:
label: "{{ item.file }}"
register: container_archive_images
- name: Save container archive images
containers.podman.podman_save:
image:
- "{{ item.item.image }}"
dest: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
format: "oci-archive"
force: false
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
when: not item.stat.exists
- name: Fetch container archive images
ansible.builtin.fetch:
src: "{{ node['home_path'] }}/archives/containers/{{ item.item.file }}.tar"
dest: "{{ hostvars['console']['node']['data_path'] }}/images/containers/"
flat: true
loop: "{{ container_archive_images.results }}"
loop_control:
label: "{{ item.item.file }}"
@@ -149,8 +149,8 @@
loop:
- image: "docker.io/library/redis:{{ version['containers']['redis'] }}"
file: "docker.io_library_redis_{{ version['containers']['redis'] }}"
- image: "ilnmors.internal/{{ node['name'] }}/paperless-ngx:{{ version['containers']['paperless'] }}"
file: "ilnmors.internal_{{ node['name'] }}_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
@@ -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
@@ -12,6 +12,7 @@
- "affine"
- "nextcloud"
- "sure"
- "outline"
- name: Create postgresql directory
ansible.builtin.file:
@@ -176,15 +177,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 +195,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
+7 -3
View File
@@ -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 \
+13 -2
View File
@@ -120,6 +120,7 @@ postgresql:
affine: ENC[AES256_GCM,data:XPXrcszsV06YqCJZ7CDqc4rCwqqNlbtLCFYfLAQ8jamLtft8L2UVrMA4WZo=,iv:vrWdBeckxB9tmEE628j4jhU+hSpE6TXYMGt0hh1Cg84=,tag:hlWwWUGht8NqWTZREMsa1Q==,type:str]
nextcloud: ENC[AES256_GCM,data:ROsximNuWYMTZktmLJPx7W1Qol/uT+APgwoCtFO/6ZYYc3KxKvlk344eqEc=,iv:4d+MrfIHjJKAcwhvZ3g4go66uZcieuL7lngKErJd+fg=,tag:QbWOtxeCbiu62GyrE2atXg==,type:str]
sure: ENC[AES256_GCM,data:FULJ2gjJ2gZC3s324itW+CjGRBHIP9RnOqw5TT1UaiUhb7UHAPm1na+LsZk=,iv:c0GnVZkxprJUzPPq3TCQaZvAes9QQuvDXqgVLLaiQIg=,tag:uDxy/Lkd2hNK4AWwMNMslw==,type:str]
outline: ENC[AES256_GCM,data:0eJFCfHgS4olWXHOnUPixs4Dl5PoxJa+2H1mk3YgC4GUv+nX8gTPmzKTMVM=,iv:HGxX7aLODUc2nVLC8YXdet9qphU/wxMapWhk93YmHDM=,tag:6/2uckW/a24LgVFRH6E85Q==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
@@ -251,6 +252,16 @@ sure:
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:H6BtfLuevnDCEpHSmf6pxQ==,iv:9Jw0UZwVwS2It7c4e1rAN/BEiqDKp9DHD4W9xdpJ/Vs=,tag:Xj8HKvxgCA260CXBVK2S1A==,type:comment]
outline:
session_secret: ENC[AES256_GCM,data:A26j8/ghGA9MjDiuq+H84iTFXkHZp3l3LbGKO9w+Cgv+u1dkTF5q4zuBnOSBi+Tnz+2Y4jIoxsac+CS0dguRMg==,iv:NzJiJxVxOvud3e4mP8urnHrqugE8SmjFVnZtwfzrUzs=,tag:YR/jexb3G0HfC2qHpn6fPA==,type:str]
utils_secret: ENC[AES256_GCM,data:ACI+NZzqHBsYeb4WwTJ1nfJcy6zNwJn2hBqA93aM5cgNWQ4KtQHDXgmS6UTwmSpxrteiaH25J3Np03H3eQoVeA==,iv:nitwxxwTPQvoQXGLVMnk2zsdC7M+JIXkTY4zJdQ3DFo=,tag:IQewG7EE9IgQk+Rfz7M5Iw==,type:str]
oidc:
secret: ENC[AES256_GCM,data:zR6sN205C51BIiQxLYOwC4ZbTYGr2AveiPeEbdadairmfnNJkGOtm0zT4Gg=,iv:uyYoJYuAuH/Gy2dlLr2SkkLYq+lMdwBgmzUsEj1EAKc=,tag:uhgvE6BagvPPKf7Y3NaGfA==,type:str]
hash: ENC[AES256_GCM,data:xOibOUdmfhvUk6HABHf/MGIaUqRtrmbBpD5eTIrlt+Ll5IxuIaECMIyBufqV6KStoNsAxtlWmPqoVYROnVh7Z50ntywP6w6YNFbihMGr5tYnNBAS2ITViCq0Z7GQpNVmhNkFbmZv4bX14YOI557CS1KeDVrKrHnZ3fPgEgDwEiUdCwo=,iv:3lVQEw/gmTvJ6wteyKhAHAYUUw1AfKku8YEveddJTZU=,tag:0jimA4vof+nKOQtCOrVFdw==,type:str]
#ENC[AES256_GCM,data:ODXFUxxxdQ==,iv:s9zJVx6wo6x517tbNvC+FZ0dFzqbjqeLI6rXBq72hQA=,tag:bXoV2I3LbpmQyddJrtS3Qg==,type:comment]
#
#
#ENC[AES256_GCM,data:T4Wtn49AAxPd2QUFTR+q,iv:bH5goGWBDqumAat9dUv2OwfCUJUpuVqncTMqMBZUXhI=,tag:G+W6hHA+yftQ+4RJpXrxHg==,type:comment]
switch:
password: ENC[AES256_GCM,data:qu0f9L7A0eFq/UCpaRs=,iv:W8LLOp3MSfd/+EfNEZNf91K8GgI5eUfVPoWTRES2C0Y=,tag:Q5FlAOfwqwJwPvd7k6i+0g==,type:str]
@@ -280,7 +291,7 @@ sops:
UmliaFNxVTBqRkI1QWJpWGpTRWxETW8KEY/8AfU73UOzCGhny1cNnd5dCNv7bHXt
k+uyWPPi+enFkVaceSwMFrA66uaWWrwAj11sXEB7yzvGFPrnAGezjQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-05-09T14:26:51Z"
mac: ENC[AES256_GCM,data:TYs08ZSS2kcO5lYuhQ/IySUSQ3DpL+ba3/uNLyszht4OttR110/W/WQLiRuu/Ql6FwtDtjq6I3iNpOhmCHSv1kMCam1l99GEIYCaPUIY+TY3Zw0j7518dFXe8p/DrKRwIVXfK5lIKLIEd+eizD50HzwXXJFmU+7YDkQ1Dx+55kw=,iv:arJKJ4wO4sdQlu3GZbtultsfM6s8vbhG93tnf2EjJDc=,tag:m95gUqvn4w85XI8qVvCZpQ==,type:str]
lastmodified: "2026-05-16T08:55:23Z"
mac: ENC[AES256_GCM,data:UXueLe3zZcuvUUH9OJGWcwUqzPoIhw+vtTpqZIB2fGOa4KpFCUmOXtRgws+mQVzBRB30xpbfbbwWM/ZIWjVx2EoE4uP09uk8fgtmMD9ZBacHxEzjxksKW78cX4SoOj6tzzhACTkj61RRrXS8Ft1RNZJFZlzeQVodEGt5eoJUq28=,iv:eCsQAiTqqL1w6xQ69u/Yi4iKUXS44sPUOSDuYPp8WFM=,tag:2kEdh4VB0XWhv0KBgD8EGA==,type:str]
unencrypted_suffix: _unencrypted
version: 3.12.1
@@ -0,0 +1,77 @@
[Quadlet]
DefaultDependencies=false
[Unit]
Description=Outline
After=redis_outline.service
Wants=redis_outline.service
[Container]
Image=docker.io/outlinewiki/outline:{{ version['containers']['outline'] }}
ContainerName=outline
HostName=outline
PublishPort={{ services['outline']['ports']['http'] }}:3000/tcp
# Volumes
Volume=%h/data/containers/outline/data:/var/lib/outline/data:rw
Volume=%h/containers/outline/ssl:/etc/ssl/outline:ro
# General
Environment="TZ={{ timezone }}"
Environment="NODE_ENV=production"
Environment="URL=https://{{ services['outline']['domain']['public'] }}.{{ domain['public'] }}"
Environment="PORT=3000"
#Environment="FORCE_HTTPS=true" #HTST function is not required
# Secrets
Secret=OUTLINE_SECRET_KEY,type=env,target=SECRET_KEY
Secret=OUTLINE_UTILS_SECRET,type=env,target=UTILS_SECRET
# Database
Environment="DATABASE_HOST={{ services['postgresql']['domain'] }}.{{ domain['internal'] }}"
Environment="DATABASE_PORT={{ services['postgresql']['ports']['tcp'] }}"
Environment="DATABASE_NAME=outline_db"
Environment="DATABASE_USER=outline"
Environment="PGSSLMODE=verify-full"
Environment="NODE_EXTRA_CA_CERTS=/etc/ssl/outline/{{ root_cert_filename }}"
Secret=OUTLINE_DATABASE_PASSWORD,type=env,target=DATABASE_PASSWORD
# Redis
Environment="REDIS_URL=redis://host.containers.internal:{{ services['outline']['ports']['redis'] }}"
# Storage
Environment="FILE_STORAGE=local"
Environment="FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data"
Environment="FILE_STORAGE_UPLOAD_MAX_SIZE=262144000"
# OIDC
Environment="OIDC_CLIENT_ID=outline"
Environment="OIDC_AUTH_URI=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}/api/oidc/authorization"
Environment="OIDC_TOKEN_URI=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}/api/oidc/token"
Environment="OIDC_USERINFO_URI=https://{{ services['authelia']['domain'] }}.{{ domain['public'] }}/api/oidc/userinfo"
Environment="OIDC_USERNAME_CLAIM=preferred_username"
Environment="OIDC_DISPLAY_NAME=Authelia"
Environment="OIDC_SCOPES=openid offline_access profile email"
Secret=OUTLINE_OIDC_CLIENT_SECRET,type=env,target=OIDC_CLIENT_SECRET
# Mail
#Environment="SMTP_HOST=smtp.example.com"
#Environment="SMTP_PORT=587"
#Environment="SMTP_USERNAME=outline@example.com"
#Environment="SMTP_PASSWORD=change_me"
#Environment="SMTP_FROM_EMAIL=outline@example.com"
#Environment="SMTP_REPLY_EMAIL=outline@example.com"
#Environment="SMTP_SECURE=false"
# Optional
Environment="LOG_LEVEL=warn"
[Service]
ExecStartPre=/usr/bin/nc -zv {{ services['postgresql']['domain'] }}.{{ domain['internal'] }} {{ services['postgresql']['ports']['tcp'] }}
Restart=always
RestartSec=10s
TimeoutStopSec=120
[Install]
WantedBy=default.target
@@ -8,7 +8,7 @@ After=redis_paperless.service
Wants=redis_paperless.service
[Container]
Image=ilnmors.internal/app/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
@@ -260,3 +260,26 @@ identity_providers:
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_basic'
# https://www.authelia.com/integration/openid-connect/clients/outline/
- client_id: 'outline'
client_name: 'Outline'
client_secret: '{{ hostvars['console']['outline']['oidc']['hash'] }}'
public: false
authorization_policy: 'one_factor'
require_pkce: false
pkce_challenge_method: ''
redirect_uris:
- 'https://{{ services['outline']['domain']['public'] }}.{{ domain['public'] }}/auth/oidc.callback'
scopes:
- 'openid'
- 'offline_access'
- 'profile'
- 'email'
response_types:
- 'code'
grant_types:
- 'authorization_code'
- 'refresh_token'
access_token_signed_response_alg: 'none'
userinfo_signed_response_alg: 'none'
token_endpoint_auth_method: 'client_secret_post'
@@ -77,3 +77,9 @@
header_up Host {http.request.header.X-Forwarded-Host}
}
}
{{ services['outline']['domain']['internal'] }}.{{ domain['internal'] }} {
import private_tls
reverse_proxy host.containers.internal:{{ services['outline']['ports']['http'] }} {
header_up Host {http.request.header.X-Forwarded-Host}
}
}
@@ -136,6 +136,15 @@
}
}
}
{{ services['outline']['domain']['public'] }}.{{ domain['public'] }} {
import crowdsec_log
route {
crowdsec
reverse_proxy https://{{services['outline']['domain']['internal'] }}.{{ domain['internal'] }} {
header_up Host {http.reverse_proxy.upstream.host}
}
}
}
# Internal domain
{{ node['name'] }}.{{ domain['internal'] }} {
@@ -148,4 +148,4 @@ if [ "$TYPE" == "ENV" ]; then
log "error" "SOPS extract error"
exit 1
fi
fi
fi
+52
View File
@@ -0,0 +1,52 @@
# Outline
## Prerequisite
### Create database
- Create the password with `openssl rand -base64 32`
- Save this value in secrets.yaml in `postgresql.password.outline`
- Access infra server to create outline_db with `podman exec -it postgresql psql -U postgres`
```SQL
CREATE USER outline WITH PASSWORD 'postgresql.password.outline';
CREATE DATABASE outline_db;
ALTER DATABASE outline_db OWNER TO outline;
```
### Create oidc secret and hash
- Create the secret with `openssl rand -base64 32`
- access to auth vm
- `podman exec -it authelia sh`
- `authelia crypto hash generate pbkdf2 --password 'outline.oidc.secret'`
- Save this value in secrets.yaml in `outline.oidc.secret` and `outline.oidc.hash`
### Create session secret and utils value
- Create two secrets with `openssl rand -hex 32`
- Save this value in secrets.yaml in `outline.session_secret`
- Save this value in secrets.yaml in `outline.utils_secret`
### Add postgresql dump backup list
- [set_postgresql.yaml](../../../ansible/roles/infra/tasks/services/set_postgresql.yaml)
```yaml
- name: Set connected services list
ansible.builtin.set_fact:
connected_services:
- ...
- "outline"
```
## Configuration
### Notice
- Outline is a BSL, not fully open-sourced stack. Always ensure they can change their policy of the outline
### Access to outline
- https://outline.ilnmors.com
- Authelia redirect and automatically sign-up
+2
View File
@@ -126,6 +126,8 @@
- Link to Nextcloud
- [x] sure
- budget and finance
- [x] outline
- Compare to affine, the whiteboard and canvas functions are not useful enough
- [ ] memos
- WriteFreely or directus + frontend(Astro)
- MediaCMS or PeerTube