diff --git a/README.md b/README.md index 393a112..783dc53 100644 --- a/README.md +++ b/README.md @@ -37,36 +37,39 @@ platforms such as GitHub discussions/issues might be added in the future. :exclamation: if using Docker these can be arguments. :warning: Never publicly expose your tokens, secrets, or ids. -| variable | required | default | description | -|----------------------------------|----------|------------------------------------------------------|-------------------------------------------------------------------------| -| DAILY_TASKS | False | `true` | Daily tasks on or off. | -| DAILY_RELEASES | False | `true` | Send a message for each game released on this day in history. | -| DAILY_CHANNEL_ID | False | `None` | Required if daily_tasks is enabled. | -| DAILY_TASKS_UTC_HOUR | False | `12` | The hour to run daily tasks. | -| DATA_REPO | False | `https://github.com/LizardByte/support-bot-data` | Repository to store persistent data. This repository should be private! | -| DATA_REPO_BRANCH | False | `master` | Branch to store persistent data. | -| DISCORD_BOT_TOKEN | True | `None` | Token from Bot page on discord developer portal. | -| DISCORD_CLIENT_ID | True | `None` | Discord OAuth2 client id. | -| DISCORD_CLIENT_SECRET | True | `None` | Discord OAuth2 client secret. | -| DISCORD_GITHUB_STATUS_CHANNEL_ID | True | `None` | Channel ID to send GitHub status updates to. | -| DISCORD_REDDIT_CHANNEL_ID | True | `None` | Channel ID to send Reddit post updates to. | -| DISCORD_REDIRECT_URI | False | `https://localhost:8080/discord/callback` | The redirect uri for OAuth2. Must be publicly accessible. | -| DISCORD_SPONSORS_CHANNEL_ID | True | `None` | Channel ID to send sponsorship updates to. | -| GITHUB_CLIENT_ID | True | `None` | GitHub OAuth2 client id. | -| GITHUB_CLIENT_SECRET | True | `None` | GitHub OAuth2 client secret. | -| GITHUB_REDIRECT_URI | False | `https://localhost:8080/github/callback` | The redirect uri for OAuth2. Must be publicly accessible. | -| GITHUB_TOKEN | True | `None` | GitHub personal access token. Must have `read:org` | -| GITHUB_WEBHOOK_SECRET_KEY | True | `None` | A secret value to ensure webhooks are from trusted sources. | -| GRAVATAR_EMAIL | False | `None` | Gravatar email address for bot avatar. | -| IGDB_CLIENT_ID | False | `None` | Required if daily_releases is enabled. | -| IGDB_CLIENT_SECRET | False | `None` | Required if daily_releases is enabled. | -| PRAW_CLIENT_ID | True | `None` | `client_id` from reddit app setup page. | -| PRAW_CLIENT_SECRET | True | `None` | `client_secret` from reddit app setup page. | -| PRAW_SUBREDDIT | True | `None` | Subreddit to monitor (reddit user should be moderator of the subreddit) | -| REDDIT_USERNAME | True | `None` | Reddit username | -| REDDIT_PASSWORD | True | `None` | Reddit password | -| SUPPORT_COMMANDS_REPO | False | `https://github.com/LizardByte/support-bot-commands` | Repository for support commands. | -| SUPPORT_COMMANDS_BRANCH | False | `master` | Branch for support commands. | +| variable | required | default | description | +|----------------------------------|----------|------------------------------------------------------|--------------------------------------------------------------------------------------------| +| DAILY_TASKS | False | `true` | Daily tasks on or off. | +| DAILY_RELEASES | False | `true` | Send a message for each game released on this day in history. | +| DAILY_CHANNEL_ID | False | `None` | Required if daily_tasks is enabled. | +| DAILY_TASKS_UTC_HOUR | False | `12` | The hour to run daily tasks. | +| DATA_REPO | False | `https://github.com/LizardByte/support-bot-data` | Repository to store persistent data. This repository should be private! | +| DATA_REPO_BRANCH | False | `master` | Branch to store persistent data. | +| DISCORD_BOT_TOKEN | True | `None` | Token from Bot page on discord developer portal. | +| DISCORD_CLIENT_ID | True | `None` | Discord OAuth2 client id. | +| DISCORD_CLIENT_SECRET | True | `None` | Discord OAuth2 client secret. | +| DISCORD_GITHUB_STATUS_CHANNEL_ID | True | `None` | Channel ID to send GitHub status updates to. | +| DISCORD_REDDIT_CHANNEL_ID | True | `None` | Channel ID to send Reddit post updates to. | +| DISCORD_REDIRECT_URI | False | `https://localhost:8080/discord/callback` | The redirect uri for OAuth2. Must be publicly accessible. | +| DISCORD_SPONSORS_CHANNEL_ID | True | `None` | Channel ID to send sponsorship updates to. | +| GIT_USER_EMAIL | True | `None` | Email address for git commits. | +| GIT_USER_NAME | True | `None` | Username for git commits. | +| GIT_TOKEN | True | `None` | GitHub personal access token. Must have `repo` write access. Falls back to `GITHUB_TOKEN`. | +| GITHUB_CLIENT_ID | True | `None` | GitHub OAuth2 client id. | +| GITHUB_CLIENT_SECRET | True | `None` | GitHub OAuth2 client secret. | +| GITHUB_REDIRECT_URI | False | `https://localhost:8080/github/callback` | The redirect uri for OAuth2. Must be publicly accessible. | +| GITHUB_TOKEN | True | `None` | GitHub personal access token. Must have `read:org` | +| GITHUB_WEBHOOK_SECRET_KEY | True | `None` | A secret value to ensure webhooks are from trusted sources. | +| GRAVATAR_EMAIL | False | `None` | Gravatar email address for bot avatar. | +| IGDB_CLIENT_ID | False | `None` | Required if daily_releases is enabled. | +| IGDB_CLIENT_SECRET | False | `None` | Required if daily_releases is enabled. | +| PRAW_CLIENT_ID | True | `None` | `client_id` from reddit app setup page. | +| PRAW_CLIENT_SECRET | True | `None` | `client_secret` from reddit app setup page. | +| PRAW_SUBREDDIT | True | `None` | Subreddit to monitor (reddit user should be moderator of the subreddit) | +| REDDIT_USERNAME | True | `None` | Reddit username | +| REDDIT_PASSWORD | True | `None` | Reddit password | +| SUPPORT_COMMANDS_REPO | False | `https://github.com/LizardByte/support-bot-commands` | Repository for support commands. | +| SUPPORT_COMMANDS_BRANCH | False | `master` | Branch for support commands. | ### Start diff --git a/src/common/database.py b/src/common/database.py index 4f18c51..1295358 100644 --- a/src/common/database.py +++ b/src/common/database.py @@ -36,18 +36,35 @@ def __init__(self, db_name: str, db_dir: Union[str, Path] = data_dir, use_git: b self.repo_branch = os.getenv("DATA_REPO_BRANCH", "master") self.db_dir = os.path.join(self.db_dir, "support-bot-data") + # Get Git user configuration from environment variables + self.git_user_name = os.environ["GIT_USER_NAME"] + self.git_user_email = os.environ["GIT_USER_EMAIL"] + + # Git credentials for authentication (required for private repo) + self.git_token = os.getenv("GIT_TOKEN") or os.getenv("GITHUB_TOKEN") + if not self.git_token: + raise ValueError("GIT_TOKEN or GITHUB_TOKEN must be provided for private repository access") + + # Prepare URL with credentials for private repo access + protocol, repo_path = self.repo_url.split("://", 1) + clone_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}" + if not os.path.exists(self.db_dir): # Clone repo if it doesn't exist print(f"Cloning repository {self.repo_url} to {self.db_dir}") try: # Try cloning with the specified branch - self.repo = git.Repo.clone_from(self.repo_url, self.db_dir, branch=self.repo_branch) + self.repo = git.Repo.clone_from(clone_url, self.db_dir, branch=self.repo_branch) + # Configure the repo + self._configure_repo() except git.exc.GitCommandError as e: # Check if the error is due to branch not found if "Remote branch" in str(e) and "not found in upstream origin" in str(e): print(f"Branch '{self.repo_branch}' not found in remote. Creating a new empty branch.") # Clone with default branch first - self.repo = git.Repo.clone_from(self.repo_url, self.db_dir) + self.repo = git.Repo.clone_from(clone_url, self.db_dir) + # Configure the repo + self._configure_repo() # Create a new orphan branch (not based on any other branch) 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 else: # Use existing repo self.repo = git.Repo(self.db_dir) + # Configure the repo + self._configure_repo() # Make sure the correct branch is checked out 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 indent=4, ) + def _configure_repo(self): + """Configure the Git repository with user identity from environment variables.""" + if self.repo: + with self.repo.config_writer() as config: + # Set user name and email for this repository + config.set_value("user", "name", self.git_user_name) + config.set_value("user", "email", self.git_user_email) + + # Configure credentials for private repo access + domain = self.repo_url.split("://")[-1].split("/")[0] + + # Set credential store helper + config.set_value("credential", "helper", "store") + + # Set credential helper specific to this domain + if self.git_user_name and self.git_token: + config.set_value(f"credential \"{domain}\"", "username", self.git_user_name) + + # Update origin URL with credentials to ensure push works + protocol, repo_path = self.repo_url.split("://", 1) + new_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}" + try: + origin = self.repo.remote('origin') + origin.set_url(new_url) + except git.exc.GitCommandError as e: + print(f"Failed to update remote URL: {str(e)}") + # Continue anyway, might work with stored credentials + def _check_for_migration(self): # Check if migration is needed (shelve exists but json doesn't) # No extension is used on Linux @@ -253,15 +300,20 @@ def sync(self): # Check if we have anything to commit after adding if self.repo.git.status('--porcelain'): + # Ensure the repository is configured with user identity + self._configure_repo() + # Commit all changes at once with a general message commit_message = "Update database files" self.repo.git.commit('-m', commit_message) print("Committed changes to git data repository") - # Push to remote + # Push to remote with credentials try: - origin = self.repo.remote('origin') - origin.push() + # Ensure we're using the credentials for push + protocol, repo_path = self.repo_url.split("://", 1) + push_url = f"{protocol}://{self.git_user_name}:{self.git_token}@{repo_path}" + self.repo.git.push(push_url, self.repo_branch) print("Pushed changes to remote git data repository") except git.exc.GitCommandError as e: print(f"Failed to push changes: {str(e)}")