add self-upgrader (#3501) (#5035)

this allows to upgrade MediaMTX to latest version by running

./mediamtx --upgrade
This commit is contained in:
Alessandro Ros 2025-10-13 13:06:47 +02:00 committed by GitHub
parent 02e2b9d640
commit 7bca38badb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 311 additions and 26 deletions

View file

@ -65,13 +65,13 @@ jobs:
+ `Binaries are compiled from source through the [Release workflow](https://github.com/${owner}/${repo}/actions/workflows/release.yml) without human intervention,` + `Binaries are compiled from source through the [Release workflow](https://github.com/${owner}/${repo}/actions/workflows/release.yml) without human intervention,`
+ ` preventing any external interference.\n` + ` preventing any external interference.\n`
+ `\n` + `\n`
+ 'You can verify that binaries have been produced by the workflow by using [GitHub Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds):\n' + 'You can verify that binaries have been produced by the workflow by using [GitHub Attestations](https://docs.github.com/en/actions/concepts/security/artifact-attestations):\n'
+ `\n` + `\n`
+ '```\n' + '```\n'
+ `ls mediamtx_* | xargs -L1 gh attestation verify --repo bluenviron/mediamtx\n` + `ls mediamtx_* | xargs -L1 gh attestation verify --repo bluenviron/mediamtx\n`
+ '```\n' + '```\n'
+ `\n` + `\n`
+ 'You can verify the binaries checksum by downloading `checksums.sha256` and running:\n' + 'You can verify checksums of binaries by downloading `checksums.sha256` and running:\n'
+ `\n` + `\n`
+ '```\n' + '```\n'
+ `cat checksums.sha256 | grep "$(ls mediamtx_*)" | sha256sum --check\n` + `cat checksums.sha256 | grep "$(ls mediamtx_*)" | sha256sum --check\n`

View file

@ -20,7 +20,7 @@ _MediaMTX_ is a ready-to-use and zero-dependency real-time media server and medi
<div align="center"> <div align="center">
|[Installation](https://mediamtx.org/docs/kickoff/installation)|[Documentation](https://mediamtx.org/docs/kickoff/introduction)| |[Install](https://mediamtx.org/docs/kickoff/install)|[Documentation](https://mediamtx.org/docs/kickoff/introduction)|
|-|-| |-|-|
</div> </div>

View file

@ -1,4 +1,4 @@
# Installation # Install
There are several installation methods available: standalone binary, Docker image, Arch Linux package, FreeBSD Ports Collection or package and OpenWrt binary. There are several installation methods available: standalone binary, Docker image, Arch Linux package, FreeBSD Ports Collection or package and OpenWrt binary.
@ -17,17 +17,21 @@ There are several installation methods available: standalone binary, Docker imag
Download and launch the image: Download and launch the image:
```sh ```sh
docker run --rm -it --network=host bluenviron/mediamtx:latest docker run --rm -it --network=host bluenviron/mediamtx:1
``` ```
Available images: The `1` tag corresponds to the latest `v1.x.x` release, that should guarantee backward compatibility when upgrading.
It is also possible to bind the image to a specific release, by using the release name as tag (`bluenviron/mediamtx:{docker_version_tag}`).
There are four image variants:
| name | FFmpeg included | RPI Camera support | | name | FFmpeg included | RPI Camera support |
| ------------------------------------- | ------------------ | ------------------ | | -------------------------------- | ------------------ | ------------------ |
| bluenviron/mediamtx:latest | :x: | :x: | | bluenviron/mediamtx:1 | :x: | :x: |
| bluenviron/mediamtx:latest-ffmpeg | :heavy_check_mark: | :x: | | bluenviron/mediamtx:1-ffmpeg | :heavy_check_mark: | :x: |
| bluenviron/mediamtx:latest-rpi | :x: | :heavy_check_mark: | | bluenviron/mediamtx:1-rpi | :x: | :heavy_check_mark: |
| bluenviron/mediamtx:latest-ffmpeg-rpi | :heavy_check_mark: | :heavy_check_mark: | | bluenviron/mediamtx:1-ffmpeg-rpi | :heavy_check_mark: | :heavy_check_mark: |
The `--network=host` flag is mandatory for RTSP to work, since Docker can change the source port of UDP packets for routing reasons, and this doesn't allow the server to identify the senders of the packets. The `--network=host` flag is mandatory for RTSP to work, since Docker can change the source port of UDP packets for routing reasons, and this doesn't allow the server to identify the senders of the packets.

View file

@ -0,0 +1,36 @@
# Upgrade
If you have an existing MediaMTX installation and want to upgrade it to the latest version, the procedure depends on how MediaMTX was installed.
## Standalone binary
The standalone binary comes with a upgrade utility that can be launched with:
```sh
./mediamtx --upgrade
```
This will replace the MediaMTX executable with its latest version. Privileges to write to the executable location are required.
## Docker image
If you used the `1` tag or the `latest` tag, remove the image from cache and re-download it:
```sh
docker rm bluenviron/mediamtx:1
docker restart id-of-mediamtx-container
```
## Arch Linux package
Repeat the installation procedure.
## FreeBSD
Repeat the installation procedure.
## OpenWrt binary
If the architecture of the OpenWrt device is amd64, armv6, armv7 or arm64, you can use the standalone binary method.
Otherwise, recompile the server from source.

View file

@ -1,21 +1,33 @@
# Security # Security
## Reporting vulnerabilities ## Security of released binaries
Vulnerabilities can be reported privately by using the [Security Advisory](https://github.com/bluenviron/mediamtx/security/advisories/new) feature of GitHub. Binaries published in the [Releases](https://github.com/bluenviron/mediamtx/releases) section of GitHub are the output of a process which has been designed with a security-first approach. Every step from source code to the intended final destination of binaries is fully visible, immune from external interference and can be independently verified.
## Binaries This is the process:
Binaries are compiled from source through the [Release workflow](https://github.com/bluenviron/mediamtx/actions/workflows/release.yml) without human intervention, preventing any external interference. 1. During every release, the [Release workflow](https://github.com/bluenviron/mediamtx/actions/workflows/release.yml) is triggered on GitHub.
You can verify that binaries have been produced by the workflow by using [GitHub Attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds): 2. The release workflow pulls the source code and builds binaries.
3. The release workflow computes SHA256 checksums of binaries and publishes them on the Sigstore Public Good Instance through [GitHub Attestations](https://docs.github.com/en/actions/concepts/security/artifact-attestations).
4. Checksums and binaries are published on the Release page.
5. Binaries are downloaded by users to the intended final destination.
It is possible to verify that SHA256 checksums of binaries correspond to the one published on Sigstore by running:
```sh ```sh
ls mediamtx_* | xargs -L1 gh attestation verify --repo bluenviron/mediamtx ls mediamtx_* | xargs -L1 gh attestation verify --repo bluenviron/mediamtx
``` ```
You can verify the binaries checksum by downloading `checksums.sha256` and running: It is possible to verify that binaries have not been altered during transfer from GitHub to the final destination by downloading `checksums.sha256` and running:
```sh ```sh
cat checksums.sha256 | grep "$(ls mediamtx_*)" | sha256sum --check cat checksums.sha256 | grep "$(ls mediamtx_*)" | sha256sum --check
``` ```
## Reporting vulnerabilities
Vulnerabilities can be reported privately by using the [Security Advisory](https://github.com/bluenviron/mediamtx/security/advisories/new) feature of GitHub.

3
go.mod
View file

@ -4,6 +4,7 @@ go 1.25.0
require ( require (
code.cloudfoundry.org/bytefmt v0.54.0 code.cloudfoundry.org/bytefmt v0.54.0
github.com/Masterminds/semver/v3 v3.4.0
github.com/MicahParks/jwkset v0.11.0 github.com/MicahParks/jwkset v0.11.0
github.com/MicahParks/keyfunc/v3 v3.6.2 github.com/MicahParks/keyfunc/v3 v3.6.2
github.com/abema/go-mp4 v1.4.1 github.com/abema/go-mp4 v1.4.1
@ -25,6 +26,7 @@ require (
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/matthewhartstonge/argon2 v1.4.0 github.com/matthewhartstonge/argon2 v1.4.0
github.com/minio/selfupdate v0.6.0
github.com/pion/ice/v4 v4.0.10 github.com/pion/ice/v4 v4.0.10
github.com/pion/interceptor v0.1.41 github.com/pion/interceptor v0.1.41
github.com/pion/logging v0.2.4 github.com/pion/logging v0.2.4
@ -40,6 +42,7 @@ require (
) )
require ( require (
aead.dev/minisign v0.2.0 // indirect
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect

12
go.sum
View file

@ -1,3 +1,5 @@
aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=
aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
code.cloudfoundry.org/bytefmt v0.54.0 h1:xMSFK/+X7MPRmwRExB7wrMvZZOzHoGK2/y9TUx01P14= code.cloudfoundry.org/bytefmt v0.54.0 h1:xMSFK/+X7MPRmwRExB7wrMvZZOzHoGK2/y9TUx01P14=
code.cloudfoundry.org/bytefmt v0.54.0/go.mod h1:uW5EGeU/vy+HOUwUnI38Lz2QK0eHQ+ua31nLOAdatzQ= code.cloudfoundry.org/bytefmt v0.54.0/go.mod h1:uW5EGeU/vy+HOUwUnI38Lz2QK0eHQ+ua31nLOAdatzQ=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
@ -139,6 +141,8 @@ github.com/matthewhartstonge/argon2 v1.4.0 h1:/nBFmqLd5SG0itPEvASchx3dwLXCHxEeVy
github.com/matthewhartstonge/argon2 v1.4.0/go.mod h1:Z0HuEBPD4vGtn881oo/LVTRx7RMzyc1BYSHNBVmS65o= github.com/matthewhartstonge/argon2 v1.4.0/go.mod h1:Z0HuEBPD4vGtn881oo/LVTRx7RMzyc1BYSHNBVmS65o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -233,6 +237,9 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
@ -240,25 +247,30 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=

View file

@ -55,8 +55,9 @@ var defaultConfPathsNotWin = []string{
} }
var cli struct { var cli struct {
Version bool `help:"print version"`
Confpath string `arg:"" default:""` Confpath string `arg:"" default:""`
Version bool `help:"print version"`
Upgrade bool `help:"upgrade executable to the latest version"`
} }
func atLeastOneRecordDeleteAfter(pathConfs map[string]*conf.Path) bool { func atLeastOneRecordDeleteAfter(pathConfs map[string]*conf.Path) bool {
@ -137,6 +138,15 @@ func New(args []string) (*Core, bool) {
os.Exit(0) os.Exit(0)
} }
if cli.Upgrade {
err = upgrade()
if err != nil {
fmt.Printf("ERR: %v\n", err)
os.Exit(1)
}
os.Exit(0)
}
ctx, ctxCancel := context.WithCancel(context.Background()) ctx, ctxCancel := context.WithCancel(context.Background())
p := &Core{ p := &Core{

195
internal/core/upgrade.go Normal file
View file

@ -0,0 +1,195 @@
//go:build enableUpgrade
package core
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"regexp"
"runtime"
"runtime/debug"
"sort"
"github.com/Masterminds/semver/v3"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/minio/selfupdate"
)
const (
gitRepo = "https://github.com/bluenviron/mediamtx"
downloadURL = "https://github.com/bluenviron/mediamtx/releases/download/%s/mediamtx_%s_%s_%s.%s"
executable = "mediamtx"
)
var (
tagsRegexp = regexp.MustCompile(`^refs/tags/(v1\.[0-9]+\.[0-9]+)$`)
currentRegexp = regexp.MustCompile(`^(v1\.[0-9]+\.[0-9]+)$`)
)
func latestRemoteVersion() (*semver.Version, error) {
rem := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
URLs: []string{gitRepo},
})
refs, err := rem.List(&git.ListOptions{})
if err != nil {
return nil, err
}
var versions []*semver.Version
for _, ref := range refs {
matches := tagsRegexp.FindStringSubmatch(ref.Name().String())
if matches != nil {
v, _ := semver.NewVersion(matches[1])
versions = append(versions, v)
}
}
if len(versions) == 0 {
return nil, fmt.Errorf("no versions found")
}
sort.Sort(sort.Reverse(semver.Collection(versions)))
return versions[0], nil
}
func goArm() string {
bi, _ := debug.ReadBuildInfo()
for _, bs := range bi.Settings {
if bs.Key == "GOARM" {
return bs.Value
}
}
return ""
}
func extractExecutable(r io.Reader) ([]byte, error) {
gzReader, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
defer gzReader.Close()
tarReader := tar.NewReader(gzReader)
for {
header, err := tarReader.Next()
if err == io.EOF {
return nil, fmt.Errorf("executable not found")
}
if err != nil {
return nil, err
}
if header.Name == executable {
buf, err := io.ReadAll(tarReader)
if err != nil {
return nil, err
}
return buf, nil
}
}
}
func extractExecutableWin(r io.Reader) ([]byte, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
if err != nil {
return nil, err
}
for _, file := range zipReader.File {
if file.Name == executable+".exe" {
rc, err := file.Open()
if err != nil {
return nil, err
}
defer rc.Close()
buf, err := io.ReadAll(rc)
if err != nil {
return nil, err
}
return buf, nil
}
}
return nil, fmt.Errorf("executable not found")
}
func upgrade() error {
if !currentRegexp.MatchString(string(version)) {
return fmt.Errorf("current version (%v) is not official and cannot be upgraded", string(version))
}
fmt.Println("getting latest version...")
latest, err := latestRemoteVersion()
if err != nil {
return err
}
current, _ := semver.NewVersion(string(version))
if current.GreaterThanEqual(latest) {
fmt.Printf("current version (%v) is up to date\n", "v"+current.String())
return nil
}
fmt.Printf("downloading version %v...\n", "v"+latest.String())
var arch string
if runtime.GOARCH == "arm" {
arch = "armv" + goArm()
} else {
arch = runtime.GOARCH
}
var extension string
if runtime.GOOS == "windows" {
extension = "zip"
} else {
extension = "tar.gz"
}
ur := fmt.Sprintf(downloadURL, "v"+latest.String(), "v"+latest.String(), runtime.GOOS, arch, extension)
res, err := http.Get(ur)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return fmt.Errorf("bad status code: %v", res.StatusCode)
}
var exe []byte
if runtime.GOOS == "windows" {
exe, err = extractExecutableWin(res.Body)
} else {
exe, err = extractExecutable(res.Body)
}
err = selfupdate.Apply(bytes.NewReader(exe), selfupdate.Options{})
if err != nil {
return err
}
fmt.Printf("MediaMTX upgraded successfully from %v to %v.\n", "v"+current.String(), "v"+latest.String())
return nil
}

View file

@ -0,0 +1,9 @@
//go:build !enableUpgrade
package core
import "fmt"
func upgrade() error {
return fmt.Errorf("upgrade command is not available")
}

View file

@ -15,37 +15,37 @@ RUN go generate ./...
FROM build-base AS build-windows-amd64 FROM build-base AS build-windows-amd64
ENV GOOS=windows GOARCH=amd64 ENV GOOS=windows GOARCH=amd64
RUN go build -o "tmp/$(BINARY_NAME).exe" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME).exe"
RUN cd tmp && zip -q "../binaries/$(BINARY_NAME)_$$(cat ../internal/core/VERSION)_windows_amd64.zip" "$(BINARY_NAME).exe" mediamtx.yml LICENSE RUN cd tmp && zip -q "../binaries/$(BINARY_NAME)_$$(cat ../internal/core/VERSION)_windows_amd64.zip" "$(BINARY_NAME).exe" mediamtx.yml LICENSE
FROM build-base AS build-linux-amd64 FROM build-base AS build-linux-amd64
ENV GOOS=linux GOARCH=amd64 ENV GOOS=linux GOARCH=amd64
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM build-base AS build-darwin-amd64 FROM build-base AS build-darwin-amd64
ENV GOOS=darwin GOARCH=amd64 ENV GOOS=darwin GOARCH=amd64
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_amd64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM build-base AS build-darwin-arm64 FROM build-base AS build-darwin-arm64
ENV GOOS=darwin GOARCH=arm64 ENV GOOS=darwin GOARCH=arm64
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_darwin_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM build-base AS build-linux-armv6 FROM build-base AS build-linux-armv6
ENV GOOS=linux GOARCH=arm GOARM=6 ENV GOOS=linux GOARCH=arm GOARM=6
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv6.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv6.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM build-base AS build-linux-armv7 FROM build-base AS build-linux-armv7
ENV GOOS=linux GOARCH=arm GOARM=7 ENV GOOS=linux GOARCH=arm GOARM=7
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv7.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_armv7.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM build-base AS build-linux-arm64 FROM build-base AS build-linux-arm64
ENV GOOS=linux GOARCH=arm64 ENV GOOS=linux GOARCH=arm64
RUN go build -o "tmp/$(BINARY_NAME)" RUN go build -tags enableUpgrade -o "tmp/$(BINARY_NAME)"
RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE RUN tar -C tmp -czf "binaries/$(BINARY_NAME)_$$(cat internal/core/VERSION)_linux_arm64.tar.gz" --owner=0 --group=0 "$(BINARY_NAME)" mediamtx.yml LICENSE
FROM $(BASE_IMAGE) FROM $(BASE_IMAGE)

View file

@ -12,6 +12,7 @@ dockerhub:
-f docker/ffmpeg-rpi.Dockerfile . \ -f docker/ffmpeg-rpi.Dockerfile . \
--platform=linux/arm/v6,linux/arm/v7,linux/arm64 \ --platform=linux/arm/v6,linux/arm/v7,linux/arm64 \
-t $(DOCKER_REPOSITORY):$(VERSION)-ffmpeg-rpi \ -t $(DOCKER_REPOSITORY):$(VERSION)-ffmpeg-rpi \
-t $(DOCKER_REPOSITORY):1-ffmpeg-rpi \
-t $(DOCKER_REPOSITORY):latest-ffmpeg-rpi \ -t $(DOCKER_REPOSITORY):latest-ffmpeg-rpi \
--push --push
@ -19,6 +20,7 @@ dockerhub:
-f docker/rpi.Dockerfile . \ -f docker/rpi.Dockerfile . \
--platform=linux/arm/v6,linux/arm/v7,linux/arm64 \ --platform=linux/arm/v6,linux/arm/v7,linux/arm64 \
-t $(DOCKER_REPOSITORY):$(VERSION)-rpi \ -t $(DOCKER_REPOSITORY):$(VERSION)-rpi \
-t $(DOCKER_REPOSITORY):1-rpi \
-t $(DOCKER_REPOSITORY):latest-rpi \ -t $(DOCKER_REPOSITORY):latest-rpi \
--push --push
@ -26,6 +28,7 @@ dockerhub:
-f docker/ffmpeg.Dockerfile . \ -f docker/ffmpeg.Dockerfile . \
--platform=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \ --platform=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \
-t $(DOCKER_REPOSITORY):$(VERSION)-ffmpeg \ -t $(DOCKER_REPOSITORY):$(VERSION)-ffmpeg \
-t $(DOCKER_REPOSITORY):1-ffmpeg \
-t $(DOCKER_REPOSITORY):latest-ffmpeg \ -t $(DOCKER_REPOSITORY):latest-ffmpeg \
--push --push
@ -33,6 +36,7 @@ dockerhub:
-f docker/standard.Dockerfile . \ -f docker/standard.Dockerfile . \
--platform=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \ --platform=linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \
-t $(DOCKER_REPOSITORY):$(VERSION) \ -t $(DOCKER_REPOSITORY):$(VERSION) \
-t $(DOCKER_REPOSITORY):1 \
-t $(DOCKER_REPOSITORY):latest \ -t $(DOCKER_REPOSITORY):latest \
--push --push