Compare commits
5
Commits
61dba61025
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01e6ad90f0 | ||
|
|
008fd5bf7b | ||
|
|
fc2ad85e19 | ||
|
|
2d039ae32f | ||
|
|
94c77daf13 |
No files matched your search
@@ -20,6 +20,17 @@
|
||||
# multica_workspaces_root_mount: "/mnt/utilidor"
|
||||
multica_agent_clis:
|
||||
- claude
|
||||
# Merged into the daemon user's ~/.claude/settings.json. Mirrors the model
|
||||
# mix already live on haunted-mansion (unpinned runs on sonnet, subagents
|
||||
# on haiku), and stops the npm install self-updating past the pin.
|
||||
multica_claude_settings:
|
||||
model: sonnet
|
||||
alwaysThinkingEnabled: false
|
||||
env:
|
||||
DISABLE_AUTOUPDATER: "1"
|
||||
CLAUDE_CODE_SUBAGENT_MODEL: haiku
|
||||
MAX_THINKING_TOKENS: "4000"
|
||||
BASH_MAX_OUTPUT_LENGTH: "20000"
|
||||
|
||||
# --- Secret source ----------------------------------------------------
|
||||
# This homelab keeps Ansible secrets in ansible-vault encrypted files, so
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
# Directory layout on mad-tea-party
|
||||
gitea_compose_dir: /opt/gitea
|
||||
|
||||
# Image versions
|
||||
gitea_version: "28.2.0"
|
||||
gitea_postgres_version: "17.11"
|
||||
|
||||
# Gitea application settings
|
||||
gitea_domain: gitea.mk-labs.cloud
|
||||
gitea_root_url: "https://{{ gitea_domain }}"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
# ── Compose & env ──────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Deploy Gitea compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/gitea/compose.yaml"
|
||||
- name: Deploy Gitea compose file
|
||||
ansible.builtin.template:
|
||||
src: compose.yaml.j2
|
||||
dest: "{{ gitea_compose_dir }}/compose.yaml"
|
||||
owner: root
|
||||
group: root
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/roles/gitea/templates/compose.yaml.j2
|
||||
# DESCRIPTION: Gitea + PostgreSQL compose stack. Values come from role defaults.
|
||||
# Postgres stays on 17.x; a major bump needs dump + restore.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
services:
|
||||
gitea:
|
||||
image: docker.io/gitea/gitea:{{ gitea_version }}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea_db
|
||||
- GITEA__database__NAME={{ gitea_db_name }}
|
||||
- GITEA__database__USER={{ gitea_db_user }}
|
||||
- GITEA__database__PASSWD=${DATABASE_PASSWORD}
|
||||
- GITEA__server__SSH_PORT={{ gitea_ssh_port }}
|
||||
- GITEA__server__ROOT_URL={{ gitea_root_url }}
|
||||
- GITEA__server__DOMAIN={{ gitea_domain }}
|
||||
- GITEA__server__HTTP_PORT={{ gitea_http_port }}
|
||||
- GITEA__service__TRUSTED_PROXIES={{ gitea_trusted_proxies }}
|
||||
networks:
|
||||
- gitea_backend
|
||||
ports:
|
||||
- "{{ gitea_http_port }}:{{ gitea_http_port }}"
|
||||
- "{{ gitea_ssh_port }}:22"
|
||||
volumes:
|
||||
- gitea_data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
gitea_db:
|
||||
condition: service_healthy
|
||||
|
||||
gitea_db:
|
||||
image: docker.io/library/postgres:{{ gitea_postgres_version }}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ={{ gitea_timezone }}
|
||||
- POSTGRES_USER={{ gitea_db_user }}
|
||||
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
|
||||
- POSTGRES_DB={{ gitea_db_name }}
|
||||
networks:
|
||||
- gitea_backend
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U {{ gitea_db_user }}"]
|
||||
start_period: 30s
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- gitea_db:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
gitea_data:
|
||||
driver: local
|
||||
gitea_db:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
gitea_backend:
|
||||
driver: bridge
|
||||
@@ -307,13 +307,19 @@ Every step reads before it writes:
|
||||
| CLI install | `multica --version` equals the pin |
|
||||
| Agent CLI install | `command -v <binary>` and its version command |
|
||||
| Config | `multica config show` parsed and compared key by key |
|
||||
| Login | `multica auth status` already names `multica_server_url` |
|
||||
| Login | `multica auth status` already names `multica_server_url` (it prints to **stderr**; the guard reads both streams) |
|
||||
| Autostart | `multica daemon autostart status` |
|
||||
| Daemon start | `multica daemon status --output json` reports `running` |
|
||||
| Daemon start | `systemctl --user is-active <unit>`; a daemon running *outside* the unit is stopped first, then the unit is started |
|
||||
|
||||
A binary, config or credential change notifies a `multica daemon restart`
|
||||
A binary, config or credential change notifies a `systemctl --user restart`
|
||||
handler, which is flushed *before* verification reads the daemon's state back.
|
||||
|
||||
The role never calls `multica daemon start` or `multica daemon restart`. Both
|
||||
fork the daemon inside the caller's session (Ansible's SSH session scope),
|
||||
outside the unit's cgroup. The unit then shows `inactive`, nothing supervises
|
||||
the process, and a later `systemctl --user start` collides with it on
|
||||
`daemon.lock`.
|
||||
|
||||
A converged host reports zero changed tasks.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -129,6 +129,15 @@ multica_agent_cli_catalog:
|
||||
manual_login: true
|
||||
manual_login_hint: "configure Hermes credentials as {{ multica_user }} (Hermes reads 1Password natively; confirm against its docs)"
|
||||
|
||||
# Keys MERGED (recursively) into the daemon user's ~/.claude/settings.json.
|
||||
# Merged, not templated: Claude Code writes that file itself. Keys not named
|
||||
# here are left alone. Empty = don't touch the file.
|
||||
#
|
||||
# An npm-installed Claude Code updates itself in place, which silently breaks
|
||||
# the package_version pin above (and the next play downgrades it again).
|
||||
# Setting env.DISABLE_AUTOUPDATER: "1" here is what makes the pin hold.
|
||||
multica_claude_settings: {}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Node.js (installed only when a selected agent CLI needs npm)
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
---
|
||||
# Through systemd, not `multica daemon restart`: that respawns the daemon in
|
||||
# the caller's session, outside the unit (see tasks/daemon.yml).
|
||||
- name: Restart multica daemon
|
||||
ansible.builtin.command:
|
||||
argv: "{{ _multica_cmd + ['daemon', 'restart'] }}"
|
||||
argv: [systemctl, --user, restart, "{{ _multica_unit | default('multica-daemon.service') }}"]
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
---
|
||||
# `multica auth status` prints the server and user when authenticated and exits
|
||||
# non-zero otherwise, so it is the idempotency guard for login.
|
||||
#
|
||||
# It prints to STDERR, not stdout (0.6.5). A stdout-only guard never matches,
|
||||
# so every run re-logs in and restarts the daemon. It also echoes a token
|
||||
# prefix, hence no_log.
|
||||
- name: Auth | Read the current authentication state
|
||||
ansible.builtin.command:
|
||||
argv: "{{ _multica_cmd + ['auth', 'status'] }}"
|
||||
@@ -11,13 +15,15 @@
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
no_log: true
|
||||
|
||||
- name: Auth | Decide whether a login is needed
|
||||
ansible.builtin.set_fact:
|
||||
_multica_needs_login: >-
|
||||
{{ multica_force_relogin | bool
|
||||
or _multica_auth_status.rc != 0
|
||||
or multica_server_url.rstrip('/') not in (_multica_auth_status.stdout | default('')) }}
|
||||
or multica_server_url.rstrip('/') not in
|
||||
((_multica_auth_status.stdout | default('')) ~ (_multica_auth_status.stderr | default(''))) }}
|
||||
|
||||
# no_log is mandatory: without it Ansible records the full argv — including the
|
||||
# PAT — in play output, CI logs, and every callback plugin.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
- name: Claude settings | Ensure ~/.claude exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ multica_user_home }}/.claude"
|
||||
state: directory
|
||||
owner: "{{ multica_user }}"
|
||||
group: "{{ multica_group }}"
|
||||
mode: "0700"
|
||||
become: true
|
||||
|
||||
- name: Claude settings | Read the current settings
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ multica_user_home }}/.claude/settings.json"
|
||||
register: _multica_claude_settings_raw
|
||||
failed_when: false
|
||||
become: true
|
||||
|
||||
- name: Claude settings | Merge the declared keys over them
|
||||
ansible.builtin.set_fact:
|
||||
_multica_claude_settings_current: >-
|
||||
{{ (_multica_claude_settings_raw.content | b64decode | from_json)
|
||||
if _multica_claude_settings_raw.content is defined else {} }}
|
||||
|
||||
- name: Claude settings | Write them back if anything differs
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ multica_user_home }}/.claude/settings.json"
|
||||
content: "{{ _multica_claude_settings_current | combine(multica_claude_settings, recursive=true) | to_nice_json }}\n"
|
||||
owner: "{{ multica_user }}"
|
||||
group: "{{ multica_group }}"
|
||||
mode: "0600"
|
||||
become: true
|
||||
when: >-
|
||||
_multica_claude_settings_current | combine(multica_claude_settings, recursive=true)
|
||||
!= _multica_claude_settings_current
|
||||
@@ -43,8 +43,61 @@
|
||||
- multica_autostart | bool
|
||||
- _multica_autostart_status.rc != 0
|
||||
or 'enabled' not in (_multica_autostart_status.stdout | default('') | lower)
|
||||
register: _multica_autostart_enable
|
||||
changed_when: true
|
||||
|
||||
# Lifecycle goes through systemd, never `multica daemon start|restart`. Those
|
||||
# fork the daemon inside the CALLER's session (here, Ansible's SSH session
|
||||
# scope), outside the unit's cgroup: the unit stays inactive, nothing
|
||||
# supervises the process, and the next `systemctl --user start` collides with
|
||||
# it on daemon.lock. Seen on haunted-mansion (2026-10-21) and spaceship-earth
|
||||
# (2026-10-22).
|
||||
#
|
||||
# The unit name comes from `autostart status` ("Location: .../<name>.service")
|
||||
# so a --profile host resolves its own unit.
|
||||
- name: Daemon | Re-read the autostart registration
|
||||
ansible.builtin.command:
|
||||
argv: "{{ _multica_cmd + ['daemon', 'autostart', 'status'] }}"
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
register: _multica_autostart_reread
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
when: _multica_autostart_enable is changed
|
||||
|
||||
- name: Daemon | Resolve the systemd user unit
|
||||
ansible.builtin.set_fact:
|
||||
_multica_unit: >-
|
||||
{{ ((_s.stdout | default('')) ~ '\n' ~ (_s.stderr | default('')))
|
||||
| regex_search('Location:\s*\S*/([^/\s]+\.service)', '\1')
|
||||
| default(['multica-daemon.service'], true) | first }}
|
||||
vars:
|
||||
_s: "{{ _multica_autostart_reread if _multica_autostart_enable is changed else _multica_autostart_status }}"
|
||||
|
||||
# `autostart enable` may rewrite the unit file while the user manager still
|
||||
# holds the old definition.
|
||||
- name: Daemon | Reload the user manager after a (re)registration
|
||||
ansible.builtin.command:
|
||||
argv: [systemctl, --user, daemon-reload]
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
when: _multica_autostart_enable is changed
|
||||
changed_when: true
|
||||
|
||||
- name: Daemon | Read the unit state
|
||||
ansible.builtin.command:
|
||||
argv: [systemctl, --user, is-active, "{{ _multica_unit }}"]
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
register: _multica_unit_state
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Daemon | Read daemon status
|
||||
ansible.builtin.command:
|
||||
argv: "{{ _multica_cmd + ['daemon', 'status', '--output', 'json'] }}"
|
||||
@@ -56,15 +109,27 @@
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Daemon | Start it if it is not already running
|
||||
# Running, but not under the unit: an orphan. Stop it so the unit can own the
|
||||
# lock and the process.
|
||||
- name: Daemon | Stop a daemon running outside its unit
|
||||
ansible.builtin.command:
|
||||
argv: "{{ _multica_cmd + ['daemon', 'start'] }}"
|
||||
argv: "{{ _multica_cmd + ['daemon', 'stop'] }}"
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
when: >-
|
||||
_multica_daemon_status.rc != 0
|
||||
or ((_multica_daemon_status.stdout | from_json).status | default('')) != 'running'
|
||||
when:
|
||||
- (_multica_unit_state.stdout | trim) != 'active'
|
||||
- _multica_daemon_status.rc == 0
|
||||
- ((_multica_daemon_status.stdout | from_json).status | default('')) == 'running'
|
||||
changed_when: true
|
||||
|
||||
- name: Daemon | Start the unit if it is not active
|
||||
ansible.builtin.command:
|
||||
argv: [systemctl, --user, start, "{{ _multica_unit }}"]
|
||||
become: true
|
||||
become_user: "{{ multica_user }}"
|
||||
environment: "{{ _multica_user_env }}"
|
||||
when: (_multica_unit_state.stdout | trim) != 'active'
|
||||
changed_when: true
|
||||
|
||||
# Anything that changed the binary, the config or the credentials restarts the
|
||||
|
||||
@@ -27,6 +27,13 @@
|
||||
ansible.builtin.import_tasks: agent_clis.yml
|
||||
tags: [multica, multica_agents]
|
||||
|
||||
- name: Claude Code settings
|
||||
ansible.builtin.import_tasks: claude_settings.yml
|
||||
when:
|
||||
- "'claude' in multica_agent_clis"
|
||||
- multica_claude_settings | length > 0
|
||||
tags: [multica, multica_agents, multica_claude_settings]
|
||||
|
||||
- name: CLI configuration
|
||||
ansible.builtin.import_tasks: config.yml
|
||||
tags: [multica, multica_config]
|
||||
|
||||
@@ -25,7 +25,7 @@ only. Entry point: `playbooks/provision-wong-host.yml`.
|
||||
| `/srv/obsidian/` | `0710 obsync` | `user:multica:--x` |
|
||||
| `/srv/obsidian/vault/` | `0710 obsync` | `user:multica:--x`, so `multica` can traverse but not list |
|
||||
| `vault/everpure/`, `vault/wip/` | `0700 obsync` | none |
|
||||
| `vault/inbox/` | `0770 obsync` | `user:multica:rwx`, plus the same as a **default** entry |
|
||||
| `vault/inbox/` | `0770 obsync` | `user:multica:rwx` and `user:obsync:rwx`, each also as a **default** entry |
|
||||
|
||||
On a directory with an ACL, the mode's group bits are the ACL mask. A
|
||||
`chmod 0700` would therefore void every named entry, which is why the
|
||||
@@ -38,6 +38,9 @@ only. Entry point: `playbooks/provision-wong-host.yml`.
|
||||
- `multica` writes, reads and deletes in `inbox/`.
|
||||
- A folder and file created by `obsync` under umask `0077` (the way Sync
|
||||
creates them) stay readable and writable by `multica`.
|
||||
- What `multica` creates stays readable and writable by `obsync`. Those
|
||||
files are owned `multica:multica`, so without the named `user:obsync`
|
||||
default entry Sync could not read them and would never upload them.
|
||||
- `multica` is refused both listing the vault root and listing or writing
|
||||
`everpure/` and `wip/`.
|
||||
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
# <parent>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
||||
# <vault>/ obsync 0710 + user:<shared>:--x traverse, no listing
|
||||
# <vault>/<private>/ obsync 0700 nobody else
|
||||
# <vault>/<shared>/ obsync 0770 + user:<u>:rwx, and the same as a
|
||||
# DEFAULT entry, so new children inherit
|
||||
# <vault>/<shared>/ obsync 0770 + user:<u>:rwx and user:obsync:rwx,
|
||||
# each also as a DEFAULT entry, so new
|
||||
# children inherit both
|
||||
#
|
||||
# The user:obsync entry matters for content the SHARED user creates: that is
|
||||
# owned <u>:<u>, so without a named entry obsync falls into "other" (---)
|
||||
# and Sync can neither read nor upload it.
|
||||
#
|
||||
# The owning group is obsync's private group, so its bits grant nobody. On a
|
||||
# directory that carries an ACL, the group bits of the mode ARE the ACL mask,
|
||||
@@ -62,6 +67,18 @@
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
|
||||
# Each shared folder gets its user AND the service user, both as access and
|
||||
# default entries.
|
||||
- name: Layout | Build the shared-folder grants
|
||||
ansible.builtin.set_fact:
|
||||
_obh_grants: >-
|
||||
{{ (obsidian_headless_shared_folders
|
||||
+ (obsidian_headless_shared_folders
|
||||
| map(attribute='path')
|
||||
| map('community.general.dict_kv', 'path') | list
|
||||
| map('combine', {'user': obsidian_headless_user}) | list))
|
||||
| unique | list }}
|
||||
|
||||
- name: Layout | Grant shared folders (access and default entries)
|
||||
ansible.posix.acl:
|
||||
path: "{{ obsidian_headless_vault_path }}/{{ item.0.path }}"
|
||||
@@ -70,6 +87,6 @@
|
||||
permissions: rwx
|
||||
default: "{{ item.1 }}"
|
||||
state: present
|
||||
loop: "{{ obsidian_headless_shared_folders | product([false, true]) | list }}"
|
||||
loop: "{{ _obh_grants | product([false, true]) | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.0.path }} -> {{ item.0.user }}{{ ' (default)' if item.1 else '' }}"
|
||||
@@ -18,6 +18,61 @@
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
# The reverse direction: what the shared user writes, Sync must be able to
|
||||
# read (to upload it) and rewrite (to merge remote edits into it).
|
||||
- name: Verify | Content a shared user creates stays usable by the service (create)
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- 'd="$1/.obh-verify-rev"; mkdir -p "$d" && echo ok > "$d/note.md"'
|
||||
- sh
|
||||
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||
become: true
|
||||
become_user: "{{ item.user }}"
|
||||
loop: "{{ obsidian_headless_shared_folders }}"
|
||||
loop_control:
|
||||
label: "{{ item.user }} -> {{ item.path }}"
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Verify | Content a shared user creates stays usable by the service (use)
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- 'd="$1/.obh-verify-rev"; grep -q ok "$d/note.md" && echo more >> "$d/note.md" && echo ok > "$d/reply.md"'
|
||||
- sh
|
||||
- "{{ obsidian_headless_vault_path }}/{{ item.path }}"
|
||||
become: true
|
||||
become_user: "{{ obsidian_headless_user }}"
|
||||
loop: "{{ obsidian_headless_shared_folders }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
register: _obh_reverse
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
check_mode: false
|
||||
|
||||
- name: Verify | Remove the reverse probes
|
||||
ansible.builtin.file:
|
||||
path: "{{ obsidian_headless_vault_path }}/{{ item.path }}/.obh-verify-rev"
|
||||
state: absent
|
||||
loop: "{{ obsidian_headless_shared_folders }}"
|
||||
loop_control:
|
||||
label: "{{ item.path }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Verify | The service can read and rewrite shared users' content
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _obh_reverse.results | rejectattr('rc', 'equalto', 0) | list | length == 0
|
||||
fail_msg: >-
|
||||
{{ obsidian_headless_user }} cannot read or write content a shared user
|
||||
created; Sync would never upload it. Check the user:{{ obsidian_headless_user }}
|
||||
default ACL entry on the shared folder.
|
||||
quiet: true
|
||||
|
||||
# Simulates the service: obsync, umask 0077, creating a folder and a file the
|
||||
# way Sync does. The shared user must still reach both, via the default ACL.
|
||||
- name: Verify | Content the service creates in a shared folder inherits the grant (create)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
services:
|
||||
gitea:
|
||||
image: docker.io/gitea/gitea:1.25.4
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- GITEA__database__DB_TYPE=postgres
|
||||
- GITEA__database__HOST=gitea_db
|
||||
- GITEA__database__NAME=gitea
|
||||
- GITEA__database__USER=gitea
|
||||
- GITEA__database__PASSWD=${DATABASE_PASSWORD}
|
||||
- GITEA__server__SSH_PORT=2221
|
||||
- GITEA__server__ROOT_URL=https://gitea.mk-labs.cloud
|
||||
- GITEA__server__DOMAIN=gitea.mk-labs.cloud
|
||||
- GITEA__server__HTTP_PORT=3000
|
||||
- GITEA__service__TRUSTED_PROXIES=10.1.71.35
|
||||
networks:
|
||||
- gitea_backend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "2221:22"
|
||||
volumes:
|
||||
- gitea_data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
depends_on:
|
||||
gitea_db:
|
||||
condition: service_healthy
|
||||
|
||||
gitea_db:
|
||||
image: docker.io/library/postgres:17.8
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=America/Chicago
|
||||
- POSTGRES_USER=gitea
|
||||
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
|
||||
- POSTGRES_DB=gitea
|
||||
networks:
|
||||
- gitea_backend
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U gitea"]
|
||||
start_period: 30s
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
volumes:
|
||||
- gitea_db:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
gitea_data:
|
||||
driver: local
|
||||
gitea_db:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
gitea_backend:
|
||||
driver: bridge
|
||||
@@ -1,13 +1,15 @@
|
||||
http:
|
||||
routers:
|
||||
gitea:
|
||||
rule: "Host(`gitea.mk-labs.cloud`)"
|
||||
rule: "Host(`gitea.mk-labs.cloud`) || Host(`mad-tea-party.wdw-labs.cloud`)"
|
||||
entryPoints:
|
||||
- websecure
|
||||
tls:
|
||||
certResolver: cloudflare
|
||||
domains:
|
||||
- main: "gitea.mk-labs.cloud"
|
||||
sans:
|
||||
- "mad-tea-party.wdw-labs.cloud"
|
||||
service: gitea
|
||||
|
||||
services:
|
||||
|
||||
Reference in new issue
Block a user