Compare commits

...

72 commits

Author SHA1 Message Date
github-actions[bot]
6975589341 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.905687%2Brev-1327e798cb055f96f92685df444e9a2c326ab5ed/019bb874-9b65-73ec-9dd5-8f14598e59e0/source.tar.gz' (2026-01-12)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.906484%2Brev-1cd347bf3355fce6c64ab37d3967b4a2cb4b878c/019bfb68-fb8e-7f55-bb2a-5bee98516c95/source.tar.gz' (2026-01-25)
2026-01-29 14:19:01 +00:00
dependabot[bot]
5df0f028e2 chore(deps): bump DeterminateSystems/update-flake-lock from 27 to 28
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 27 to 28.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v27...v28)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-version: '28'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 08:26:45 +00:00
dependabot[bot]
8393e053b9 chore(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-27 08:26:31 +00:00
Martin Wimpress
7a63368963
docs(readme): remove uncertain RTMPS support statement
The statement about untested RTMPS support was speculative and
potentially confusing to users. Removed as it has now been verified as
working.

Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:22:11 +00:00
Martin Wimpress
da76e1c219
chore: symlink Dockerfile to Containerfile
Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:09:33 +00:00
Martin Wimpress
2bda8192f1
docs(agents): add AGENTS.md for AI agent context
Provides comprehensive project documentation including:
- Overview of Stream Sprout RTMP restreaming tool
- Tech stack and build instructions
- Code style and linting requirements
- Project structure and configuration details
- Commit guidelines and security considerations

Signed-off-by: Martin Wimpress <martin@wimpress.org>
2026-01-22 16:06:33 +00:00
github-actions[bot]
a811fe527e flake.lock: Update
Flake lock file updates:

• Updated input 'flake-schemas':
    'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz' (2024-07-26)
  → 'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.2.0/019a4a84-544d-7c59-b26d-e334e320c932/source.tar.gz' (2025-10-27)
• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808080%2Brev-ddae11e58c0c345bf66efbddbf2192ed0e58f896/01989f5e-b09d-7b09-9699-5d522e6f12ce/source.tar.gz' (2025-08-11)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.905687%2Brev-1327e798cb055f96f92685df444e9a2c326ab5ed/019bb874-9b65-73ec-9dd5-8f14598e59e0/source.tar.gz' (2026-01-12)
2026-01-22 15:59:17 +00:00
dependabot[bot]
c417dc1b10 chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:59:00 +00:00
dependabot[bot]
b3be5c43d9 chore(deps): bump DeterminateSystems/nix-installer-action from 19 to 21
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 19 to 21.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v19...v21)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '21'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:45 +00:00
dependabot[bot]
90eb31a5e5 chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:27 +00:00
dependabot[bot]
f76da1c62b chore(deps): bump anchore/scan-action from 6 to 7
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 6 to 7.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/RELEASE.md)
- [Commits](https://github.com/anchore/scan-action/compare/v6...v7)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:58:12 +00:00
dependabot[bot]
5d864aacb8 chore(deps): bump amannn/action-semantic-pull-request from 5 to 6
Bumps [amannn/action-semantic-pull-request](https://github.com/amannn/action-semantic-pull-request) from 5 to 6.
- [Release notes](https://github.com/amannn/action-semantic-pull-request/releases)
- [Changelog](https://github.com/amannn/action-semantic-pull-request/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/action-semantic-pull-request/compare/v5...v6)

---
updated-dependencies:
- dependency-name: amannn/action-semantic-pull-request
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-22 15:57:46 +00:00
Martin Wimpress
382dff7a48
chore: remove coreutils from container dependencies
Removes the coreutils package from the Alpine container dependencies.
2025-08-26 23:51:25 +01:00
Martin Wimpress
bd1676efa6
chore: bump version from 0.1.5 to 0.1.6
Update version number in the stream-sprout script for a new release
2025-08-26 23:35:43 +01:00
dependabot[bot]
a79d451d0c chore(deps): bump DeterminateSystems/magic-nix-cache-action from 8 to 13
Bumps [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action) from 8 to 13.
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/v8...v13)

---
updated-dependencies:
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-version: '13'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:29:51 +01:00
Martin Wimpress
c470ca46e4 refactor(dockerfile): switch from custom ffmpeg to jellyfin-ffmpeg
- Replace custom ffmpeg image with alpine base and jellyfin-ffmpeg package
- Add symlinks for ffmpeg and ffprobe to standard locations
- Set USER directive to run as nobody for improved security
2025-08-26 23:27:57 +01:00
dependabot[bot]
c156db1f64 chore(deps): bump DeterminateSystems/flake-checker-action from 9 to 12
Bumps [DeterminateSystems/flake-checker-action](https://github.com/determinatesystems/flake-checker-action) from 9 to 12.
- [Release notes](https://github.com/determinatesystems/flake-checker-action/releases)
- [Commits](https://github.com/determinatesystems/flake-checker-action/compare/v9...v12)

---
updated-dependencies:
- dependency-name: DeterminateSystems/flake-checker-action
  dependency-version: '12'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:27:12 +01:00
dependabot[bot]
7c57494674 chore(deps): bump DeterminateSystems/nix-installer-action from 16 to 19
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 16 to 19.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v16...v19)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-version: '19'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:54 +01:00
dependabot[bot]
57a1f800d2 chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:36 +01:00
dependabot[bot]
9fbbde4d6c chore(deps): bump DeterminateSystems/update-flake-lock from 24 to 27
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 24 to 27.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v24...v27)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-version: '27'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-26 23:26:18 +01:00
dependabot[bot]
1d7e3e8247 chore(deps): bump anchore/scan-action from 5 to 6
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 5 to 6.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:47:39 +01:00
dependabot[bot]
84a1e43137 chore(deps): bump DeterminateSystems/nix-installer-action from 15 to 16
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 15 to 16.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v15...v16)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 09:47:23 +01:00
github-actions[bot]
48c4943d72 flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.636163%2Brev-cd3e8833d70618c4eea8df06f95b364b016d4950/0192cd43-85cd-7ff3-b9be-a3f7995e917d/source.tar.gz?narHash=sha256-knnVBGfTCZlQgxY1SgH0vn2OyehH9ykfF8geZgS95bk%3D' (2024-10-26)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2505.808080%2Brev-ddae11e58c0c345bf66efbddbf2192ed0e58f896/01989f5e-b09d-7b09-9699-5d522e6f12ce/source.tar.gz?narHash=sha256-3sWA5WJybUE16kIMZ3%2BuxcxKZY/JRR4DFBqLdSLBo7w%3D' (2025-08-11)
2025-08-18 09:47:08 +01:00
dependabot[bot]
1a19e85d94 chore(deps): bump DeterminateSystems/nix-installer-action from 14 to 15
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 14 to 15.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v14...v15)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:23:19 +00:00
dependabot[bot]
3f91c0f573 chore(deps): bump anchore/scan-action from 4 to 5
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 4 to 5.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:17:00 +00:00
github-actions[bot]
e5a0db3a8f flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.635490%2Brev-f65141456289e81ea0d5a05af8898333cab5c53d/019237db-783b-7330-a22e-7d60c20ce855/source.tar.gz?narHash=sha256-pojbL/qteElw/nIXlN8kmHn/w6PQbEHr7Iz%2BWOXs0EM%3D' (2024-09-27)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.636163%2Brev-cd3e8833d70618c4eea8df06f95b364b016d4950/0192cd43-85cd-7ff3-b9be-a3f7995e917d/source.tar.gz?narHash=sha256-knnVBGfTCZlQgxY1SgH0vn2OyehH9ykfF8geZgS95bk%3D' (2024-10-26)
2024-11-12 10:16:48 +00:00
dependabot[bot]
6ec390f406 chore(deps): bump DeterminateSystems/magic-nix-cache-action from 7 to 8
Bumps [DeterminateSystems/magic-nix-cache-action](https://github.com/determinatesystems/magic-nix-cache-action) from 7 to 8.
- [Release notes](https://github.com/determinatesystems/magic-nix-cache-action/releases)
- [Commits](https://github.com/determinatesystems/magic-nix-cache-action/compare/v7...v8)

---
updated-dependencies:
- dependency-name: DeterminateSystems/magic-nix-cache-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-12 10:16:34 +00:00
dependabot[bot]
0bb875c287 chore(deps): bump DeterminateSystems/flake-checker-action from 8 to 9
Bumps [DeterminateSystems/flake-checker-action](https://github.com/determinatesystems/flake-checker-action) from 8 to 9.
- [Release notes](https://github.com/determinatesystems/flake-checker-action/releases)
- [Commits](https://github.com/determinatesystems/flake-checker-action/compare/v8...v9)

---
updated-dependencies:
- dependency-name: DeterminateSystems/flake-checker-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:21:39 +01:00
dependabot[bot]
901586e4bf chore(deps): bump DeterminateSystems/update-flake-lock from 23 to 24
Bumps [DeterminateSystems/update-flake-lock](https://github.com/determinatesystems/update-flake-lock) from 23 to 24.
- [Release notes](https://github.com/determinatesystems/update-flake-lock/releases)
- [Commits](https://github.com/determinatesystems/update-flake-lock/compare/v23...v24)

---
updated-dependencies:
- dependency-name: DeterminateSystems/update-flake-lock
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:21:18 +01:00
github-actions[bot]
ed5d5d136b flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.634418%2Brev-2527da1ef492c495d5391f3bcf9c1dd9f4514e32/019193c7-3325-7c5c-9d46-f2d05135ea41/source.tar.gz?narHash=sha256-XROVLf9ti4rrNCFLr%2BDmXRZtPjCQTW4cYy59owTEmxk%3D' (2024-08-24)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.635490%2Brev-f65141456289e81ea0d5a05af8898333cab5c53d/019237db-783b-7330-a22e-7d60c20ce855/source.tar.gz?narHash=sha256-pojbL/qteElw/nIXlN8kmHn/w6PQbEHr7Iz%2BWOXs0EM%3D' (2024-09-27)
2024-09-30 17:21:08 +01:00
dependabot[bot]
5aa579111e chore(deps): bump DeterminateSystems/nix-installer-action from 13 to 14
Bumps [DeterminateSystems/nix-installer-action](https://github.com/determinatesystems/nix-installer-action) from 13 to 14.
- [Release notes](https://github.com/determinatesystems/nix-installer-action/releases)
- [Commits](https://github.com/determinatesystems/nix-installer-action/compare/v13...v14)

---
updated-dependencies:
- dependency-name: DeterminateSystems/nix-installer-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 17:20:47 +01:00
Dale Visser
39c182ecf7 docs: Fix README link 2024-09-07 17:47:44 +01:00
github-actions[bot]
6984d04f7a flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633516%2Brev-8c50662509100d53229d4be607f1a3a31157fa12/0190f691-c019-7d99-b723-4b2dd6dfd38f/source.tar.gz?narHash=sha256-2ShmEaFi0kJVOEEu5gmlykN5dwjWYWYUJmlRTvZQRpU%3D' (2024-07-27)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.634418%2Brev-2527da1ef492c495d5391f3bcf9c1dd9f4514e32/019193c7-3325-7c5c-9d46-f2d05135ea41/source.tar.gz?narHash=sha256-XROVLf9ti4rrNCFLr%2BDmXRZtPjCQTW4cYy59owTEmxk%3D' (2024-08-24)
2024-09-07 17:47:20 +01:00
dependabot[bot]
1cb4c8ced3 chore(deps): bump anchore/scan-action from 3 to 4
Bumps [anchore/scan-action](https://github.com/anchore/scan-action) from 3 to 4.
- [Release notes](https://github.com/anchore/scan-action/releases)
- [Changelog](https://github.com/anchore/scan-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anchore/scan-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: anchore/scan-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:58 +01:00
dependabot[bot]
a9ed96eaea chore(deps): bump docker/build-push-action from 4 to 6
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:32 +01:00
dependabot[bot]
43d6b9ad88 chore(deps): bump docker/setup-buildx-action from 2 to 3
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-07 17:46:11 +01:00
Alan Pope
8e3b4dc089 chore: remove armhf snap build
The armhf snap hasn't been published, and I doubt anyone would use it if it were. It also blocks other architectures and revisions from being reviewed as it fails review in the store. Other architectures do not fail.

```
Found files with executable stack. This adds PROT_EXEC to mmap(2) during mediation which may cause security denials. Either adjust your program to not require an executable stack, strip it with 'execstack --clear-execstack ...' or remove the affected file from your snap. Affected files: usr/lib/arm-linux-gnueabihf/libx264.so.164 functional-snap-v2_execstack 
```
2024-08-02 15:36:32 +01:00
dependabot[bot]
f1b552c2bd
chore(deps): bump actions/upload-artifact from 2 to 4 (#38)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-30 10:37:31 +01:00
Alan Pope
9de404f4b2
feat: Add SBOM generation and vulnerability scanning in workflows (#39)
* feat: generate container sbom during release

* No need to publish separately, it's automatic

* feat: Add regular vulnerability scanning

* syntax

* specify container file

* vital missing step

* Display grype output in the log in table format
2024-07-30 10:37:08 +01:00
github-actions[bot]
84b36880cb flake.lock: Update
Flake lock file updates:

• Updated input 'flake-schemas':
    'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.3/0190b841-54d3-7b7a-8550-24942bc38caf/source.tar.gz?narHash=sha256-c2AZH9cOnSpPXV8Lwy19/I8EgW7G%2BE%2BZh6YQBZZwzxI%3D' (2024-07-15)
  → 'https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz?narHash=sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw%3D' (2024-07-26)
• Updated input 'nixpkgs':
    'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633334%2Brev-63d37ccd2d178d54e7fb691d7ec76000740ea24a/0190d847-0241-7628-8ab0-d49f442300f4/source.tar.gz?narHash=sha256-7cCC8%2BTdq1%2B3OPyc3%2BgVo9dzUNkNIQfwSDJ2HSi2u3o%3D' (2024-07-21)
  → 'https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633516%2Brev-8c50662509100d53229d4be607f1a3a31157fa12/0190f691-c019-7d99-b723-4b2dd6dfd38f/source.tar.gz?narHash=sha256-2ShmEaFi0kJVOEEu5gmlykN5dwjWYWYUJmlRTvZQRpU%3D' (2024-07-27)
2024-07-29 11:36:42 +01:00
Martin Wimpress
198c807a07
fix: check the version of bash is new enough 2024-07-26 07:31:47 +01:00
Martin Wimpress
5514c3da26 fix: actually wrap the runtime requirement in the nix package 2024-07-26 07:22:29 +01:00
Martin Wimpress
a11f8f57b0 feat: add show_info() 2024-07-25 14:28:03 +01:00
Martin Wimpress
544822aaa9 ci: run test builds if ci jobs change 2024-07-25 14:21:23 +01:00
Martin Wimpress
4495463a74 ci: use the actual stream-sprout version for tagging container releases 2024-07-25 14:21:23 +01:00
Martin Wimpress
46d611bbda docs: update bug report template 2024-07-25 10:36:07 +01:00
Martin Wimpress
3ae05ef362 style: remove quotes from example yaml 2024-07-25 10:36:07 +01:00
Martin Wimpress
e25f977afd docs: add warnings about exposing Stream Sprout on the public internet 2024-07-25 10:04:27 +01:00
Martin Wimpress
c78953780a feat: expand ~ to $HOME in the yaml parser 2024-07-25 09:00:48 +01:00
Martin Wimpress
132b240133 ci: tag containers with alpine so I can namespace future variants 2024-07-25 08:55:16 +01:00
Martin Wimpress
7b548aa7df refactor: give the snap artefact a more descriptive name 2024-07-25 08:25:03 +01:00
Martin Wimpress
6f89206695 fix: get current version from stream-sprout VERSION 2024-07-25 08:25:03 +01:00
Martin Wimpress
86352205af refactor: move test-snap-build alongside the other test build jobs 2024-07-25 08:25:03 +01:00
Martin Wimpress
9830543ac5 docs: minor updates for clarity 2024-07-25 08:25:03 +01:00
Martin Wimpress
f20bde1521 docs: add snap to install instructions 2024-07-25 08:25:03 +01:00
Martin Wimpress
cb86dfc5f4 chore: white space clean up 2024-07-25 08:25:03 +01:00
Martin Wimpress
ec278996b9 refactor: change awk to mawk in snapcraft.yaml; conform to Ubuntu defaults
Ubuntu ships mawk by default, Stage mawk in the snap so the behaviour between the .deb and snap is consistent.
2024-07-25 08:25:03 +01:00
Martin Wimpress
1cc2100527 docs: update versions in README 2024-07-25 08:25:03 +01:00
Martin Wimpress
5801fecb3d fix: remove incorrect query parameters from server URL 2024-07-25 08:25:03 +01:00
Martin Wimpress
1f5231c6e9 fix: get the complete video codec information in stream_details() 2024-07-25 08:25:03 +01:00
Martin Wimpress
cd6a8185c6 fix: avoid injection of substitution commands when parsing yaml 2024-07-25 01:39:10 +01:00
Martin Wimpress
f3b1271813 docs: update documentation to explain the new server config 2024-07-24 23:14:45 +01:00
Martin Wimpress
830edfabab refactor: add get_server_url() to validate server configuration 2024-07-24 23:14:45 +01:00
Martin Wimpress
f0262fbd38 refactor: update example yaml for new server configuration 2024-07-24 23:14:45 +01:00
Alan Pope
0eba5601d7
feat: Add snap support (#23)
* WIP: snapcraft config

* chore: tidy up snap workflow

* fix: update ld_library_path

* fix the version of the snap

This uses a combination of most recent git tag and short rev.

* Add git as a build package

Required because we have a dump plugin and a nill plugin which pull in next to nothing. Making it hard to do a version stamp without the git command
2024-07-24 19:23:58 +01:00
Martin Wimpress
a79438f0d2 fix: do not build STREAM_TEE inside a subshell. fixes #25
Iterate over the variables already exposed by parse_yaml and avoid populating the global variable STREAM_TEE inside a subshell because when a subshell exits the variables are reset.
2024-07-24 18:06:43 +01:00
Martin Wimpress
4928a51bd1 ci: add build/publish container image 2024-07-24 02:03:58 +01:00
Martin Wimpress
dda746f103 ci: add build container image 2024-07-24 02:03:58 +01:00
Martin Wimpress
4e32f890bc docs: update README with details about using the container 2024-07-24 02:03:58 +01:00
Martin Wimpress
f0916a091b feat: add Containerfile 2024-07-24 02:03:58 +01:00
Martin Wimpress
7998737c0e fix: also actually parse the config file 2024-07-24 02:03:58 +01:00
Martin Wimpress
8ca09f94b5
chore: bump version to 0.1.5 2024-07-23 23:20:23 +01:00
18 changed files with 618 additions and 97 deletions

View file

@ -21,21 +21,25 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Stream Sprout output**
Run `stream-sprout` and include the output of the failure below:
**Stream Sprout output and logs**
Run `stream-sprout` and include the output, along with **redacted logs** (*remove your IP address and keys*), and wrap it in the collapsible markdown section below.
<details>
<summary>Stream Sprout output</summary>
```text
stream-sprout output here
stream-sprout output from the time of the error here
```
<summary>Stream Sprout logs</summary>
```text
stream-sprout logs from the time of the error here
```
</details>
**System information**
- OS: [e.g. Ubuntu 20.04]
- stream-sprout version: [e.g. 0.1.0]
- FFmpeg version: [e.g. 4.2.4]
Run `stream-sprout --info` and include the output here.
**Screenshots**
If applicable, add screenshots to help explain your problem.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

@ -13,9 +13,9 @@ jobs:
name: Flake Checker
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: DeterminateSystems/nix-installer-action@v13
- uses: DeterminateSystems/magic-nix-cache-action@v7
- uses: DeterminateSystems/flake-checker-action@v8
- uses: DeterminateSystems/nix-installer-action@v21
- uses: DeterminateSystems/magic-nix-cache-action@v13
- uses: DeterminateSystems/flake-checker-action@v12

View file

@ -10,11 +10,11 @@ jobs:
name: Flake Lock Updater
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: DeterminateSystems/nix-installer-action@v13
- uses: DeterminateSystems/magic-nix-cache-action@v7
- uses: DeterminateSystems/update-flake-lock@v23
- uses: DeterminateSystems/nix-installer-action@v21
- uses: DeterminateSystems/magic-nix-cache-action@v13
- uses: DeterminateSystems/update-flake-lock@v28
with:
pr-title: "chore: update flake.lock"

View file

@ -15,7 +15,7 @@ jobs:
name: Validate pull request title
runs-on: ubuntu-22.04
steps:
- uses: amannn/action-semantic-pull-request@v5
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View file

@ -10,7 +10,7 @@ jobs:
name: Shellcheck
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:

View file

@ -16,7 +16,7 @@ jobs:
name: "Check versions ⚖️"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: "Compare App and Git versions 🟰"
@ -37,7 +37,7 @@ jobs:
name: "Build Release 👨‍🔧"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: "Build .deb 🍥"
env:
DEBFULLNAME: "Martin Wimpress"
@ -69,7 +69,7 @@ jobs:
id-token: "write"
contents: "read"
steps:
- uses: "actions/checkout@v4"
- uses: "actions/checkout@v6"
with:
ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}"
- uses: "DeterminateSystems/nix-installer-action@main"
@ -79,3 +79,45 @@ jobs:
visibility: "public"
name: "wimpysworld/stream-sprout"
tag: "${{ inputs.tag }}"
publish-container:
needs: [version-check]
name: "Publish Container 🐋"
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Container Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get stream-sprout version 🔢
id: get_version
run: |
STREAM_SPROUT_VER=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)
echo "STREAM_SPROUT_VER=$STREAM_SPROUT_VER" >> $GITHUB_ENV
- name: "Build Container 🐋"
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
push: true
tags: |
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ env.STREAM_SPROUT_VER }}-alpine
ghcr.io/${{ github.repository }}:${{ github.sha }}-alpine
platforms: linux/amd64, linux/arm64
- name: "Generate SBOM"
uses: anchore/sbom-action@v0
with:
image: ghcr.io/${{ github.repository }}:latest-alpine
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}
- name: Logout from Container Registry
run: docker logout ghcr.io

35
.github/workflows/scan-container.yaml vendored Normal file
View file

@ -0,0 +1,35 @@
name: "Vulnerability 🐞 scan 🔍 container"
on:
schedule:
- cron: "0 10 * * 2"
workflow_dispatch:
jobs:
vulnerability-scan:
name: "Build and scan"
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: build local container
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
tags: localbuild/testimage:latest
push: false
load: true
- name: Scan image
uses: anchore/scan-action@v7
with:
image: "localbuild/testimage:latest"
output-format: table
- name: Inspect action report
run: cat ${{ steps.scan.outputs.table }}

View file

@ -5,18 +5,24 @@ on:
branches:
- main
paths:
- .github/workflows/*.yml
- stream-sprout
- debian/**
- flake.nix
- package.nix
- Containerfile
- snap/snapcraft.yaml
push:
branches:
- main
paths:
- .github/workflows/*.yml
- stream-sprout
- debian/**
- flake.nix
- package.nix
- Containerfile
- snap/snapcraft.yaml
workflow_dispatch:
# TODO: arm64 runner
@ -27,7 +33,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: "Build & Test .deb 🍥"
env:
DEBFULLNAME: "Martin Wimpress"
@ -49,13 +55,69 @@ jobs:
contents: "read"
steps:
- name: "Checkout 🥡"
uses: "actions/checkout@v4"
uses: "actions/checkout@v6"
- name: "Install Nix ❄️"
uses: "DeterminateSystems/nix-installer-action@v13"
uses: "DeterminateSystems/nix-installer-action@v21"
- name: "Enable Magic Nix Cache 🪄"
uses: "DeterminateSystems/magic-nix-cache-action@v7"
uses: "DeterminateSystems/magic-nix-cache-action@v13"
- name: "Build & Test .nix ❄️"
run: |
nix build .#stream-sprout
tree ./result
test-container-build:
runs-on: ubuntu-24.04
steps:
- name: "Checkout 🥡"
uses: actions/checkout@v6
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Container Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get stream-sprout version 🔢
id: get_version
run: |
STREAM_SPROUT_VER=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)
echo "STREAM_SPROUT_VER=$STREAM_SPROUT_VER" >> $GITHUB_ENV
- name: "Build Container 🐋"
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
push: false
tags: |
ghcr.io/${{ github.repository }}:latest-alpine
ghcr.io/${{ github.repository }}:${{ env.STREAM_SPROUT_VER }}-alpine
ghcr.io/${{ github.repository }}:${{ github.sha }}-alpine
platforms: linux/amd64, linux/arm64
- name: Logout from Container Registry
run: docker logout ghcr.io
test-snap-build:
runs-on: ubuntu-24.04
steps:
- name: Checkout 🥡
uses: actions/checkout@v6
- name: Build snap 🐊
uses: snapcore/action-build@v1
id: snapcraft
- name: Show log 🪵
if: ${{ failure() }}
run: |
cat /home/runner/.local/state/snapcraft/log/snapcraft*.log
- name: Review snap 🕵️
uses: diddlesnaps/snapcraft-review-action@v1
with:
snap: ${{ steps.snapcraft.outputs.snap }}
isClassic: false
- name: Upload artifacts ⤴️
uses: actions/upload-artifact@v6
with:
name: stream-sprout-snap
path: ${{ steps.snapcraft.outputs.snap}}

105
AGENTS.md Normal file
View file

@ -0,0 +1,105 @@
# AGENTS.md
## Overview
Stream Sprout is a bash-based RTMP restreaming tool that forwards a single video source (from OBS Studio or similar) to multiple destinations like Twitch, YouTube, Owncast, and Peertube simultaneously. It uses FFmpeg's tee muxer to copy streams without transcoding.
## Tech Stack
- **Language:** Bash 5.0+ (single script: `stream-sprout`)
- **Runtime dependency:** FFmpeg (RTMP server and restreaming)
- **Configuration:** YAML parsed via awk/sed
- **Packaging:** Nix flake, Debian .deb, Snap, Docker/Podman
## Build and Run Commands
```bash
# Run directly (requires ffmpeg, bash 5.0+, awk, grep, sed)
./stream-sprout --config stream-sprout.yaml
# Show version and FFmpeg info
./stream-sprout --version
# Show system info (useful for bug reports)
./stream-sprout --info
# Nix build
nix build
# Enter development shell with all dependencies
nix develop
# Docker build and run
docker build -t stream-sprout .
docker run -p 1935:1935 -it -v $PWD:/data stream-sprout --config /data/stream-sprout.yaml
```
## Linting
ShellCheck is enforced via CI on all pull requests.
```bash
# Run locally before committing
shellcheck stream-sprout
```
The script includes `# shellcheck disable=SC2154` for variables set dynamically via `eval` from YAML parsing.
## Code Style
- Bash scripts use `#!/usr/bin/env bash`
- Functions use `function name() {}` syntax
- Use `local` for function-scoped variables
- Use `readonly` for constants
- Validation with informative error messages using Unicode icons and ANSI colours
- Version is tracked in the script: `readonly VERSION="x.y.z"`
## Project Structure
```
stream-sprout # Main bash script (single file)
stream-sprout.yaml # Local config (gitignored)
stream-sprout.yaml.example # Example configuration
package.nix # Nix package definition
devshell.nix # Nix development shell
flake.nix # Nix flake
Dockerfile # Alpine-based container
```
## Configuration
YAML config with two main sections:
- `server:` - RTMP server settings (ip, port, app, key, archive options)
- `services:` - Destination services (each with enabled, rtmp_server, key)
Config search order: `./stream-sprout.yaml`, `$XDG_CONFIG_HOME/stream-sprout.yaml`, `/etc/stream-sprout.yaml`
## PR and Commit Guidelines
- **Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/)**
- PR titles are validated against Conventional Commits format
- Single-commit PRs must have matching PR title and commit message
- ShellCheck must pass with no warnings
Common prefixes: `feat:`, `fix:`, `chore:`, `refactor:`, `docs:`
## Version Updates
When changing version:
1. Update `VERSION` in `stream-sprout` script
2. The Nix package extracts version automatically from the script
## Constraints
- Requires bash 5.0 or newer
- FFmpeg must be available on PATH
- RTMP only (no RTMPS support currently)
- FFmpeg does not enforce stream keys (documented security limitation)
## Security Considerations
- Stream keys are stored in plain text in YAML config
- FFmpeg accepts any RTMP stream on the configured port regardless of app/key path
- Do not expose the RTMP port to untrusted networks without additional protection (VPN, firewall, SSH tunnel)

17
Containerfile Normal file
View file

@ -0,0 +1,17 @@
FROM alpine:latest
RUN apk add --no-cache --update \
bash \
jellyfin-ffmpeg \
gawk \
grep \
sed
RUN ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/local/bin/ffmpeg && \
ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe /usr/local/bin/ffprobe
COPY --chown=nobody:nobody --chmod=755 stream-sprout /usr/bin/stream-sprout
EXPOSE 1935
USER nobody
ENTRYPOINT [ "stream-sprout" ]

1
Dockerfile Symbolic link
View file

@ -0,0 +1 @@
Containerfile

129
README.md
View file

@ -23,9 +23,9 @@ Stream Sprout 🌱 is a simple, self-contained, and easy-to-use solution for str
<img src=".github/demo.gif" alt="Stream Sprout" width="505" height="415"/>
</div>
It uses [FFmpeg](https://ffmpeg.org/) to receive the video stream from OBS Studio (or anything that can publish a RTMP stream) and then restreams it to multiple destinations; providing similar functionality as services like Restream.io and Livepush.io but without the need to pay 💸 for a third-party service or run something like nginx with the [RTMP module](https://github.com/arut/nginx-rtmp-module).
It uses [FFmpeg](https://ffmpeg.org/) to receive the video stream from OBS Studio (or any encoder that can produce RTMP) and then restreams it to multiple destinations. This provides similar functionality as services like Restream.io and Livepush.io but without the need to pay 💸 for a third-party service or run something like nginx with the [RTMP module](https://github.com/arut/nginx-rtmp-module).
Stream Sprout is configured with a simple YAML file and designed to be run on the same computer as your [OBS Studio](https://obsproject.com/) instance (it can be run remotely too) and does not require root privileges.
Stream Sprout is configured with a simple YAML file and designed to be run on the same computer as your [OBS Studio](https://obsproject.com/) instance (it can be run remotely, [**with appropriate security measures**](#-ffmpeg-rtmp-server-accepts-any-rtmp-stream-on-the-listening-port-), and does not require root privileges.
There is no transcoding or processing of the video stream 🎞️
The stream is received and then restreamed to the destinations you configure without modification.
@ -39,7 +39,7 @@ Stream Sprout is developed on Linux 🐧 and should work on macOS 🍏 or any ot
## Get Started
- [Install](#installation) Stream Sprout 🧑‍💻
- [Configure](#configuration) Stream Sprout 🧑‍💻
- [Configure](#configure-stream-sprout) Stream Sprout 🧑‍💻
- [Configure](#configure-obs-studio) OBS Studio 🎛️
- Start `stream-sprout` ⌨️
- Click the *Start Streaming* button in OBS Studio 🖱️
@ -52,7 +52,7 @@ Stream Sprout is developed on Linux 🐧 and should work on macOS 🍏 or any ot
### Debian
- Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️
- Install it with `apt-get install ./stream-sprout_0.1.4-1_all.deb`.
- Install it with `apt-get install ./stream-sprout_0.1.5-1_all.deb`.
### macOS
@ -78,10 +78,66 @@ See the flake on FlakeHub for more details:
- <https://flakehub.com/flake/wimpysworld/stream-sprout>
### Snap
[![stream-sprout](https://snapcraft.io/stream-sprout/badge.svg)](https://snapcraft.io/stream-sprout)
For Linux distributions that support snap packages, Stream Sprout is available from the Snap Store 🛍️
```shell
sudo snap install stream-sprout
```
### Ubuntu
- Download the Stream Sprout .deb package from the [releases page](https://github.com/wimpysworld/stream-sprout/releases) 📦️
- Install it with `apt-get install ./stream-sprout_0.1.4-1_all.deb`.
- Install it with `apt-get install ./stream-sprout_0.1.5-1_all.deb`.
### Docker & Podman
#### Pull the container
The Stream Sprout container image is available from the GitHub Container Registry for amd64 and arm64.
To pull the latest container image:
```shell
docker pull ghcr.io/wimpysworld/stream-sprout:latest-alpine
```
Or if you want a specific version:
```shell
docker pull ghcr.io/wimpysworld/stream-sprout:0.1.5-alpine
```
#### Run the container
The `stream-sprout.yaml` configuration file will be on the host computer so you need mount a volume to access it from the container.
If you have already pulled the container image, you can run Stream Sprout with:
```shell
docker run -p 1935:1935 -it -v $PWD:/data stream-sprout --config /data/stream-sprout.yaml
```
If you have not pulled or built the container image, you can run Stream Sprout with:
```shell
docker run -p 1935:1935 -it -v $PWD:/data ghcr.io/wimpysworld/stream-sprout:alpine-latest --config /data/stream-sprout.yaml
```
- The `-p 1935:1935` part will expose the RTMP server port `1935` on the host computer.
- If you have configured Stream Sprout to use a different port, you should change the port number here too.
- The `-it` options will run the container in interactive mode.
- The `-v $PWD:/data` part will mount your current directory `$PWD` as `/data` within the container, allowing you to access your files using the `/data` path.
#### Build the container
Build the Stream Sprout container image:
```shell
docker build -t stream-sprout .
```
### From source
@ -105,24 +161,43 @@ If you don't specify a configuration file, Stream Sprout will look for a configu
### Server
Here's an example configuration for the Stream Sprout `server:` section.
```yaml
server:
url: "rtmp://127.0.0.1:1935"
key: "create your key with uuidgen here"
ip: 127.0.0.1
port: 1935
app: sprout
key: create your key with uuidgen here
archive_stream: false
archive_path: "${HOME}/Streams"
archive_path: ~/Streams
```
The `server:` section is used to configure the RTMP server that Stream Sprout creates; it must be an RTMP URL.
The default port for RTMP is `1935`, but you can use any port you like.
If you remotely host Stream Sprout, you should use an IP address in the `url:` that accessible by your computer that runs OBS Studio and also set `key:` to a secure value to prevent unauthorized access.
Running `uuidgen` will generate a suitable value.
The `server:` section is used to configure the RTMP server that Stream Sprout creates.
- The default `ip` address is `127.0.0.1`. Use `0.0.0.0` to allow connections to any network interface.
- If you remotely host Stream Sprout, use an IP address that is accessible by your computer that runs OBS Studio.
- The default `port` for RTMP is `1935`, but you can use any port between `1024` and `65535`.
- The default `app` name is `sprout`, but you can use any name you like.
- Set `key:` to a secure value to prevent unauthorized access. Running `uuidgen` will generate a suitable value.
The IP address, port, app name and key are composed to create the RTMP URL that you will use in OBS Studio.
For example, `rtmp://ip:port/app/key`.
### 🚨 FFMPEG WILL ACCEPT ANY RTMP STREAM ON THE CORRECT PORT 🚨
**FFmpeg does not currently enforce `app` or `key` paths for its incoming RTMP server.**
**Regardless of the `app` or `key` you set in the Stream Sprout YAML FFmpeg will accept *any* incoming stream on the correct `port`**
⚠️ Do not expose the Stream Sprout RTMP server to the public internet without additional security measures ⚠️
- Consider using a VPN or SSH tunnel to secure the connection 🔐
- Or firewall the RTMP port to only allow connections from trusted IP addresses 🔥🧱
- See the [Limitations section](#limitations) section below for more information.
#### Archive streams
If `archive_stream:` is `true` Stream Sprout will archive the stream to disk in the directory specified by `archive_path:`.
If `archive_path:` is not accessible, Stream Sprout will fallback to using the current working directory.
Here's an example configuration for the Stream Sprout `server:` section.
### Services
`services:` are arbitrarily named.
@ -181,24 +256,32 @@ services:
- Go to `Settings` > `Stream`
- Select `Custom` from the `Service` dropdown
- Copy the server `url:` from your Stream Sprout configuration to the `Server` field:
- `rtmp://127.0.0.1:1935` (*default*)
- `rtmp://127.0.0.1:1935/sprout` (*default*)
- Copy the `key:` (if you specified one) from your Stream Sprout configuration to the `Stream Key` field
![OBS Studio Stream Settings](.github/obs-settings.png)
## Limitations
- Stream Sprout does not support secure RTMP (RTMPS) at this time.
- *At least I don't think it does, but I haven't fully tested it.*
- Protecting the Stream Sprout RTMP server with a key does not work
- FFmpeg does not currently support enforcing RTMP stream app paths or keys
- https://www.reddit.com/r/ffmpeg/comments/s4keuu/enforce_rtmp_stream_keys_and_strict_paths/
- https://patchwork.ffmpeg.org/project/ffmpeg/patch/20190925185708.70924-1-unique.will.martin@gmail.com/
```
[rtmp @ 0x2ca9be80] Unexpected stream STREAMBOMB, expecting c5b559b2-589d-4925-a28e-20d1954fd6c5
Last message repeated 1 times
```
- Each destination you add will increase your bandwidth requirements.
## References
These are some of the references used to create this project:
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites
- https://ffmpeg.org/ffmpeg-protocols.html#rtmp
- https://ffmpeg.org/ffmpeg-formats.html#flv
- https://ffmpeg.org/ffmpeg-formats.html#tee-1
- https://obsproject.com/forum/resources/obs-studio-stream-to-multiple-platforms-or-channels-at-once.932/
- https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
- https://trac.ffmpeg.org/wiki/EncodingForStreamingSites
- https://ffmpeg.org/ffmpeg-protocols.html#rtmp
- https://ffmpeg.org/ffmpeg-formats.html#flv
- https://ffmpeg.org/ffmpeg-formats.html#tee-1
- https://obsproject.com/forum/resources/obs-studio-stream-to-multiple-platforms-or-channels-at-once.932/
- https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
- https://dev.to/ajeetraina/run-ffmpeg-within-a-docker-container-a-step-by-step-guide-c0l
- https://github.com/jrottenberg/ffmpeg

20
flake.lock generated
View file

@ -2,12 +2,12 @@
"nodes": {
"flake-schemas": {
"locked": {
"lastModified": 1721078157,
"narHash": "sha256-c2AZH9cOnSpPXV8Lwy19/I8EgW7G+E+Zh6YQBZZwzxI=",
"rev": "29e53dd33b1a38f235ef073e768c62821cb6146e",
"revCount": 66,
"lastModified": 1761577921,
"narHash": "sha256-eK3/xbUOrxp9fFlei09XNjqcdiHXxndzrTXp7jFpOk8=",
"rev": "47849c7625e223d36766968cc6dc23ba0e135922",
"revCount": 107,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.3/0190b841-54d3-7b7a-8550-24942bc38caf/source.tar.gz"
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.2.0/019a4a84-544d-7c59-b26d-e334e320c932/source.tar.gz"
},
"original": {
"type": "tarball",
@ -16,12 +16,12 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1721548954,
"narHash": "sha256-7cCC8+Tdq1+3OPyc3+gVo9dzUNkNIQfwSDJ2HSi2u3o=",
"rev": "63d37ccd2d178d54e7fb691d7ec76000740ea24a",
"revCount": 633334,
"lastModified": 1769318308,
"narHash": "sha256-Mjx6p96Pkefks3+aA+72lu1xVehb6mv2yTUUqmSet6Q=",
"rev": "1cd347bf3355fce6c64ab37d3967b4a2cb4b878c",
"revCount": 906484,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.633334%2Brev-63d37ccd2d178d54e7fb691d7ec76000740ea24a/0190d847-0241-7628-8ab0-d49f442300f4/source.tar.gz"
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2511.906484%2Brev-1cd347bf3355fce6c64ab37d3967b4a2cb4b878c/019bfb68-fb8e-7f55-bb2a-5bee98516c95/source.tar.gz"
},
"original": {
"type": "tarball",

View file

@ -34,6 +34,8 @@ stdenv.mkDerivation rec {
installPhase = ''
runHook preInstall
install -Dm755 -t "$out/bin" stream-sprout
wrapProgram $out/bin/stream-sprout \
--prefix PATH : "${lib.makeBinPath runtimePaths}"
runHook postInstall
'';

53
snap/snapcraft.yaml Normal file
View file

@ -0,0 +1,53 @@
name: stream-sprout
base: core24
adopt-info: stream-sprout
summary: Restream video to multiple destinations
description: |
Restream a video source to multiple destinations such as Twitch, YouTube,
and Owncast
grade: stable
confinement: strict
platforms:
amd64:
build-on: [ amd64 ]
build-for: [ amd64 ]
arm64:
build-on: [ arm64 ]
build-for: [arm64 ]
parts:
stream-sprout:
after: [ deps ]
plugin: dump
source: .
build-packages:
- git
override-pull: |
craftctl default
craftctl set version=$(grep "^readonly VERSION" stream-sprout | cut -d'"' -f2)-$(git rev-parse --short HEAD)
prime:
- stream-sprout
- stream-sprout.yaml.example
- LICENSE
- SECURITY.md
deps:
plugin: nil
stage-packages:
- ffmpeg
- sed
- mawk
- grep
apps:
stream-sprout:
command: stream-sprout
environment:
LD_LIBRARY_PATH: $SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/pulseaudio:$SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/blas:$SNAP/usr/lib/$CRAFT_ARCH_BUILD_FOR/lapack
plugs:
- home
- removable-media
- network-bind
- network

View file

@ -5,7 +5,7 @@
stty -echoctl
readonly STREAM_SPROUT_YAML="stream-sprout.yaml"
readonly VERSION="0.1.4"
readonly VERSION="0.1.6"
function cleanup() {
echo -e " \e[31m\U26D4\e[0m Control-C"
@ -28,10 +28,61 @@ function show_help() {
echo ""
echo "Options:"
echo " --config <path> Specify a custom config file path."
echo " --info Show system information; useful when filing bug reports."
echo " --version Show version information."
echo " --help Display this help message."
}
function show_info() {
local CONTAINER_ENV
local CONTAINER_RUNTIME
local CONTAINER_RUNTIMES=("docker" "lxc" "podman")
local OS_KERNEL
local PRETTY_NAME
OS_KERNEL=$(uname -s)
if [ "${OS_KERNEL}" == "Darwin" ]; then
# Get macOS product name and version using swvers
if [ -x "$(command -v sw_vers)" ]; then
PRETTY_NAME="$(sw_vers -productName) $(sw_vers -productVersion)"
else
PRETTY_NAME="macOS"
fi
elif [ -e /etc/os-release ]; then
PRETTY_NAME=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
else
PRETTY_NAME="Unknown OS"
fi
echo -e "Operating System : ${PRETTY_NAME}"
# Check for container environment
if [ "${OS_KERNEL}" == "Linux" ]; then
if [ -n "${SNAP}" ]; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="snapd"
else
for runtime in "${CONTAINER_RUNTIMES[@]}"; do
if grep -qa ":/${runtime}/" /proc/1/cgroup; then
CONTAINER_ENV="Yes"
CONTAINER_RUNTIME="${runtime}"
break
else
CONTAINER_ENV="No"
CONTAINER_RUNTIME="Unknown"
fi
done
fi
echo -e "Containerized : ${CONTAINER_ENV}"
if [ "${CONTAINER_ENV,,}" == "yes" ]; then
echo -e "Container Runtime: ${CONTAINER_RUNTIME}"
fi
fi
echo -e "Stream Sprout : ${VERSION}"
echo -e "awk : $(awk --version | head -n 1)"
echo -e "bash : $(bash --version | head -n 1)"
echo -e "ffmpeg : $(ffmpeg -version | head -n 1)"
}
function show_version() {
echo -e "\e[92mStream Sprout\e[0m ${VERSION} using FFmpeg ${FFMPEG_VER}"
}
@ -46,6 +97,8 @@ function parse_yaml() {
w='[a-zA-Z0-9_]*'
fs=$'\034'
sed -ne "s|^\(${s}\):|\1|" \
-e 's|`||g;s|\$||g;' \
-e "s|~|${HOME}|g;" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}[\"']\(.*\)[\"']$s\$|\1${fs}\2${fs}\3|p" \
-e "s|^\(${s}\)\(${w}\)${s}:${s}\(.*\)${s}\$|\1${fs}\2${fs}\3|p" "${1}" |
awk -F"${fs}" '{
@ -101,30 +154,94 @@ function add_service() {
}
function get_stream_tee() {
local SERVICE=""
local SERVICE_ENABLED=""
local SERVICE_KEY=""
local SERVICE_NAME=""
local SERVICE_RTMP=""
local URI=""
local URI_ENV=""
local KEY_ENV=""
parse_yaml "${STREAM_SPROUT_YAML}" sprout_ | grep '^sprout_services_.*_enabled=' | while read -r SERVICES; do
SERVICE=$(echo "${SERVICES}" | cut -d'_' -f3)
ENABLED=$(echo "${SERVICES}" | cut -d'=' -f2 | tr -d \'\" )
if [[ "${ENABLED,,}" == "true" || "${ENABLED}" == "1" ]]; then
echo -e " \e[35m\U1F4E1\e[0m ${SERVICE}"
# Construct the variable name
URI_ENV="sprout_services_${SERVICE}_rtmp_server"
KEY_ENV="sprout_services_${SERVICE}_key"
# Use indirect expansion to get the value
URI="${!URI_ENV}${!KEY_ENV}"
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then
echo -e " \e[31m\U1F6AB\e[0m ${SERVICE} is not a valid RTMP service URL"
return
STREAM_TEE=""
# Iterate over all the sprout_services variables
for var in "${!sprout_services@}"; do
# Check the variable matches the pattern: sprout_services_*_enabled
if [[ "${var}" =~ ^sprout_services_.*_enabled$ ]]; then
# Derive the service name
# - First remove `sprout_services_` prefix from the beginning of the value stored in the variable $var.
# - Next remove the suffix `_enabled` from the end of the SERVICE_NAME variable's value.
SERVICE_NAME="${var#sprout_services_}"
SERVICE_NAME="${SERVICE_NAME%_enabled}"
# Get the value of the variable $var
SERVICE_ENABLED="${!var}"
if [[ "${SERVICE_ENABLED,,}" == "true" || "${SERVICE_ENABLED}" == "1" ]]; then
echo -e " \e[35m\U1F4E1\e[0m ${SERVICE_NAME}"
# TODO: This assumes that the RTMP URL and key are set in the YAML file.
# Construct the variable name
SERVICE_RTMP="sprout_services_${SERVICE_NAME}_rtmp_server"
SERVICE_KEY="sprout_services_${SERVICE_NAME}_key"
# Use indirect expansion to get the value
# By concatenating these two indirectly referenced values, URI
# is set to the full URI needed for streaming. For instance, if
# SERVICE_RTMP points to a variable holding rtmp://example.com/live
# and SERVICE_KEY points to a variable holding abcd1234, then URI
# would be set to rtmp://example.com/live/abcd1234.
URI="${!SERVICE_RTMP}/${!SERVICE_KEY}"
if [[ ! "${URI}" =~ ^rtmp://.* ]]; then
echo -e " \e[31m\U1F6AB\e[0m ${SERVICE_NAME} is not a valid RTMP service URL"
continue
fi
add_service "${URI}"
fi
add_service "${URI}"
fi
done
add_archive
}
function get_server_url() {
local asterisks=""
local key_length=0
# Check if the sprout_server_url is set and display a deprecation notice if it is
if [ -n "${sprout_server_url}" ]; then
echo -e " \e[31m\U1F6AB\e[0m server:"
echo -e " ╰─url: in the YAML is deprecated. Please configure ip: and port: instead."
exit 1
fi
# Validate the sprout_server_ip is valid
if [[ ! "${sprout_server_ip}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─ip: in the YAML is not valid. Falling back to '127.0.0.1'."
sprout_server_ip="127.0.0.1"
fi
# Validate the sprout_server_port is valid
if [[ ! "${sprout_server_port}" =~ ^[0-9]+$ ]] || [[ "${sprout_server_port}" -lt 1024 ]] || [[ "${sprout_server_port}" -gt 65535 ]]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─port: in the YAML is not valid. Must be between 1024 and 65535. Falling back to '1935'."
sprout_server_port="1935"
fi
# Check that sprout_server_app is not empty
if [ -z "${sprout_server_app}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─app: is not configured in the YAML. Falling back to 'sprout'."
sprout_server_app="sprout"
fi
# Check that sprout_server_key is not empty
if [ -z "${sprout_server_key}" ]; then
echo -e " \e[33m\U26A0\e[0m server:"
echo -e " ╰─key: is not configured in the YAML. \e[1;97mYour Stream Sprout server is unprotected.\e[0m"
fi
sprout_server_url="rtmp://${sprout_server_ip}:${sprout_server_port}/${sprout_server_app}"
if [ -n "${sprout_server_key}" ]; then
# Calculate the length of sprout_server_key
key_length=${#sprout_server_key}
# Create a string of asterisks equal to the length of sprout_server_key
asterisks=$(printf "%*s" "${key_length}" "" | tr ' ' '*')
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}/${asterisks}"
# Append the sprout_server_key to the sprout_server_url
sprout_server_url+="/${sprout_server_key}"
else
echo -e " \e[36m\U1F310\e[0m ${sprout_server_url}"
fi
}
function stream_details() {
local AUDIO=""
local VIDEO=""
@ -145,7 +262,7 @@ function stream_details() {
AUDIO_FREQ=$(echo "${AUDIO}" | awk -F', ' '{print $2}' | awk '{print $1 " " $2}')
AUDIO_CHANNELS=$(echo "${AUDIO}" | awk -F', ' '{print $3}' | awk '{print $1}')
AUDIO_BITRATE=$(echo "${AUDIO}" | awk -F', ' '{print $5}' | awk '{print $1 " " $2}')
VIDEO_CODEC=$(echo "${VIDEO}" | awk -F': ' '{print $3}' | awk '{print $1}')
VIDEO_CODEC=$(echo "${VIDEO}" | awk -F', ' '{print $1}' | awk '{print $4 " " $5}')
VIDEO_FPS=$(echo "${VIDEO}" | awk -F', ' '{print $7}' | awk '{print $1 " " $2}')
VIDEO_RES=$(echo "${VIDEO}" | awk -F', ' '{print $5}' | awk '{print $1}')
VIDEO_BITRATE=$(echo "${VIDEO}" | awk -F', ' '{print $6}' | awk '{print $1 " " $2}')
@ -163,6 +280,11 @@ function banner() {
echo -e $'\E[38;2;249;35;101m \E[39m\E[38;2;244;27;114m \E[39m\E[38;2;238;20;126m \E[39m\E[38;2;232;14;138m \E[39m\E[38;2;224;9;151m \E[39m\E[38;2;215;5;163m \E[39m\E[38;2;206;3;175m \E[39m\E[38;2;195;2;187m \E[39m\E[38;2;184;2;198m \E[39m\E[38;2;173;3;208m \E[39m\E[38;2;161;6;217m \E[39m\E[38;2;148;10;226m \E[39m\E[38;2;136;15;233m \E[39m\E[38;2;124;21;240m \E[39m\E[38;2;111;28;245m \E[39m\E[38;2;99;36;249m \E[39m\E[38;2;87;46;252m \E[39m\E[38;2;75;56;254m \E[39m\E[38;2;64;66;254m \E[39m\E[38;2;53;78;254m \E[39m\E[38;2;43;90;252m \E[39m\E[38;2;34;102;248m \E[39m\E[38;2;26;115;244m \E[39m\E[38;2;19;127;238m \E[39m\E[38;2;13;139;231m \E[39m\E[38;2;9;151;224m \E[39m\E[38;2;5;164;215m \E[39m\E[38;2;3;176;205m \E[39m\E[38;2;2;187;195m \E[39m\E[38;2;2;198;184m \E[39m\E[38;2;3;208;172m \E[39m\E[38;2;6;218;160m \E[39m\E[38;2;10;226;147m \E[39m\E[38;2;15;234;135m \E[39m\E[38;2;21;240;123m \E[39m\E[38;2;29;245;110m \E[39m\E[38;2;37;250;98m \E[39m\E[38;2;46;252;86m|\E[39m\E[38;2;56;254;74m_\E[39m\E[38;2;67;254;63m|\E[39m\E[38;2;78;254;52m \E[39m\E[38;2;90;251;43m \E[39m\E[38;2;103;248;34m \E[39m\E[38;2;115;244;26m \E[39m\E[38;2;128;238;19m \E[39m\E[38;2;140;231;13m \E[39m\E[38;2;152;223;8m \E[39m\E[38;2;164;214;5m \E[39m\E[38;2;176;205;3m \E[39m\E[38;2;188;194;2m \E[39m\E[38;2;199;183;2m \E[39m\E[38;2;209;171;3m \E[39m\E[38;2;218;159;6m \E[39m\E[38;2;227;147;10m \E[39m\E[38;2;234;134;15m \E[39m\E[38;2;240;122;22m \E[39m\E[38;2;246;110;29m \E[39m\E[38;2;250;97;37m \E[39m\E[38;2;253;85;47m\E[39m'
}
if ((BASH_VERSINFO[0] < 5)); then
echo -e " \e[31m\U1F6AB\e[0m bash 5.0 or newer is required to run this script. You have ${BASH_VERSION}"
exit 1
fi
# Check that ffmpeg are available on the PATH
if ! command -v ffmpeg &> /dev/null; then
echo -e " \e[31m\U1F6AB\e[0m ffmpeg is not installed. Exiting."
@ -181,6 +303,9 @@ while [[ "$#" -gt 0 ]]; do
echo -e " \e[31m\U1F6AB\e[0m ${STREAM_SPROUT_CONFIG} was not found. Exiting."
exit 1
fi;;
--info)
show_info
exit 0;;
--version)
show_version
exit 0;;
@ -221,25 +346,14 @@ while true; do
eval "$(parse_yaml "${STREAM_SPROUT_CONFIG}" sprout_)"
show_version
echo -e " \U2699 ${STREAM_SPROUT_CONFIG}"
if [[ ! "${sprout_server_url}" =~ ^rtmp://.* ]]; then
echo -e " \e[31m\U1F6AB\e[0m ${sprout_server_url} is not a valid RTMP server URL."
exit 1
fi
echo -en " \e[36m\U1F310\e[0m ${sprout_server_url}"
if [ -n "${sprout_server_key}" ]; then
sprout_server_url+="/${sprout_server_key}"
echo " (key required)"
else
echo ""
fi
STREAM_TEE=""
get_server_url
get_stream_tee
FFMPEG_LOG=$(mktemp /tmp/stream-sprout.XXXXXX.log)
ffmpeg \
-hide_banner \
-flags +global_header \
-fflags nobuffer \
-listen 1 -i "${sprout_server_url}?rtmp_buffer=0&rtmp_live=live" \
-listen 1 -i "${sprout_server_url}" \
-flvflags no_duration_filesize \
-c:v copy -c:a copy -map 0 \
-movflags +faststart \
@ -288,4 +402,5 @@ while true; do
done
rename_archive
echo
unset sprout_server_url
done

View file

@ -1,19 +1,21 @@
server:
url: "rtmp://127.0.0.1:1935"
key: "create your key with uuidgen here"
ip: 127.0.0.1
port: 1935
app: sprout
key: create your key with uuidgen here
archive_stream: false
archive_path: "${HOME}/Streams"
archive_path: ~/Streams
services:
trovo:
enabled: false
rtmp_server: "rtmp://livepush.trovo.live/live/"
key: "your_trovo_stream_key"
rtmp_server: rtmp://livepush.trovo.live/live/
key: your_trovo_stream_key
twitch:
enabled: true
rtmp_server: "rtmp://live.twitch.tv/app/"
key: "your_twitch_stream_key"
rtmp_server: rtmp://live.twitch.tv/app/
key: your_twitch_stream_key
youtube:
enabled: true
rtmp_server: "rtmp://a.rtmp.youtube.com/live2/"
key: "your_youtube_stream_key"
rtmp_server: rtmp://a.rtmp.youtube.com/live2/
key: your_youtube_stream_key