Skip to content

Commit 9133303

Browse files
authored
Merge pull request #55 from Dodelidoo-Labs/develop
v0.12.1
2 parents 007587e + 8cba5db commit 9133303

File tree

14 files changed

+368
-58
lines changed

14 files changed

+368
-58
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.12.1] - 2026-03-03
8+
### Added
9+
- Add a per-user auto-approve toggle so selected non-admin users can add artists directly without manual approval
10+
- Granular "developer" documentation in /doc folder (living documentation)
11+
12+
### Fixed
13+
- Fix super-admin bootstrap credential fallback by defaulting blank passwords to `change-me` for consistent reset behavior
14+
15+
### Changed
16+
- README.md documentation
17+
718
## [0.11.0] - 2026-01-21
819
### Added
920
- Add OIDC SSO integration with login flow by @tinkermesomething

CONTRIBUTING.md

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1-
# Contributing
1+
# Contributing to Sonobarr
22

3-
1. Clone the repository.
4-
2. Checkout the `develop` branch: `git checkout develop`.
5-
3. **Create** a `docker-compose.override.yml` with the following contents:
3+
Thank you for contributing to Sonobarr. This guide defines the required workflow for local development, testing, and pull requests.
4+
5+
## Branch and Pull Request Policy
6+
7+
- Base all work on the `develop` branch.
8+
- Open pull requests against `develop`.
9+
- Keep each pull request focused on one feature or one fix.
10+
- For substantial non-bugfix work, open an issue first to align on scope.
11+
12+
## Local Development Setup
13+
14+
1. Clone the repository and switch to `develop`:
15+
```bash
16+
git clone https://github.com/Dodelidoo-Labs/sonobarr.git
17+
cd sonobarr
18+
git checkout develop
19+
```
20+
2. Create `docker-compose.override.yml` with a local source mount and your reverse proxy network:
621
```yaml
722
services:
823
sonobarr:
@@ -15,27 +30,60 @@
1530
- ./config:/sonobarr/config
1631
- /etc/localtime:/etc/localtime:ro
1732
- ./src:/sonobarr/src
18-
#ports:
19-
# - "5000:5000"
33+
# ports:
34+
# - "5000:5000"
2035
networks:
2136
npm_proxy:
22-
ipv4_address: 192.168.97.23 #change as you need
37+
ipv4_address: 192.168.97.23 # update for your environment
2338

2439
networks:
2540
npm_proxy:
2641
external: true
2742
```
28-
4. Build the image with `sudo docker compose up -d` - later this will re-use the local image.
29-
5. Make code changes in `src/` or other required files.
30-
6. Test the changes by restarting the docker image `sudo docker compose down && sudo docker compose up -d` and clearing cache in browser.
31-
7. Once ready to commit, make sure the build still works as well `sudo docker compose down -v --remove-orphans && sudo docker system prune -a --volumes -f && sudo docker compose up -d`.
32-
33-
### Important
34-
- We only accept PR's, thus, open a Pull Request on the origin with your code changes against `develop` branch.
35-
- A maintainer will always review both code and functionality (User Testing), discuss/approve and finally merge the changes.
36-
- All changes will be adequately credited in changelog. It is up to you to leave `by xxx` comments in the code.
37-
- **Please only commit one feature per PR**.
38-
- If making substantial changes other than bug fixes please first open a Issue to discuss it.
39-
40-
**Always test your changes with at least two accounts - admin and a common user - in the app, in at least two distinct browser builds (such as safari and chrome, for example).**
41-
**Remember that if you made changes affecting config (that is, database or configuration) you have to delete the `./config` folder before rebuilding or restarting the app.**
43+
3. Build and start the local stack:
44+
```bash
45+
sudo docker compose up -d
46+
```
47+
4. Implement your changes in `src/`, `migrations/`, and related project files.
48+
49+
## Validation Requirements
50+
51+
Before opening a pull request, verify the change in a running container:
52+
53+
1. Restart and validate normal startup:
54+
```bash
55+
sudo docker compose down && sudo docker compose up -d --build
56+
```
57+
2. Confirm behavior in the UI and clear browser cache if needed.
58+
3. Run final clean start validation:
59+
```bash
60+
sudo docker compose down -v --remove-orphans
61+
sudo docker system prune -a --volumes -f
62+
sudo docker compose up -d --build
63+
```
64+
65+
### Manual Test Coverage
66+
67+
- Test as both an admin and a regular user account.
68+
- Test in at least two different browsers, for example Safari and Chrome.
69+
- Validate both developer expectations and end-user behavior.
70+
- Preserve backward compatibility and verify upgrade and downgrade paths for schema changes.
71+
72+
If your change affects configuration or database state, remove `./config` before rebuilding so migrations and initialization paths are tested from a clean state.
73+
74+
## Pull Request Quality Bar
75+
76+
- Include a clear summary of what changed and why.
77+
- Describe how you tested it, including user roles and browsers used.
78+
- Note any migration impact, compatibility concerns, or operational risks.
79+
- Expect maintainer review of both code quality and runtime behavior before merge.
80+
81+
## Attribution and Changelog
82+
83+
Maintainers curate release notes and changelog entries. If you want explicit credit text, include your preferred attribution in the pull request description. Contributors will always be attributed in changelog and release notes.
84+
85+
## AI-Assisted Contributions
86+
87+
AI-assisted contributions are allowed. You are responsible for the submitted code quality and correctness, including security and maintainability.
88+
89+
Low-quality, unreviewed, or non-functional generated code will be rejected. Repeated low-quality submissions after maintainer feedback can result in loss of contribution privileges.

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,17 +157,19 @@ All variables can be supplied in lowercase (preferred for `.env`) or uppercase (
157157
| `similar_artist_batch_size` | `10` | Number of cards sent per batch while streaming results. |
158158
| `auto_start` | `false` | Automatically start a discovery session on load. |
159159
| `auto_start_delay` | `60` | Delay (seconds) before auto-start kicks in. |
160-
| `sonobarr_superadmin_username` | `admin` | Username of the bootstrap admin account. |
161-
| `sonobarr_superadmin_password` | `change-me` | Password for the bootstrap admin. Set to a secure value before first launch. |
160+
| `sonobarr_superadmin_username` | `admin` | Username of the bootstrap admin account. If unset or blank, Sonobarr uses `admin`. |
161+
| `sonobarr_superadmin_password` | `change-me` | Password for the bootstrap admin. If unset or blank, Sonobarr uses `change-me`. |
162162
| `sonobarr_superadmin_display_name` | `Super Admin` | Friendly display name shown in the UI. |
163-
| `sonobarr_superadmin_reset` | `false` | Set to `true` **once** to reapply the bootstrap credentials on next start. |
163+
| `sonobarr_superadmin_reset` | `false` | Set to `true` and restart Sonobarr to reapply bootstrap credentials for the configured username (default `admin`). Set it back to `false` after that restart. |
164164
| `release_version` | `unknown` | Populated automatically inside the Docker image; shown in the footer. No need to set manually. |
165165
| `sonobarr_config_dir` | `/sonobarr/config` | Override where Sonobarr writes `app.db`, `settings_config.json`, and migrations. |
166166

167167
> ✅ Docker UID/GID mapping: set `PUID`/`PGID` in `.env`. The entrypoint fixes ownership and then drops privileges to that UID/GID.
168168
169169
> ℹ️ `secret_key` is mandatory. If missing, the app refuses to boot to prevent insecure session cookies. With Docker Compose, make sure the key exists in `.env` and that `.env` is declared via `env_file:` as shown above.
170170
171+
> ℹ️ Super-admin bootstrap applies only to the configured bootstrap username. With `sonobarr_superadmin_reset=true`, Sonobarr updates that user if it exists or creates it if it does not; it does not modify other admin accounts.
172+
171173
### OIDC SSO Configuration
172174

173175
| Key | Default | Description |

doc/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Technical Documentation
2+
3+
This directory contains developer-facing technical documentation for Sonobarr.
4+
5+
## Index
6+
7+
- [User management and approval flow](./user-management.md)
8+
- [Super-admin bootstrap behavior](./superadmin-bootstrap.md)

doc/superadmin-bootstrap.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Super-admin Bootstrap Behavior
2+
3+
## Scope
4+
5+
This document defines the startup bootstrap contract for the super-admin account.
6+
7+
## Environment Variables
8+
9+
Sonobarr reads these variables from environment values:
10+
11+
- `sonobarr_superadmin_username`
12+
- `sonobarr_superadmin_password`
13+
- `sonobarr_superadmin_display_name`
14+
- `sonobarr_superadmin_reset`
15+
16+
If username or password is missing or blank, Sonobarr falls back to:
17+
18+
- Username: `admin`
19+
- Password: `change-me`
20+
21+
## Startup Behavior
22+
23+
On startup, Sonobarr runs super-admin bootstrap once per process launch:
24+
25+
1. If no admin exists yet, it creates the bootstrap user and sets admin privileges.
26+
2. If at least one admin exists and `sonobarr_superadmin_reset` is not truthy, bootstrap exits without changes.
27+
3. If `sonobarr_superadmin_reset` is truthy and the configured bootstrap username already exists, Sonobarr updates its password, display name, and admin flag.
28+
4. If `sonobarr_superadmin_reset` is truthy and the configured bootstrap username does not exist, Sonobarr creates that user as admin.
29+
30+
Accepted truthy values for `sonobarr_superadmin_reset` are: `1`, `true`, `yes` (case-insensitive).
31+
32+
## Operational Notes
33+
34+
- `sonobarr_superadmin_reset` is evaluated only at startup, so a restart is required.
35+
- Leave `sonobarr_superadmin_reset=false` during normal operation.
36+
- For one-time recovery, set `sonobarr_superadmin_reset=true`, restart once, then switch it back to `false`.
37+
- Reset targets only the configured bootstrap username; other admin users are not rewritten.

doc/user-management.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# User Management And Artist Approval
2+
3+
## Scope
4+
5+
This document describes the user access flags that control account status, administrative privileges, and artist approval behavior.
6+
7+
## User Flags
8+
9+
The `users` table includes these access-control fields:
10+
11+
- `is_active`: Controls whether the user can authenticate.
12+
- `is_admin`: Grants access to admin routes and direct artist addition.
13+
- `auto_approve_artist_requests`: Allows non-admin users to add artists directly without waiting for manual admin approval.
14+
15+
## Admin UI Controls
16+
17+
The user management page at `/admin/users` provides toggles for:
18+
19+
- Active account
20+
- Admin privileges
21+
- Auto approve artist additions
22+
23+
Admins can set these flags when creating a user and when editing an existing user.
24+
25+
## Artist Addition And Request Flow
26+
27+
Server-side behavior is enforced in `DataHandler`:
28+
29+
- Users with `is_admin = true` or `auto_approve_artist_requests = true` can add artists directly to Lidarr.
30+
- Users without either flag submit a pending artist request for manual admin approval.
31+
- Unauthorized direct add attempts are rejected with an "Approval Required" message.
32+
33+
The frontend receives permission metadata from the socket `user_info` payload and renders the action button accordingly:
34+
35+
- Direct-add users see the default add action.
36+
- Manual-approval users see the request action.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""add auto approve artist requests flag to users
2+
3+
Revision ID: 20260303_01
4+
Revises: 20251222_01
5+
Create Date: 2026-03-03 10:00:00.000000
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy import inspect
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "20260303_01"
15+
down_revision = "20251222_01"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
bind = op.get_bind()
22+
inspector = inspect(bind)
23+
existing_columns = {column["name"] for column in inspector.get_columns("users")}
24+
25+
with op.batch_alter_table("users", schema=None) as batch_op:
26+
if "auto_approve_artist_requests" not in existing_columns:
27+
batch_op.add_column(
28+
sa.Column(
29+
"auto_approve_artist_requests",
30+
sa.Boolean(),
31+
nullable=False,
32+
server_default=sa.false(),
33+
)
34+
)
35+
36+
37+
def downgrade():
38+
bind = op.get_bind()
39+
inspector = inspect(bind)
40+
existing_columns = {column["name"] for column in inspector.get_columns("users")}
41+
42+
with op.batch_alter_table("users", schema=None) as batch_op:
43+
if "auto_approve_artist_requests" in existing_columns:
44+
batch_op.drop_column("auto_approve_artist_requests")

src/sonobarr_app/bootstrap.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from __future__ import annotations
22

3-
import secrets
4-
53
from sqlalchemy.exc import OperationalError, ProgrammingError
64

75
from .extensions import db
86
from .models import User
97

8+
DEFAULT_BOOTSTRAP_SUPERADMIN_PASSWORD = "change-me"
9+
1010

1111
def bootstrap_super_admin(logger, data_handler) -> None:
12+
"""Ensure the configured bootstrap super-admin user exists with the expected credentials."""
1213
try:
1314
admin_count = User.query.filter_by(is_admin=True).count()
1415
except (OperationalError, ProgrammingError) as exc:
@@ -22,10 +23,12 @@ def bootstrap_super_admin(logger, data_handler) -> None:
2223
username = data_handler.superadmin_username
2324
password = data_handler.superadmin_password
2425
display_name = data_handler.superadmin_display_name
25-
generated_password = False
2626
if not password:
27-
password = secrets.token_urlsafe(16)
28-
generated_password = True
27+
password = DEFAULT_BOOTSTRAP_SUPERADMIN_PASSWORD
28+
logger.warning(
29+
"Super-admin password was empty during bootstrap; using fallback default for username %s.",
30+
username,
31+
)
2932

3033
existing = User.query.filter_by(username=username).first()
3134
if existing:
@@ -52,11 +55,4 @@ def bootstrap_super_admin(logger, data_handler) -> None:
5255
db.session.rollback()
5356
return
5457

55-
if generated_password:
56-
logger.warning(
57-
"Generated super-admin credentials. Username: %s Password: %s",
58-
username,
59-
password,
60-
)
61-
else:
62-
logger.info("Super-admin %s %s.", username, action)
58+
logger.info("Super-admin %s %s.", username, action)

src/sonobarr_app/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010

1111
class User(UserMixin, db.Model):
12+
"""Application user account with authentication and role flags."""
13+
1214
__tablename__ = "users"
1315

1416
id = db.Column(db.Integer, primary_key=True)
@@ -21,6 +23,7 @@ class User(UserMixin, db.Model):
2123
listenbrainz_username = db.Column(db.String(120), nullable=True)
2224
is_admin = db.Column(db.Boolean, default=False, nullable=False)
2325
is_active = db.Column(db.Boolean, default=True, nullable=False)
26+
auto_approve_artist_requests = db.Column(db.Boolean, default=False, nullable=False)
2427
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
2528
updated_at = db.Column(
2629
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False
@@ -39,7 +42,10 @@ def name(self) -> str:
3942
return self.display_name or self.username
4043

4144
def __repr__(self) -> str: # pragma: no cover - representation helper
42-
return f"<User id={self.id} username={self.username!r} admin={self.is_admin}>"
45+
return (
46+
f"<User id={self.id} username={self.username!r} "
47+
f"admin={self.is_admin} auto_approve={self.auto_approve_artist_requests}>"
48+
)
4349

4450

4551
class ArtistRequest(db.Model):

0 commit comments

Comments
 (0)