Skip to content

Commit c2bbb6b

Browse files
fix(database): ensure git uses proper credentials (#403)
1 parent a67430e commit c2bbb6b

File tree

2 files changed

+90
-35
lines changed

2 files changed

+90
-35
lines changed

README.md

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,36 +37,39 @@ platforms such as GitHub discussions/issues might be added in the future.
3737
:exclamation: if using Docker these can be arguments.
3838
:warning: Never publicly expose your tokens, secrets, or ids.
3939

40-
| variable | required | default | description |
41-
|----------------------------------|----------|------------------------------------------------------|-------------------------------------------------------------------------|
42-
| DAILY_TASKS | False | `true` | Daily tasks on or off. |
43-
| DAILY_RELEASES | False | `true` | Send a message for each game released on this day in history. |
44-
| DAILY_CHANNEL_ID | False | `None` | Required if daily_tasks is enabled. |
45-
| DAILY_TASKS_UTC_HOUR | False | `12` | The hour to run daily tasks. |
46-
| DATA_REPO | False | `https://github.com/LizardByte/support-bot-data` | Repository to store persistent data. This repository should be private! |
47-
| DATA_REPO_BRANCH | False | `master` | Branch to store persistent data. |
48-
| DISCORD_BOT_TOKEN | True | `None` | Token from Bot page on discord developer portal. |
49-
| DISCORD_CLIENT_ID | True | `None` | Discord OAuth2 client id. |
50-
| DISCORD_CLIENT_SECRET | True | `None` | Discord OAuth2 client secret. |
51-
| DISCORD_GITHUB_STATUS_CHANNEL_ID | True | `None` | Channel ID to send GitHub status updates to. |
52-
| DISCORD_REDDIT_CHANNEL_ID | True | `None` | Channel ID to send Reddit post updates to. |
53-
| DISCORD_REDIRECT_URI | False | `https://localhost:8080/discord/callback` | The redirect uri for OAuth2. Must be publicly accessible. |
54-
| DISCORD_SPONSORS_CHANNEL_ID | True | `None` | Channel ID to send sponsorship updates to. |
55-
| GITHUB_CLIENT_ID | True | `None` | GitHub OAuth2 client id. |
56-
| GITHUB_CLIENT_SECRET | True | `None` | GitHub OAuth2 client secret. |
57-
| GITHUB_REDIRECT_URI | False | `https://localhost:8080/github/callback` | The redirect uri for OAuth2. Must be publicly accessible. |
58-
| GITHUB_TOKEN | True | `None` | GitHub personal access token. Must have `read:org` |
59-
| GITHUB_WEBHOOK_SECRET_KEY | True | `None` | A secret value to ensure webhooks are from trusted sources. |
60-
| GRAVATAR_EMAIL | False | `None` | Gravatar email address for bot avatar. |
61-
| IGDB_CLIENT_ID | False | `None` | Required if daily_releases is enabled. |
62-
| IGDB_CLIENT_SECRET | False | `None` | Required if daily_releases is enabled. |
63-
| PRAW_CLIENT_ID | True | `None` | `client_id` from reddit app setup page. |
64-
| PRAW_CLIENT_SECRET | True | `None` | `client_secret` from reddit app setup page. |
65-
| PRAW_SUBREDDIT | True | `None` | Subreddit to monitor (reddit user should be moderator of the subreddit) |
66-
| REDDIT_USERNAME | True | `None` | Reddit username |
67-
| REDDIT_PASSWORD | True | `None` | Reddit password |
68-
| SUPPORT_COMMANDS_REPO | False | `https://github.com/LizardByte/support-bot-commands` | Repository for support commands. |
69-
| SUPPORT_COMMANDS_BRANCH | False | `master` | Branch for support commands. |
40+
| variable | required | default | description |
41+
|----------------------------------|----------|------------------------------------------------------|--------------------------------------------------------------------------------------------|
42+
| DAILY_TASKS | False | `true` | Daily tasks on or off. |
43+
| DAILY_RELEASES | False | `true` | Send a message for each game released on this day in history. |
44+
| DAILY_CHANNEL_ID | False | `None` | Required if daily_tasks is enabled. |
45+
| DAILY_TASKS_UTC_HOUR | False | `12` | The hour to run daily tasks. |
46+
| DATA_REPO | False | `https://github.com/LizardByte/support-bot-data` | Repository to store persistent data. This repository should be private! |
47+
| DATA_REPO_BRANCH | False | `master` | Branch to store persistent data. |
48+
| DISCORD_BOT_TOKEN | True | `None` | Token from Bot page on discord developer portal. |
49+
| DISCORD_CLIENT_ID | True | `None` | Discord OAuth2 client id. |
50+
| DISCORD_CLIENT_SECRET | True | `None` | Discord OAuth2 client secret. |
51+
| DISCORD_GITHUB_STATUS_CHANNEL_ID | True | `None` | Channel ID to send GitHub status updates to. |
52+
| DISCORD_REDDIT_CHANNEL_ID | True | `None` | Channel ID to send Reddit post updates to. |
53+
| DISCORD_REDIRECT_URI | False | `https://localhost:8080/discord/callback` | The redirect uri for OAuth2. Must be publicly accessible. |
54+
| DISCORD_SPONSORS_CHANNEL_ID | True | `None` | Channel ID to send sponsorship updates to. |
55+
| GIT_USER_EMAIL | True | `None` | Email address for git commits. |
56+
| GIT_USER_NAME | True | `None` | Username for git commits. |
57+
| GIT_TOKEN | True | `None` | GitHub personal access token. Must have `repo` write access. Falls back to `GITHUB_TOKEN`. |
58+
| GITHUB_CLIENT_ID | True | `None` | GitHub OAuth2 client id. |
59+
| GITHUB_CLIENT_SECRET | True | `None` | GitHub OAuth2 client secret. |
60+
| GITHUB_REDIRECT_URI | False | `https://localhost:8080/github/callback` | The redirect uri for OAuth2. Must be publicly accessible. |
61+
| GITHUB_TOKEN | True | `None` | GitHub personal access token. Must have `read:org` |
62+
| GITHUB_WEBHOOK_SECRET_KEY | True | `None` | A secret value to ensure webhooks are from trusted sources. |
63+
| GRAVATAR_EMAIL | False | `None` | Gravatar email address for bot avatar. |
64+
| IGDB_CLIENT_ID | False | `None` | Required if daily_releases is enabled. |
65+
| IGDB_CLIENT_SECRET | False | `None` | Required if daily_releases is enabled. |
66+
| PRAW_CLIENT_ID | True | `None` | `client_id` from reddit app setup page. |
67+
| PRAW_CLIENT_SECRET | True | `None` | `client_secret` from reddit app setup page. |
68+
| PRAW_SUBREDDIT | True | `None` | Subreddit to monitor (reddit user should be moderator of the subreddit) |
69+
| REDDIT_USERNAME | True | `None` | Reddit username |
70+
| REDDIT_PASSWORD | True | `None` | Reddit password |
71+
| SUPPORT_COMMANDS_REPO | False | `https://github.com/LizardByte/support-bot-commands` | Repository for support commands. |
72+
| SUPPORT_COMMANDS_BRANCH | False | `master` | Branch for support commands. |
7073

7174
### Start
7275

src/common/database.py

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,35 @@ def __init__(self, db_name: str, db_dir: Union[str, Path] = data_dir, use_git: b
3636
self.repo_branch = os.getenv("DATA_REPO_BRANCH", "master")
3737
self.db_dir = os.path.join(self.db_dir, "support-bot-data")
3838

39+
# Get Git user configuration from environment variables
40+
self.git_user_name = os.environ["GIT_USER_NAME"]
41+
self.git_user_email = os.environ["GIT_USER_EMAIL"]
42+
43+
# Git credentials for authentication (required for private repo)
44+
self.git_token = os.getenv("GIT_TOKEN") or os.getenv("GITHUB_TOKEN")
45+
if not self.git_token:
46+
raise ValueError("GIT_TOKEN or GITHUB_TOKEN must be provided for private repository access")
47+
48+
# Prepare URL with credentials for private repo access
49+
protocol, repo_path = self.repo_url.split("://", 1)
50+
clone_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}"
51+
3952
if not os.path.exists(self.db_dir):
4053
# Clone repo if it doesn't exist
4154
print(f"Cloning repository {self.repo_url} to {self.db_dir}")
4255
try:
4356
# Try cloning with the specified branch
44-
self.repo = git.Repo.clone_from(self.repo_url, self.db_dir, branch=self.repo_branch)
57+
self.repo = git.Repo.clone_from(clone_url, self.db_dir, branch=self.repo_branch)
58+
# Configure the repo
59+
self._configure_repo()
4560
except git.exc.GitCommandError as e:
4661
# Check if the error is due to branch not found
4762
if "Remote branch" in str(e) and "not found in upstream origin" in str(e):
4863
print(f"Branch '{self.repo_branch}' not found in remote. Creating a new empty branch.")
4964
# Clone with default branch first
50-
self.repo = git.Repo.clone_from(self.repo_url, self.db_dir)
65+
self.repo = git.Repo.clone_from(clone_url, self.db_dir)
66+
# Configure the repo
67+
self._configure_repo()
5168

5269
# Create a new orphan branch (not based on any other branch)
5370
self.repo.git.checkout('--orphan', self.repo_branch)
@@ -91,6 +108,8 @@ def __init__(self, db_name: str, db_dir: Union[str, Path] = data_dir, use_git: b
91108
else:
92109
# Use existing repo
93110
self.repo = git.Repo(self.db_dir)
111+
# Configure the repo
112+
self._configure_repo()
94113

95114
# Make sure the correct branch is checked out
96115
if self.repo_branch not in [ref.name.split('/')[-1] for ref in self.repo.refs]:
@@ -136,6 +155,34 @@ def __init__(self, db_name: str, db_dir: Union[str, Path] = data_dir, use_git: b
136155
indent=4,
137156
)
138157

158+
def _configure_repo(self):
159+
"""Configure the Git repository with user identity from environment variables."""
160+
if self.repo:
161+
with self.repo.config_writer() as config:
162+
# Set user name and email for this repository
163+
config.set_value("user", "name", self.git_user_name)
164+
config.set_value("user", "email", self.git_user_email)
165+
166+
# Configure credentials for private repo access
167+
domain = self.repo_url.split("://")[-1].split("/")[0]
168+
169+
# Set credential store helper
170+
config.set_value("credential", "helper", "store")
171+
172+
# Set credential helper specific to this domain
173+
if self.git_user_name and self.git_token:
174+
config.set_value(f"credential \"{domain}\"", "username", self.git_user_name)
175+
176+
# Update origin URL with credentials to ensure push works
177+
protocol, repo_path = self.repo_url.split("://", 1)
178+
new_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}"
179+
try:
180+
origin = self.repo.remote('origin')
181+
origin.set_url(new_url)
182+
except git.exc.GitCommandError as e:
183+
print(f"Failed to update remote URL: {str(e)}")
184+
# Continue anyway, might work with stored credentials
185+
139186
def _check_for_migration(self):
140187
# Check if migration is needed (shelve exists but json doesn't)
141188
# No extension is used on Linux
@@ -253,15 +300,20 @@ def sync(self):
253300

254301
# Check if we have anything to commit after adding
255302
if self.repo.git.status('--porcelain'):
303+
# Ensure the repository is configured with user identity
304+
self._configure_repo()
305+
256306
# Commit all changes at once with a general message
257307
commit_message = "Update database files"
258308
self.repo.git.commit('-m', commit_message)
259309
print("Committed changes to git data repository")
260310

261-
# Push to remote
311+
# Push to remote with credentials
262312
try:
263-
origin = self.repo.remote('origin')
264-
origin.push()
313+
# Ensure we're using the credentials for push
314+
protocol, repo_path = self.repo_url.split("://", 1)
315+
push_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}"
316+
self.repo.git.push(push_url, self.repo_branch)
265317
print("Pushed changes to remote git data repository")
266318
except git.exc.GitCommandError as e:
267319
print(f"Failed to push changes: {str(e)}")

0 commit comments

Comments
 (0)