Notice: This project contains code generated by Large Language Models such as Claude and Gemini. All code is experimental whether explicitly stated or not.
A lightweight, self-hosted blogging platform built with .NET 10 and Blazor Server.
- [cite_start]Markdown-based content: Write posts in Markdown with live preview and support for ordered/unordered lists[cite: 1406, 1409].
- [cite_start]User Management: Create, edit, and delete multiple users with role-based access control[cite: 412, 423].
- [cite_start]Social Sharing: Built-in "Share" button on posts using the Web Share API (mobile) or Clipboard fallback (desktop)[cite: 1971].
- [cite_start]Image management: Upload and manage images stored in the database[cite: 1096].
- Security:
- [cite_start]Login rate limiting to prevent brute-force attacks[cite: 1859].
- [cite_start]Slug collision prevention[cite: 302].
- [cite_start]OpenTelemetry: Built-in observability with file-based and database telemetry export[cite: 1563, 1579].
- [cite_start]Cross-platform: Runs on Windows, Linux, and macOS[cite: 859].
- [cite_start]CI/CD ready: GitHub Actions workflow for automated testing and deployment[cite: 1286].
- .NET 10 SDK or later
# Clone the repository
git clone [https://github.com/yourusername/dotnetcms.git](https://github.com/yourusername/dotnetcms.git)
cd dotnetcms/src
# Restore and run
dotnet restore MyBlog.slnx
cd MyBlog.Web
dotnet run
The application will start at http://localhost:5000 (or the next available port).
- Username:
admin
Password: ChangeMe123! (or value of MYBLOG_ADMIN_PASSWORD environment variable).
Important: The default password is only used when creating the initial admin user. Once the user exists, you must change the password through the website.
| Variable | Description | Default |
|---|---|---|
MYBLOG_ADMIN_PASSWORD |
Initial admin password (only used on first run) | ChangeMe123! |
ASPNETCORE_ENVIRONMENT |
Runtime environment | Production |
{
"ConnectionStrings": {
"DefaultConnection": "Data Source=myblog.db"
},
"Authentication": {
"SessionTimeoutMinutes": 30,
"DefaultAdminPassword": "ChangeMe123!"
},
"Application": {
"Title": "MyBlog",
"PostsPerPage": 10,
"RequireHttps": false
},
"Telemetry": {
"RetentionDays": 30,
"EnableFileLogging": true,
"EnableDatabaseLogging": true
}
}
The SQLite database is stored in a platform-specific location following XDG conventions:
| Platform | Path |
|---|---|
| Linux | ~/.local/share/MyBlog/myblog.db |
| macOS | ~/Library/Application Support/MyBlog/myblog.db |
| Windows | %LOCALAPPDATA%\MyBlog\myblog.db |
The admin dashboard provides an overview of your blog with quick access to all management features.
-
Create Post (
/admin/posts/new): Write a new blog post in Markdown. -
Edit Post (
/admin/posts/edit/{id}): Modify existing posts. Slugs are automatically handled to prevent duplicates. -
Post List (
/admin/posts): View and manage all posts.
User List (/admin/users): View all registered users.
Create User (/admin/users/new): Add new authors or administrators.
- Edit User: Update email, display name, or reset passwords.
- Upload Images (
/admin/images): Upload images to use in posts. - Image Library: Browse and delete uploaded images.
Usage: Reference images in Markdown using /api/images/{id}.
Navigate to /admin/change-password to change your own password:
- Enter your current password.
- Enter your new password (minimum 8 characters).
- Confirm the new password.
- Click "Change Password".
Note: The
MYBLOG_ADMIN_PASSWORDenvironment variable only affects the initial password when the admin user is first created. It does not override existing passwords in the database.
The repository includes a GitHub Actions workflow that:
-
Builds and tests on Windows, Linux, and macOS.
-
Deploys to your server via WebDeploy (on main/master/develop branches).
Set these in your repository settings under Settings > Secrets and variables > Actions:
| Secret | Description | Example |
|---|---|---|
WEBSITE_NAME |
IIS site name | MyBlog |
SERVER_COMPUTER_NAME |
Server hostname | myserver.example.com |
SERVER_USERNAME |
WebDeploy username | deploy-user |
SERVER_PASSWORD |
WebDeploy password | (your password) |
| Variable | Description | Example |
|---|---|---|
MYBLOG_ADMIN_PASSWORD |
Initial admin password | (strong password) |
Note:
MYBLOG_ADMIN_PASSWORDshould be set as a secret, not a variable, if you want it to remain hidden in logs.
# Publish for Windows
dotnet publish src/MyBlog.Web/MyBlog.Web.csproj -c Release -o ./publish -r win-x64
# Publish for Linux
dotnet publish src/MyBlog.Web/MyBlog.Web.csproj -c Release -o ./publish -r linux-x64
Copy the contents of ./publish to your server.
- Install the .NET 10 Hosting Bundle
- Create a new IIS site pointing to your publish folder
- Set the Application Pool to "No Managed Code"
- Ensure the Application Pool identity has write access to the database directory
This occurs when the application is running and DLLs are locked.
Solution: The workflow now includes -enableRule:AppOffline which automatically:
- Creates
app_offline.htmto stop the application. - Waits for the app to release file locks.
- Deploys the new files.
- Removes
app_offline.htmto restart the app.
The environment variable only works when no users exist in the database.
To reset with a new password:
- Stop the application.
- Delete the database file (see Database Location above).
- Set
MYBLOG_ADMIN_PASSWORDto your desired password. - Start the application.
Or, log in with the current password and use /admin/change-password.
SQLite can have locking issues with concurrent access.
Solutions:
- Ensure only one instance of the application is running.
- Check that no database tools have the file open.
- Verify file permissions on the database directory.
cd src
dotnet test MyBlog.slnx
src/
├── MyBlog.Core/ # Domain models and interfaces
├── MyBlog.Infrastructure/ # Data access, services
├── MyBlog.Web/ # Blazor Server application
└── MyBlog.Tests/ # xUnit test project
- Define interfaces in
MyBlog.Core/Interfaces - Implement in
MyBlog.Infrastructure/Services - Add UI in
MyBlog.Web/Components/Pages - Write tests in
MyBlog.Tests
| Endpoint | Method | Description |
|---|---|---|
/api/images/{id} |
GET | Retrieve an image by ID |
|
MIT License - see LICENSE for details.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request