Просмотр исходного кода

Allow any git instance (not just GitHub)

Tim Jones 3 дней назад
Родитель
Сommit
c403592eac

+ 45 - 15
RackPeek.Domain/Git/LibGit2GitRepository.cs

@@ -7,7 +7,12 @@ public interface IGitCredentialsProvider {
     CredentialsHandler GetHandler();
 }
 
-public sealed class GitHubTokenCredentialsProvider(string username, string token) : IGitCredentialsProvider {
+/// <summary>
+/// HTTP Basic auth using a personal access token as the password. Works with
+/// any forge that accepts a token over HTTPS — GitHub, Gitea, GitLab, Bitbucket,
+/// Forgejo, etc.
+/// </summary>
+public sealed class TokenCredentialsProvider(string username, string token) : IGitCredentialsProvider {
     private readonly string _username = username ?? throw new ArgumentNullException(nameof(username));
     private readonly string _token = token ?? throw new ArgumentNullException(nameof(token));
 
@@ -19,22 +24,47 @@ public sealed class GitHubTokenCredentialsProvider(string username, string token
     }
 }
 
-public sealed class LibGit2GitRepository(
-    string configDirectory,
-    IGitCredentialsProvider credentialsProvider) : IGitRepository {
-    private readonly CredentialsHandler _credentials = credentialsProvider.GetHandler();
+public sealed class LibGit2GitRepository : IGitRepository {
+    private readonly string _configDirectory;
+    private readonly CredentialsHandler _credentials;
+    private readonly CertificateCheckHandler? _certificateCheck;
+
+    public LibGit2GitRepository(
+        string configDirectory,
+        IGitCredentialsProvider credentialsProvider,
+        bool insecureTls = false) {
+        _configDirectory = configDirectory;
+        _credentials = credentialsProvider.GetHandler();
+        _isAvailable = Repository.IsValid(configDirectory);
+        // When insecureTls is true, accept any TLS certificate. Required for
+        // self-hosted forges (Gitea, GitLab) behind a private CA or self-signed
+        // cert. Public hosts already ship trusted certs; leave it off for them.
+        _certificateCheck = insecureTls
+            ? (_, _, _) => true
+            : null;
+    }
+
+    private FetchOptions BuildFetchOptions() => new() {
+        CredentialsProvider = _credentials,
+        CertificateCheck = _certificateCheck
+    };
+
+    private PushOptions BuildPushOptions() => new() {
+        CredentialsProvider = _credentials,
+        CertificateCheck = _certificateCheck
+    };
 
-    private bool _isAvailable = Repository.IsValid(configDirectory);
+    private bool _isAvailable;
 
     public bool IsAvailable => _isAvailable;
 
     public void Init() {
-        Repository.Init(configDirectory);
+        Repository.Init(_configDirectory);
 
         _isAvailable = true;
     }
 
-    private Repository OpenRepo() => new(configDirectory);
+    private Repository OpenRepo() => new(_configDirectory);
 
     private static Signature GetSignature(Repository repo) {
         var name = repo.Config.Get<string>("user.name")?.Value ?? "RackPeek";
@@ -156,7 +186,7 @@ public sealed class LibGit2GitRepository(
             repo,
             remote.Name,
             remote.FetchRefSpecs.Select(r => r.Specification),
-            new FetchOptions { CredentialsProvider = _credentials },
+            BuildFetchOptions(),
             null);
 
         // If the repo has no commits yet (unborn branch)
@@ -188,7 +218,7 @@ public sealed class LibGit2GitRepository(
             repo.Network.Push(
                 remote,
                 refSpec,
-                new PushOptions { CredentialsProvider = _credentials });
+                BuildPushOptions());
         }
         catch (NonFastForwardException) {
             PullInternal(repo);
@@ -196,7 +226,7 @@ public sealed class LibGit2GitRepository(
             repo.Network.Push(
                 remote,
                 refSpec,
-                new PushOptions { CredentialsProvider = _credentials });
+                BuildPushOptions());
         }
 
         if (repo.Head.TrackedBranch is null) {
@@ -220,7 +250,7 @@ public sealed class LibGit2GitRepository(
             repo,
             remote.Name,
             remote.FetchRefSpecs.Select(r => r.Specification),
-            new FetchOptions { CredentialsProvider = _credentials },
+            BuildFetchOptions(),
             null);
 
         Branch? remoteBranch = repo.Branches[$"{remote.Name}/{repo.Head.FriendlyName}"];
@@ -249,7 +279,7 @@ public sealed class LibGit2GitRepository(
             repo,
             remote.Name,
             remote.FetchRefSpecs.Select(r => r.Specification),
-            new FetchOptions { CredentialsProvider = _credentials },
+            BuildFetchOptions(),
             null);
 
         // detect if remote has a default branch
@@ -288,7 +318,7 @@ public sealed class LibGit2GitRepository(
                 repo.Network.Push(
                     remote,
                     $"refs/heads/{importBranchName}:refs/heads/{importBranchName}",
-                    new PushOptions { CredentialsProvider = _credentials });
+                    BuildPushOptions());
 
                 repo.Branches.Update(importBranch,
                     b => b.TrackedBranch = $"refs/remotes/{remote.Name}/{importBranchName}");
@@ -316,7 +346,7 @@ public sealed class LibGit2GitRepository(
             repo.Network.Push(
                 remote,
                 $"refs/heads/{branchName}:refs/heads/{branchName}",
-                new PushOptions { CredentialsProvider = _credentials });
+                BuildPushOptions());
 
             repo.Branches.Update(branch,
                 b => b.TrackedBranch = $"refs/remotes/{remote.Name}/{branchName}");

+ 4 - 2
RackPeek.Domain/ServiceCollectionExtensions.cs

@@ -30,13 +30,15 @@ public static class ServiceCollectionExtensions {
         var gitToken = config["GIT_TOKEN"];
         if (!string.IsNullOrEmpty(gitToken) && !string.IsNullOrWhiteSpace(yamlPath)) {
             var gitUsername = config["GIT_USERNAME"] ?? "git";
+            var insecureTls = string.Equals(
+                config["GIT_INSECURE_TLS"], "true", StringComparison.OrdinalIgnoreCase);
 
             services.AddSingleton<IGitCredentialsProvider>(
-                _ => new GitHubTokenCredentialsProvider(gitUsername, gitToken));
+                _ => new TokenCredentialsProvider(gitUsername, gitToken));
 
             services.AddSingleton<IGitRepository>(sp => {
                 IGitCredentialsProvider creds = sp.GetRequiredService<IGitCredentialsProvider>();
-                return new LibGit2GitRepository(yamlPath, creds);
+                return new LibGit2GitRepository(yamlPath, creds, insecureTls);
             });
             RpkConstants.HasGitServices = true;
         }

+ 1 - 1
Shared.Rcl/Layout/GitStatusIndicator.razor

@@ -51,7 +51,7 @@
         {
             <input type="text"
                    class="px-2 py-1 text-xs rounded bg-zinc-800 border border-zinc-700 text-zinc-200 w-56"
-                   placeholder="https://github.com/user/repo.git"
+                   placeholder="https://your-git-host/user/repo.git"
                    @bind="_remoteUrl"
                    @bind:event="oninput" />
 

+ 57 - 21
Shared.Rcl/wwwroot/raw_docs/git-integration.md

@@ -1,13 +1,41 @@
 # Git Integration Guide
 
-RackPeek can automatically save and sync its configuration using Git.
-To enable this you need a GitHub Personal Access Token with permission to push to the repository that will store your config.
+RackPeek can automatically save and sync its configuration with any Git host
+that accepts a personal access token over HTTPS — GitHub, Gitea, GitLab,
+Bitbucket, Forgejo, self-hosted, etc. RackPeek does not use any host-specific
+APIs; the integration is plain `git` over HTTPS.
 
-Create a fine-grained access token on GitHub. Select the repository that will contain your RackPeek config and grant **Contents: Read and Write** access. Copy the token when it is created.
+## What you need
 
-Provide the token to the container using the `GIT_TOKEN` environment variable. You should also provide your GitHub username with `GIT_USERNAME`.
+1. **A repository** on your chosen Git host, dedicated to storing the
+   RackPeek configuration. It can start empty.
+2. **A personal access token** with permission to push to that repository.
+3. **The username** the token authenticates as.
 
-Example using Docker Compose:
+### Where to create a token
+
+* **GitHub** — Settings → Developer settings → Personal access tokens →
+  Fine-grained tokens. Select the target repository and grant
+  **Contents: Read and Write**.
+* **Gitea / Forgejo** — Settings → Applications → Generate New Token.
+  Scope: `write:repository`.
+* **GitLab** — User Settings → Access Tokens. Scope: `write_repository`.
+* **Bitbucket** — Personal settings → App passwords → Create app password
+  with `Repositories: Read, Write`.
+
+Copy the token immediately — most hosts only show it once.
+
+## Configuring RackPeek
+
+Provide the token and username to the container via environment variables:
+
+| Variable | Required | Description |
+|---|---|---|
+| `GIT_TOKEN` | Yes | Personal access token (used as the password for HTTP Basic auth). |
+| `GIT_USERNAME` | Yes | The username the token belongs to. |
+| `GIT_INSECURE_TLS` | No | Set to `true` to skip TLS certificate validation — useful when pointing at a self-hosted Gitea / GitLab behind a private CA or self-signed certificate. Leave unset for any public host. |
+
+Example with Docker Compose, pointing at a self-hosted Gitea:
 
 ```yaml
 version: "3.9"
@@ -22,14 +50,17 @@ services:
       - rackpeek-config:/app/config
     environment:
       - GIT_TOKEN=your_token_here
-      - GIT_USERNAME=your_github_username
+      - GIT_USERNAME=your_username
+      # Uncomment the next line only if your Git host uses a private CA
+      # or a self-signed certificate (common for home-lab Gitea instances):
+      # - GIT_INSECURE_TLS=true
     restart: unless-stopped
 
 volumes:
   rackpeek-config:
 ```
 
-Example using the Docker CLI:
+Example with the Docker CLI:
 
 ```bash
 docker run -d \
@@ -37,22 +68,27 @@ docker run -d \
   -p 8080:8080 \
   -v rackpeek-config:/app/config \
   -e GIT_TOKEN=your_token_here \
-  -e GIT_USERNAME=your_github_username \
+  -e GIT_USERNAME=your_username \
   aptacode/rackpeek:latest
 ```
 
-Or with health check:
+## Wiring up the remote
 
-```bash
-docker run -d \
-  --name rackpeek \
-  -p 8080:8080 \
-  -v rackpeek-config:/app/config \
-  --health-cmd="curl -s http://localhost:8080 | grep -q 'rackpeek'" \
-  --health-interval=30s \
-  --health-timeout=10s \
-  --health-retries=3 \
-  aptacode/rackpeek:latest
-```
+Open RackPeek in the browser. With `GIT_TOKEN` set, a Git status indicator
+appears in the header. Enable Git when prompted, then enter the repository
+remote URL — for example:
+
+* `https://github.com/youruser/rackpeek-config.git`
+* `https://gitea.example.com/youruser/rackpeek-config.git`
+* `https://gitlab.example.com/youruser/rackpeek-config.git`
+
+RackPeek will commit and sync configuration changes from there on.
+
+## Security notes
 
-Open RackPeek in the browser, enable Git when prompted, then add the repository remote URL. RackPeek will commit and sync configuration changes automatically.
+* The token is read from the environment at container start; it is not
+  persisted in the YAML config.
+* `GIT_INSECURE_TLS=true` disables certificate validation on push, pull,
+  and fetch. Only set it on networks you trust. Public hosts (github.com,
+  gitlab.com, gitea.com, …) ship correct, trusted certificates — you do
+  not need this flag for them.