Compare commits

..

8 Commits

Author SHA1 Message Date
Stavros 73cc4808bc feat: improve binary size with build tags (#976) 2026-07-06 23:49:55 +03:00
dependabot[bot] 7abd9fe310 chore(deps): bump the minor-patch group across 1 directory with 25 updates (#969)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-06 23:30:37 +03:00
Stavros 6ab9c0a0c5 feat: log warning when experimental features are enabled 2026-07-03 16:57:53 +03:00
Stavros 4aa05aeb79 refactor: use some colors in CLI output (#962) 2026-07-03 16:40:22 +03:00
Stavros 440a3a3ef5 chore: cleanup codegen (#965) 2026-07-02 23:35:34 +03:00
Stavros a3c4d6ac83 chore: move tailscale to experimental config (#964) 2026-07-02 23:17:03 +03:00
Stavros c8b31c54a0 chore: remove prettier from frontend 2026-07-02 22:23:52 +03:00
Stavros 04b93fa107 fix: remove shutdown from serve error path 2026-07-02 15:07:04 +03:00
32 changed files with 2082 additions and 1863 deletions
+17 -17
View File
@@ -220,6 +220,23 @@ TINYAUTH_LDAP_AUTHCERT=
TINYAUTH_LDAP_AUTHKEY= TINYAUTH_LDAP_AUTHKEY=
# Cache duration for LDAP group membership in seconds. # Cache duration for LDAP group membership in seconds.
TINYAUTH_LDAP_GROUPCACHETTL=900 TINYAUTH_LDAP_GROUPCACHETTL=900
# experimental config
# Enable Tailscale integration.
TINYAUTH_EXPERIMENTAL_TAILSCALE_ENABLED=false
# Tailscale state directory.
TINYAUTH_EXPERIMENTAL_TAILSCALE_DIR="./tailscale_state"
# Tailscale hostname.
TINYAUTH_EXPERIMENTAL_TAILSCALE_HOSTNAME=
# Tailscale auth key.
TINYAUTH_EXPERIMENTAL_TAILSCALE_AUTHKEY=
# Use ephemeral Tailscale node.
TINYAUTH_EXPERIMENTAL_TAILSCALE_EPHEMERAL=false
# Enable Tailscale Funnel.
TINYAUTH_EXPERIMENTAL_TAILSCALE_FUNNEL=false
# Listen on the Tailscale address instead of standard address.
TINYAUTH_EXPERIMENTAL_TAILSCALE_LISTEN=false
# Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment. # Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment.
TINYAUTH_LABELPROVIDER="auto" TINYAUTH_LABELPROVIDER="auto"
@@ -241,20 +258,3 @@ TINYAUTH_LOG_STREAMS_APP_LEVEL=
TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false TINYAUTH_LOG_STREAMS_AUDIT_ENABLED=false
# Log level for this stream. Use global if empty. # Log level for this stream. Use global if empty.
TINYAUTH_LOG_STREAMS_AUDIT_LEVEL= TINYAUTH_LOG_STREAMS_AUDIT_LEVEL=
# tailscale config
# Enable Tailscale integration.
TINYAUTH_TAILSCALE_ENABLED=false
# Tailscale state directory.
TINYAUTH_TAILSCALE_DIR="./tailscale_state"
# Tailscale hostname.
TINYAUTH_TAILSCALE_HOSTNAME=
# Tailscale auth key.
TINYAUTH_TAILSCALE_AUTHKEY=
# Use ephemeral Tailscale node.
TINYAUTH_TAILSCALE_EPHEMERAL=false
# Enable Tailscale Funnel.
TINYAUTH_TAILSCALE_FUNNEL=false
# Listen on the Tailscale address instead of standard address.
TINYAUTH_TAILSCALE_LISTEN=false
+3 -3
View File
@@ -36,9 +36,9 @@ jobs:
- name: Check codegen is up to date - name: Check codegen is up to date
run: | run: |
sqlc generate sqlc generate
go generate ./internal/repository/... go generate ./...
git diff --exit-code -- internal/repository/ git diff --exit-code
git status --porcelain -- internal/repository/ | grep -q . && echo "untracked files in internal/repository/" && exit 1 || true git status --porcelain | grep -q . && echo "untracked files code gen files" && exit 1 || true
- name: Install frontend dependencies - name: Install frontend dependencies
working-directory: ./frontend working-directory: ./frontend
+30 -9
View File
@@ -35,18 +35,27 @@ jobs:
VERSION: ${{ steps.metadata.outputs.VERSION }} VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }} COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with: with:
ref: nightly ref: nightly
- name: Install go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "^1.26.4"
- name: Generate metadata - name: Generate metadata
id: metadata id: metadata
run: | run: |
echo "VERSION=nightly" >> "$GITHUB_OUTPUT" echo "VERSION=nightly" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT" echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
export TAILSCALE_BUILD_TAGS=$(go run tailscale.com/cmd/featuretags@v1.100.0 -min -add acme,serve,netstack)
export GIN_BUILD_TAGS=nomsgpack
echo "BUILD_TAGS=$TAILSCALE_BUILD_TAGS,$GIN_BUILD_TAGS" >> "$GITHUB_OUTPUT"
binary-build: binary-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -83,7 +92,11 @@ jobs:
- name: Build - name: Build
run: | run: |
cp -r frontend/dist internal/assets/dist cp -r frontend/dist internal/assets/dist
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
-o tinyauth-amd64 ./cmd/tinyauth
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -128,7 +141,11 @@ jobs:
- name: Build - name: Build
run: | run: |
cp -r frontend/dist internal/assets/dist cp -r frontend/dist internal/assets/dist
go build -ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
-ldflags "-X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
-o tinyauth-arm64 ./cmd/tinyauth
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -166,7 +183,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -180,6 +197,7 @@ jobs:
VERSION=${{ needs.generate-metadata.outputs.VERSION }} VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -190,7 +208,7 @@ jobs:
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: digests-linux-amd64 name: digests-alpine-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -224,7 +242,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -239,6 +257,7 @@ jobs:
VERSION=${{ needs.generate-metadata.outputs.VERSION }} VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -282,7 +301,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -296,6 +315,7 @@ jobs:
VERSION=${{ needs.generate-metadata.outputs.VERSION }} VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -306,7 +326,7 @@ jobs:
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: digests-linux-arm64 name: digests-alpine-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -340,7 +360,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -355,6 +375,7 @@ jobs:
VERSION=${{ needs.generate-metadata.outputs.VERSION }} VERSION=${{ needs.generate-metadata.outputs.VERSION }}
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -380,7 +401,7 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-alpine-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
+33 -10
View File
@@ -16,16 +16,27 @@ jobs:
VERSION: ${{ steps.metadata.outputs.VERSION }} VERSION: ${{ steps.metadata.outputs.VERSION }}
COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }} COMMIT_HASH: ${{ steps.metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP: ${{ steps.metadata.outputs.BUILD_TIMESTAMP }}
BUILD_TAGS: ${{ steps.metadata.outputs.BUILD_TAGS }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: "^1.26.4"
- name: Generate metadata - name: Generate metadata
id: metadata id: metadata
env:
REF_NAME: ${{ github.ref_name }}
run: | run: |
echo "VERSION=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" echo "VERSION=$REF_NAME" >> "$GITHUB_OUTPUT"
echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" echo "COMMIT_HASH=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT" echo "BUILD_TIMESTAMP=$(date '+%Y-%m-%dT%H:%M:%S')" >> "$GITHUB_OUTPUT"
export TAILSCALE_BUILD_TAGS=$(go run tailscale.com/cmd/featuretags@v1.100.0 -min -add acme,serve,netstack)
export GIN_BUILD_TAGS=nomsgpack
echo "BUILD_TAGS=$TAILSCALE_BUILD_TAGS,$GIN_BUILD_TAGS" >> "$GITHUB_OUTPUT"
binary-build: binary-build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -59,7 +70,11 @@ jobs:
- name: Build - name: Build
run: | run: |
cp -r frontend/dist internal/assets/dist cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-amd64 ./cmd/tinyauth go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
-ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
-o tinyauth-amd64 ./cmd/tinyauth
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -101,7 +116,11 @@ jobs:
- name: Build - name: Build
run: | run: |
cp -r frontend/dist internal/assets/dist cp -r frontend/dist internal/assets/dist
go build -ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" -o tinyauth-arm64 ./cmd/tinyauth go build -tags ${{ needs.generate-metadata.outputs.BUILD_TAGS }} \
-ldflags "-s -w -X github.com/tinyauthapp/tinyauth/internal/model.Version=${{ needs.generate-metadata.outputs.VERSION }} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${{ needs.generate-metadata.outputs.COMMIT_HASH }} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}" \
-o tinyauth-arm64 ./cmd/tinyauth
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
@@ -136,7 +155,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -151,6 +170,7 @@ jobs:
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
LDFLAGS=-s -w LDFLAGS=-s -w
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -161,7 +181,7 @@ jobs:
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: digests-linux-amd64 name: digests-alpine-linux-amd64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -192,7 +212,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/amd64 platforms: linux/amd64
@@ -208,6 +228,7 @@ jobs:
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
LDFLAGS=-s -w LDFLAGS=-s -w
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -248,7 +269,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -263,6 +284,7 @@ jobs:
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
LDFLAGS=-s -w LDFLAGS=-s -w
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -273,7 +295,7 @@ jobs:
- name: Upload digest - name: Upload digest
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: digests-linux-arm64 name: digests-alpine-linux-arm64
path: ${{ runner.temp }}/digests/* path: ${{ runner.temp }}/digests/*
if-no-files-found: error if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -304,7 +326,7 @@ jobs:
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build and push - name: Build and push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7
id: build id: build
with: with:
platforms: linux/arm64 platforms: linux/arm64
@@ -320,6 +342,7 @@ jobs:
COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }} COMMIT_HASH=${{ needs.generate-metadata.outputs.COMMIT_HASH }}
BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }} BUILD_TIMESTAMP=${{ needs.generate-metadata.outputs.BUILD_TIMESTAMP }}
LDFLAGS=-s -w LDFLAGS=-s -w
BUILD_TAGS=${{ needs.generate-metadata.outputs.BUILD_TAGS }}
- name: Export digest - name: Export digest
run: | run: |
@@ -345,7 +368,7 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with: with:
path: ${{ runner.temp }}/digests path: ${{ runner.temp }}/digests
pattern: digests-* pattern: digests-alpine-*
merge-multiple: true merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
+3
View File
@@ -51,3 +51,6 @@ config.certify.yml
# deepsec # deepsec
/.deepsec /.deepsec
# jetbrains
/.idea/
+4 -2
View File
@@ -28,6 +28,7 @@ ARG VERSION
ARG COMMIT_HASH ARG COMMIT_HASH
ARG BUILD_TIMESTAMP ARG BUILD_TIMESTAMP
ARG LDFLAGS ARG LDFLAGS
ARG BUILD_TAGS
WORKDIR /tinyauth WORKDIR /tinyauth
@@ -40,10 +41,11 @@ COPY ./cmd ./cmd
COPY ./internal ./internal COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \ RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \ -X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \ -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
-o tinyauth ./cmd/tinyauth
# Runner # Runner
FROM alpine:3.24 AS runner FROM alpine:3.24 AS runner
+5 -3
View File
@@ -28,6 +28,7 @@ ARG VERSION
ARG COMMIT_HASH ARG COMMIT_HASH
ARG BUILD_TIMESTAMP ARG BUILD_TIMESTAMP
ARG LDFLAGS ARG LDFLAGS
ARG BUILD_TAGS
WORKDIR /tinyauth WORKDIR /tinyauth
@@ -36,14 +37,15 @@ COPY go.sum ./
RUN go mod download RUN go mod download
COPY ./cmd ./cmd COPY ./cmd ./cmd/
COPY ./internal ./internal COPY ./internal ./internal
COPY --from=frontend-builder /frontend/dist ./internal/assets/dist COPY --from=frontend-builder /frontend/dist ./internal/assets/dist
RUN CGO_ENABLED=0 go build -ldflags "${LDFLAGS} \ RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \ -X github.com/tinyauthapp/tinyauth/internal/model.Version=${VERSION} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \ -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" ./cmd/tinyauth -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
-o tinyauth ./cmd/tinyauth
# Make the data directory with a non-root user # Make the data directory with a non-root user
RUN addgroup tinyauth && adduser -DH tinyauth -G tinyauth RUN addgroup tinyauth && adduser -DH tinyauth -G tinyauth
+19 -5
View File
@@ -9,6 +9,11 @@ COMMIT_HASH := $(shell git rev-parse HEAD)
BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S') BUILD_TIMESTAMP := $(shell date '+%Y-%m-%dT%H:%M:%S')
BIN_NAME := tinyauth-$(GOARCH) BIN_NAME := tinyauth-$(GOARCH)
LDFLAGS := -s -w LDFLAGS := -s -w
# We don't want all of the tailscale feature-set
TAILSCALE_BUILD_TAGS = $(shell go run tailscale.com/cmd/featuretags@v1.100.0 -min -add acme,serve,netstack)
# Whatever 6MB serialization lib Gin is using
GIN_BUILD_TAGS := nomsgpack
BUILD_TAGS = $(GIN_BUILD_TAGS),$(TAILSCALE_BUILD_TAGS)
# Development vars # Development vars
DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" ) DEV_COMPOSE := $(shell test -f "docker-compose.test.yml" && echo "docker-compose.test.yml" || echo "docker-compose.dev.yml" )
@@ -39,7 +44,7 @@ webui: clean-webui
# Build the binary # Build the binary
binary: webui binary: webui
CGO_ENABLED=$(CGO_ENABLED) go build -ldflags "${LDFLAGS} \ CGO_ENABLED=$(CGO_ENABLED) go build -tags "${BUILD_TAGS}" -ldflags "${LDFLAGS} \
-X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \ -X github.com/tinyauthapp/tinyauth/internal/model.Version=${TAG_NAME} \
-X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \ -X github.com/tinyauthapp/tinyauth/internal/model.CommitHash=${COMMIT_HASH} \
-X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \ -X github.com/tinyauthapp/tinyauth/internal/model.BuildTimestamp=${BUILD_TIMESTAMP}" \
@@ -93,13 +98,22 @@ sql:
# Go gen # Go gen
generate: generate:
go run ./gen go generate ./...
go generate ./internal/repository/...
# Docker image # Docker image
docker: docker:
docker buildx build -t tinyauthapp/tinyauth:dev --build-arg=VERSION=$(TAG_NAME) --build-arg=COMMIT_HASH=$(COMMIT_HASH) --build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) -f Dockerfile . docker buildx build -t tinyauthapp/tinyauth:dev \
--build-arg=VERSION=$(TAG_NAME) \
--build-arg=COMMIT_HASH=$(COMMIT_HASH) \
--build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
--build-arg=BUILD_TAGS=$(BUILD_TAGS) \
-f Dockerfile .
# Docker image distroless # Docker image distroless
docker-distroless: docker-distroless:
docker buildx build -t tinyauthapp/tinyauth:dev-distroless --build-arg=VERSION=$(TAG_NAME) --build-arg=COMMIT_HASH=$(COMMIT_HASH) --build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) -f Dockerfile.distroless . docker buildx build -t tinyauthapp/tinyauth:dev-distroless \
--build-arg=VERSION=$(TAG_NAME) \
--build-arg=COMMIT_HASH=$(COMMIT_HASH) \
--build-arg=BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) \
--build-arg=BUILD_TAGS=$(BUILD_TAGS) \
-f Dockerfile.distroless .
+11 -5
View File
@@ -1,8 +1,8 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"strings"
"github.com/tinyauthapp/paerser/cli" "github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/model"
@@ -11,15 +11,21 @@ import (
func configCmd(tconfig *model.Config, loaders []cli.ResourceLoader) *cli.Command { func configCmd(tconfig *model.Config, loaders []cli.ResourceLoader) *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "config", Name: "config",
Description: "Print the configuration of Tinyauth", Description: "Dump the current configuration in YAML format, useful for debugging",
Configuration: tconfig, Configuration: tconfig,
Resources: loaders, Resources: loaders,
Run: func(_ []string) error { Run: func(_ []string) error {
jsonBytes, err := json.MarshalIndent(tconfig, "", " ") buf := strings.Builder{}
fmt.Fprint(&buf, "Your current configuration in YAML is:\n\n")
err := renderYamlToBuf(&buf, tconfig)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal configuration: %w", err) return fmt.Errorf("failed to render yaml config: %w", err)
} }
fmt.Println(string(jsonBytes))
fmt.Print(buf.String())
return nil return nil
}, },
} }
+64 -19
View File
@@ -7,8 +7,9 @@ import (
"strings" "strings"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/paerser/cli" "github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils"
) )
func createOidcClientCmd() *cli.Command { func createOidcClientCmd() *cli.Command {
@@ -31,40 +32,84 @@ func createOidcClientCmd() *cli.Command {
return errors.New("client name can only contain alphanumeric characters and hyphens") return errors.New("client name can only contain alphanumeric characters and hyphens")
} }
uuid := uuid.New() u := uuid.New()
clientId := uuid.String() clientId := u.String()
clientSecret := "ta-" + utils.GenerateString(61) clientSecret := "ta-" + utils.GenerateString(61)
uclientName := strings.ToUpper(clientName) uclientName := strings.ToUpper(clientName)
lclientName := strings.ToLower(clientName) lclientName := strings.ToLower(clientName)
builder := strings.Builder{} buf := strings.Builder{}
// header // header
fmt.Fprintf(&builder, "Created credentials for client %s\n\n", clientName) fmt.Fprintf(&buf, "Created '%s' OIDC client.\n\n", clientName)
// credentials // credentials
fmt.Fprintf(&builder, "Client Name: %s\n", clientName) fmt.Fprintf(&buf, "Credentials:\n\n")
fmt.Fprintf(&builder, "Client ID: %s\n", clientId) fmt.Fprintf(&buf, "Client Name: %s\n", clientName)
fmt.Fprintf(&builder, "Client Secret: %s\n\n", clientSecret) fmt.Fprintf(&buf, "Client ID: %s\n", clientId)
fmt.Fprintf(&buf, "Client Secret: %s\n\n", clientSecret)
// env variables // end variables
fmt.Fprint(&builder, "Environment variables:\n\n") fmt.Fprintf(&buf, "Environment variables:\n\n")
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTID=%s\n", uclientName, clientId) renderToBuf(&buf, []kv{
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET=%s\n", uclientName, clientSecret) {
fmt.Fprintf(&builder, "TINYAUTH_OIDC_CLIENTS_%s_NAME=%s\n\n", uclientName, utils.Capitalize(lclientName)) k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTID", uclientName),
v: clientId,
},
{
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_CLIENTSECRET", uclientName),
v: clientSecret,
},
{
k: fmt.Sprintf("TINYAUTH_OIDC_CLIENTS_%s_NAME", uclientName),
v: utils.Capitalize(lclientName),
},
}, "=")
fmt.Fprintf(&buf, "\n")
// cli flags // cli flags
fmt.Fprint(&builder, "CLI flags:\n\n") fmt.Fprintf(&buf, "CLI flags:\n\n")
fmt.Fprintf(&builder, "--oidc.clients.%s.clientid=%s\n", lclientName, clientId) renderToBuf(&buf, []kv{
fmt.Fprintf(&builder, "--oidc.clients.%s.clientsecret=%s\n", lclientName, clientSecret) {
fmt.Fprintf(&builder, "--oidc.clients.%s.name=%s\n\n", lclientName, utils.Capitalize(lclientName)) k: fmt.Sprintf("--oidc.clients.%s.clientid", lclientName),
v: clientId,
},
{
k: fmt.Sprintf("--oidc.clients.%s.clientsecret", lclientName),
v: clientSecret,
},
{
k: fmt.Sprintf("--oidc.clients.%s.name", lclientName),
v: utils.Capitalize(lclientName),
},
}, "=")
fmt.Fprintf(&buf, "\n")
// yaml config
fmt.Fprintf(&buf, "YAML config:\n\n")
err = renderYamlToBuf(&buf, &model.OIDCConfig{
Clients: map[string]model.OIDCClientConfig{
lclientName: {
ClientID: clientId,
ClientSecret: clientSecret,
Name: utils.Capitalize(lclientName),
},
},
})
if err != nil {
return fmt.Errorf("failed to render yaml config: %w", err)
}
buf.WriteString("\n")
// footer // footer
fmt.Fprintln(&builder, "You can use either option to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.") fmt.Fprintln(&buf, "You can use any of the above options to configure your OIDC client. Make sure to save these credentials as there is no way to regenerate them.")
// print // print
out := builder.String() out := buf.String()
fmt.Print(out) fmt.Print(out)
return nil return nil
}, },
+61 -15
View File
@@ -3,11 +3,12 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"strings" "strings"
"charm.land/huh/v2" "charm.land/huh/v2"
"github.com/tinyauthapp/paerser/cli" "github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/utils/logger" "github.com/tinyauthapp/tinyauth/internal/model"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -34,62 +35,107 @@ func createUserCmd() *cli.Command {
&cli.FlagLoader{}, &cli.FlagLoader{},
} }
return &cli.Command{ cmd := &cli.Command{
Name: "create", Name: "create",
Description: "Create a user", Description: "Create a user",
Configuration: tCfg, Configuration: tCfg,
Resources: loaders, Resources: loaders,
Run: func(_ []string) error { }
log := logger.NewLogger().WithSimpleConfig()
log.Init()
cmd.Run = func(_ []string) error {
if tCfg.Interactive { if tCfg.Interactive {
form := huh.NewForm( form := huh.NewForm(
huh.NewGroup( huh.NewGroup(
huh.NewInput().Title("Username").Value(&tCfg.Username).Validate((func(s string) error { huh.NewInput().Title("Username").Value(&tCfg.Username).Validate(func(s string) error {
if s == "" { if s == "" {
return errors.New("username cannot be empty") return errors.New("username cannot be empty")
} }
if strings.Contains(s, ":") {
return errors.New("username cannot contain ':'")
}
return nil return nil
})), }),
huh.NewInput().Title("Password").Value(&tCfg.Password).Validate((func(s string) error { huh.NewInput().Title("Password").Value(&tCfg.Password).Validate(func(s string) error {
if s == "" { if s == "" {
return errors.New("password cannot be empty") return errors.New("password cannot be empty")
} }
return nil return nil
})), }),
huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker), huh.NewSelect[bool]().Title("Format the output for Docker?").Options(huh.NewOption("Yes", true), huh.NewOption("No", false)).Value(&tCfg.Docker),
), ),
) )
theme := new(themeBase) theme := new(themeBase)
err := form.WithTheme(theme).Run()
err := form.WithTheme(theme).Run()
if err != nil { if err != nil {
return fmt.Errorf("failed to run interactive prompt: %w", err) return fmt.Errorf("failed to run interactive prompt: %w", err)
} }
} }
if tCfg.Username == "" || tCfg.Password == "" { if tCfg.Username == "" || tCfg.Password == "" {
cmd.PrintHelp(os.Stdout)
return errors.New("username and password cannot be empty") return errors.New("username and password cannot be empty")
} }
log.App.Info().Str("username", tCfg.Username).Msg("Creating user") if strings.Contains(tCfg.Username, ":") {
return errors.New("username cannot contain ':'")
}
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost) passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
if err != nil { if err != nil {
return fmt.Errorf("failed to hash password: %w", err) return fmt.Errorf("failed to hash password: %w", err)
} }
// If docker format is enabled, escape the dollar sign // Only the docker compose output needs $ escaped, the raw hash is correct everywhere else
passwdStr := string(passwd) passwdStr := string(passwd)
outputStr := passwdStr
if tCfg.Docker { if tCfg.Docker {
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$") outputStr = strings.ReplaceAll(passwdStr, "$", "$$")
} }
log.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created") user := fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)
escapedUser := fmt.Sprintf("%s:%s", tCfg.Username, outputStr)
return nil buf := strings.Builder{}
// header
fmt.Fprintf(&buf, "Created user '%s'.\n\n", tCfg.Username)
// environment variable
fmt.Fprint(&buf, "Environment variable:\n\n")
renderToBuf(&buf, []kv{
{"TINYAUTH_AUTH_USERS", escapedUser},
}, "=")
// cli flags
fmt.Fprint(&buf, "\nCLI flags:\n\n")
renderToBuf(&buf, []kv{
{"--auth.users", user},
}, "=")
// yaml config
fmt.Fprint(&buf, "\nYAML config:\n\n")
err = renderYamlToBuf(&buf, &model.Config{
Auth: model.AuthConfig{
Users: []string{user},
}, },
})
if err != nil {
return fmt.Errorf("failed to render yaml config: %w", err)
} }
buf.WriteString("\n")
// footer
fmt.Fprint(&buf, "Use your config option of choice to add the user to Tinyauth and then restart.")
fmt.Println(buf.String())
return nil
}
return cmd
} }
+22 -10
View File
@@ -7,7 +7,6 @@ import (
"strings" "strings"
"github.com/tinyauthapp/tinyauth/internal/utils" "github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"charm.land/huh/v2" "charm.land/huh/v2"
"github.com/mdp/qrterminal/v3" "github.com/mdp/qrterminal/v3"
@@ -34,14 +33,15 @@ func generateTotpCmd() *cli.Command {
&cli.FlagLoader{}, &cli.FlagLoader{},
} }
return &cli.Command{ cmd := &cli.Command{
Name: "generate", Name: "generate",
Description: "Generate a TOTP secret", Description: "Generate a TOTP secret",
Configuration: tCfg, Configuration: tCfg,
Resources: loaders, Resources: loaders,
Run: func(_ []string) error { }
log := logger.NewLogger().WithSimpleConfig()
log.Init() cmd.Run = func(_ []string) error {
colors := getColors()
if tCfg.Interactive { if tCfg.Interactive {
form := huh.NewForm( form := huh.NewForm(
@@ -63,6 +63,11 @@ func generateTotpCmd() *cli.Command {
} }
} }
if tCfg.User == "" {
cmd.PrintHelp(os.Stdout)
return fmt.Errorf("user is required")
}
user, err := utils.ParseUser(tCfg.User) user, err := utils.ParseUser(tCfg.User)
if err != nil { if err != nil {
@@ -89,9 +94,7 @@ func generateTotpCmd() *cli.Command {
secret := key.Secret() secret := key.Secret()
log.App.Info().Str("secret", secret).Msg("Generated TOTP secret") fmt.Printf("Scan the following QR code with your authenticator app (e.g., Google Authenticator, 2fauth, Microsoft Authenticator):\n\n")
log.App.Info().Msg("Generated QR code")
config := qrterminal.Config{ config := qrterminal.Config{
Level: qrterminal.L, Level: qrterminal.L,
@@ -110,9 +113,18 @@ func generateTotpCmd() *cli.Command {
user.Password = strings.ReplaceAll(user.Password, "$", "$$") user.Password = strings.ReplaceAll(user.Password, "$", "$$")
} }
log.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.") userStr := fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)
fmt.Print("\nOr add the following TOTP secret to your authenticator app: ")
fmt.Print(colors.green.Render(secret))
fmt.Print("\n\n")
fmt.Printf("Finally, add your user '%s' back to your configuration: ", user.Username)
fmt.Print(colors.green.Render(userStr))
fmt.Print("\n")
return nil return nil
},
} }
return cmd
} }
+140 -16
View File
@@ -2,13 +2,17 @@ package main
import ( import (
"fmt" "fmt"
"os"
"reflect"
"strings"
"charm.land/huh/v2" "charm.land/huh/v2"
"charm.land/lipgloss/v2"
"github.com/tinyauthapp/tinyauth/internal/bootstrap" "github.com/tinyauthapp/tinyauth/internal/bootstrap"
"github.com/tinyauthapp/tinyauth/internal/model" "github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/loaders" "github.com/tinyauthapp/tinyauth/internal/utils/loaders"
"gopkg.in/yaml.v3"
"github.com/rs/zerolog/log"
"github.com/tinyauthapp/paerser/cli" "github.com/tinyauthapp/paerser/cli"
) )
@@ -28,89 +32,114 @@ func main() {
Configuration: tConfig, Configuration: tConfig,
Resources: loaders, Resources: loaders,
Run: func(_ []string) error { Run: func(_ []string) error {
if !reflect.DeepEqual(model.NewDefaultConfiguration(env).Experimental, tConfig.Experimental) {
colors := getColors()
fmt.Println(colors.yellow.Render("⚠") + " Experimental features are enabled, use with caution. Experimental features may change with each release.")
}
return runCmd(*tConfig) return runCmd(*tConfig)
}, },
} }
cmdUser := &cli.Command{ cmdUser := &cli.Command{
Name: "user", Name: "user",
Description: "Manage Tinyauth users", Description: "Manage users",
} }
cmdTotp := &cli.Command{ cmdTotp := &cli.Command{
Name: "totp", Name: "totp",
Description: "Manage Tinyauth TOTP users", Description: "Manage TOTP users",
} }
cmdOidc := &cli.Command{ cmdOidc := &cli.Command{
Name: "oidc", Name: "oidc",
Description: "Manage Tinyauth OIDC clients", Description: "Manage OIDC clients",
} }
err := cmdTinyauth.AddCommand(versionCmd()) helpCmd := &cli.Command{
Name: "help",
Description: "Show the help message",
Run: func(_ []string) error {
return cmdTinyauth.PrintHelp(os.Stdout)
},
}
err := cmdTinyauth.AddCommand(helpCmd)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add version command") fatalf(err, "Failed to add help command")
}
err = cmdTinyauth.AddCommand(versionCmd())
if err != nil {
fatalf(err, "Failed to add version command")
} }
err = cmdTinyauth.AddCommand(configCmd(tConfig, loaders)) err = cmdTinyauth.AddCommand(configCmd(tConfig, loaders))
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add config command") fatalf(err, "Failed to add config command")
} }
err = cmdUser.AddCommand(verifyUserCmd()) err = cmdUser.AddCommand(verifyUserCmd())
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add verify command") fatalf(err, "Failed to add user verify command")
} }
err = cmdTinyauth.AddCommand(healthcheckCmd()) err = cmdTinyauth.AddCommand(healthcheckCmd())
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add healthcheck command") fatalf(err, "Failed to add healthcheck command")
} }
err = cmdTotp.AddCommand(generateTotpCmd()) err = cmdTotp.AddCommand(generateTotpCmd())
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add generate command") fatalf(err, "Failed to add totp generate command")
} }
err = cmdUser.AddCommand(createUserCmd()) err = cmdUser.AddCommand(createUserCmd())
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add create command") fatalf(err, "Failed to add create user command")
} }
err = cmdOidc.AddCommand(createOidcClientCmd()) err = cmdOidc.AddCommand(createOidcClientCmd())
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add create command") fatalf(err, "Failed to add create oidc client command")
} }
err = cmdTinyauth.AddCommand(cmdUser) err = cmdTinyauth.AddCommand(cmdUser)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add user command") fatalf(err, "Failed to add user command")
} }
err = cmdTinyauth.AddCommand(cmdTotp) err = cmdTinyauth.AddCommand(cmdTotp)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add totp command") fatalf(err, "Failed to add totp command")
} }
err = cmdTinyauth.AddCommand(cmdOidc) err = cmdTinyauth.AddCommand(cmdOidc)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to add oidc command") fatalf(err, "Failed to add oidc command")
} }
err = cli.Execute(cmdTinyauth) err = cli.Execute(cmdTinyauth)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to execute command") if strings.Contains(err.Error(), "command not found") {
fmt.Println("Command not found. Use 'tinyauth help' to see available commands.")
return
}
if strings.Contains(err.Error(), "is not runnable") {
return
}
fatalf(err, "Failed to execute command")
} }
} }
@@ -131,3 +160,98 @@ type themeBase struct{}
func (t *themeBase) Theme(isDark bool) *huh.Styles { func (t *themeBase) Theme(isDark bool) *huh.Styles {
return huh.ThemeBase(isDark) return huh.ThemeBase(isDark)
} }
type colors struct {
blue lipgloss.Style
gray lipgloss.Style
lightGray lipgloss.Style
green lipgloss.Style
yellow lipgloss.Style
}
func getColors() colors {
noColor := os.Getenv("NO_COLOR")
forceColor := os.Getenv("FORCE_COLOR")
colorOut := colors{
green: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(34)),
gray: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(245)),
yellow: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(214)),
blue: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(75)),
lightGray: lipgloss.NewStyle().Foreground(lipgloss.ANSIColor(250)),
}
noColorOut := colors{
green: lipgloss.NewStyle(),
gray: lipgloss.NewStyle(),
yellow: lipgloss.NewStyle(),
blue: lipgloss.NewStyle(),
lightGray: lipgloss.NewStyle(),
}
useColors := true
if noColor == "true" || noColor == "1" {
useColors = false
}
if forceColor == "true" || forceColor == "1" {
useColors = true
}
if !useColors {
return noColorOut
}
return colorOut
}
func fatalf(err error, msg string) {
fmt.Printf("%s: %v\n", msg, err)
os.Exit(1)
}
type kv struct {
k string
v string
}
func renderToBuf(buf *strings.Builder, kv []kv, sep string) {
colors := getColors()
for _, i := range kv {
buf.WriteString(colors.blue.Render(i.k))
buf.WriteString(colors.gray.Render(sep))
buf.WriteString(colors.lightGray.Render(i.v))
buf.WriteString("\n")
}
}
func renderYamlToBuf(buf *strings.Builder, i any) error {
colors := getColors()
yout, err := yaml.Marshal(i)
if err != nil {
return fmt.Errorf("failed to marshal yaml: %w", err)
}
for l := range strings.SplitSeq(string(yout), "\n") {
if l == "" {
continue
}
if strings.HasPrefix(strings.TrimLeft(l, " "), "- ") {
buf.WriteString(colors.lightGray.Render(l))
buf.WriteString("\n")
continue
}
lp := strings.SplitN(l, ":", 2)
buf.WriteString(colors.blue.Render(lp[0]))
buf.WriteString(colors.gray.Render(":"))
if len(lp) == 2 {
buf.WriteString(colors.lightGray.Render(lp[1]))
}
buf.WriteString("\n")
}
return nil
}
+16 -10
View File
@@ -3,9 +3,9 @@ package main
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"github.com/tinyauthapp/tinyauth/internal/utils" "github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
"charm.land/huh/v2" "charm.land/huh/v2"
"github.com/pquerna/otp/totp" "github.com/pquerna/otp/totp"
@@ -38,14 +38,15 @@ func verifyUserCmd() *cli.Command {
&cli.FlagLoader{}, &cli.FlagLoader{},
} }
return &cli.Command{ cmd := &cli.Command{
Name: "verify", Name: "verify",
Description: "Verify a user is set up correctly", Description: "Verify a user is set up correctly",
Configuration: tCfg, Configuration: tCfg,
Resources: loaders, Resources: loaders,
Run: func(_ []string) error { }
log := logger.NewLogger().WithSimpleConfig()
log.Init() cmd.Run = func(_ []string) error {
colors := getColors()
if tCfg.Interactive { if tCfg.Interactive {
form := huh.NewForm( form := huh.NewForm(
@@ -80,6 +81,11 @@ func verifyUserCmd() *cli.Command {
} }
} }
if tCfg.User == "" || tCfg.Username == "" || tCfg.Password == "" {
cmd.PrintHelp(os.Stdout)
return fmt.Errorf("user, username, and password are required")
}
user, err := utils.ParseUser(tCfg.User) user, err := utils.ParseUser(tCfg.User)
if err != nil { if err != nil {
@@ -98,21 +104,21 @@ func verifyUserCmd() *cli.Command {
if user.TOTPSecret == "" { if user.TOTPSecret == "" {
if tCfg.Totp != "" { if tCfg.Totp != "" {
log.App.Warn().Msg("User does not have TOTP secret") fmt.Println(colors.yellow.Render("⚠") + " TOTP code provided but user does not have TOTP enabled")
} }
log.App.Info().Msg("User verified") fmt.Println(colors.green.Render("✓") + " User verified")
return nil return nil
} }
ok := totp.Validate(tCfg.Totp, user.TOTPSecret) ok := totp.Validate(tCfg.Totp, user.TOTPSecret)
if !ok { if !ok {
return fmt.Errorf("TOTP code incorrect") return fmt.Errorf("TOTP code incorrect")
} }
log.App.Info().Msg("User verified") fmt.Println(colors.green.Render("✓") + " User verified")
return nil return nil
},
} }
return cmd
} }
+4 -3
View File
@@ -14,9 +14,10 @@ func versionCmd() *cli.Command {
Configuration: nil, Configuration: nil,
Resources: nil, Resources: nil,
Run: func(_ []string) error { Run: func(_ []string) error {
fmt.Printf("Version: %s\n", model.Version) colors := getColors()
fmt.Printf("Commit Hash: %s\n", model.CommitHash) fmt.Printf("Version: %s\n", colors.blue.Render(model.Version))
fmt.Printf("Build Timestamp: %s\n", model.BuildTimestamp) fmt.Printf("Commit Hash: %s\n", colors.blue.Render(model.CommitHash))
fmt.Printf("Build Timestamp: %s\n", colors.blue.Render(model.BuildTimestamp))
return nil return nil
}, },
} }
+25 -26
View File
@@ -12,50 +12,49 @@
}, },
"packageManager": "pnpm@11.1.2", "packageManager": "pnpm@11.1.2",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.4.0",
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.19",
"@radix-ui/react-label": "^2.1.8", "@radix-ui/react-label": "^2.1.11",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.3.2",
"@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-separator": "^1.1.11",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.3.0",
"@tailwindcss/vite": "^4.2.2", "@tailwindcss/vite": "^4.3.2",
"@tanstack/react-query": "^5.99.0", "@tanstack/react-query": "^5.101.2",
"axios": "^1.15.0", "axios": "^1.18.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"i18next": "^26.0.4", "i18next": "^26.3.4",
"i18next-browser-languagedetector": "^8.2.1", "i18next-browser-languagedetector": "^8.2.1",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"lucide-react": "^1.8.0", "lucide-react": "^1.23.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"radix-ui": "^1.4.3", "radix-ui": "^1.6.1",
"react": "^19.2.5", "react": "^19.2.7",
"react-dom": "^19.2.5", "react-dom": "^19.2.7",
"react-hook-form": "^7.72.1", "react-hook-form": "^7.81.0",
"react-i18next": "^17.0.2", "react-i18next": "^17.0.8",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router": "^7.14.0", "react-router": "^7.14.0",
"sonner": "^2.0.7", "sonner": "^2.0.7",
"tailwind-merge": "^3.5.0", "tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.2", "tailwindcss": "^4.3.2",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@tanstack/eslint-plugin-query": "^5.99.0", "@tanstack/eslint-plugin-query": "^5.101.2",
"@types/node": "^25.6.0", "@types/node": "^25.6.0",
"@types/react": "^19.2.14", "@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1", "@vitejs/plugin-react": "^6.0.3",
"eslint": "^10.2.0", "eslint": "^10.6.0",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2", "eslint-plugin-react-refresh": "^0.5.3",
"globals": "^17.5.0", "globals": "^17.7.0",
"prettier": "3.8.2",
"rollup-plugin-visualizer": "^7.0.1", "rollup-plugin-visualizer": "^7.0.1",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"typescript": "~6.0.2", "typescript": "~6.0.2",
"typescript-eslint": "^8.58.1", "typescript-eslint": "^8.62.1",
"vite": "^8.0.8" "vite": "^8.1.3"
} }
} }
+1289 -1377
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -1,4 +1,3 @@
dangerouslyAllowAllBuilds: false dangerouslyAllowAllBuilds: false
blockExoticSubdeps: true blockExoticSubdeps: true
minimumReleaseAge: 1440 # 1 day minimumReleaseAge: 1440 # 1 day
trustPolicy: no-downgrade
View File
@@ -1,4 +1,4 @@
// gen/sqlc-wrapper generates store.go wrapper files for each sqlc driver package under // gen/sqlc_wrapper generates store.go wrapper files for each sqlc driver package under
// internal/repository/<driver>/. Run via: // internal/repository/<driver>/. Run via:
// //
// go generate ./internal/repository/... // go generate ./internal/repository/...
@@ -32,7 +32,7 @@ import (
var storeSrc string var storeSrc string
func main() { func main() {
fmt.Println("sqlc-wrapper: generating store.go files for sqlc driver packages...") fmt.Println("sqlc_wrapper: generating store.go files for sqlc driver packages...")
if err := run(); err != nil { if err := run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }
+3
View File
@@ -0,0 +1,3 @@
package tinyauth
//go:generate go run github.com/tinyauthapp/tinyauth/gen/docs
+2 -2
View File
@@ -4,6 +4,7 @@ go 1.26.4
require ( require (
charm.land/huh/v2 v2.0.3 charm.land/huh/v2 v2.0.3
charm.land/lipgloss/v2 v2.0.1
github.com/cenkalti/backoff/v5 v5.0.3 github.com/cenkalti/backoff/v5 v5.0.3
github.com/docker/docker v28.5.2+incompatible github.com/docker/docker v28.5.2+incompatible
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
@@ -25,6 +26,7 @@ require (
golang.org/x/crypto v0.53.0 golang.org/x/crypto v0.53.0
golang.org/x/oauth2 v0.36.0 golang.org/x/oauth2 v0.36.0
golang.org/x/tools v0.47.0 golang.org/x/tools v0.47.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apimachinery v0.36.2 k8s.io/apimachinery v0.36.2
k8s.io/client-go v0.36.2 k8s.io/client-go v0.36.2
modernc.org/sqlite v1.53.0 modernc.org/sqlite v1.53.0
@@ -34,7 +36,6 @@ require (
require ( require (
charm.land/bubbles/v2 v2.0.0 // indirect charm.land/bubbles/v2 v2.0.0 // indirect
charm.land/bubbletea/v2 v2.0.2 // indirect charm.land/bubbletea/v2 v2.0.2 // indirect
charm.land/lipgloss/v2 v2.0.1 // indirect
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.2.0 // indirect filippo.io/edwards25519 v1.2.0 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect github.com/Azure/go-ntlmssp v0.1.1 // indirect
@@ -169,7 +170,6 @@ require (
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.2 // indirect gotest.tools/v3 v3.5.2 // indirect
gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect
k8s.io/klog/v2 v2.140.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect
+1 -1
View File
@@ -279,7 +279,7 @@ func (app *BootstrapApp) Setup() error {
app.runtime.ConfiguredProviders = configuredProviders app.runtime.ConfiguredProviders = configuredProviders
// if tailscale is enabled and listening, replace the app url with the tailscale hostname // if tailscale is enabled and listening, replace the app url with the tailscale hostname
if app.services.tailscaleService != nil && app.config.Tailscale.Listen { if app.services.tailscaleService != nil && app.config.Experimental.Tailscale.Listen {
tailscaleUrl := "https://" + app.services.tailscaleService.GetHostname() tailscaleUrl := "https://" + app.services.tailscaleService.GetHostname()
// if the tailscale url is different from the app url, replace it // if the tailscale url is different from the app url, replace it
+2 -3
View File
@@ -130,9 +130,9 @@ func (app *BootstrapApp) setupRouter() error {
// 2. Unix socket (if server.socketPath) // 2. Unix socket (if server.socketPath)
// 3. HTTP - default // 3. HTTP - default
func (app *BootstrapApp) getListenerFunc() (func(ctx context.Context) error, error) { func (app *BootstrapApp) getListenerFunc() (func(ctx context.Context) error, error) {
if app.config.Tailscale.Listen { if app.config.Experimental.Tailscale.Listen {
if app.services.tailscaleService == nil { if app.services.tailscaleService == nil {
return nil, fmt.Errorf("tailscale.listen is enabled but tailscale service is not initialized") return nil, fmt.Errorf("experimental.tailscale.listen is enabled but tailscale service is not initialized")
} }
return app.serveTailscale, nil return app.serveTailscale, nil
} }
@@ -227,7 +227,6 @@ func (app *BootstrapApp) serve(listener net.Listener, server *http.Server, ctx c
err := server.Serve(listener) err := server.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) { if err != nil && !errors.Is(err, http.ErrServerClosed) {
shutdown()
return fmt.Errorf("failed to start %s listener: %w", name, err) return fmt.Errorf("failed to start %s listener: %w", name, err)
} }
+1 -1
View File
@@ -739,7 +739,7 @@ func (controller *OIDCController) resolveRequestObject(c *gin.Context) (*service
func (controller *OIDCController) resolveNormalParams(c *gin.Context) (*service.AuthorizeRequest, error) { func (controller *OIDCController) resolveNormalParams(c *gin.Context) (*service.AuthorizeRequest, error) {
var req service.AuthorizeRequest var req service.AuthorizeRequest
bind := binding.Query var bind binding.Binding = binding.Query
if c.Request.Method == http.MethodPost { if c.Request.Method == http.MethodPost {
bind = binding.Form bind = binding.Form
+143 -141
View File
@@ -81,9 +81,11 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
PrivateKeyPath: "./tinyauth_oidc_key", PrivateKeyPath: "./tinyauth_oidc_key",
PublicKeyPath: "./tinyauth_oidc_key.pub", PublicKeyPath: "./tinyauth_oidc_key.pub",
}, },
Experimental: ExperimentalConfig{
Tailscale: TailscaleConfig{ Tailscale: TailscaleConfig{
Dir: "./tailscale_state", Dir: "./tailscale_state",
}, },
},
LabelProvider: "auto", LabelProvider: "auto",
} }
@@ -93,245 +95,245 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
cfg.Resources.Path = "/data/resources" cfg.Resources.Path = "/data/resources"
cfg.OIDC.PrivateKeyPath = "/data/oidc/key.pem" cfg.OIDC.PrivateKeyPath = "/data/oidc/key.pem"
cfg.OIDC.PublicKeyPath = "/data/oidc/key.pub" cfg.OIDC.PublicKeyPath = "/data/oidc/key.pub"
cfg.Tailscale.Dir = "/data/tailscale" cfg.Experimental.Tailscale.Dir = "/data/tailscale"
} }
return cfg return cfg
} }
type Config struct { type Config struct {
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"` AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl,omitempty"`
Database DatabaseConfig `description:"Database configuration." yaml:"database"` Database DatabaseConfig `description:"Database configuration." yaml:"database,omitempty"`
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"` Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics,omitempty"`
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"` Resources ResourcesConfig `description:"Resources configuration." yaml:"resources,omitempty"`
Server ServerConfig `description:"Server configuration." yaml:"server"` Server ServerConfig `description:"Server configuration." yaml:"server,omitempty"`
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"` Auth AuthConfig `description:"Authentication configuration." yaml:"auth,omitempty"`
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"` Apps map[string]App `description:"Application ACLs configuration." yaml:"apps,omitempty"`
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"` OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth,omitempty"`
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"` OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc,omitempty"`
UI UIConfig `description:"UI customization." yaml:"ui"` UI UIConfig `description:"UI customization." yaml:"ui,omitempty"`
LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap"` LDAP LDAPConfig `description:"LDAP configuration." yaml:"ldap,omitempty"`
// Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"` Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental,omitempty"`
LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider"` LabelProvider string `description:"Label provider to use for ACLs (auto, docker, kubernetes or none to disable). auto detects the environment." yaml:"labelProvider,omitempty"`
Log LogConfig `description:"Logging configuration." yaml:"log"` Log LogConfig `description:"Logging configuration." yaml:"log,omitempty"`
Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
ConfigFile string `description:"Path to config file." yaml:"-"` ConfigFile string `description:"Path to config file." yaml:"-"`
} }
type DatabaseConfig struct { type DatabaseConfig struct {
Driver string `description:"The database driver to use. Valid values: sqlite, postgres, memory." yaml:"driver"` Driver string `description:"The database driver to use. Valid values: sqlite, postgres, memory." yaml:"driver,omitempty"`
Path string `description:"The path to the SQLite database file, or connection URL when driver is postgres." yaml:"path"` Path string `description:"The path to the SQLite database file, or connection URL when driver is postgres." yaml:"path,omitempty"`
} }
type AnalyticsConfig struct { type AnalyticsConfig struct {
Enabled bool `description:"Enable periodic version information collection." yaml:"enabled"` Enabled bool `description:"Enable periodic version information collection." yaml:"enabled,omitempty"`
} }
type ResourcesConfig struct { type ResourcesConfig struct {
Enabled bool `description:"Enable the resources server." yaml:"enabled"` Enabled bool `description:"Enable the resources server." yaml:"enabled,omitempty"`
Path string `description:"The directory where resources are stored." yaml:"path"` Path string `description:"The directory where resources are stored." yaml:"path,omitempty"`
} }
type ServerConfig struct { type ServerConfig struct {
Port int `description:"The port on which the server listens." yaml:"port"` Port int `description:"The port on which the server listens." yaml:"port,omitempty"`
Address string `description:"The address on which the server listens." yaml:"address"` Address string `description:"The address on which the server listens." yaml:"address,omitempty"`
SocketPath string `description:"The path to the Unix socket." yaml:"socketPath"` SocketPath string `description:"The path to the Unix socket." yaml:"socketPath,omitempty"`
} }
type AuthConfig struct { type AuthConfig struct {
IP IPConfig `description:"IP whitelisting config options." yaml:"ip"` IP IPConfig `description:"IP whitelisting config options." yaml:"ip,omitempty"`
Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users"` Users []string `description:"Comma-separated list of users (username:hashed_password)." yaml:"users,omitempty"`
SubdomainsEnabled bool `description:"Enable subdomains support." yaml:"subdomainsEnabled"` SubdomainsEnabled bool `description:"Enable subdomains support." yaml:"subdomainsEnabled,omitempty"`
UserAttributes map[string]UserAttributes `description:"Map of per-user OIDC attributes (username -> attributes)." yaml:"userAttributes"` UserAttributes map[string]UserAttributes `description:"Map of per-user OIDC attributes (username -> attributes)." yaml:"userAttributes,omitempty"`
UsersFile string `description:"Path to the users file." yaml:"usersFile"` UsersFile string `description:"Path to the users file." yaml:"usersFile,omitempty"`
SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie"` SecureCookie bool `description:"Enable secure cookies." yaml:"secureCookie,omitempty"`
SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry"` SessionExpiry int `description:"Session expiry time in seconds." yaml:"sessionExpiry,omitempty"`
SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime"` SessionMaxLifetime int `description:"Maximum session lifetime in seconds." yaml:"sessionMaxLifetime,omitempty"`
LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout"` LoginTimeout int `description:"Login timeout in seconds." yaml:"loginTimeout,omitempty"`
LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries"` LoginMaxRetries int `description:"Maximum login retries." yaml:"loginMaxRetries,omitempty"`
LockdownEnabled bool `description:"Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically." yaml:"lockdownEnabled"` LockdownEnabled bool `description:"Enable lockdown mode after maximum login retries. Lockdown mode limit is calculated automatically." yaml:"lockdownEnabled,omitempty"`
TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies"` TrustedProxies []string `description:"Comma-separated list of trusted proxy addresses." yaml:"trustedProxies,omitempty"`
ACLs ACLsConfig `description:"ACLs configuration." yaml:"acls"` ACLs ACLsConfig `description:"ACLs configuration." yaml:"acls,omitempty"`
} }
type UserAttributes struct { type UserAttributes struct {
Name string `description:"Full name of the user." yaml:"name"` Name string `description:"Full name of the user." yaml:"name,omitempty"`
GivenName string `description:"Given (first) name of the user." yaml:"givenName"` GivenName string `description:"Given (first) name of the user." yaml:"givenName,omitempty"`
FamilyName string `description:"Family (last) name of the user." yaml:"familyName"` FamilyName string `description:"Family (last) name of the user." yaml:"familyName,omitempty"`
MiddleName string `description:"Middle name of the user." yaml:"middleName"` MiddleName string `description:"Middle name of the user." yaml:"middleName,omitempty"`
Nickname string `description:"Nickname of the user." yaml:"nickname"` Nickname string `description:"Nickname of the user." yaml:"nickname,omitempty"`
Profile string `description:"URL of the user's profile page." yaml:"profile"` Profile string `description:"URL of the user's profile page." yaml:"profile,omitempty"`
Picture string `description:"URL of the user's profile picture." yaml:"picture"` Picture string `description:"URL of the user's profile picture." yaml:"picture,omitempty"`
Website string `description:"URL of the user's website." yaml:"website"` Website string `description:"URL of the user's website." yaml:"website,omitempty"`
Email string `description:"Email address of the user." yaml:"email"` Email string `description:"Email address of the user." yaml:"email,omitempty"`
Gender string `description:"Gender of the user." yaml:"gender"` Gender string `description:"Gender of the user." yaml:"gender,omitempty"`
Birthdate string `description:"Birthdate of the user (YYYY-MM-DD)." yaml:"birthdate"` Birthdate string `description:"Birthdate of the user (YYYY-MM-DD)." yaml:"birthdate,omitempty"`
Zoneinfo string `description:"Time zone of the user (e.g. Europe/Athens)." yaml:"zoneinfo"` Zoneinfo string `description:"Time zone of the user (e.g. Europe/Athens)." yaml:"zoneinfo,omitempty"`
Locale string `description:"Locale of the user (e.g. en-US)." yaml:"locale"` Locale string `description:"Locale of the user (e.g. en-US)." yaml:"locale,omitempty"`
PhoneNumber string `description:"Phone number of the user." yaml:"phoneNumber"` PhoneNumber string `description:"Phone number of the user." yaml:"phoneNumber,omitempty"`
Address AddressClaim `description:"Address of the user." yaml:"address"` Address AddressClaim `description:"Address of the user." yaml:"address,omitempty"`
} }
type AddressClaim struct { type AddressClaim struct {
Formatted string `description:"Full mailing address, formatted for display." yaml:"formatted" json:"formatted,omitempty"` Formatted string `description:"Full mailing address, formatted for display." yaml:"formatted,omitempty" json:"formatted,omitempty"`
StreetAddress string `description:"Street address." yaml:"streetAddress" json:"street_address,omitempty"` StreetAddress string `description:"Street address." yaml:"streetAddress,omitempty" json:"street_address,omitempty"`
Locality string `description:"City or locality." yaml:"locality" json:"locality,omitempty"` Locality string `description:"City or locality." yaml:"locality,omitempty" json:"locality,omitempty"`
Region string `description:"State, province, or region." yaml:"region" json:"region,omitempty"` Region string `description:"State, province, or region." yaml:"region,omitempty" json:"region,omitempty"`
PostalCode string `description:"Zip or postal code." yaml:"postalCode" json:"postal_code,omitempty"` PostalCode string `description:"Zip or postal code." yaml:"postalCode,omitempty" json:"postal_code,omitempty"`
Country string `description:"Country." yaml:"country" json:"country,omitempty"` Country string `description:"Country." yaml:"country,omitempty" json:"country,omitempty"`
} }
type IPConfig struct { type IPConfig struct {
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"` Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow,omitempty"`
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"` Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block,omitempty"`
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass"` Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication entirely." yaml:"bypass,omitempty"`
} }
type OAuthConfig struct { type OAuthConfig struct {
Whitelist []string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist"` Whitelist []string `description:"Comma-separated list of allowed OAuth domains." yaml:"whitelist,omitempty"`
WhitelistFile string `description:"Path to the OAuth whitelist file." yaml:"whitelistFile"` WhitelistFile string `description:"Path to the OAuth whitelist file." yaml:"whitelistFile,omitempty"`
AutoRedirect string `description:"The OAuth provider to use for automatic redirection." yaml:"autoRedirect"` AutoRedirect string `description:"The OAuth provider to use for automatic redirection." yaml:"autoRedirect,omitempty"`
Providers map[string]OAuthServiceConfig `description:"OAuth providers configuration." yaml:"providers"` Providers map[string]OAuthServiceConfig `description:"OAuth providers configuration." yaml:"providers,omitempty"`
} }
type OIDCConfig struct { type OIDCConfig struct {
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath"` PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath,omitempty"`
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath"` PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath,omitempty"`
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients"` Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients,omitempty"`
} }
type UIConfig struct { type UIConfig struct {
Title string `description:"The title of the UI." yaml:"title"` Title string `description:"The title of the UI." yaml:"title,omitempty"`
ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage"` ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage,omitempty"`
BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage"` BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage,omitempty"`
WarningsEnabled bool `description:"Enable UI warnings." yaml:"warningsEnabled"` WarningsEnabled bool `description:"Enable UI warnings." yaml:"warningsEnabled,omitempty"`
} }
type LDAPConfig struct { type LDAPConfig struct {
Address string `description:"LDAP server address." yaml:"address"` Address string `description:"LDAP server address." yaml:"address,omitempty"`
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"` BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn,omitempty"`
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"` BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword,omitempty"`
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile"` BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile,omitempty"`
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"` BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn,omitempty"`
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"` Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure,omitempty"`
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"` SearchFilter string `description:"LDAP search filter." yaml:"searchFilter,omitempty"`
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"` AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert,omitempty"`
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"` AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey,omitempty"`
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL"` GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL,omitempty"`
} }
type LogConfig struct { type LogConfig struct {
Level string `description:"Log level (trace, debug, info, warn, error)." yaml:"level"` Level string `description:"Log level (trace, debug, info, warn, error)." yaml:"level,omitempty"`
Json bool `description:"Enable JSON formatted logs." yaml:"json"` Json bool `description:"Enable JSON formatted logs." yaml:"json,omitempty"`
Streams LogStreams `description:"Configuration for specific log streams." yaml:"streams"` Streams LogStreams `description:"Configuration for specific log streams." yaml:"streams,omitempty"`
} }
type LogStreams struct { type LogStreams struct {
HTTP LogStreamConfig `description:"HTTP request logging." yaml:"http"` HTTP LogStreamConfig `description:"HTTP request logging." yaml:"http,omitempty"`
App LogStreamConfig `description:"Application logging." yaml:"app"` App LogStreamConfig `description:"Application logging." yaml:"app,omitempty"`
Audit LogStreamConfig `description:"Audit logging." yaml:"audit"` Audit LogStreamConfig `description:"Audit logging." yaml:"audit,omitempty"`
} }
type LogStreamConfig struct { type LogStreamConfig struct {
Enabled bool `description:"Enable this log stream." yaml:"enabled"` Enabled bool `description:"Enable this log stream." yaml:"enabled,omitempty"`
Level string `description:"Log level for this stream. Use global if empty." yaml:"level"` Level string `description:"Log level for this stream. Use global if empty." yaml:"level,omitempty"`
} }
// no experimental features type ExperimentalConfig struct {
type ExperimentalConfig struct{} Tailscale TailscaleConfig `description:"Tailscale configuration." yaml:"tailscale"`
}
type TailscaleConfig struct { type TailscaleConfig struct {
Enabled bool `description:"Enable Tailscale integration." yaml:"enabled"` Enabled bool `description:"Enable Tailscale integration." yaml:"enabled,omitempty"`
Dir string `description:"Tailscale state directory." yaml:"dir"` Dir string `description:"Tailscale state directory." yaml:"dir,omitempty"`
Hostname string `description:"Tailscale hostname." yaml:"hostname"` Hostname string `description:"Tailscale hostname." yaml:"hostname,omitempty"`
AuthKey string `description:"Tailscale auth key." yaml:"authKey"` AuthKey string `description:"Tailscale auth key." yaml:"authKey,omitempty"`
Ephemeral bool `description:"Use ephemeral Tailscale node." yaml:"ephemeral"` Ephemeral bool `description:"Use ephemeral Tailscale node." yaml:"ephemeral,omitempty"`
Funnel bool `description:"Enable Tailscale Funnel." yaml:"funnel"` Funnel bool `description:"Enable Tailscale Funnel." yaml:"funnel,omitempty"`
Listen bool `description:"Listen on the Tailscale address instead of standard address." yaml:"listen"` Listen bool `description:"Listen on the Tailscale address instead of standard address." yaml:"listen,omitempty"`
} }
// OAuth/OIDC config // OAuth/OIDC config
type OAuthServiceConfig struct { type OAuthServiceConfig struct {
ClientID string `description:"OAuth client ID." yaml:"clientId"` ClientID string `description:"OAuth client ID." yaml:"clientId,omitempty"`
ClientSecret string `description:"OAuth client secret." yaml:"clientSecret"` ClientSecret string `description:"OAuth client secret." yaml:"clientSecret,omitempty"`
ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile"` ClientSecretFile string `description:"Path to the file containing the OAuth client secret." yaml:"clientSecretFile,omitempty"`
Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist"` Whitelist []string `description:"Comma-separated list of allowed OAuth domains for this provider." yaml:"whitelist,omitempty"`
WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile"` WhitelistFile string `description:"Path to the OAuth whitelist file for this provider." yaml:"whitelistFile,omitempty"`
Scopes []string `description:"OAuth scopes." yaml:"scopes"` Scopes []string `description:"OAuth scopes." yaml:"scopes,omitempty"`
RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl"` RedirectURL string `description:"OAuth redirect URL." yaml:"redirectUrl,omitempty"`
AuthURL string `description:"OAuth authorization URL." yaml:"authUrl"` AuthURL string `description:"OAuth authorization URL." yaml:"authUrl,omitempty"`
TokenURL string `description:"OAuth token URL." yaml:"tokenUrl"` TokenURL string `description:"OAuth token URL." yaml:"tokenUrl,omitempty"`
UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl"` UserinfoURL string `description:"OAuth userinfo URL." yaml:"userinfoUrl,omitempty"`
Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure"` Insecure bool `description:"Allow insecure OAuth connections." yaml:"insecure,omitempty"`
Name string `description:"Provider name in UI." yaml:"name"` Name string `description:"Provider name in UI." yaml:"name,omitempty"`
} }
type OIDCClientConfig struct { type OIDCClientConfig struct {
ID string `description:"OIDC client ID." yaml:"-"` ID string `description:"OIDC client ID." yaml:"-"`
ClientID string `description:"OIDC client ID." yaml:"clientId"` ClientID string `description:"OIDC client ID." yaml:"clientId,omitempty"`
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"` ClientSecret string `description:"OIDC client secret." yaml:"clientSecret,omitempty"`
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"` ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile,omitempty"`
TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris"` TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris,omitempty"`
Name string `description:"Client name in UI." yaml:"name"` Name string `description:"Client name in UI." yaml:"name,omitempty"`
} }
type ACLsConfig struct { type ACLsConfig struct {
Policy string `description:"ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow." yaml:"policy"` Policy string `description:"ACL policy for allow-by-default or deny-by-default, available options are allow and deny, default is allow." yaml:"policy,omitempty"`
} }
// ACLs // ACLs
type Apps struct { type Apps struct {
Apps map[string]App `description:"App ACLs configuration." yaml:"apps"` Apps map[string]App `description:"App ACLs configuration." yaml:"apps,omitempty"`
} }
type App struct { type App struct {
Config AppConfig `description:"App configuration." yaml:"config"` Config AppConfig `description:"App configuration." yaml:"config,omitempty"`
Users AppUsers `description:"User access configuration." yaml:"users"` Users AppUsers `description:"User access configuration." yaml:"users,omitempty"`
OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth"` OAuth AppOAuth `description:"OAuth access configuration." yaml:"oauth,omitempty"`
IP AppIP `description:"IP access configuration." yaml:"ip"` IP AppIP `description:"IP access configuration." yaml:"ip,omitempty"`
Response AppResponse `description:"Response customization." yaml:"response"` Response AppResponse `description:"Response customization." yaml:"response,omitempty"`
Path AppPath `description:"Path access configuration." yaml:"path"` Path AppPath `description:"Path access configuration." yaml:"path,omitempty"`
LDAP AppLDAP `description:"LDAP access configuration." yaml:"ldap"` LDAP AppLDAP `description:"LDAP access configuration." yaml:"ldap,omitempty"`
} }
type AppConfig struct { type AppConfig struct {
Domain string `description:"The domain of the app." yaml:"domain"` Domain string `description:"The domain of the app." yaml:"domain,omitempty"`
} }
type AppUsers struct { type AppUsers struct {
Allow string `description:"Comma-separated list of allowed users." yaml:"allow"` Allow string `description:"Comma-separated list of allowed users." yaml:"allow,omitempty"`
Block string `description:"Comma-separated list of blocked users." yaml:"block"` Block string `description:"Comma-separated list of blocked users." yaml:"block,omitempty"`
} }
type AppOAuth struct { type AppOAuth struct {
Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist"` Whitelist string `description:"Comma-separated list of allowed OAuth groups." yaml:"whitelist,omitempty"`
Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"` Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups,omitempty"`
} }
type AppLDAP struct { type AppLDAP struct {
Groups string `description:"Comma-separated list of required LDAP groups." yaml:"groups"` Groups string `description:"Comma-separated list of required LDAP groups." yaml:"groups,omitempty"`
} }
type AppIP struct { type AppIP struct {
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"` Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow,omitempty"`
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"` Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block,omitempty"`
Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass"` Bypass []string `description:"List of IPs or CIDR ranges that bypass authentication." yaml:"bypass,omitempty"`
} }
type AppResponse struct { type AppResponse struct {
Headers []string `description:"Custom headers to add to the response." yaml:"headers"` Headers []string `description:"Custom headers to add to the response." yaml:"headers,omitempty"`
BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth"` BasicAuth AppBasicAuth `description:"Basic authentication for the app." yaml:"basicAuth,omitempty"`
} }
type AppBasicAuth struct { type AppBasicAuth struct {
Username string `description:"Basic auth username." yaml:"username"` Username string `description:"Basic auth username." yaml:"username,omitempty"`
Password string `description:"Basic auth password." yaml:"password"` Password string `description:"Basic auth password." yaml:"password,omitempty"`
PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile"` PasswordFile string `description:"Path to the file containing the basic auth password." yaml:"passwordFile,omitempty"`
} }
type AppPath struct { type AppPath struct {
Allow string `description:"Comma-separated list of allowed paths." yaml:"allow"` Allow string `description:"Comma-separated list of allowed paths." yaml:"allow,omitempty"`
Block string `description:"Comma-separated list of blocked paths." yaml:"block"` Block string `description:"Comma-separated list of blocked paths." yaml:"block,omitempty"`
} }
+1 -1
View File
@@ -1,3 +1,3 @@
package postgres package postgres
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc-wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/postgres //go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc_wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/postgres
+1 -1
View File
@@ -1,3 +1,3 @@
package sqlite package sqlite
//go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc-wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/sqlite //go:generate go run github.com/tinyauthapp/tinyauth/gen/sqlc_wrapper -pkg github.com/tinyauthapp/tinyauth/internal/repository/sqlite
+7 -7
View File
@@ -45,17 +45,17 @@ type TailscaleServiceInput struct {
} }
func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) { func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
if !i.Config.Tailscale.Enabled { if !i.Config.Experimental.Tailscale.Enabled {
return nil, nil return nil, nil
} }
srv := new(tsnet.Server) srv := new(tsnet.Server)
// node options // node options
srv.Dir = i.Config.Tailscale.Dir srv.Dir = i.Config.Experimental.Tailscale.Dir
srv.Hostname = i.Config.Tailscale.Hostname srv.Hostname = i.Config.Experimental.Tailscale.Hostname
srv.AuthKey = i.Config.Tailscale.AuthKey srv.AuthKey = i.Config.Experimental.Tailscale.AuthKey
srv.Ephemeral = i.Config.Tailscale.Ephemeral srv.Ephemeral = i.Config.Experimental.Tailscale.Ephemeral
// redirect logs to zerolog // redirect logs to zerolog
srv.Logf = i.Log.App.Printf srv.Logf = i.Log.App.Printf
@@ -94,7 +94,7 @@ func NewTailscaleService(i TailscaleServiceInput) (*TailscaleService, error) {
i.Ding.Go(service.watchAndClose, ding.RingMajor) i.Ding.Go(service.watchAndClose, ding.RingMajor)
if i.Config.Tailscale.Funnel && !i.Config.Tailscale.Listen { if i.Config.Experimental.Tailscale.Funnel && !i.Config.Experimental.Tailscale.Listen {
service.log.App.Warn().Msg("Tailscale Funnel is enabled but listen is disabled. Funnel will not work without listen enabled.") service.log.App.Warn().Msg("Tailscale Funnel is enabled but listen is disabled. Funnel will not work without listen enabled.")
} }
@@ -153,7 +153,7 @@ func (ts *TailscaleService) CreateListener() (net.Listener, error) {
return *ts.ln, nil return *ts.ln, nil
} }
if ts.config.Tailscale.Funnel { if ts.config.Experimental.Tailscale.Funnel {
ln, err := ts.srv.ListenFunnel("tcp", ":443") ln, err := ts.srv.ListenFunnel("tcp", ":443")
if err != nil { if err != nil {
return nil, err return nil, err