How to add new storage to Postgresus

This guide will walk you through the process of contributing a new storage integration to Postgresus. You'll learn how to implement the backend logic, create database migrations and build the frontend UI components for managing storage providers.

💡 Note: This is a contribution guide for developers who want to add new storage integrations to the Postgresus project. If you only want to use existing storage providers, check out the Storages documentation.

Backend implementation

The backend implementation involves creating models, updating enums and setting up database migrations for your storage provider.

1. Create new storage model

Create a new model in backend/internal/features/storages/models/{storage_name}/ folder. Your model must implement the StorageFileSaver interface from the parent folder.

The interface requires the following methods:

  • SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error - saves a backup file to the storage
  • GetFile(fileID uuid.UUID) (io.ReadCloser, error) - retrieves a backup file from the storage
  • DeleteFile(fileID uuid.UUID) error - deletes a backup file from the storage
  • Validate() error - validates the storage configuration
  • TestConnection() error - tests connectivity to the storage provider
  • HideSensitiveData() - masks sensitive fields like API keys before logging/display

🔑 Important: Use UUID primary key as StorageID that references the main storages table. This maintains referential integrity across the database. Also add a TableName() string method to return the proper table name.

2. Add storage type to enums

Add your new storage type to backend/internal/features/storages/enums.go in the StorageType constants section. This enum is used throughout the application to identify different storage types.

3. Update the main Storage model

Modify backend/internal/features/storages/model.go to integrate your new storage:

  • Add a new field with GORM foreign key relation to your storage model
  • Update the getSpecificStorage() method to handle your new type
  • Update the SaveFile(), GetFile(), and DeleteFile() methods to route to your storage implementation
  • Update the Validate() method to include your storage validation

4. Add environment variables (if needed)

If your storage requires environment variables for testing, add them to backend/internal/config/config.go. This allows the test suite to access necessary configuration values such as API keys or credentials.

5. Configure Docker containers (if needed)

If your storage needs external services for testing (like MinIO for S3-compatible storage or FTP servers), add them to backend/docker-compose.yml.example. Keep sensitive data blank in the example file.

6. Add CI/CD secrets (if needed)

For sensitive environment variables needed in the GitHub Actions pipeline (like cloud storage API keys or credentials), contact @rostislav_dugin to add them securely to the CI/CD environment.

7. Create database migration

Create a new migration file in the backend/migrations folder:

  • Create a table with storage_id as UUID primary key
  • Add a foreign key constraint to the storages table with CASCADE DELETE
  • Include all necessary columns for your storage configuration (endpoints, credentials, bucket names, etc.)
  • Look at existing storage migrations (e.g., Google Drive, Cloudflare R2) for reference

💡 Tip: The CASCADE DELETE ensures that when a storage is deleted from the main storages table, the related configuration is automatically removed.

8. Update storage tests

Update tests in backend/internal/features/storages/model_test.go to include tests for your new storage. Your tests should verify:

  • Successful file upload to storage
  • File retrieval from storage
  • File deletion from storage
  • Connection testing
  • Validation of configuration parameters

9. Run all tests

Before submitting your pull request, make sure all existing tests pass and your new tests are working correctly. This ensures your storage integration doesn't break existing functionality.

Frontend implementation

The frontend work involves creating TypeScript models, validators and React components for managing your storage provider.

â„šī¸ Backend-only contributions: If you're only comfortable with backend development, that's perfectly fine! Complete the backend part and contact @rostislav_dugin to help with the UI implementation.

1. Add TypeScript models and API

Create your storage model and API integration in frontend/src/entity/storages/models/{storage_name}/. Update the index.ts file to export your new model for use throughout the application.

Your model should include:

  • TypeScript interface matching your backend model
  • Validation function to ensure all required fields are present
  • Any helper functions specific to your storage configuration

2. Upload icon and update utilities

Complete the following steps to add visual assets:

  1. Upload an SVG icon to public/icons/storages/
  2. Update src/entity/storages/models/getStorageLogoFromType.ts to return your icon path
  3. Update src/entity/storages/models/StorageType.ts to include your new storage type constant
  4. Update src/entity/storages/models/getStorageNameFromType.ts to return the display name for your storage

3. Build UI components

Create two main React components:

  • src/features/storages/ui/edit/storages/Edit{StorageName}Component.tsx - for creating and editing storage configuration
  • src/features/storages/ui/show/storages/Show{StorageName}Component.tsx - for displaying storage details in read-only mode

These components should handle form inputs, validation and data formatting specific to your storage provider. Include features like:

  • Input fields for all required configuration parameters
  • Test connection button to verify credentials
  • Clear error messages for validation failures
  • Masked display of sensitive information

4. Integrate with main components

Update the main storage management components to handle your new type:

  • EditStorageComponent.tsx - add import and component rendering for your storage
  • ShowStorageComponent.tsx - add import and component rendering to display your storage

These updates enable the application to dynamically render the correct component based on the storage type selected by the user.

5. Test the implementation

Thoroughly test your implementation to ensure everything works as expected:

  • Create a new storage of your type
  • Edit existing storage configurations
  • Test connection to verify credentials
  • Upload a backup file to the storage
  • Download a backup file from the storage
  • Delete a backup file from the storage
  • Verify validation works correctly
  • Check that sensitive data is properly masked
  • Test the delete functionality

Submission guidelines

When you're ready to contribute your work:

  1. Fork the Postgresus repository
  2. Create a feature branch with a descriptive name
  3. Commit your changes with clear commit messages
  4. Ensure all tests pass
  5. Test the integration end-to-end with actual backup operations
  6. Submit a pull request with a description of your changes and any special considerations

🎉 Thank you! Your contributions help make Postgresus more versatile and valuable for the community. We appreciate your effort in expanding the storage capabilities!