mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-02-22 08:52:06 +00:00
Compare commits
2 Commits
6112f977ea
...
nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4c4e34d5c | ||
|
|
3f3072254e |
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
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -22,3 +22,6 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Stats out
|
||||
stats.html
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"globals": "^17.3.0",
|
||||
"prettier": "3.8.1",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.55.0",
|
||||
@@ -401,6 +402,10 @@
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
@@ -431,8 +436,14 @@
|
||||
|
||||
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
@@ -451,6 +462,8 @@
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
@@ -465,6 +478,8 @@
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.151", "", {}, "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
@@ -531,6 +546,8 @@
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
@@ -583,14 +600,20 @@
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
@@ -721,6 +744,8 @@
|
||||
|
||||
"node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
|
||||
|
||||
"open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
@@ -773,8 +798,12 @@
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"rollup": ["rollup@4.46.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.46.2", "@rollup/rollup-android-arm64": "4.46.2", "@rollup/rollup-darwin-arm64": "4.46.2", "@rollup/rollup-darwin-x64": "4.46.2", "@rollup/rollup-freebsd-arm64": "4.46.2", "@rollup/rollup-freebsd-x64": "4.46.2", "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", "@rollup/rollup-linux-arm-musleabihf": "4.46.2", "@rollup/rollup-linux-arm64-gnu": "4.46.2", "@rollup/rollup-linux-arm64-musl": "4.46.2", "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", "@rollup/rollup-linux-ppc64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-gnu": "4.46.2", "@rollup/rollup-linux-riscv64-musl": "4.46.2", "@rollup/rollup-linux-s390x-gnu": "4.46.2", "@rollup/rollup-linux-x64-gnu": "4.46.2", "@rollup/rollup-linux-x64-musl": "4.46.2", "@rollup/rollup-win32-arm64-msvc": "4.46.2", "@rollup/rollup-win32-ia32-msvc": "4.46.2", "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg=="],
|
||||
|
||||
"rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.5", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg=="],
|
||||
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
@@ -787,12 +816,18 @@
|
||||
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.16", "", { "dependencies": { "style-to-object": "1.0.8" } }, "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
|
||||
@@ -857,8 +892,16 @@
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"globals": "^17.3.0",
|
||||
"prettier": "3.8.1",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.55.0",
|
||||
|
||||
@@ -2,15 +2,43 @@ import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [react(), tailwindcss(), visualizer()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes("node_modules")) {
|
||||
if (id.includes("/react")) {
|
||||
return "vendor-react";
|
||||
}
|
||||
|
||||
if (id.includes("/@radix-ui")) {
|
||||
return "vendor-radix";
|
||||
}
|
||||
|
||||
if (id.includes("/i18next")) {
|
||||
return "vendor-i18next";
|
||||
}
|
||||
|
||||
if (id.includes("/zod")) {
|
||||
return "vendor-zod";
|
||||
}
|
||||
|
||||
return "vendor";
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
proxy: {
|
||||
|
||||
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