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'