Skip to content

Commit 2853763

Browse files
authored
設定管理の実装 (#1292)
1 parent 3cc8a07 commit 2853763

File tree

7 files changed

+844
-23
lines changed

7 files changed

+844
-23
lines changed

.github/workflows/go-ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: Go CI
22

3-
on: push
3+
on:
4+
push:
5+
paths:
6+
- "go/**"
7+
- ".github/workflows/go-ci.yml"
48

59
env:
610
GO_VERSION: "1.25.4"

.github/workflows/rails-ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: Rails CI
22

3-
on: push
3+
on:
4+
push:
5+
paths:
6+
- "rails/**"
7+
- ".github/workflows/rails-ci.yml"
48

59
jobs:
610
zeitwerk:

go/cmd/server/main.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ import (
1212

1313
"github.com/go-chi/chi/v5"
1414
"github.com/go-chi/chi/v5/middleware"
15+
"github.com/mewstcom/mewst/internal/config"
1516
)
1617

1718
func main() {
18-
// ポート設定(環境変数から取得、デフォルトは3000)
19-
port := os.Getenv("MEWST_PORT")
20-
if port == "" {
21-
port = "3000"
19+
// 設定を読み込み
20+
cfg, err := config.Load()
21+
if err != nil {
22+
slog.Error("設定の読み込みに失敗しました", "error", err)
23+
os.Exit(1)
2224
}
2325

24-
slog.Info("サーバーを起動します", "port", port)
26+
slog.Info("サーバーを起動します", "port", cfg.Port, "env", cfg.Env)
2527

2628
// Chiルーターの設定
2729
r := chi.NewRouter()
@@ -40,7 +42,7 @@ func main() {
4042

4143
// サーバー起動
4244
// Dockerコンテナ内で動かす場合、0.0.0.0でリッスンする必要がある
43-
addr := fmt.Sprintf("0.0.0.0:%s", port)
45+
addr := fmt.Sprintf("0.0.0.0:%s", cfg.Port)
4446
slog.Info("HTTPサーバーを起動します", "addr", addr)
4547

4648
// HTTPサーバーの作成

go/go.mod

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,8 @@ module github.com/mewstcom/mewst
33
go 1.25.4
44

55
require (
6-
github.com/BurntSushi/toml v1.5.0
76
github.com/a-h/templ v0.3.960
87
github.com/go-chi/chi/v5 v5.2.3
9-
github.com/lib/pq v1.10.9
10-
github.com/nicksnyder/go-i18n/v2 v2.6.0
11-
golang.org/x/crypto v0.45.0
12-
golang.org/x/text v0.31.0
138
golang.org/x/tools v0.39.0
149
)
1510

go/go.sum

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
2-
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
31
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
42
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
53
github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
@@ -20,23 +18,17 @@ github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
2018
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
2119
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2220
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
23-
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
24-
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
2521
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
2622
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
2723
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
2824
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
2925
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
3026
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
3127
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
32-
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
33-
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
3428
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3529
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3630
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
3731
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
38-
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
39-
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
4032
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
4133
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
4234
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
@@ -49,8 +41,6 @@ golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
4941
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
5042
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
5143
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
52-
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
53-
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
5444
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
5545
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
5646
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

go/internal/config/config.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Package config はアプリケーション設定の管理機能を提供します
2+
package config
3+
4+
import (
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"strconv"
9+
"strings"
10+
"time"
11+
)
12+
13+
// Config はアプリケーションの設定を保持する構造体です
14+
type Config struct {
15+
// 環境
16+
Env string
17+
18+
// データベース
19+
DatabaseURL string
20+
21+
// サーバー
22+
Port string
23+
Domain string
24+
25+
// Cookie設定
26+
CookieDomain string
27+
28+
// セッション
29+
SessionSecure bool
30+
SessionHTTPOnly bool
31+
32+
// Rate Limiting設定
33+
DisableRateLimit bool
34+
35+
// Rails版アプリのURL(リバースプロキシ用)
36+
RailsAppURL string
37+
38+
// Cloudflare Turnstile(Bot対策)
39+
TurnstileSiteKey string
40+
TurnstileSecretKey string
41+
42+
// メンテナンスモード
43+
MaintenanceMode bool
44+
AdminIPs []string
45+
46+
// アセットバージョン(CDNキャッシュ対策用)
47+
AssetVersion string
48+
}
49+
50+
// Load は環境変数から設定を読み込みます
51+
func Load() (*Config, error) {
52+
// APP_ENVの値を取得(デフォルト: dev)
53+
// dev: 開発環境、test: テスト環境、prod: 本番環境
54+
//
55+
// すべての環境でGoプロセス起動時には既に環境変数がセット済みです:
56+
// - ローカル開発/テスト: op run --env-file=".env" が処理済み
57+
// - CI環境: GitHub Actionsが設定済み
58+
// - 本番環境: Dokkuが設定済み
59+
env := os.Getenv("APP_ENV")
60+
if env == "" {
61+
env = "dev"
62+
}
63+
64+
cfg := &Config{
65+
Env: env,
66+
}
67+
68+
// 必須の環境変数をチェック
69+
cfg.DatabaseURL = os.Getenv("DATABASE_URL")
70+
if cfg.DatabaseURL == "" {
71+
return nil, fmt.Errorf("必須の環境変数 DATABASE_URL が設定されていません")
72+
}
73+
74+
cfg.Port = os.Getenv("MEWST_PORT")
75+
if cfg.Port == "" {
76+
return nil, fmt.Errorf("必須の環境変数 MEWST_PORT が設定されていません")
77+
}
78+
79+
cfg.Domain = os.Getenv("MEWST_DOMAIN")
80+
if cfg.Domain == "" {
81+
return nil, fmt.Errorf("必須の環境変数 MEWST_DOMAIN が設定されていません")
82+
}
83+
84+
cfg.CookieDomain = os.Getenv("MEWST_COOKIE_DOMAIN")
85+
if cfg.CookieDomain == "" {
86+
return nil, fmt.Errorf("必須の環境変数 MEWST_COOKIE_DOMAIN が設定されていません")
87+
}
88+
89+
sessionSecureStr := os.Getenv("MEWST_SESSION_SECURE")
90+
if sessionSecureStr == "" {
91+
return nil, fmt.Errorf("必須の環境変数 MEWST_SESSION_SECURE が設定されていません")
92+
}
93+
cfg.SessionSecure = sessionSecureStr == "true"
94+
95+
sessionHTTPOnlyStr := os.Getenv("MEWST_SESSION_HTTPONLY")
96+
if sessionHTTPOnlyStr == "" {
97+
return nil, fmt.Errorf("必須の環境変数 MEWST_SESSION_HTTPONLY が設定されていません")
98+
}
99+
cfg.SessionHTTPOnly = sessionHTTPOnlyStr == "true"
100+
101+
// Rate Limiting設定(オプショナル - 開発環境でRate Limitingを無効化)
102+
cfg.DisableRateLimit = os.Getenv("MEWST_DISABLE_RATE_LIMIT") == "true"
103+
104+
// Rails版アプリのURL(オプショナル - リバースプロキシ機能で使用)
105+
cfg.RailsAppURL = os.Getenv("MEWST_RAILS_APP_URL")
106+
107+
// Cloudflare Turnstile(オプショナル - ログイン・サインアップフォームで使用)
108+
// テスト環境では空文字列でも動作する(モック設定として使用)
109+
cfg.TurnstileSiteKey = os.Getenv("MEWST_TURNSTILE_SITE_KEY")
110+
cfg.TurnstileSecretKey = os.Getenv("MEWST_TURNSTILE_SECRET_KEY")
111+
112+
// メンテナンスモード(オプショナル - "on"のときメンテナンスモードを有効化)
113+
cfg.MaintenanceMode = os.Getenv("MEWST_MAINTENANCE_MODE") == "on"
114+
115+
// 管理者IP(オプショナル - カンマ区切りで複数指定可能)
116+
adminIPStr := os.Getenv("MEWST_ADMIN_IP")
117+
if adminIPStr != "" {
118+
cfg.AdminIPs = parseAdminIPs(adminIPStr)
119+
}
120+
121+
// アセットバージョン(Gitコミットハッシュ)を設定
122+
cfg.AssetVersion = getGitCommitHash()
123+
124+
return cfg, nil
125+
}
126+
127+
// DatabaseDSN は PostgreSQL 接続文字列を返します
128+
func (c *Config) DatabaseDSN() string {
129+
return c.DatabaseURL
130+
}
131+
132+
// IsDev は開発環境かどうかを返します
133+
func (c *Config) IsDev() bool {
134+
return c.Env == "dev"
135+
}
136+
137+
// IsTest はテスト環境かどうかを返します
138+
func (c *Config) IsTest() bool {
139+
return c.Env == "test"
140+
}
141+
142+
// IsProduction は本番環境かどうかを返します
143+
func (c *Config) IsProduction() bool {
144+
return c.Env == "prod"
145+
}
146+
147+
// AppURL はアプリケーションのベースURLを返します
148+
func (c *Config) AppURL() string {
149+
return "https://" + c.Domain
150+
}
151+
152+
// getGitCommitHash はGitのコミットハッシュ(短縮版)を取得します
153+
// CDNキャッシュ対策として、CSS/JSファイルのクエリパラメータに使用します
154+
func getGitCommitHash() string {
155+
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
156+
out, err := cmd.Output()
157+
if err != nil {
158+
// Gitが利用できない場合は "dev" を返す(開発環境用のフォールバック)
159+
return "dev"
160+
}
161+
return strings.TrimSpace(string(out))
162+
}
163+
164+
// GetAssetVersion はアセットのバージョン文字列を返します
165+
// 開発環境: 現在時刻のUnixタイムスタンプ(ミリ秒)を返す(キャッシュを無効化)
166+
// 本番/テスト環境: Gitコミットハッシュを返す(起動時に設定された値)
167+
func (c *Config) GetAssetVersion() string {
168+
if c.IsDev() {
169+
// 開発環境では毎回異なる値を返す(現在時刻のUnixタイムスタンプ、ミリ秒)
170+
return strconv.FormatInt(time.Now().UnixMilli(), 10)
171+
}
172+
// 本番/テスト環境では起動時に設定されたGitコミットハッシュを返す
173+
return c.AssetVersion
174+
}
175+
176+
// parseAdminIPs はカンマ区切りのIP文字列をスライスに変換します
177+
// 各IPアドレスの前後の空白は除去されます
178+
func parseAdminIPs(s string) []string {
179+
parts := strings.Split(s, ",")
180+
ips := make([]string, 0, len(parts))
181+
for _, p := range parts {
182+
ip := strings.TrimSpace(p)
183+
if ip != "" {
184+
ips = append(ips, ip)
185+
}
186+
}
187+
return ips
188+
}

0 commit comments

Comments
 (0)