diff --git a/.justfile b/.justfile
index b37ae07..93d7c2b 100644
--- a/.justfile
+++ b/.justfile
@@ -1,5 +1,5 @@
-export ANSIBLE_VAULT_PASSWORD_FILE := ".decrypt-pass.txt"
-export ANSIBLE_BECOME_PASSWORD_FILE := ".become-pass.txt"
+export ANSIBLE_VAULT_PASSWORD_FILE := justfile_directory() + "/.decrypt-pass.txt"
+export ANSIBLE_BECOME_PASSWORD_FILE := justfile_directory() + "/.become-pass.txt"
# Debug output, disabled in CI
export ANSIBLE_DISPLAY_ARGS_TO_STDOUT := if env('CI', '') == 'true' { 'false' } else { 'true' }
@@ -17,6 +17,7 @@ ansible +ARGS:
list-host:
uv run ansible-inventory --list
+[no-cd]
encrypt +ARGS:
uv run ansible-vault encrypt {{ ARGS }}
@@ -26,5 +27,6 @@ encrypt-var NAME +CONTENT='':
decrypt-var FILE NAME:
uv run ansible localhost -m ansible.builtin.debug -e "@{{ FILE }}" -a var="{{ NAME }}"
+[no-cd]
decrypt +ARGS:
uv run ansible-vault edit {{ ARGS }}
diff --git a/files/docker/compose-traefik.yaml b/files/docker/compose-traefik.yaml
deleted file mode 100644
index 6d50240..0000000
--- a/files/docker/compose-traefik.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-version: '3'
-networks:
- reverse-proxy:
- external: true
-services:
- traefik:
- container_name: traefix-proxy
- image: 'traefik:latest'
- restart: unless-stopped
- networks:
- - reverse-proxy
- ports:
- - '80:80'
- - '443:443'
- - '8080:8080'
- healthcheck:
- test: 'wget -qO- http://localhost:80/ping || exit 1'
- interval: 4s
- timeout: 2s
- retries: 5
- volumes:
- - '/var/run/docker.sock:/var/run/docker.sock:ro'
- - '/data/coolify/proxy:/traefik'
- command:
- - '--ping=true'
- - '--ping.entrypoint=http'
- - '--api.dashboard=true'
- - '--api.insecure=true'
- - '--entrypoints.http.address=:80'
- - '--entryPoints.http.forwardedHeaders.trustedIPs=10.0.10.0/24'
- - '--entrypoints.https.address=:443'
- - '--entryPoints.https.forwardedHeaders.trustedIPs=10.0.10.0/24'
- - '--entrypoints.http.http.encodequerysemicolons=true'
- - '--entryPoints.http.http2.maxConcurrentStreams=50'
- - '--entrypoints.https.http.encodequerysemicolons=true'
- - '--entryPoints.https.http2.maxConcurrentStreams=50'
- - '--providers.docker.exposedbydefault=false'
- - "--providers.swarm.endpoint=tcp://127.0.0.1:2377"
- labels:
- - traefik.enable=true
- - traefik.http.routers.traefik.entrypoints=http
- - traefik.http.routers.traefik.service=api@internal
- - traefik.http.services.traefik.loadbalancer.server.port=8080
diff --git a/files/docker/index/adminer-theme-switcher.php b/files/docker/index/adminer-theme-switcher.php
deleted file mode 100644
index 11b6a60..0000000
--- a/files/docker/index/adminer-theme-switcher.php
+++ /dev/null
@@ -1,191 +0,0 @@
-
- * @link https://github.com/felladrin/adminer-theme-switcher
- * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
- * @license http://www.gnu.org/licenses/gpl-2.0.html GNU General Public License, version 2 (one or other)
- */
-class AdminerThemeSwitcher
-{
- protected static $themeList;
-
- protected static $option;
-
- public static $prompt = 'Type the number of the theme you want to use: ';
-
- public static function run()
- {
- if (static::isRunningFromCommandLine()) {
- static::printListAvailableThemes();
- static::readOptionFromCommandLine();
- static::switchTheme();
- return;
- }
-
- if (static::hasNotSelectedAnOptionFromBrowserYet()) {
- static::printListAvailableThemes();
- static::printJavascriptPrompt();
- } else {
- static::readOptionFromBrowser();
- static::switchTheme();
- }
- }
-
- public static function isRunningFromCommandLine()
- {
- return (php_sapi_name() === 'cli');
- }
-
- public static function isRunningOnWindows()
- {
- return (PHP_OS == 'WINNT');
- }
-
- public static function getLineEnding()
- {
- return (static::isRunningFromCommandLine() ? PHP_EOL : '
');
- }
-
- public static function getThemeList()
- {
- if (!empty(static::$themeList)) {
- return static::$themeList;
- }
-
- $context = stream_context_create([
- 'http' => [
- 'method' => 'GET',
- 'header' => [
- 'User-Agent: PHP'
- ]
- ],
- 'ssl' => [
- "verify_peer" => false,
- "verify_peer_name" => false
- ]
- ]);
-
- $urlOfThemesDirFromGithubRepo = 'https://api.github.com/repos/vrana/adminer/contents/designs';
-
- $jsonThemeList = file_get_contents($urlOfThemesDirFromGithubRepo, false, $context);
-
- static::$themeList = ($jsonThemeList ? json_decode($jsonThemeList) : false);
-
- return static::$themeList;
- }
-
- public static function downloadTheme($themeIndex = null)
- {
- if (is_null($themeIndex)) {
- $themeIndex = static::$option;
- }
-
- $themeName = static::getThemeList()[$themeIndex]->name;
- $urlOfCssFileFromGithubRepo = "https://raw.githubusercontent.com/vrana/adminer/master/designs/{$themeName}/adminer.css";
- $cssContent = file_get_contents($urlOfCssFileFromGithubRepo);
- $filePath = __DIR__ . '/adminer.css';
- $fileExists = file_exists('adminer.css');
- if (file_put_contents($filePath, $cssContent) !== false) {
- if (!$fileExists) {
- chmod($filePath, 0777);
- }
-
- return true;
- }
-
- return false;
- }
-
- public static function printListAvailableThemes()
- {
- echo "List of available Adminer Themes:" . static::getLineEnding() . static::getLineEnding();
-
- if (static::getThemeList()) {
- foreach (static::getThemeList() as $index => $theme) {
- if ($theme->type !== 'dir') {
- continue;
- }
-
- echo "[{$index}] {$theme->name}" . static::getLineEnding();
- }
- }
-
- echo static::getLineEnding();
- }
-
- public static function readOptionFromCommandLine()
- {
- if (static::isRunningOnWindows()) {
- echo static::$prompt;
- static::$option = stream_get_line(STDIN, 1024, static::getLineEnding());
- } else {
- static::$option = readline(static::$prompt);
- }
-
- return static::$option;
- }
-
- public static function printResultOf($download)
- {
- if ($download) {
- echo 'Theme switched successfully!' . static::getLineEnding();
- } else {
- echo 'Something went wrong with the download. Try again!' . static::getLineEnding();
- }
- }
-
- public static function printJavascriptPrompt()
- {
- echo '';
- }
-
- public static function readOptionFromBrowser()
- {
- if (isset($_GET['option'])) {
- static::$option = $_GET['option'];
- }
-
- return static::$option;
- }
-
- public static function hasNotSelectedAnOptionFromBrowserYet()
- {
- return !isset($_GET['option']);
- }
-
- public static function hasSelectedAValidOption()
- {
- return is_numeric(static::$option) && static::$option < count(static::getThemeList());
- }
-
- public static function printInvalidOptionErrorMessage()
- {
- $errorMessage = static::$option . " is not a number from the options! Try again!";
-
- if (static::isRunningFromCommandLine()) {
- echo $errorMessage . static::getLineEnding();
- static::run();
- } else {
- echo '';
- echo '';
- }
- }
-
- public static function switchTheme()
- {
- if (static::hasSelectedAValidOption()) {
- static::printResultOf(static::downloadTheme());
- } else {
- static::printInvalidOptionErrorMessage();
- }
- }
-}
-
-AdminerThemeSwitcher::run();
diff --git a/files/docker/index/docker-stack.yaml b/files/docker/index/docker-stack.yaml
index a7a9e42..b7bb787 100644
--- a/files/docker/index/docker-stack.yaml
+++ b/files/docker/index/docker-stack.yaml
@@ -8,23 +8,21 @@ networks:
volumes:
index_db:
- adminer_plugins:
+ webdb_ssh:
+ webdb_time_machine:
services:
- adminer:
- image: ghcr.io/shyim/adminerevo:latest
+ webdb:
+ image: webdb/app
restart: unless-stopped
networks:
- default
- reverse_proxy
volumes:
- - adminer_plugins:/var/www/html/plugins-custom
+ - "webdb_ssh:/root/.ssho"
+ - "webdb_time_machine:/usr/src/app/static/version"
environment:
- ADMINER_DEFAULT_DRIVER: pgsql
- ADMINER_DEFAULT_SERVER: tasks.db
- ADMINER_DEFAULT_USER: postgres
- ADMINER_PLUGINS: tables-filter tinymce edit-calendar edit-foreign enum-option enum-types file-upload slugify struct-comments
- ADMINER_DESIGN: 'pepa-linha-dark'
+ SCAN_HOSTS: db,tasks.db
deploy:
rollback_config:
failure_action: continue
@@ -38,11 +36,14 @@ services:
labels:
- traefik.enable=true
- traefik.http.routers.index.rule=Host(`index.alecodes.page`)
- - traefik.http.services.index.loadbalancer.server.port=8080
+ - traefik.http.services.index.loadbalancer.server.port=22071
db:
image: postgres
restart: unless-stopped
+ networks:
+ - default
+ - reverse_proxy
secrets:
- index_db_pass
volumes:
@@ -63,3 +64,8 @@ services:
placement:
constraints:
- node.labels.services_kind==${SERVICE_KIND:-common}
+ labels:
+ - traefik.enable=true
+ - traefik.tcp.routers.index_db.entrypoints=postgres
+ - traefik.tcp.routers.index_db.rule=HostSNI(`*`)
+ - traefik.tcp.services.index_db.loadbalancer.server.port=5432
diff --git a/files/docker/lemmy/customPostgresql.sql b/files/docker/lemmy/customPostgresql.sql
new file mode 100644
index 0000000..4cff0f6
--- /dev/null
+++ b/files/docker/lemmy/customPostgresql.sql
@@ -0,0 +1,32 @@
+-- DB Version: 17
+-- OS Type: linux
+-- DB Type: web
+-- Total Memory (RAM): 512 MB
+-- Data Storage: hdd
+
+ALTER SYSTEM SET
+ max_connections = '200';
+ALTER SYSTEM SET
+ shared_buffers = '128MB';
+ALTER SYSTEM SET
+ effective_cache_size = '384MB';
+ALTER SYSTEM SET
+ maintenance_work_mem = '32MB';
+ALTER SYSTEM SET
+ checkpoint_completion_target = '0.9';
+ALTER SYSTEM SET
+ wal_buffers = '3932kB';
+ALTER SYSTEM SET
+ default_statistics_target = '100';
+ALTER SYSTEM SET
+ random_page_cost = '4';
+ALTER SYSTEM SET
+ effective_io_concurrency = '2';
+ALTER SYSTEM SET
+ work_mem = '327kB';
+ALTER SYSTEM SET
+ huge_pages = 'off';
+ALTER SYSTEM SET
+ min_wal_size = '1GB';
+ALTER SYSTEM SET
+ max_wal_size = '4GB';
diff --git a/files/docker/lemmy/docker-stack.yaml b/files/docker/lemmy/docker-stack.yaml
new file mode 100644
index 0000000..441787d
--- /dev/null
+++ b/files/docker/lemmy/docker-stack.yaml
@@ -0,0 +1,131 @@
+networks:
+ reverse_proxy:
+ external: true
+
+configs:
+ lemmy_customPostgresql.sql:
+ external: true
+
+secrets:
+ lemmy_lemmy.hjson:
+ external: true
+ lemmy_postgres_pass.txt:
+ external: true
+ lemmy_pictrs.toml:
+ external: true
+
+volumes:
+ ui_themes:
+ pictrs:
+ db:
+
+services:
+ lemmy:
+ image: dessalines/lemmy:0.19.8
+ restart: always
+ networks:
+ - default
+ - reverse_proxy
+ environment:
+ - RUST_LOG="info"
+ secrets:
+ - source: lemmy_lemmy.hjson
+ target: /config/config.hjson
+ deploy:
+ rollback_config:
+ failure_action: continue
+ update_config:
+ delay: 2s
+ failure_action: rollback
+ order: start-first
+ placement:
+ constraints:
+ - node.labels.services_kind==${SERVICE_KIND:-common}
+ labels:
+ - traefik.enable=true
+ - traefik.http.routers.lemmy.rule=Host(`lemmy.alecodes.page`) && (PathRegexp(`^/(api|pictrs|feeds|nodeinfo|\\.well-known)`) || HeaderRegexp(`Accept`, `^application/.*`))
+ - traefik.http.services.lemmy.loadbalancer.server.port=8536
+ - traefik.http.middlewares.lemmy-max-bodysize.buffering.maxRequestBodyBytes=20971520 # 20M
+ - traefik.http.routers.lemmy.middlewares=lemmy-max-bodysize
+
+ lemmy_ui:
+ image: dessalines/lemmy-ui:0.19.8
+ restart: always
+ networks:
+ - default
+ - reverse_proxy
+ environment:
+ - LEMMY_UI_LEMMY_INTERNAL_HOST=tasks.lemmy:8536
+ - LEMMY_UI_LEMMY_EXTERNAL_HOST=lemmy.alecodes.page
+ - LEMMY_UI_HTTPS=true
+ volumes:
+ - ui_themes:/app/extra_themes
+ deploy:
+ rollback_config:
+ failure_action: continue
+ update_config:
+ delay: 2s
+ failure_action: rollback
+ order: start-first
+ placement:
+ constraints:
+ - node.labels.services_kind==${SERVICE_KIND:-common}
+ labels:
+ - "traefik.enable=true"
+ - "traefik.http.middlewares.lemmy-ui-client-max-bodysize.buffering.maxRequestBodyBytes=20971520" # 20M
+ - "traefik.http.routers.lemmy-ui.middlewares=lemmy-ui-client-max-bodysize"
+ - "traefik.http.routers.lemmy-ui.rule=Host(`lemmy.alecodes.page`)"
+ - "traefik.http.routers.lemmy-ui.service=lemmy-ui"
+ - "traefik.http.services.lemmy-ui.loadbalancer.server.port=1234"
+
+ - "traefik.http.routers.lemmy-security-txt.rule=Host(`lemmy.alecodes.page`) && Path(`/.well-known/security.txt`)"
+ - "traefik.http.routers.lemmy-security-txt.service=lemmy-security-txt"
+ - "traefik.http.services.lemmy-security-txt.loadbalancer.server.port=1234"
+
+ pictrs:
+ image: asonix/pictrs:0.5.16
+ restart: always
+ # this needs to match the pictrs url in lemmy_lemmy.hjson
+ entrypoint: /sbin/tini -- /usr/local/bin/pict-rs -c /run/secrets/lemmy_pictrs.toml run
+ secrets:
+ - lemmy_pictrs.toml
+ environment:
+ - RUST_BACKTRACE=full
+ user: 991:991
+ volumes:
+ - pictrs:/mnt:Z
+ deploy:
+ rollback_config:
+ failure_action: continue
+ update_config:
+ delay: 2s
+ failure_action: rollback
+ order: start-first
+ placement:
+ constraints:
+ - node.labels.services_kind==${SERVICE_KIND:-common}
+
+ lemmy_db:
+ image: pgautoupgrade/pgautoupgrade:17-bookworm
+ restart: always
+ secrets:
+ - lemmy_postgres_pass.txt
+ configs:
+ - source: lemmy_customPostgresql.sql
+ target: /docker-entrypoint-initdb.d/config.sql
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD_FILE=/run/secrets/lemmy_postgres_pass.txt
+ - POSTGRES_DB=lemmy
+ volumes:
+ - db:/var/lib/postgresql/data:Z
+ deploy:
+ rollback_config:
+ failure_action: continue
+ update_config:
+ delay: 2s
+ failure_action: rollback
+ order: start-first
+ placement:
+ constraints:
+ - node.labels.services_kind==${SERVICE_KIND:-common}
diff --git a/files/docker/lemmy/lemmy.hjson b/files/docker/lemmy/lemmy.hjson
new file mode 100644
index 0000000..00222e4
--- /dev/null
+++ b/files/docker/lemmy/lemmy.hjson
@@ -0,0 +1,21 @@
+{
+ # for more info about the config, check out the documentation
+ # https://join-lemmy.org/docs/en/administration/configuration.html
+ hostname: "lemmy.alecodes.page"
+ tls_enabled: true
+ database: {
+ host: "tasks.lemmy_db"
+ password: "529a6b836665075b535f8cc56d8f30cde7b7c9b01062feaa1b0da817fd7af2f8"
+ }
+ pictrs: {
+ url: "http://tasks.pictrs:8080/"
+ api_key: "529a6b836665075b535f8cc56d8f30cde7b7c9b01062feaa1b0da817fd7af2f8"
+ }
+ email: {
+ smtp_server: "smtp.gmail.com:587"
+ smtp_login: "ale.navarro.parra@gmail.com"
+ smtp_password: "steuuamhzngjgfwn"
+ smtp_from_address: "ale.navarro.parra@gmail.com"
+ tls_type: "starttls"
+ }
+}
diff --git a/files/docker/lemmy/pictrs.toml b/files/docker/lemmy/pictrs.toml
new file mode 100644
index 0000000..6d156d0
--- /dev/null
+++ b/files/docker/lemmy/pictrs.toml
@@ -0,0 +1,10 @@
+[server]
+api_key = '529a6b836665075b535f8cc56d8f30cde7b7c9b01062feaa1b0da817fd7af2f8'
+
+[media.animation]
+max_width = 256
+max_height = 256
+max_frame_count = 400
+
+[media.video]
+video_codec = 'vp9'
diff --git a/files/docker/lemmy/postgres_pass.txt b/files/docker/lemmy/postgres_pass.txt
new file mode 100644
index 0000000..ac5ba52
--- /dev/null
+++ b/files/docker/lemmy/postgres_pass.txt
@@ -0,0 +1,9 @@
+$ANSIBLE_VAULT;1.1;AES256
+65343339376264393533303231656562316534643432643737653132646561316266386363376331
+6137323165303633633535653537336436333834363564660a303934353533643965323636346536
+38613331623336303130383261623162333437363830326434393463333564623032383434316130
+6564646161353937320a666531326338663433326431346539346335346430653032643530386231
+64636263343437333066323163336637386639643836336438663730623633633666383737353461
+62656262626537303838613764366565393863393961373564343230363433343737303834353037
+31653136323563333164303766636539313362363434336430303962653633316661623932396137
+39353136643865303636
diff --git a/playbooks/docker/services.yaml b/playbooks/docker/services.yaml
index ff8a244..dabe244 100644
--- a/playbooks/docker/services.yaml
+++ b/playbooks/docker/services.yaml
@@ -3,34 +3,55 @@
- name: Deploy homelab services
hosts: 10.0.10.50
tasks:
- - name: Deploy RSS Services
+ # - name: Deploy RSS Services
+ # vars:
+ # project_name: rss
+ # block:
+ # - name: Load environment variables
+ # include_vars:
+ # file: ../../files/docker/rss/env.yaml
+ # name: env_vars
+ #
+ # - name: Deploy RSS Feed
+ # environment: "{{ env_vars }}"
+ # community.docker.docker_stack:
+ # state: present
+ # prune: true
+ # name: "{{ project_name }}"
+ # compose:
+ # - "{{ lookup('file', '../../files/docker/rss/docker-stack.yaml') | from_yaml }}"
+
+ - name: Deploy Lemmy Services
vars:
- project_name: rss
+ project_name: lemmy
block:
- # - name: Generate random hash
- # no_log: true
- # community.crypto.openssl_random:
- # length: 32
- # hex: false
- # register: random_hash
- #
- # - name: Create Docker secret for PostgreSQL password
- # no_log: true
- # community.docker.docker_secret:
- # state: present
- # name: "{{ project_name + '_db_password'}}"
- # secret: "{{ random_hash.stdout }}"
-
- - name: Load environment variables
- include_vars:
- file: ../../files/docker/rss/env.yaml
- name: env_vars
-
- - name: Deploy RSS Feed
- environment: "{{ env_vars }}"
+ - name: Create config
+ loop:
+ - customPostgresql.sql
+ community.docker.docker_config:
+ name: '{{ project_name + "_" + item }}'
+ data: "{{ lookup('file', '../../files/docker/lemmy/{{ item }}') | b64encode }}"
+ data_is_b64: true
+ state: present
+ labels:
+ com.docker.stack.namespace: "{{ project_name }}"
+ - name: Create secrets
+ loop:
+ - lemmy.hjson
+ - postgres_pass.txt
+ - pictrs.toml
+ community.docker.docker_secret:
+ name: '{{ project_name + "_" + item }}'
+ data: "{{ lookup('file', '../../files/docker/lemmy/{{ item }}') | b64encode }}"
+ data_is_b64: true
+ state: present
+ labels:
+ com.docker.stack.namespace: "{{ project_name }}"
+ - name: Deploy lemmy stack
+ # environment: "{{ lookup('ini', '../../files/docker/lemmy/.env') }}"
community.docker.docker_stack:
state: present
prune: true
name: "{{ project_name }}"
compose:
- - "{{ lookup('file', '../../files/docker/rss/docker-stack.yaml') | from_yaml }}"
+ - "{{ lookup('file', '../../files/docker/lemmy/docker-stack.yaml') | from_yaml }}"
diff --git a/roles/common/tasks/main.yaml b/roles/common/tasks/main.yaml
index 97801ca..671f4b8 100644
--- a/roles/common/tasks/main.yaml
+++ b/roles/common/tasks/main.yaml
@@ -10,6 +10,7 @@
create_home: true
password: "{{ (item.value.password != '!' or item.value.password != '*') | ternary(item.value.password | password_hash('sha512'), item.value.password) }}"
groups: "{{ item.value.groups + (extra_groups | default([])) }}"
+ append: true
- name: Add SSH public key to users
loop: "{{ users | dict2items }}"
diff --git a/roles/docker/tasks/swarm_manager.yaml b/roles/docker/tasks/swarm_manager.yaml
index 70f03c3..3bab21d 100644
--- a/roles/docker/tasks/swarm_manager.yaml
+++ b/roles/docker/tasks/swarm_manager.yaml
@@ -49,6 +49,10 @@
published: 443
protocol: tcp
mode: host
+ - target: 5432
+ published: 5432
+ protocol: tcp
+ mode: host
- target: 8080
published: 8080
protocol: tcp
@@ -57,9 +61,11 @@
- '--api.dashboard=true'
- '--api.insecure=true'
- '--entrypoints.http.address=:80'
+ - '--entrypoints.http.asDefault=true'
- '--entryPoints.http.forwardedHeaders.trustedIPs=10.0.10.0/24'
- '--entrypoints.http.http.encodequerysemicolons=true'
- '--entryPoints.http.http2.maxConcurrentStreams=50'
+ - '--entrypoints.postgres.address=:5432'
- '--providers.swarm=true'
- '--providers.swarm.endpoint=tcp://{{ ansible_default_ipv4.address }}:2375'
- '--providers.swarm.exposedByDefault=false'