mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-02-22 00:42:03 +00:00
Compare commits
2 Commits
436e30cc21
...
f54838709c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f54838709c | ||
|
|
e4c4e34d5c |
81
.env.example
81
.env.example
@@ -2,220 +2,171 @@
|
||||
|
||||
# The base URL where the app is hosted.
|
||||
TINYAUTH_APPURL=
|
||||
|
||||
# The directory where resources are stored.
|
||||
TINYAUTH_RESOURCESDIR="./resources"
|
||||
|
||||
# The path to the database file.
|
||||
TINYAUTH_DATABASEPATH="./tinyauth.db"
|
||||
|
||||
# Disable analytics.
|
||||
TINYAUTH_DISABLEANALYTICS=false
|
||||
|
||||
# Disable resources server.
|
||||
TINYAUTH_DISABLERESOURCES=false
|
||||
|
||||
# server config
|
||||
|
||||
# The port on which the server listens.
|
||||
TINYAUTH_SERVER_PORT=3000
|
||||
|
||||
# The address on which the server listens.
|
||||
TINYAUTH_SERVER_ADDRESS="0.0.0.0"
|
||||
|
||||
# The path to the Unix socket.
|
||||
TINYAUTH_SERVER_SOCKETPATH=
|
||||
|
||||
# auth config
|
||||
|
||||
# List of allowed IPs or CIDR ranges.
|
||||
TINYAUTH_AUTH_IP_ALLOW=
|
||||
|
||||
# List of blocked IPs or CIDR ranges.
|
||||
TINYAUTH_AUTH_IP_BLOCK=
|
||||
|
||||
# Comma-separated list of users (username:hashed_password).
|
||||
TINYAUTH_AUTH_USERS=
|
||||
|
||||
# Path to the users file.
|
||||
TINYAUTH_AUTH_USERSFILE=
|
||||
|
||||
# Enable secure cookies.
|
||||
TINYAUTH_AUTH_SECURECOOKIE=false
|
||||
|
||||
# Session expiry time in seconds.
|
||||
TINYAUTH_AUTH_SESSIONEXPIRY=86400
|
||||
|
||||
# Maximum session lifetime in seconds.
|
||||
TINYAUTH_AUTH_SESSIONMAXLIFETIME=0
|
||||
|
||||
# Login timeout in seconds.
|
||||
TINYAUTH_AUTH_LOGINTIMEOUT=300
|
||||
|
||||
# Maximum login retries.
|
||||
TINYAUTH_AUTH_LOGINMAXRETRIES=3
|
||||
|
||||
# Comma-separated list of trusted proxy addresses.
|
||||
TINYAUTH_AUTH_TRUSTEDPROXIES=
|
||||
|
||||
# apps config
|
||||
|
||||
# The domain of the app.
|
||||
TINYAUTH_APPS_name_CONFIG_DOMAIN=
|
||||
|
||||
# Comma-separated list of allowed users.
|
||||
TINYAUTH_APPS_name_USERS_ALLOW=
|
||||
|
||||
# Comma-separated list of blocked users.
|
||||
TINYAUTH_APPS_name_USERS_BLOCK=
|
||||
|
||||
# Comma-separated list of allowed OAuth groups.
|
||||
TINYAUTH_APPS_name_OAUTH_WHITELIST=
|
||||
|
||||
# Comma-separated list of required OAuth groups.
|
||||
TINYAUTH_APPS_name_OAUTH_GROUPS=
|
||||
|
||||
# List of allowed IPs or CIDR ranges.
|
||||
TINYAUTH_APPS_name_IP_ALLOW=
|
||||
|
||||
# List of blocked IPs or CIDR ranges.
|
||||
TINYAUTH_APPS_name_IP_BLOCK=
|
||||
|
||||
# List of IPs or CIDR ranges that bypass authentication.
|
||||
TINYAUTH_APPS_name_IP_BYPASS=
|
||||
|
||||
# Custom headers to add to the response.
|
||||
TINYAUTH_APPS_name_RESPONSE_HEADERS=
|
||||
|
||||
# Basic auth username.
|
||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_USERNAME=
|
||||
|
||||
# Basic auth password.
|
||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORD=
|
||||
|
||||
# Path to the file containing the basic auth password.
|
||||
TINYAUTH_APPS_name_RESPONSE_BASICAUTH_PASSWORDFILE=
|
||||
|
||||
# Comma-separated list of allowed paths.
|
||||
TINYAUTH_APPS_name_PATH_ALLOW=
|
||||
|
||||
# Comma-separated list of blocked paths.
|
||||
TINYAUTH_APPS_name_PATH_BLOCK=
|
||||
|
||||
# Comma-separated list of required LDAP groups.
|
||||
TINYAUTH_APPS_name_LDAP_GROUPS=
|
||||
|
||||
# oauth config
|
||||
|
||||
# Comma-separated list of allowed OAuth domains.
|
||||
TINYAUTH_OAUTH_WHITELIST=
|
||||
|
||||
# The OAuth provider to use for automatic redirection.
|
||||
TINYAUTH_OAUTH_AUTOREDIRECT=
|
||||
|
||||
# OAuth client ID.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTID=
|
||||
|
||||
# OAuth client secret.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRET=
|
||||
|
||||
# Path to the file containing the OAuth client secret.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_CLIENTSECRETFILE=
|
||||
|
||||
# OAuth scopes.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_SCOPES=
|
||||
|
||||
# OAuth redirect URL.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_REDIRECTURL=
|
||||
|
||||
# OAuth authorization URL.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_AUTHURL=
|
||||
|
||||
# OAuth token URL.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_TOKENURL=
|
||||
|
||||
# OAuth userinfo URL.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_USERINFOURL=
|
||||
|
||||
# Allow insecure OAuth connections.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_INSECURE=false
|
||||
|
||||
# Provider name in UI.
|
||||
TINYAUTH_OAUTH_PROVIDERS_name_NAME=
|
||||
|
||||
# oidc config
|
||||
|
||||
# Path to the private key file.
|
||||
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
|
||||
|
||||
# Path to the public key file.
|
||||
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
|
||||
|
||||
# OIDC client ID.
|
||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
|
||||
|
||||
# OIDC client secret.
|
||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET=
|
||||
|
||||
# Path to the file containing the OIDC client secret.
|
||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE=
|
||||
|
||||
# List of trusted redirect URIs.
|
||||
TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS=
|
||||
|
||||
# Client name in UI.
|
||||
TINYAUTH_OIDC_CLIENTS_name_NAME=
|
||||
|
||||
# ui config
|
||||
|
||||
# The title of the UI.
|
||||
TINYAUTH_UI_TITLE="Tinyauth"
|
||||
|
||||
# Message displayed on the forgot password page.
|
||||
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
|
||||
|
||||
# Path to the background image.
|
||||
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
|
||||
|
||||
# Disable UI warnings.
|
||||
TINYAUTH_UI_DISABLEWARNINGS=false
|
||||
|
||||
# ldap config
|
||||
|
||||
# LDAP server address.
|
||||
TINYAUTH_LDAP_ADDRESS=
|
||||
|
||||
# Bind DN for LDAP authentication.
|
||||
TINYAUTH_LDAP_BINDDN=
|
||||
|
||||
# Bind password for LDAP authentication.
|
||||
TINYAUTH_LDAP_BINDPASSWORD=
|
||||
|
||||
# Base DN for LDAP searches.
|
||||
TINYAUTH_LDAP_BASEDN=
|
||||
|
||||
# Allow insecure LDAP connections.
|
||||
TINYAUTH_LDAP_INSECURE=false
|
||||
|
||||
# LDAP search filter.
|
||||
TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)"
|
||||
|
||||
# Certificate for mTLS authentication.
|
||||
TINYAUTH_LDAP_AUTHCERT=
|
||||
|
||||
# Certificate key for mTLS authentication.
|
||||
TINYAUTH_LDAP_AUTHKEY=
|
||||
|
||||
# Cache duration for LDAP group membership in seconds.
|
||||
TINYAUTH_LDAP_GROUPCACHETTL=900
|
||||
|
||||
# log config
|
||||
|
||||
# Log level (trace, debug, info, warn, error).
|
||||
TINYAUTH_LOG_LEVEL="info"
|
||||
|
||||
# Enable JSON formatted logs.
|
||||
TINYAUTH_LOG_JSON=false
|
||||
|
||||
# Enable this log stream.
|
||||
TINYAUTH_LOG_STREAMS_HTTP_ENABLED=true
|
||||
|
||||
# Log level for this stream. Use global if empty.
|
||||
TINYAUTH_LOG_STREAMS_HTTP_LEVEL=
|
||||
|
||||
# Enable this log stream.
|
||||
TINYAUTH_LOG_STREAMS_APP_ENABLED=true
|
||||
|
||||
# Log level for this stream. Use global if empty.
|
||||
TINYAUTH_LOG_STREAMS_APP_LEVEL=
|
||||
|
||||
# Enable this log stream.
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
|
||||
|
||||
# Log level for this stream. Use global if empty.
|
||||
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -42,3 +42,6 @@ __debug_*
|
||||
|
||||
# traefik data
|
||||
/traefik
|
||||
|
||||
# generated markdown (for docs)
|
||||
/config.gen.md
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# Tinyauth Example Configuration
|
||||
|
||||
# The base URL where Tinyauth is accessible
|
||||
appUrl: "https://auth.example.com"
|
||||
# Directory for static resources
|
||||
resourcesDir: "./resources"
|
||||
# Path to SQLite database file
|
||||
databasePath: "./tinyauth.db"
|
||||
# Disable usage analytics
|
||||
disableAnalytics: false
|
||||
# Disable static resource serving
|
||||
disableResources: false
|
||||
# Disable UI warning messages
|
||||
disableUIWarnings: false
|
||||
|
||||
# Logging Configuration
|
||||
log:
|
||||
# Log level: trace, debug, info, warn, error
|
||||
level: "info"
|
||||
json: false
|
||||
streams:
|
||||
app:
|
||||
enabled: true
|
||||
level: "warn"
|
||||
http:
|
||||
enabled: true
|
||||
level: "debug"
|
||||
audit:
|
||||
enabled: false
|
||||
level: "info"
|
||||
|
||||
# Server Configuration
|
||||
server:
|
||||
# Port to listen on
|
||||
port: 3000
|
||||
# Interface to bind to (0.0.0.0 for all interfaces)
|
||||
address: "0.0.0.0"
|
||||
# Unix socket path (optional, overrides port/address if set)
|
||||
socketPath: ""
|
||||
# Comma-separated list of trusted proxy IPs/CIDRs
|
||||
trustedProxies: ""
|
||||
|
||||
# Authentication Configuration
|
||||
auth:
|
||||
# Format: username:bcrypt_hash (use bcrypt to generate hash)
|
||||
users: "admin:$2a$10$example_bcrypt_hash_here"
|
||||
# Path to external users file (optional)
|
||||
usersFile: ""
|
||||
# Enable secure cookies (requires HTTPS)
|
||||
secureCookie: false
|
||||
# Session expiry in seconds (3600 = 1 hour)
|
||||
sessionExpiry: 3600
|
||||
# Session maximum lifetime in seconds (0 = unlimited)
|
||||
sessionMaxLifetime: 0
|
||||
# Login timeout in seconds (300 = 5 minutes)
|
||||
loginTimeout: 300
|
||||
# Maximum login retries before lockout
|
||||
loginMaxRetries: 3
|
||||
|
||||
# OAuth Configuration
|
||||
oauth:
|
||||
# Regex pattern for allowed email addresses (e.g., /@example\.com$/)
|
||||
whitelist: ""
|
||||
# Provider ID to auto-redirect to (skips login page)
|
||||
autoRedirect: ""
|
||||
# OAuth Provider Configuration (replace myprovider with your provider name)
|
||||
providers:
|
||||
myprovider:
|
||||
clientId: "your_client_id_here"
|
||||
clientSecret: "your_client_secret_here"
|
||||
authUrl: "https://provider.example.com/oauth/authorize"
|
||||
tokenUrl: "https://provider.example.com/oauth/token"
|
||||
userInfoUrl: "https://provider.example.com/oauth/userinfo"
|
||||
redirectUrl: "https://auth.example.com/api/oauth/callback/myprovider"
|
||||
scopes: "openid email profile"
|
||||
name: "My OAuth Provider"
|
||||
# Allow insecure connections (self-signed certificates)
|
||||
insecure: false
|
||||
|
||||
# UI Customization
|
||||
ui:
|
||||
# Custom title for login page
|
||||
title: "Tinyauth"
|
||||
# Message shown on forgot password page
|
||||
forgotPasswordMessage: "Contact your administrator to reset your password"
|
||||
# Background image URL for login page
|
||||
backgroundImage: ""
|
||||
|
||||
# LDAP Configuration (optional)
|
||||
ldap:
|
||||
# LDAP server address
|
||||
address: "ldap://ldap.example.com:389"
|
||||
# DN for binding to LDAP server
|
||||
bindDn: "cn=readonly,dc=example,dc=com"
|
||||
# Password for bind DN
|
||||
bindPassword: "your_bind_password"
|
||||
# Base DN for user searches
|
||||
baseDn: "dc=example,dc=com"
|
||||
# Search filter (%s will be replaced with username)
|
||||
searchFilter: "(&(uid=%s)(memberOf=cn=users,ou=groups,dc=example,dc=com))"
|
||||
# Allow insecure LDAP connections
|
||||
insecure: false
|
||||
29
gen/gen.go
29
gen/gen.go
@@ -2,10 +2,37 @@ package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func main() {
|
||||
slog.Info("generating example env file")
|
||||
|
||||
generateExampleEnv()
|
||||
slog.Info("generating config reference markdown file")
|
||||
generateMarkdown()
|
||||
}
|
||||
|
||||
func walkAndBuild[T any](parent reflect.Type, parentValue reflect.Value,
|
||||
parentPath string, entries *[]T,
|
||||
buildEntry func(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]T),
|
||||
buildMap func(child reflect.StructField, parentPath string, entries *[]T),
|
||||
buildChildPath func(parentPath string, childName string) string,
|
||||
) {
|
||||
for i := 0; i < parent.NumField(); i++ {
|
||||
field := parent.Field(i)
|
||||
fieldType := field.Type
|
||||
fieldValue := parentValue.Field(i)
|
||||
|
||||
switch fieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
childPath := buildChildPath(parentPath, field.Name)
|
||||
walkAndBuild[T](fieldType, fieldValue, childPath, entries, buildEntry, buildMap, buildChildPath)
|
||||
case reflect.Map:
|
||||
buildMap(field, parentPath, entries)
|
||||
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int:
|
||||
buildEntry(field, fieldValue, parentPath, entries)
|
||||
default:
|
||||
slog.Info("unknown type", "type", fieldType.Kind())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
100
gen/gen_env.go
100
gen/gen_env.go
@@ -2,7 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -11,7 +13,7 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
type EnvEntry struct {
|
||||
Name string
|
||||
Description string
|
||||
Value any
|
||||
@@ -19,17 +21,17 @@ type Path struct {
|
||||
|
||||
func generateExampleEnv() {
|
||||
cfg := config.NewDefaultConfiguration()
|
||||
paths := make([]Path, 0)
|
||||
entries := make([]EnvEntry, 0)
|
||||
|
||||
root := reflect.TypeOf(cfg).Elem()
|
||||
rootValue := reflect.ValueOf(cfg).Elem()
|
||||
rootPath := "TINYAUTH_"
|
||||
|
||||
buildPaths(root, rootValue, rootPath, &paths)
|
||||
compiled := compileEnv(paths)
|
||||
walkAndBuild(root, rootValue, rootPath, &entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
|
||||
compiled := compileEnv(entries)
|
||||
|
||||
err := os.Remove(".env.example")
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
slog.Error("failed to remove example env file", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -41,96 +43,88 @@ func generateExampleEnv() {
|
||||
}
|
||||
}
|
||||
|
||||
func buildPaths(parent reflect.Type, parentValue reflect.Value, parentPath string, paths *[]Path) {
|
||||
for i := 0; i < parent.NumField(); i++ {
|
||||
field := parent.Field(i)
|
||||
fieldType := field.Type
|
||||
fieldValue := parentValue.Field(i)
|
||||
switch fieldType.Kind() {
|
||||
case reflect.Struct:
|
||||
childPath := parentPath + strings.ToUpper(field.Name) + "_"
|
||||
buildPaths(fieldType, fieldValue, childPath, paths)
|
||||
case reflect.Map:
|
||||
buildMapPaths(field, parentPath, paths)
|
||||
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int:
|
||||
buildPath(field, fieldValue, parentPath, paths)
|
||||
default:
|
||||
slog.Info("unknown type", "type", fieldType.Kind())
|
||||
}
|
||||
}
|
||||
}
|
||||
func buildEnvEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]EnvEntry) {
|
||||
desc := child.Tag.Get("description")
|
||||
tag := child.Tag.Get("yaml")
|
||||
|
||||
func buildPath(field reflect.StructField, fieldValue reflect.Value, parent string, paths *[]Path) {
|
||||
desc := field.Tag.Get("description")
|
||||
yamlTag := field.Tag.Get("yaml")
|
||||
|
||||
// probably internal logic, should be skipped
|
||||
if yamlTag == "-" {
|
||||
if tag == "-" {
|
||||
return
|
||||
}
|
||||
|
||||
defaultValue := fieldValue.Interface()
|
||||
value := childValue.Interface()
|
||||
|
||||
path := Path{
|
||||
Name: parent + strings.ToUpper(field.Name),
|
||||
entry := EnvEntry{
|
||||
Name: parentPath + strings.ToUpper(child.Name),
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
switch fieldValue.Kind() {
|
||||
switch childValue.Kind() {
|
||||
case reflect.Slice:
|
||||
sl, ok := defaultValue.([]string)
|
||||
sl, ok := value.([]string)
|
||||
if !ok {
|
||||
slog.Error("invalid default value", "value", defaultValue)
|
||||
slog.Error("invalid default value", "value", value)
|
||||
return
|
||||
}
|
||||
path.Value = strings.Join(sl, ",")
|
||||
entry.Value = strings.Join(sl, ",")
|
||||
case reflect.String:
|
||||
st, ok := defaultValue.(string)
|
||||
st, ok := value.(string)
|
||||
if !ok {
|
||||
slog.Error("invalid default value", "value", defaultValue)
|
||||
slog.Error("invalid default value", "value", value)
|
||||
return
|
||||
}
|
||||
// good idea to escape strings probably
|
||||
if st != "" {
|
||||
path.Value = fmt.Sprintf(`"%s"`, st)
|
||||
entry.Value = fmt.Sprintf(`"%s"`, st)
|
||||
} else {
|
||||
path.Value = ""
|
||||
entry.Value = ""
|
||||
}
|
||||
default:
|
||||
path.Value = defaultValue
|
||||
entry.Value = value
|
||||
}
|
||||
*paths = append(*paths, path)
|
||||
*entries = append(*entries, entry)
|
||||
}
|
||||
|
||||
func buildMapPaths(field reflect.StructField, parentPath string, paths *[]Path) {
|
||||
fieldType := field.Type
|
||||
func buildEnvMapEntry(child reflect.StructField, parentPath string, entries *[]EnvEntry) {
|
||||
fieldType := child.Type
|
||||
|
||||
if fieldType.Key().Kind() != reflect.String {
|
||||
slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
|
||||
return
|
||||
}
|
||||
|
||||
mapPath := parentPath + strings.ToUpper(field.Name) + "_name_"
|
||||
mapPath := parentPath + strings.ToUpper(child.Name) + "_name_"
|
||||
valueType := fieldType.Elem()
|
||||
|
||||
if valueType.Kind() == reflect.Struct {
|
||||
zeroValue := reflect.New(valueType).Elem()
|
||||
buildPaths(valueType, zeroValue, mapPath, paths)
|
||||
walkAndBuild(valueType, zeroValue, mapPath, entries, buildEnvEntry, buildEnvMapEntry, buildEnvChildPath)
|
||||
}
|
||||
}
|
||||
|
||||
func compileEnv(paths []Path) []byte {
|
||||
func buildEnvChildPath(parent string, child string) string {
|
||||
return parent + strings.ToUpper(child) + "_"
|
||||
}
|
||||
|
||||
func compileEnv(entries []EnvEntry) []byte {
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.WriteString("# Tinyauth example configuration\n\n")
|
||||
|
||||
for _, path := range paths {
|
||||
previousSection := ""
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.Count(entry.Name, "_") > 1 {
|
||||
section := strings.Split(strings.TrimPrefix(entry.Name, "TINYAUTH_"), "_")[0]
|
||||
if section != previousSection {
|
||||
buffer.WriteString("\n# " + strings.ToLower(section) + " config\n\n")
|
||||
previousSection = section
|
||||
}
|
||||
}
|
||||
buffer.WriteString("# ")
|
||||
buffer.WriteString(path.Description)
|
||||
buffer.WriteString(entry.Description)
|
||||
buffer.WriteString("\n")
|
||||
buffer.WriteString(path.Name)
|
||||
buffer.WriteString(entry.Name)
|
||||
buffer.WriteString("=")
|
||||
fmt.Fprintf(&buffer, "%v", path.Value)
|
||||
buffer.WriteString("\n\n")
|
||||
fmt.Fprintf(&buffer, "%v", entry.Value)
|
||||
buffer.WriteString("\n")
|
||||
}
|
||||
|
||||
return buffer.Bytes()
|
||||
|
||||
127
gen/gen_md.go
Normal file
127
gen/gen_md.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type MarkdownEntry struct {
|
||||
Env string
|
||||
Flag string
|
||||
Description string
|
||||
Default any
|
||||
}
|
||||
|
||||
func generateMarkdown() {
|
||||
cfg := config.NewDefaultConfiguration()
|
||||
entries := make([]MarkdownEntry, 0)
|
||||
|
||||
root := reflect.TypeOf(cfg).Elem()
|
||||
rootValue := reflect.ValueOf(cfg).Elem()
|
||||
rootPath := "tinyauth."
|
||||
|
||||
walkAndBuild(root, rootValue, rootPath, &entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
|
||||
compiled := compileMd(entries)
|
||||
|
||||
err := os.Remove("config.gen.md")
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
slog.Error("failed to remove example env file", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = os.WriteFile("config.gen.md", compiled, 0644)
|
||||
if err != nil {
|
||||
slog.Error("failed to write example env file", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func buildMdEntry(child reflect.StructField, childValue reflect.Value, parentPath string, entries *[]MarkdownEntry) {
|
||||
desc := child.Tag.Get("description")
|
||||
tag := child.Tag.Get("yaml")
|
||||
|
||||
if tag == "-" {
|
||||
return
|
||||
}
|
||||
|
||||
value := childValue.Interface()
|
||||
|
||||
entry := MarkdownEntry{
|
||||
Env: strings.ToUpper(strings.ReplaceAll(parentPath, ".", "_")) + strings.ToUpper(child.Name),
|
||||
Flag: fmt.Sprintf("--%s%s", strings.TrimPrefix(parentPath, "tinyauth."), tag),
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
switch childValue.Kind() {
|
||||
case reflect.Slice:
|
||||
sl, ok := value.([]string)
|
||||
if !ok {
|
||||
slog.Error("invalid default value", "value", value)
|
||||
return
|
||||
}
|
||||
entry.Default = fmt.Sprintf("`%s`", strings.Join(sl, ","))
|
||||
default:
|
||||
entry.Default = fmt.Sprintf("`%v`", value)
|
||||
}
|
||||
*entries = append(*entries, entry)
|
||||
}
|
||||
|
||||
func buildMdMapEntry(child reflect.StructField, parentPath string, entries *[]MarkdownEntry) {
|
||||
fieldType := child.Type
|
||||
|
||||
if fieldType.Key().Kind() != reflect.String {
|
||||
slog.Info("unsupported map key type", "type", fieldType.Key().Kind())
|
||||
return
|
||||
}
|
||||
|
||||
tag := child.Tag.Get("yaml")
|
||||
|
||||
if tag == "-" {
|
||||
return
|
||||
}
|
||||
|
||||
mapPath := parentPath + tag + ".[name]."
|
||||
valueType := fieldType.Elem()
|
||||
|
||||
if valueType.Kind() == reflect.Struct {
|
||||
zeroValue := reflect.New(valueType).Elem()
|
||||
walkAndBuild(valueType, zeroValue, mapPath, entries, buildMdEntry, buildMdMapEntry, buildMdChildPath)
|
||||
}
|
||||
}
|
||||
|
||||
func buildMdChildPath(parent string, child string) string {
|
||||
return parent + strings.ToLower(child) + "."
|
||||
}
|
||||
|
||||
func compileMd(entries []MarkdownEntry) []byte {
|
||||
buffer := bytes.Buffer{}
|
||||
|
||||
buffer.WriteString("# Tinyauth configuration reference\n\n")
|
||||
buffer.WriteString("| Environment | Flag | Description | Default |\n")
|
||||
buffer.WriteString("| - | - | - | - |\n")
|
||||
|
||||
previousSection := ""
|
||||
|
||||
for _, entry := range entries {
|
||||
if strings.Count(entry.Env, "_") > 1 {
|
||||
section := strings.Split(strings.TrimPrefix(entry.Env, "TINYAUTH_"), "_")[0]
|
||||
if section != previousSection {
|
||||
buffer.WriteString("\n## " + strings.ToLower(section) + "\n\n")
|
||||
buffer.WriteString("| Environment | Flag | Description | Default |\n")
|
||||
buffer.WriteString("| - | - | - | - |\n")
|
||||
previousSection = section
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&buffer, "| `%s` | `%s` | %s | %s |\n", entry.Env, entry.Flag, entry.Description, entry.Default)
|
||||
}
|
||||
|
||||
return buffer.Bytes()
|
||||
}
|
||||
Reference in New Issue
Block a user