← شروع آموزش

WIUNIX ACADEMY

آموزش MinIO پس از بایگانی؛ آزمایشگاه خصوصی S3 از کد ثابت

صفحهٔ 2 از 4؛ مراحل قبلی و پیش‌نیازها را پیش از ادامه مرور کن.

آموزش متناسب با محیط تو

کجا اجرا می‌کنی؟

سیستم‌عامل و نسخه را انتخاب کن تا مراحل، دستورها و راه‌حل‌ها برای همان محیط نمایش داده شوند.

Ubuntu26.04 LTSبررسی‌شده با مستندات رسمی ·

آموزش متناسب با مقادیر تو

تنظیمات این آموزش

مقادیر نمونه را برای محیط خودت تنظیم کن؛ متن و دستورهای مرتبط با هم تغییر می‌کنند. رمز، کلید خصوصی و توکن وارد نکن. مقادیر فقط در همین زبانه نگهداری می‌شوند و به سرور یا لینک اشتراک‌گذاری فرستاده نمی‌شوند. پیش از اجرا، DNS، گواهی‌ها و سیاست شبکهٔ واقعی را بررسی کن.

آی‌پی، دامنه یا پورت مثال‌ها با محیط تو فرق دارد؟ دکمهٔ زیر را باز کن، مقادیر را تغییر بده و «اعمال در آموزش» را بزن؛ سپس دستورهای به‌روز‌شده را کپی کن.

تنظیم مقادیر برای محیط من6 مقدار قابل‌تغییر
04

قدم بعدی

آرشیوها را بررسی و کد ثابت upstream را کامپایل کنید

checksum ابزار Go1.27.3 برای linux-amd64 و hash هر دو آرشیو کد با دانلود واقعی در۲۰۲۶-۱۰-۲۱ بررسی شده است. commit سرور و کلاینت ثابت است و از latest استفاده نمی‌شود. استخراج با data filter انجام می‌شود و Go modules و go.sum یکپارچگی وابستگی‌ها را کنترل می‌کنند. ساخت به دو worker محدود است و تمام ورودی‌ها و cache در /srv/academy-minio می‌مانند.

خطای شبکه، hash نادرست، وابستگی در دسترس‌نبودن یا خطای کامپایل مسیر را متوقف می‌کند. URL ناموفق را با mirror ناشناخته یا image موجود در cache عوض نکنید. hash هویت ورودی را مشخص می‌کند و نشانهٔ بی‌نقص‌بودن امنیتی یا پشتیبانی نیست. در build مستقیم ممکن است برچسب DEVELOPMENT دیده شود؛ commit و ورودی ساخت هویت دقیق آن را تعیین می‌کنند.

bash

داخل کانتینر آزمایشگاهی · ترمینال

set -eu
python3 - <<'PY'
import os,hashlib,tarfile,urllib.request,subprocess
from pathlib import Path
r=Path('/srv/academy-minio')
items=[
 ('go','https://go.dev/dl/go1.27.3.linux-amd64.tar.gz','65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40'),
 ('server','https://codeload.github.com/minio/minio/tar.gz/7aac2a2c5b7c882e68c1ce017d8256be2feea27f','71794c2df26aad0cc99e8421c58b7aa2dd55969f979b0e7d1e931042e9fabcd6'),
 ('client','https://codeload.github.com/minio/mc/tar.gz/77f82e18b5401a65958f1619df6ebb994634bd88','167415edd21bc29f5360943dac64272aa5cda0a39f3070b15cfeca671c43d975')]
for name,url,sha in items:
    p=r/'downloads'/(name+'.tar.gz')
    with p.open('xb') as f, urllib.request.urlopen(url,timeout=90) as response:
        while block:=response.read(1024*1024): f.write(block)
    assert hashlib.sha256(p.read_bytes()).hexdigest()==sha, name+' hash mismatch'
    with tarfile.open(p) as t: t.extractall(r,filter='data')
    print(name,'SHA256 verified')
env={**os.environ,'PATH':str(r/'go/bin')+':'+os.environ['PATH'],
     'GOTOOLCHAIN':'local','CGO_ENABLED':'0','GOMAXPROCS':'2','GOPATH':str(r/'gopath')}
for directory,binary in [('minio-7aac2a2c5b7c882e68c1ce017d8256be2feea27f','minio'),('mc-77f82e18b5401a65958f1619df6ebb994634bd88','mc')]:
    target=r/'bin'/binary
    assert not target.exists() and not target.is_symlink()
    subprocess.run(['go','build','-p','2','-trimpath','-o',str(target),'.'],cwd=r/directory,env=env,check=True)
    print(binary,'built from pinned archived source')
PY
از کجا بدانم درست انجام شد؟

سه آرشیو تأیید شده و فایل‌های minio و mc بدون نصب سرویس میزبان ساخته شده‌اند.

05

قدم بعدی

اعتبارنامهٔ خصوصی و اعتماد TLS مخصوص کلاینت بسازید

کلیدهای root و اپلیکیشن تصادفی و مستقل‌اند و مستقیم در فایل0600 نوشته می‌شوند؛ نه نمایش داده می‌شوند و نه در argv قرار می‌گیرند. alias کلاینت mc مستقیماً در config خصوصی ثبت می‌شود. دستور حاوی کلید، shell tracing و خروجی JSON دیباگ به‌کار نبرید. این فایل‌ها نباید وارد Git، بستهٔ منبع، تیکت یا حافظهٔ مرورگر شوند.

گواهی self-signed فقط برای127.0.0.1 و با اعتبار دو روز است. تنها همین کلاینت‌های آزمایشگاهی به آن اعتماد صریح دارند و trust store سیستم تغییر نمی‌کند. کلید خصوصی و پوشه‌ها حساس‌اند. این مرحله یک‌بار اجرا می‌شود؛ اجرای مجدد پیش از ساخت گواهی در فایل اعتبارنامهٔ موجود متوقف می‌شود. صدور عمومی ACME یا تمدید در این آموزش آزموده نشده است.

bash

داخل کانتینر آزمایشگاهی · ترمینال

set -eu
python3 - <<'PY'
import os,json,secrets
from pathlib import Path
r=Path('/srv/academy-minio')
def private(path,content):
    fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
    with os.fdopen(fd,'w',encoding='utf-8') as f: f.write(content)
for role in ('root','app'):
    user=secrets.token_hex(12); password=secrets.token_urlsafe(36)
    private(r/'private'/(role+'-user'),user)
    private(r/'private'/(role+'-password'),password)
    cfg={'version':'10','aliases':{'lab':{'url':'https://127.0.0.1:19000','accessKey':user,'secretKey':password,'api':'S3v4','path':'on'}}}
    private(r/(role+'-client')/'config.json',json.dumps(cfg))
print('Random credentials written to exclusive0600 files; none printed')
PY
umask 077
openssl req -x509 -newkey rsa:3072 -sha256 -nodes -days 2   -subj '/CN=academy-minio-loopback'   -addext 'subjectAltName=IP:127.0.0.1'   -keyout '/srv/academy-minio/certs/private.key'   -out '/srv/academy-minio/certs/public.crt'
python3 - <<'PY'
from pathlib import Path
r=Path('/srv/academy-minio')
for role in ('root','app'):
    p=r/(role+'-client')/'certs'/'CAs';p.mkdir(parents=True,mode=0o700)
    with (p/'lab.crt').open('xb') as f: f.write((r/'certs/public.crt').read_bytes())
print('Client-scoped certificate trust only; system CA store unchanged')
PY
از کجا بدانم درست انجام شد؟

فایل‌ها خصوصی‌اند، هیچ اعتبارنامه‌ای نمایش داده نشده و اعتماد TLS محدود به گواهی آزمایشگاه است.

06

قدم بعدی

فایل اجرای مستقل سرور را ویرایش کنید

فایل خالیِ ازپیش‌ساخته را باز کنید، محتوای کامل Python بعدی را قرار دهید و ذخیره کنید. متغیرهای _FILE اعتبارنامه را از فایل می‌خوانند، هر دو رابط روی loopback می‌مانند و فقط PID همین فرایند فرزند ثبت می‌شود. واحد systemd یا راه‌اندازی خودکار ساخته نمی‌شود.

bash

داخل کانتینر آزمایشگاهی · ترمینال

set -eu
nano '/srv/academy-minio/start.py'
از کجا بدانم درست انجام شد؟

فایل ذخیره‌شده به /srv/academy-minio و دو پورت جداگانهٔ loopback اشاره دارد.

اعتبارنامه از فایل و دو listener جدا

این کادر محتوای فایل است. API روی19000 و کنسول روی19001 است. کلاینت S3 باید به API وصل شود؛ URL کنسول endpoint ذخیره‌سازی نیست.

python · /srv/academy-minio/start.py

داخل کانتینر آزمایشگاهی · ترمینال

import os,subprocess,socket,ssl,time,urllib.request
from pathlib import Path
r=Path('/srv/academy-minio')
assert not (r/'run/server.pid').exists()
for port in (19000,19001):
    with socket.socket() as s: s.bind(('127.0.0.1',port))
env={**os.environ,'MINIO_ROOT_USER_FILE':str(r/'private/root-user'),
     'MINIO_ROOT_PASSWORD_FILE':str(r/'private/root-password'),
     'MINIO_BROWSER_REDIRECT_URL':'https://127.0.0.1:19001',
     'MINIO_UPDATE':'off'}
# Never enable shell tracing, dump env, or print credentials.
with (r/'run/server.log').open('xb') as log:
    p=subprocess.Popen([str(r/'bin/minio'),'server',str(r/'data'),
        '--address','127.0.0.1:19000','--console-address','127.0.0.1:19001',
        '--certs-dir',str(r/'certs')],env=env,stdin=subprocess.DEVNULL,
        stdout=log,stderr=log,start_new_session=True)
with (r/'run/server.pid').open('x',encoding='utf-8') as f: f.write(str(p.pid))
ctx=ssl.create_default_context(cafile=str(r/'certs/public.crt'))
for attempt in range(60):
    if p.poll() is not None: raise RuntimeError('Server exited; inspect private log locally')
    try:
        with urllib.request.urlopen('https://127.0.0.1:19000/minio/health/ready',context=ctx,timeout=2) as q:
            assert q.status==200
        break
    except OSError: time.sleep(1)
else: raise RuntimeError('Ready probe timed out')
print('S3 API ready with verified TLS; separate loopback console')
07

قدم بعدی

فرایند فرزند را اجرا و آمادگی TLS را بررسی کنید

در همان کانتینر اجرا کنید. PID موجود یا پورت اشغال باعث توقف می‌شود. خطای readiness را بررسی کنید؛ سرور دوم اجرا نکنید. لاگ خصوصی می‌ماند. پاسخ200 تنها پاسخ‌گویی همین فرایند را نشان می‌دهد و آزمون دوام دیسک، مجوز S3 یا HA نیست.

bash

داخل کانتینر آزمایشگاهی · ترمینال

set -eu
python3 '/srv/academy-minio/start.py'
curl --fail --cacert '/srv/academy-minio/certs/public.crt' \
+  'https://127.0.0.1:19000/minio/health/ready'
از کجا بدانم درست انجام شد؟

آمادگی با TLS معتبر از127.0.0.1:19000 پاسخ200 می‌دهد و کنسول جداست.

08

قدم بعدی

فایل ساخت هویت محدود را آماده کنید

فایل کامل Go بعدی را در فایل خالی قرار دهید. برنامه از madmin-go و minio-go رسمی با نسخه‌های قفل‌شده در ماژول سرور استفاده می‌کند. اعتبارنامهٔ root در حافظهٔ فرایند می‌ماند و کلید اپلیکیشن آرگومان دستور یا متن لاگ نمی‌شود.

bash

داخل کانتینر آزمایشگاهی · ترمینال

set -eu
nano '/srv/academy-minio/provision.go'
از کجا بدانم درست انجام شد؟

فایل کامل provision.go ذخیره شده و هیچ کلید واقعی داخل آن نوشته نشده است.

دسترسی یک باکت، بدون مجوز حذف یا مدیریت

اپلیکیشن می‌تواند فهرست academy-objects و objectهای آن را بخواند و فایل جدید بنویسد. مجوز فهرست همهٔ باکت‌ها، حذف، نسخهٔ تاریخی، IAM یا مدیریت ندارد. باکت academy-objects-outside برای آزمون منع دسترسی است. کنترل نام موجود مانع تغییر هویت و باکت قبلی می‌شود؛ این برنامه ابزار provisioning تکرارپذیر عملیاتی نیست.

go · /srv/academy-minio/provision.go

داخل کانتینر آزمایشگاهی · ترمینال

package main

import (
 "context"
 "crypto/tls"
 "crypto/x509"
 "encoding/json"
 "fmt"
 "net/http"
 "os"
 "path/filepath"

 "github.com/minio/madmin-go/v3"
 "github.com/minio/minio-go/v7"
 "github.com/minio/minio-go/v7/pkg/credentials"
)

func must(err error) { if err != nil { panic(err) } }
func main() {
 r := "/srv/academy-minio"
 read := func(n string) string { b,e:=os.ReadFile(filepath.Join(r,n));must(e);return string(b) }
 roots:=x509.NewCertPool()
 if !roots.AppendCertsFromPEM([]byte(read("certs/public.crt"))) { panic("CA rejected") }
 transport:=&http.Transport{TLSClientConfig:&tls.Config{RootCAs:roots,MinVersion:tls.VersionTLS12}}
 endpoint:="127.0.0.1:19000"
 root,secret:=read("private/root-user"),read("private/root-password")
 app,appSecret:=read("private/app-user"),read("private/app-password")
 admin,e:=madmin.New(endpoint,root,secret,true);must(e);admin.SetCustomTransport(transport)
 s3,e:=minio.New(endpoint,&minio.Options{Creds:credentials.NewStaticV4(root,secret,""),Secure:true,Transport:transport});must(e)
 ctx:=context.Background()
 // Refuse existing names rather than changing an existing bucket or account.
 for _,bucket:=range []string{"academy-objects","academy-objects-outside"} {
  exists,e:=s3.BucketExists(ctx,bucket);must(e);if exists { panic("Bucket exists; stop") }
 }
 users,e:=admin.ListUsers(ctx);must(e);if _,ok:=users[app];ok {panic("Account exists; stop")}
 for _,bucket:=range []string{"academy-objects","academy-objects-outside"} {must(s3.MakeBucket(ctx,bucket,minio.MakeBucketOptions{}))}
 policy:=map[string]interface{}{"Version":"2012-10-17","Statement":[]interface{}{
  map[string]interface{}{"Effect":"Allow","Action":[]string{"s3:ListBucket","s3:GetBucketLocation"},"Resource":[]string{"arn:aws:s3:::academy-objects"}},
  map[string]interface{}{"Effect":"Allow","Action":[]string{"s3:PutObject","s3:GetObject"},"Resource":[]string{"arn:aws:s3:::academy-objects/*"}},
 }}
 body,e:=json.Marshal(policy);must(e)
 must(admin.AddCannedPolicy(ctx,"academy-objects-rw",body))
 must(admin.AddUser(ctx,app,appSecret))
 _,e=admin.AttachPolicy(ctx,madmin.PolicyAssociationReq{User:app,Policies:[]string{"academy-objects-rw"}});must(e)
 fmt.Println("Two new private buckets and one bucket-scoped application identity created")
}
همهٔ آموزش‌های آکادمی ↗