From 0ba4fee9e773959d4cc96fed721443973337cc05 Mon Sep 17 00:00:00 2001 From: Aster Fialla Date: Thu, 12 Feb 2026 20:05:31 -0500 Subject: [PATCH] project setup with example bot --- .dockerignore | 25 ++ .gitignore | 8 + BasicCommands.cs | 122 ++++++++++ ConfigExtension.cs | 56 +++++ Dockerfile | 21 ++ PluralFlux.csproj | 15 ++ PluralFlux.sln | 21 ++ PluralFlux.sln.DotSettings.user | 2 + Program.cs | 405 ++++++++++++++++++++++++++++++++ compose.yaml | 38 +++ 10 files changed, 713 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 BasicCommands.cs create mode 100644 ConfigExtension.cs create mode 100644 Dockerfile create mode 100644 PluralFlux.csproj create mode 100644 PluralFlux.sln create mode 100644 PluralFlux.sln.DotSettings.user create mode 100644 Program.cs create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edfb386 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +obj/ +/packages/ +riderModule.iml +/_ReSharper.Caches/ +.idea +config.yml +postgres_pwd.txt \ No newline at end of file diff --git a/BasicCommands.cs b/BasicCommands.cs new file mode 100644 index 0000000..10fe306 --- /dev/null +++ b/BasicCommands.cs @@ -0,0 +1,122 @@ + +using Fluxer.Net.Commands; +using Fluxer.Net.Commands.Attributes; +using Fluxer.Net.Data.Models; + +namespace PluralFlux; + + +/// +/// Example command module demonstrating basic commands. +/// +public class BasicCommands : ModuleBase +{ + /// + /// Simple ping command that responds with "pong". + /// + [Command("ping")] + [Summary("Check if the bot is responsive")] + public async Task PingCommand() + { + await ReplyAsync("pong ;P"); + } + + /// + /// Hello command that mentions the user. + /// + [Command("hello")] + [Alias("hi", "hey")] + [Summary("Get a friendly greeting")] + public async Task HelloCommand() + { + await ReplyAsync($"Hello, <@{Context.User.Id}>! 👋"); + } + + /// + /// Info command that shows bot information and available commands. + /// + [Command("info")] + [Summary("Show bot information and available commands")] + public async Task InfoCommand() + { + await ReplyAsync( + $"**Fluxer.Net Example Bot**\n" + + $"Version: 0.4.0\n" + + $"Framework: .NET 7.0\n" + + $"Library: Fluxer.Net\n\n" + + $"Available Commands:\n" + + $"• `/ping` - Check if bot is responsive\n" + + $"• `/hello` - Get a friendly greeting\n" + + $"• `/info` - Show this information\n" + + $"• `/embed` - Show an example rich embed\n" + + $"• `/echo ` - Echo back your message\n" + + $"• `/add ` - Add two numbers" + ); + } + + /// + /// Embed command that demonstrates rich embeds using EmbedBuilder. + /// + [Command("embed")] + [Summary("Show an example rich embed")] + public async Task EmbedCommand() + { + var embed = new Fluxer.Net.EmbedBuilder.EmbedBuilder() + .WithTitle("Example Rich Embed") + .WithDescription("This is a demonstration of Fluxer.Net's EmbedBuilder system, " + + "based on Discord.Net's implementation. Embeds support rich formatting " + + "with titles, descriptions, fields, images, and more!") + .WithColor(0x5865F2) // Blurple color + .WithAuthor( + name: Context.User.Username, + iconUrl: Context.User.Avatar != null + ? $"https://cdn.fluxer.dev/avatars/{Context.User.Id}/{Context.User.Avatar}.png" + : null + ) + .WithThumbnailUrl("https://avatars.githubusercontent.com/u/20194446") + .AddField("Field 1", "This is an inline field", inline: true) + .AddField("Field 2", "This is also inline", inline: true) + .AddField("Field 3", "This is another inline field", inline: true) + .AddField("Full Width Field", "This field takes up the full width because inline is false", inline: false) + .AddField("Bot Stats", $"Guilds: 1\nChannels: 5\nUptime: {DateTime.UtcNow:HH:mm:ss}", inline: true) + .WithFooter("Fluxer.Net v0.4.0", "https://avatars.githubusercontent.com/u/20194446") + .WithCurrentTimestamp() + .Build(); + + await Context.Client.SendMessage(Context.ChannelId, new() + { + Content = "Here's an example of a rich embed:", + Embeds = new List { embed } + }); + } + + /// + /// Echo command that repeats the user's message. + /// + [Command("echo")] + [Summary("Echo back your message")] + public async Task EchoCommand([Remainder] string message) + { + await ReplyAsync(message); + } + + /// + /// Add command that adds two numbers together. + /// + [Command("add")] + [Summary("Add two numbers together")] + public async Task AddCommand(int a, int b) + { + await ReplyAsync($"{a} + {b} = {a + b}"); + } + + /// + /// Example command with optional parameter. + /// + [Command("greet")] + [Summary("Greet someone (or yourself)")] + public async Task GreetCommand(string name = "stranger") + { + await ReplyAsync($"Hello, {name}!"); + } +} \ No newline at end of file diff --git a/ConfigExtension.cs b/ConfigExtension.cs new file mode 100644 index 0000000..aa86e5c --- /dev/null +++ b/ConfigExtension.cs @@ -0,0 +1,56 @@ +namespace PluralFlux; + +public static class ConfigExtension +{ + //Load YML configuration from file + public static Dictionary? LoadConfig() + { + //NOTE: This path may need to be modified depending on where you run the example from + if (File.Exists("./config.yml")) + { + string[] lines = File.ReadAllLines("./config.yml"); + Dictionary configValues = ParseYaml(lines); + + // Use the values from the config + if (configValues.TryGetValue("Token", out var token)) + { + Console.WriteLine($"Token: {token}"); + } + + return configValues; + } + + return null; + } + + //Custom YML parser + private static Dictionary ParseYaml(string[] lines) + { + Dictionary configValues = new Dictionary(); + string currentKey = ""; + + foreach (string line in lines) + { + if (line.Trim().StartsWith('#')) + { + // Skip comments + continue; + } + + if (line.Contains(":")) + { + string[] parts = line.Split(':'); + currentKey = parts[0].Trim(); + string value = parts.Length > 1 ? parts[1].Trim() : ""; + configValues[currentKey] = value; + } + else if (!string.IsNullOrEmpty(currentKey)) + { + // Multi-line values + configValues[currentKey] += Environment.NewLine + line.Trim(); + } + } + + return configValues; + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2c9e1c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/dotnet/runtime:10.0 AS base +USER $APP_UID +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["PluralFlux.csproj", "./"] +RUN dotnet restore "PluralFlux.csproj" +COPY . . +WORKDIR "/src/" +RUN dotnet build "./PluralFlux.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./PluralFlux.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "PluralFlux.dll"] diff --git a/PluralFlux.csproj b/PluralFlux.csproj new file mode 100644 index 0000000..6e1ca25 --- /dev/null +++ b/PluralFlux.csproj @@ -0,0 +1,15 @@ + + + + Exe + net10.0 + enable + enable + Linux + + + + + + + diff --git a/PluralFlux.sln b/PluralFlux.sln new file mode 100644 index 0000000..4137c08 --- /dev/null +++ b/PluralFlux.sln @@ -0,0 +1,21 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluralFlux", "PluralFlux.csproj", "{A1182490-2575-4A5E-9A11-1499AAF87A7C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F8342F54-03DE-42A4-BF87-29D4D90EF052}" + ProjectSection(SolutionItems) = preProject + compose.yaml = compose.yaml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1182490-2575-4A5E-9A11-1499AAF87A7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1182490-2575-4A5E-9A11-1499AAF87A7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1182490-2575-4A5E-9A11-1499AAF87A7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1182490-2575-4A5E-9A11-1499AAF87A7C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/PluralFlux.sln.DotSettings.user b/PluralFlux.sln.DotSettings.user new file mode 100644 index 0000000..af51dea --- /dev/null +++ b/PluralFlux.sln.DotSettings.user @@ -0,0 +1,2 @@ + + ForceIncluded \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..042ce45 --- /dev/null +++ b/Program.cs @@ -0,0 +1,405 @@ +// ============================================================================ +// Fluxer.Net Example Project - Getting Started Tutorial +// ============================================================================ +// This example demonstrates the core concepts of building a Fluxer bot using +// the Fluxer.Net library. You'll learn how to: +// 1. Configure logging for debugging and monitoring +// 2. Set up both the Gateway (real-time events) and API (REST operations) +// 3. Handle gateway events (like messages) +// 4. Make API calls (like sending messages) +// 5. Implement basic command handling +// +// Prerequisites: +// - A Fluxer account and bot token (add it to config.yml) +// - .NET 7.0 or higher +// - Basic understanding of async/await in C# +// ============================================================================ + +using System.Reflection; +using Serilog; +using Serilog.Core; +using Serilog.Sinks.SystemConsole.Themes; +using Fluxer.Net; +using Fluxer.Net.Commands; +using Fluxer.Net.Data.Enums; +using Fluxer.Net.Gateway.Data; +using PluralFlux; + +// ============================================================================ +// STEP 1: Configure Logging +// ============================================================================ +// Serilog provides structured logging for the Fluxer.Net library. This helps +// you debug issues and monitor your bot's activity. Logs are written to both +// the console (for development) and a file (for production debugging). + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() // Log everything (Verbose, Debug, Info, Warning, Error, Fatal) + .WriteTo.Console(theme: AnsiConsoleTheme.Code) // Pretty console output with colors + .CreateLogger(); + +// ============================================================================ +// STEP 2: Load Your Bot Token +// ============================================================================ +// The token authenticates your bot with the Fluxer API. NEVER commit your +// token to version control! Store it in config.yml (which is .gitignored). +// +// To get a token: +// 1. Create a bot in the Fluxer developer portal +// 2. Copy the bot token +// 3. Paste it into config.yml as "Token: flx_your_token_here" + +var config = ConfigExtension.LoadConfig(); +if (config == null) +{ + Log.Error("YAML file not found. Please create a config.yml file with your bot token."); + Log.Error("Example format:\n Token: flx_your_token_here"); + return; +} + +Log.Debug("Config file loaded successfully."); + +// ============================================================================ +// STEP 3: Initialize the Gateway Client (Real-Time Events) +// ============================================================================ +// The GatewayClient connects to Fluxer's WebSocket gateway to receive real-time +// events like messages, reactions, member joins, etc. This is the "listening" +// part of your bot that responds to what happens on Fluxer. +// +// Key Configuration Options: +// - ReconnectAttemptDelay: Seconds to wait between reconnection attempts +// - Serilog: Logger instance for gateway events +// - IgnoredGatewayEvents: Filter out events you don't need (reduces processing) +// - Presence: Your bot's initial status (Online, Idle, DND, Invisible) + +var gateway = new GatewayClient(config["Token"], new() +{ + ReconnectAttemptDelay = 2, // Reconnect quickly if connection drops + Serilog = Log.Logger as Logger, // Use our configured logger + + // Ignore high-volume events we don't need to reduce CPU/memory usage + // Common events to ignore: PRESENCE_UPDATE, TYPING_START, VOICE_STATE_UPDATE + IgnoredGatewayEvents = new() + { + "PRESENCE_UPDATE" // We don't need to track when users go online/offline + }, + + // Set your bot's status. Options: Online, Idle, DND, Invisible + Presence = new PresenceUpdateGatewayData(Status.Online) +}); + +// ============================================================================ +// STEP 4: Initialize the API Client (REST Operations) +// ============================================================================ +// The ApiClient handles REST API requests for creating, reading, updating, and +// deleting resources (messages, channels, guilds, users, etc.). This is the +// "action" part of your bot that makes changes on Fluxer. +// +// Key Features: +// - Automatic rate limiting (enabled by default via sliding window algorithm) +// - Token-based authentication +// - Full coverage of 150+ Fluxer API endpoints +// - Shared logging configuration with the gateway + +var api = new ApiClient(config[key: "Token"], new() +{ + Serilog = Log.Logger as Logger, // Use our configured logger + EnableRateLimiting = true // Prevent hitting rate limits (default: true) +}); + +// ============================================================================ +// STEP 4.5: Initialize the Command Service +// ============================================================================ +// The CommandService provides a Discord.Net-style command framework for handling +// text-based commands. It automatically discovers command modules, parses arguments, +// and executes commands with support for preconditions and dependency injection. +// +// Key Features: +// - Attribute-based command definition +// - Automatic type parsing (string, int, bool, DateTime, etc.) +// - Optional and remainder parameters +// - Precondition support (RequireOwner, RequireContext, etc.) +// - Sync/Async execution modes + +var commands = new CommandService( + prefixChar: '/', // Commands start with / + logger: Log.Logger as Logger, // Use our configured logger + services: null // No dependency injection for this example +); + +// Automatically register all command modules from this assembly +await commands.AddModulesAsync(Assembly.GetExecutingAssembly()); + +Log.Information("Registered {ModuleCount} command module(s) with {CommandCount} command(s)", + commands.Modules.Count, commands.Commands.Count()); + +// ============================================================================ +// STEP 5: Handle Command-Line Arguments +// ============================================================================ +// This example supports a --revoke flag to log out the bot and invalidate +// the current token. Useful for testing or emergency shutdowns. +// +// Usage: dotnet run --revoke + +if (args.Length > 0 && args[0] == "--revoke") +{ + Log.Information("Revoking token and logging out..."); + await api.Logout(); + Log.Information("Token revoked successfully. The bot is now logged out."); + return; +} + +// ============================================================================ +// STEP 6: Example API Call - Update Bot Nickname +// ============================================================================ +// This demonstrates a simple API call to update the bot's nickname in a guild. +// Replace the guild ID with your own guild/community ID. +// +// To find your guild ID: +// 1. Enable developer mode in Fluxer settings +// 2. Right-click your guild/community +// 3. Click "Copy ID" + +// NOTE: Replace this guild ID with your own! +// await api.UpdateCurrentMember(1431484523333775609, new() +// { +// Nickname = "Fluxer.Net Example Bot" +// }); + +// ============================================================================ +// STEP 7: Subscribe to Gateway Events +// ============================================================================ +// The gateway uses an event-driven architecture. You subscribe to events by +// attaching handlers to the GatewayClient. Here we demonstrate basic command +// handling by listening for MESSAGE_CREATE events. +// +// Available Events (just a few examples): +// - MessageCreate: New message posted +// - MessageUpdate: Message edited +// - MessageDelete: Message deleted +// - GuildCreate: Bot added to a guild +// - GuildMemberAdd: User joined a guild +// - MessageReactionAdd: Reaction added to a message +// ... and many more! See GatewayClient.cs for the full list. + +gateway.MessageCreate += async messageData => +{ + try + { + + // Log every message for debugging (optional - can be noisy!) + Log.Debug("Message received in channel {ChannelId} from {Username}: {Content}", + messageData.ChannelId, messageData.Author.Username, messageData.Content); + + // ======================================================================== + // Command Handling using CommandService + // ======================================================================== + // The CommandService automatically parses commands and executes them. + // Commands are defined in module classes (see Modules/BasicCommands.cs) + + // Check if the message starts with the command prefix + int argPos = 0; + if (messageData.Content?.StartsWith("pf;") == true) + { + argPos = 3; // Skip the prefix character + + // Create a command context with all the necessary information + var context = new CommandContext(api, gateway, messageData); + + // Execute the command + var result = await commands.ExecuteAsync(context, argPos); + + // Log command execution results + if (!result.IsSuccess) + { + // Log errors (you can also send error messages to the user here) + Log.Warning("Command execution failed: {Error} ({ErrorType})", + result.Error, result.ErrorType); + + // Optionally send error message to user + if (result.ErrorType == CommandError.UnknownCommand) + { + // Don't spam for unknown commands - just log it + } + else if (result.ErrorType == CommandError.BadArgCount) + { + await api.SendMessage(messageData.ChannelId, new() + { + Content = $"❌ Error: {result.Error}" + }); + } + else if (result.ErrorType == CommandError.ParseFailed) + { + await api.SendMessage(messageData.ChannelId, new() + { + Content = $"❌ Error: {result.Error}" + }); + } + else if (result.ErrorType == CommandError.UnmetPrecondition) + { + await api.SendMessage(messageData.ChannelId, new() + { + Content = $"⛔ {result.Error}" + }); + } + else + { + await api.SendMessage(messageData.ChannelId, new() + { + Content = $"❌ An error occurred: {result.Error}" + }); + } + } + else + { + Log.Information("Command executed successfully by {Username} ({UserId})", + messageData.Author.Username, messageData.Author.Id); + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Error while processing message"); + } +}; + +// ============================================================================ +// Additional Gateway Event Examples (Uncomment to use) +// ============================================================================ + +// Example: Log when the bot is ready +gateway.Ready += readyData => +{ + try + { + Log.Information("Bot is ready! Logged in as {Username}", readyData.User?.Username); + } + catch (Exception ex) + { + Log.Error(ex, "Error on ready event"); + } +}; + +// Example: Track message deletions +// gateway.MessageDelete += deleteData => +// { +// Log.Information("Message {MessageId} was deleted from channel {ChannelId}", +// deleteData.Id, deleteData.ChannelId); +// }; + +// Example: Welcome new guild members +// gateway.GuildMemberAdd += async memberData => +// { +// Log.Information("New member joined guild {GuildId}: User {UserId}", +// memberData.GuildId, memberData.UserId); +// +// // Send a welcome message (replace with your welcome channel ID) +// // await api.SendMessage(yourWelcomeChannelId, new() +// // { +// // Content = $"Welcome to the server, <@{memberData.UserId}>! 🎉" +// // }); +// }; + +// Example: Track message reactions +// gateway.MessageReactionAdd += reactionData => +// { +// Log.Debug("Reaction {Emoji} added to message {MessageId} by user {UserId}", +// reactionData.Emoji?.Name, reactionData.MessageId, reactionData.UserId); +// }; + +// ============================================================================ +// STEP 8: Connect to the Gateway +// ============================================================================ +// This establishes the WebSocket connection and starts receiving events. +// IMPORTANT: Uncomment this line to actually connect! It's commented out by +// default so you can test API calls without connecting to the gateway. + +await gateway.ConnectAsync(); +Log.Information("Connected to Fluxer gateway. Bot is now online!"); + +// ============================================================================ +// STEP 9: Keep the Bot Running +// ============================================================================ +// The bot needs to stay running to continue receiving events. Task.Delay(-1) +// blocks the main thread indefinitely. The bot will run until you stop it +// with Ctrl+C or kill the process. +// +// In production, you might want to: +// - Add graceful shutdown handling (CancellationToken) +// - Implement a /shutdown command for authorized users +// - Run as a system service or Docker container + +// await api.UpdateCurrentMember(1431484523333775609, new() { Nickname = "Fluxer.Net" }); + +Log.Information("Bot is running. Press Ctrl+C to stop."); +await Task.Delay(-1); + +// ============================================================================ +// Next Steps & Resources +// ============================================================================ +// Now that you understand the basics, here are some ideas to expand your bot: +// +// 1. Add more commands: +// - Create new command modules in the Modules/ folder +// - Use preconditions: [RequireOwner], [RequireContext(ContextType.Guild)] +// - Add parameter types: int, bool, DateTime, TimeSpan, enums, etc. +// - Use [Remainder] for multi-word parameters +// - Use [Alias] to add alternative command names +// - Implement BeforeExecute/AfterExecute hooks in your modules +// +// Example command module: +// public class ModerationCommands : ModuleBase +// { +// [Command("kick")] +// [RequireUserPermission(Permissions.KickMembers)] +// [RequireContext(ContextType.Guild)] +// public async Task KickCommand(ulong userId, [Remainder] string reason = "No reason provided") +// { +// // Kick logic here +// await ReplyAsync($"Kicked user {userId} for: {reason}"); +// } +// } +// +// 2. Use more API endpoints: +// - Create/manage channels: api.CreateChannel() +// - Manage roles: api.CreateRole(), api.UpdateRole() +// - Send embeds: Use EmbedBuilder to create rich embeds (see /embed command) +// - Manage members: api.UpdateMember(), api.KickMember() +// +// Example: Create a complex embed with error handling +// try { +// var embed = new EmbedBuilder() +// .WithTitle("Server Stats") +// .WithDescription($"Statistics for {guildName}") +// .WithColor(0x00FF00) // Green +// .AddField("Total Members", memberCount.ToString(), inline: true) +// .AddField("Online Members", onlineCount.ToString(), inline: true) +// .AddField("Total Channels", channelCount.ToString(), inline: true) +// .WithThumbnailUrl(guildIconUrl) +// .WithFooter($"Requested by {username}", userAvatarUrl) +// .WithCurrentTimestamp() +// .Build(); +// +// await api.SendMessage(channelId, new() { Embeds = new() { embed } }); +// } catch (InvalidOperationException ex) { +// Log.Error(ex, "Embed validation failed - check field lengths and URL formats"); +// } +// +// 3. Implement advanced features: +// - Database integration for persistent data (Entity Framework, Dapper, etc.) +// - Scheduled tasks and background jobs +// - Custom preconditions for advanced permission checks +// - Dependency injection with service providers +// +// 4. Explore rate limiting: +// - Check remaining requests: api.RateLimitManager.GetBucketInfoAsync() +// - Monitor active buckets: api.RateLimitManager.ActiveBucketCount +// - See RateLimiting/README.md for more details +// +// 5. Documentation: +// - API endpoints: See ApiClient.cs for all 150+ methods +// - Gateway events: See GatewayClient.cs for all event types +// - Rate limiting: See RateLimiting/README.md +// - Configuration: See FluxerConfig.cs for all options +// +// Happy coding! 🚀 +// ============================================================================ \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..b507e39 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,38 @@ +services: + pluralflux: + image: pluralflux + build: + context: . + dockerfile: Dockerfile + postgres: + image: postgres:latest + container_name: pluralflux-postgres + environment: + POSTGRES_PASSWORD: /run/secrets/postgres_pwd + secrets: + - postgres_pwd + volumes: + - pgdata:/var/lib/postgresql/data + ports: + - "5432:5432" + pgadmin: + image: dpage/pgadmin4:latest + ports: + - 5050:80 + environment: + # Required by pgAdmin + PGADMIN_DEFAULT_EMAIL: pieartsy@pm.me + PGADMIN_DEFAULT_PASSWORD: /run/secrets/postgres_pwd + # Don't require the user to login + PGADMIN_CONFIG_SERVER_MODE: 'False' + # Don't require a "master" password after logging in + PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' + secrets: + - postgres_pwd + +volumes: + pgdata: + +secrets: + postgres_pwd: + file: ./postgres_pwd.txt \ No newline at end of file