Files
microdao-daarion/site/INFRA_ASSETS_MINIO/index.html
Apple fb4f4a16d5 🔧 Fix GitHub Actions docs workflow
- Update mkdocs dependencies to latest versions
- Add permissions for GitHub Pages deployment
- Add workflow_dispatch for manual trigger
- Fix build command with fallback
2026-01-10 07:57:36 -08:00

1261 lines
49 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://IvanTytar.github.io/microdao-daarion/INFRA_ASSETS_MINIO/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.5.3, mkdocs-material-9.5.18">
<title>INFRA_ASSETS_MINIO — Налаштування MinIO для Assets - DAARION Documentation</title>
<link rel="stylesheet" href="../assets/stylesheets/main.66ac8b77.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#infra_assets_minio-minio-assets" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="DAARION Documentation" class="md-header__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
DAARION Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
INFRA_ASSETS_MINIO — Налаштування MinIO для Assets
</span>
</div>
</div>
</div>
<script>var media,input,key,value,palette=__md_get("__palette");if(palette&&palette.color){"(prefers-color-scheme)"===palette.color.media&&(media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']"),palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent"));for([key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="DAARION Documentation" class="md-nav__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
DAARION Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../public/" class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../public/getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../public/architecture-overview/" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../public/daiS_daos_overview/" class="md-nav__link">
<span class="md-ellipsis">
DAIS & DAOS
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Internal
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Internal
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_1" >
<label class="md-nav__link" for="__nav_5_1" id="__nav_5_1_label" tabindex="0">
<span class="md-ellipsis">
Infra
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_1">
<span class="md-nav__icon md-icon"></span>
Infra
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../internal/infra/INFRA_AUTOMATION_PACK_V1/" class="md-nav__link">
<span class="md-ellipsis">
Infra Automation Pack v1
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../internal/infra/monitoring_overview/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../internal/infra/nodes_registry_v0/" class="md-nav__link">
<span class="md-ellipsis">
Nodes Registry v0
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_2" >
<label class="md-nav__link" for="__nav_5_2" id="__nav_5_2_label" tabindex="0">
<span class="md-ellipsis">
Specs
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_2">
<span class="md-nav__icon md-icon"></span>
Specs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../internal/specs/matrix_presence_aggregator/" class="md-nav__link">
<span class="md-ellipsis">
Matrix Presence Aggregator
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../internal/specs/city_map_spec/" class="md-nav__link">
<span class="md-ellipsis">
City Map Spec
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../internal/specs/node_join_protocol_draft/" class="md-nav__link">
<span class="md-ellipsis">
Node Join Protocol (Draft)
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#_1" class="md-nav__link">
<span class="md-ellipsis">
Огляд
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#1" class="md-nav__link">
<span class="md-ellipsis">
1. Архітектура
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#2-docker-compose" class="md-nav__link">
<span class="md-ellipsis">
2. Docker Compose конфігурація
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#3" class="md-nav__link">
<span class="md-ellipsis">
3. Початкове налаштування
</span>
</a>
<nav class="md-nav" aria-label="3. Початкове налаштування">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-minio" class="md-nav__link">
<span class="md-ellipsis">
Крок 1: Запустити MinIO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#2" class="md-nav__link">
<span class="md-ellipsis">
Крок 2: Відкрити консоль
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#3-bucket" class="md-nav__link">
<span class="md-ellipsis">
Крок 3: Створити bucket
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#4-public-read-policy" class="md-nav__link">
<span class="md-ellipsis">
Крок 4: Встановити public read policy
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#4-dns" class="md-nav__link">
<span class="md-ellipsis">
4. DNS налаштування
</span>
</a>
<nav class="md-nav" aria-label="4. DNS налаштування">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#aaaaa" class="md-nav__link">
<span class="md-ellipsis">
A/AAAA записи
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#5-reverse-proxy-caddy" class="md-nav__link">
<span class="md-ellipsis">
5. Reverse Proxy (Caddy)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#6-reverse-proxy-nginx" class="md-nav__link">
<span class="md-ellipsis">
6. Reverse Proxy (NGINX)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#7-url" class="md-nav__link">
<span class="md-ellipsis">
7. Формат URL в БД
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#8-backend" class="md-nav__link">
<span class="md-ellipsis">
8. Backend інтеграція
</span>
</a>
<nav class="md-nav" aria-label="8. Backend інтеграція">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#upload-endpoint" class="md-nav__link">
<span class="md-ellipsis">
Upload endpoint (приклад)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#minio" class="md-nav__link">
<span class="md-ellipsis">
Клієнт MinIO
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#9-frontend" class="md-nav__link">
<span class="md-ellipsis">
9. Frontend інтеграція
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#10-backup-minio" class="md-nav__link">
<span class="md-ellipsis">
10. Backup MinIO
</span>
</a>
<nav class="md-nav" aria-label="10. Backup MinIO">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#backup-bucket" class="md-nav__link">
<span class="md-ellipsis">
Створити backup bucket
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#backup-cron" class="md-nav__link">
<span class="md-ellipsis">
Автоматичний backup (cron)
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#11" class="md-nav__link">
<span class="md-ellipsis">
11. Міграція з локальних файлів
</span>
</a>
<nav class="md-nav" aria-label="11. Міграція з локальних файлів">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#1-minio_1" class="md-nav__link">
<span class="md-ellipsis">
Крок 1: Завантажити існуючі файли в MinIO
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#2-url" class="md-nav__link">
<span class="md-ellipsis">
Крок 2: Оновити URL в БД
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#12-troubleshooting" class="md-nav__link">
<span class="md-ellipsis">
12. Troubleshooting
</span>
</a>
<nav class="md-nav" aria-label="12. Troubleshooting">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#access-denied" class="md-nav__link">
<span class="md-ellipsis">
Проблема: "Access Denied"
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#connection-refused" class="md-nav__link">
<span class="md-ellipsis">
Проблема: "Connection refused"
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#bucket-does-not-exist" class="md-nav__link">
<span class="md-ellipsis">
Проблема: "Bucket does not exist"
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#13" class="md-nav__link">
<span class="md-ellipsis">
13. Моніторинг
</span>
</a>
<nav class="md-nav" aria-label="13. Моніторинг">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#_2" class="md-nav__link">
<span class="md-ellipsis">
Перевірка використання диска
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#_3" class="md-nav__link">
<span class="md-ellipsis">
Перевірка кількості об'єктів
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#14-s3" class="md-nav__link">
<span class="md-ellipsis">
14. Реплікація на зовнішній S3 (опційно)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#15" class="md-nav__link">
<span class="md-ellipsis">
15. Чекліст налаштування
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="infra_assets_minio-minio-assets">INFRA_ASSETS_MINIO — Налаштування MinIO для Assets<a class="headerlink" href="#infra_assets_minio-minio-assets" title="Permanent link">&para;</a></h1>
<h2 id="_1">Огляд<a class="headerlink" href="#_1" title="Permanent link">&para;</a></h2>
<p>DAARION використовує <strong>MinIO</strong> (S3-compatible object storage) для зберігання assets:
- Логотипи MicroDAO
- Банери MicroDAO
- Аватарки агентів
- Інші статичні файли</p>
<p><strong>Переваги:</strong>
- Assets не залежать від локального сервера
- Можливість реплікації на зовнішній S3/R2
- Масштабованість
- Простота backup/restore</p>
<hr />
<h2 id="1">1. Архітектура<a class="headerlink" href="#1" title="Permanent link">&para;</a></h2>
<div class="codehilite"><pre><span></span><code>┌─────────────┐
│ Frontend │ → https://assets.daarion.space/daarion-assets/...
└─────────────┘
┌─────────────┐
│ Caddy │ → Reverse proxy
│ / NGINX │
└─────────────┘
┌─────────────┐
│ MinIO │ → S3 API (port 9000)
│ (Docker) │ → Console (port 9001)
└─────────────┘
┌─────────────┐
│ Volume │ → minio_data (persistent)
└─────────────┘
</code></pre></div>
<hr />
<h2 id="2-docker-compose">2. Docker Compose конфігурація<a class="headerlink" href="#2-docker-compose" title="Permanent link">&para;</a></h2>
<p><strong>Файл:</strong> <code>docker-compose.db.yml</code></p>
<div class="codehilite"><pre><span></span><code><span class="nt">services</span><span class="p">:</span>
<span class="w"> </span><span class="nt">minio</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">minio/minio:latest</span>
<span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">daarion-minio</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">unless-stopped</span>
<span class="w"> </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">server /data --console-address &quot;:9001&quot;</span>
<span class="w"> </span><span class="nt">environment</span><span class="p">:</span>
<span class="w"> </span><span class="nt">MINIO_ROOT_USER</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">${MINIO_ROOT_USER}</span>
<span class="w"> </span><span class="nt">MINIO_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">${MINIO_ROOT_PASSWORD}</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;9000:9000&quot;</span><span class="w"> </span><span class="c1"># S3 API</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;9001:9001&quot;</span><span class="w"> </span><span class="c1"># Web console</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">minio_data:/data</span>
</code></pre></div>
<p><strong>ENV змінні (.env):</strong></p>
<div class="codehilite"><pre><span></span><code><span class="n">MINIO_ROOT_USER</span><span class="o">=</span><span class="n">assets</span><span class="o">-</span><span class="n">admin</span>
<span class="n">MINIO_ROOT_PASSWORD</span><span class="o">=</span><span class="n">very</span><span class="o">-</span><span class="n">strong</span><span class="o">-</span><span class="n">password</span>
<span class="n">ASSETS_BUCKET</span><span class="o">=</span><span class="n">daarion</span><span class="o">-</span><span class="n">assets</span>
<span class="n">ASSETS_PUBLIC_BASE_URL</span><span class="o">=</span><span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">assets</span><span class="p">.</span><span class="n">daarion</span><span class="p">.</span><span class="n">space</span><span class="o">/</span><span class="n">daarion</span><span class="o">-</span><span class="n">assets</span>
<span class="n">MINIO_ENDPOINT</span><span class="o">=</span><span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">minio</span><span class="p">:</span><span class="mi">9000</span>
</code></pre></div>
<hr />
<h2 id="3">3. Початкове налаштування<a class="headerlink" href="#3" title="Permanent link">&para;</a></h2>
<h3 id="1-minio">Крок 1: Запустити MinIO<a class="headerlink" href="#1-minio" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code>docker<span class="w"> </span>compose<span class="w"> </span>-f<span class="w"> </span>docker-compose.db.yml<span class="w"> </span>up<span class="w"> </span>-d<span class="w"> </span>minio
</code></pre></div>
<h3 id="2">Крок 2: Відкрити консоль<a class="headerlink" href="#2" title="Permanent link">&para;</a></h3>
<p>Відкрити в браузері: <code>http://localhost:9001</code> (або <code>https://minio.daarion.space</code>)</p>
<p>Логін:
- Username: <code>assets-admin</code> (з .env)
- Password: <code>very-strong-password</code> (з .env)</p>
<h3 id="3-bucket">Крок 3: Створити bucket<a class="headerlink" href="#3-bucket" title="Permanent link">&para;</a></h3>
<ol>
<li>Натиснути "Create Bucket"</li>
<li>Назва: <code>daarion-assets</code></li>
<li>Region: залишити за замовчуванням</li>
<li>Натиснути "Create Bucket"</li>
</ol>
<h3 id="4-public-read-policy">Крок 4: Встановити public read policy<a class="headerlink" href="#4-public-read-policy" title="Permanent link">&para;</a></h3>
<ol>
<li>Відкрити bucket <code>daarion-assets</code></li>
<li>Перейти в "Access Policy"</li>
<li>Вибрати "Public" або "Custom"</li>
<li>Для Custom policy:</li>
</ol>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;Version&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;2012-10-17&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;Statement&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;Effect&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Allow&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="nt">&quot;Principal&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">&quot;AWS&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;*&quot;</span><span class="p">]},</span>
<span class="w"> </span><span class="nt">&quot;Action&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;s3:GetObject&quot;</span><span class="p">],</span>
<span class="w"> </span><span class="nt">&quot;Resource&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;arn:aws:s3:::daarion-assets/*&quot;</span><span class="p">]</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2 id="4-dns">4. DNS налаштування<a class="headerlink" href="#4-dns" title="Permanent link">&para;</a></h2>
<h3 id="aaaaa">A/AAAA записи<a class="headerlink" href="#aaaaa" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code>assets.daarion.space → IP NODE1 (для публічного доступу)
minio.daarion.space → IP NODE1 (опційно, для консолі)
</code></pre></div>
<hr />
<h2 id="5-reverse-proxy-caddy">5. Reverse Proxy (Caddy)<a class="headerlink" href="#5-reverse-proxy-caddy" title="Permanent link">&para;</a></h2>
<p><strong>Файл:</strong> <code>Caddyfile</code></p>
<div class="codehilite"><pre><span></span><code><span class="gh">#</span> Assets public access
assets.daarion.space {
encode gzip
reverse_proxy minio:9000 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
<span class="gh">#</span> MinIO console (опційно)
minio.daarion.space {
encode gzip
reverse_proxy minio:9001 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
</code></pre></div>
<p><strong>Docker Compose:</strong></p>
<div class="codehilite"><pre><span></span><code><span class="w"> </span><span class="nt">caddy</span><span class="p">:</span>
<span class="w"> </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">caddy:2</span>
<span class="w"> </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">daarion-caddy</span>
<span class="w"> </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">unless-stopped</span>
<span class="w"> </span><span class="nt">ports</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;80:80&quot;</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="s">&quot;443:443&quot;</span>
<span class="w"> </span><span class="nt">volumes</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">./Caddyfile:/etc/caddy/Caddyfile:ro</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">caddy_data:/data</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">caddy_config:/config</span>
<span class="w"> </span><span class="nt">depends_on</span><span class="p">:</span>
<span class="w"> </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">minio</span>
</code></pre></div>
<hr />
<h2 id="6-reverse-proxy-nginx">6. Reverse Proxy (NGINX)<a class="headerlink" href="#6-reverse-proxy-nginx" title="Permanent link">&para;</a></h2>
<p><strong>Файл:</strong> <code>nginx.conf</code> (фрагмент)</p>
<div class="codehilite"><pre><span></span><code><span class="k">http</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kn">upstream</span><span class="w"> </span><span class="s">minio_api</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kn">server</span><span class="w"> </span><span class="n">minio</span><span class="p">:</span><span class="mi">9000</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="kn">server</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kn">listen</span><span class="w"> </span><span class="mi">80</span><span class="p">;</span>
<span class="w"> </span><span class="kn">server_name</span><span class="w"> </span><span class="s">assets.daarion.space</span><span class="p">;</span>
<span class="w"> </span><span class="kn">client_max_body_size</span><span class="w"> </span><span class="s">100M</span><span class="p">;</span>
<span class="w"> </span><span class="kn">location</span><span class="w"> </span><span class="s">/</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kn">proxy_pass</span><span class="w"> </span><span class="s">http://minio_api</span><span class="p">;</span>
<span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">Host</span><span class="w"> </span><span class="nv">$http_host</span><span class="p">;</span>
<span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">X-Real-IP</span><span class="w"> </span><span class="nv">$remote_addr</span><span class="p">;</span>
<span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">X-Forwarded-For</span><span class="w"> </span><span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
<span class="w"> </span><span class="kn">proxy_set_header</span><span class="w"> </span><span class="s">X-Forwarded-Proto</span><span class="w"> </span><span class="nv">$scheme</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2 id="7-url">7. Формат URL в БД<a class="headerlink" href="#7-url" title="Permanent link">&para;</a></h2>
<p>Після завантаження файлу через API, в БД зберігається повний HTTPS URL:</p>
<div class="codehilite"><pre><span></span><code>https://assets.daarion.space/daarion-assets/microdao/logo/2025/12/02/abc123def456.png
</code></pre></div>
<p><strong>Структура:</strong>
- <code>https://assets.daarion.space</code> - public domain
- <code>/daarion-assets</code> - bucket name
- <code>/microdao/logo</code> - prefix (тип asset)
- <code>/2025/12/02</code> - дата завантаження
- <code>/abc123def456.png</code> - унікальний ID + розширення</p>
<hr />
<h2 id="8-backend">8. Backend інтеграція<a class="headerlink" href="#8-backend" title="Permanent link">&para;</a></h2>
<h3 id="upload-endpoint">Upload endpoint (приклад)<a class="headerlink" href="#upload-endpoint" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="kn">from</span><span class="w"> </span><span class="nn">fastapi</span><span class="w"> </span><span class="kn">import</span> <span class="n">UploadFile</span><span class="p">,</span> <span class="n">File</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">lib.assets_client</span><span class="w"> </span><span class="kn">import</span> <span class="n">upload_asset</span>
<span class="nd">@router</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;/microdao/</span><span class="si">{slug}</span><span class="s2">/logo&quot;</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">upload_logo</span><span class="p">(</span><span class="n">slug</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">file</span><span class="p">:</span> <span class="n">UploadFile</span> <span class="o">=</span> <span class="n">File</span><span class="p">(</span><span class="o">...</span><span class="p">)):</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">upload_asset</span><span class="p">(</span>
<span class="n">file</span><span class="o">.</span><span class="n">file</span><span class="p">,</span>
<span class="n">file</span><span class="o">.</span><span class="n">content_type</span><span class="p">,</span>
<span class="n">prefix</span><span class="o">=</span><span class="s2">&quot;microdao/logo&quot;</span><span class="p">,</span>
<span class="n">filename</span><span class="o">=</span><span class="n">file</span><span class="o">.</span><span class="n">filename</span>
<span class="p">)</span>
<span class="c1"># Зберегти url в БД</span>
<span class="k">await</span> <span class="n">repo</span><span class="o">.</span><span class="n">update_logo</span><span class="p">(</span><span class="n">slug</span><span class="o">=</span><span class="n">slug</span><span class="p">,</span> <span class="n">logo_url</span><span class="o">=</span><span class="n">url</span><span class="p">)</span>
<span class="k">return</span> <span class="p">{</span><span class="s2">&quot;logo_url&quot;</span><span class="p">:</span> <span class="n">url</span><span class="p">}</span>
</code></pre></div>
<h3 id="minio">Клієнт MinIO<a class="headerlink" href="#minio" title="Permanent link">&para;</a></h3>
<p><strong>Файл:</strong> <code>services/city-service/lib/assets_client.py</code></p>
<p>Функції:
- <code>upload_asset()</code> - завантажити файл, повернути URL
- <code>delete_asset()</code> - видалити файл
- <code>ensure_bucket()</code> - переконатися що bucket існує</p>
<hr />
<h2 id="9-frontend">9. Frontend інтеграція<a class="headerlink" href="#9-frontend" title="Permanent link">&para;</a></h2>
<p><strong>Файл:</strong> <code>apps/web/src/lib/utils/assetUrl.ts</code></p>
<div class="codehilite"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">normalizeAssetUrl</span><span class="p">(</span><span class="nx">url?</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">url</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span>
<span class="w"> </span><span class="c1">// Full HTTPS URLs (from MinIO) - return as-is</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s1">&#39;https://&#39;</span><span class="p">)</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">url</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s1">&#39;http://&#39;</span><span class="p">))</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">url</span><span class="p">;</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="c1">// Legacy local paths - handle fallback</span>
<span class="w"> </span><span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<hr />
<h2 id="10-backup-minio">10. Backup MinIO<a class="headerlink" href="#10-backup-minio" title="Permanent link">&para;</a></h2>
<h3 id="backup-bucket">Створити backup bucket<a class="headerlink" href="#backup-bucket" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Використовуючи MinIO Client (mc)</span>
mc<span class="w"> </span><span class="nb">alias</span><span class="w"> </span><span class="nb">set</span><span class="w"> </span><span class="nb">local</span><span class="w"> </span>http://localhost:9000<span class="w"> </span>assets-admin<span class="w"> </span>&lt;password&gt;
mc<span class="w"> </span>mirror<span class="w"> </span>local/daarion-assets<span class="w"> </span>./backups/minio/
</code></pre></div>
<h3 id="backup-cron">Автоматичний backup (cron)<a class="headerlink" href="#backup-cron" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Додати в crontab</span>
<span class="m">0</span><span class="w"> </span><span class="m">2</span><span class="w"> </span>*<span class="w"> </span>*<span class="w"> </span>*<span class="w"> </span>docker<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>daarion-minio<span class="w"> </span>mc<span class="w"> </span>mirror<span class="w"> </span>minio/daarion-assets<span class="w"> </span>/backups/minio/<span class="k">$(</span>date<span class="w"> </span>+<span class="se">\%</span>F<span class="k">)</span>
</code></pre></div>
<hr />
<h2 id="11">11. Міграція з локальних файлів<a class="headerlink" href="#11" title="Permanent link">&para;</a></h2>
<h3 id="1-minio_1">Крок 1: Завантажити існуючі файли в MinIO<a class="headerlink" href="#1-minio_1" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Використовуючи mc</span>
mc<span class="w"> </span>cp<span class="w"> </span>./services/city-service/static/uploads/microdao/logo/*<span class="w"> </span>local/daarion-assets/microdao/logo/
mc<span class="w"> </span>cp<span class="w"> </span>./services/city-service/static/uploads/microdao/banner/*<span class="w"> </span>local/daarion-assets/microdao/banner/
</code></pre></div>
<h3 id="2-url">Крок 2: Оновити URL в БД<a class="headerlink" href="#2-url" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="k">UPDATE</span><span class="w"> </span><span class="n">microdao</span>
<span class="k">SET</span><span class="w"> </span><span class="n">logo_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">REPLACE</span><span class="p">(</span><span class="n">logo_url</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;/api/static/uploads/&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;https://assets.daarion.space/daarion-assets/&#39;</span><span class="p">)</span>
<span class="k">WHERE</span><span class="w"> </span><span class="n">logo_url</span><span class="w"> </span><span class="k">LIKE</span><span class="w"> </span><span class="s1">&#39;/api/static/uploads/%&#39;</span><span class="p">;</span>
</code></pre></div>
<hr />
<h2 id="12-troubleshooting">12. Troubleshooting<a class="headerlink" href="#12-troubleshooting" title="Permanent link">&para;</a></h2>
<h3 id="access-denied">Проблема: "Access Denied"<a class="headerlink" href="#access-denied" title="Permanent link">&para;</a></h3>
<p>Перевірити:
1. Bucket має public read policy
2. URL правильний (включає bucket name)
3. DNS налаштований правильно</p>
<h3 id="connection-refused">Проблема: "Connection refused"<a class="headerlink" href="#connection-refused" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Перевірити статус</span>
docker<span class="w"> </span>ps<span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span>minio
<span class="c1"># Перезапустити</span>
docker<span class="w"> </span>compose<span class="w"> </span>-f<span class="w"> </span>docker-compose.db.yml<span class="w"> </span>restart<span class="w"> </span>minio
</code></pre></div>
<h3 id="bucket-does-not-exist">Проблема: "Bucket does not exist"<a class="headerlink" href="#bucket-does-not-exist" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="c1"># Створити через mc</span>
mc<span class="w"> </span>mb<span class="w"> </span>local/daarion-assets
<span class="c1"># Або через консоль MinIO</span>
</code></pre></div>
<hr />
<h2 id="13">13. Моніторинг<a class="headerlink" href="#13" title="Permanent link">&para;</a></h2>
<h3 id="_2">Перевірка використання диска<a class="headerlink" href="#_2" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code>docker<span class="w"> </span><span class="nb">exec</span><span class="w"> </span>daarion-minio<span class="w"> </span>du<span class="w"> </span>-sh<span class="w"> </span>/data
</code></pre></div>
<h3 id="_3">Перевірка кількості об'єктів<a class="headerlink" href="#_3" title="Permanent link">&para;</a></h3>
<p>Через MinIO Console → Bucket → Statistics</p>
<hr />
<h2 id="14-s3">14. Реплікація на зовнішній S3 (опційно)<a class="headerlink" href="#14-s3" title="Permanent link">&para;</a></h2>
<p>Для додаткової надійності можна налаштувати реплікацію на AWS S3 або Cloudflare R2:</p>
<div class="codehilite"><pre><span></span><code><span class="c1"># Налаштувати remote</span>
mc<span class="w"> </span><span class="nb">alias</span><span class="w"> </span><span class="nb">set</span><span class="w"> </span>s3<span class="w"> </span>https://s3.amazonaws.com<span class="w"> </span>ACCESS_KEY<span class="w"> </span>SECRET_KEY
<span class="c1"># Налаштувати реплікацію</span>
mc<span class="w"> </span>replicate<span class="w"> </span>add<span class="w"> </span>local/daarion-assets<span class="w"> </span>--remote-bucket<span class="w"> </span>s3/daarion-assets-backup
</code></pre></div>
<hr />
<h2 id="15">15. Чекліст налаштування<a class="headerlink" href="#15" title="Permanent link">&para;</a></h2>
<ul>
<li>[ ] MinIO запущений (<code>docker ps | grep minio</code>)</li>
<li>[ ] Bucket <code>daarion-assets</code> створений</li>
<li>[ ] Public read policy встановлена</li>
<li>[ ] DNS <code>assets.daarion.space</code> налаштований</li>
<li>[ ] Caddy/NGINX проксує запити до MinIO</li>
<li>[ ] Backend використовує <code>assets_client.py</code></li>
<li>[ ] Frontend відображає assets з HTTPS URLs</li>
<li>[ ] Тестовий upload працює</li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.sections", "navigation.instant", "content.code.copy"], "search": "../assets/javascripts/workers/search.b8dbb3d2.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="../assets/javascripts/bundle.3220b9d7.min.js"></script>
</body>
</html>