aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Naming/Emby.Naming.csproj2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs5
-rw-r--r--Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs132
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs190
-rw-r--r--MediaBrowser.Api/Session/SessionsService.cs11
-rw-r--r--MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs20
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs3
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs1
-rw-r--r--build.yaml1
-rwxr-xr-xbump_version136
-rwxr-xr-xdeployment/common.build.sh6
-rw-r--r--deployment/debian-package-x64/pkg-src/conf/jellyfin2
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.amd6453
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.armhf34
-rwxr-xr-xdeployment/ubuntu-package-armhf/clean.sh29
-rw-r--r--deployment/ubuntu-package-armhf/dependencies.txt1
-rwxr-xr-xdeployment/ubuntu-package-armhf/docker-build.sh20
-rwxr-xr-xdeployment/ubuntu-package-armhf/package.sh42
l---------deployment/ubuntu-package-armhf/pkg-src1
19 files changed, 447 insertions, 242 deletions
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index e344e7811..c448ec0ce 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -10,7 +10,7 @@
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup>
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 1c9a4776a..41ca2a102 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1042,8 +1042,6 @@ namespace Emby.Server.Implementations
CollectionFolder.JsonSerializer = JsonSerializer;
CollectionFolder.ApplicationHost = this;
AuthenticatedAttribute.AuthService = AuthService;
-
- InstallationManager.PluginInstalled += PluginInstalled;
}
private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
@@ -1085,6 +1083,7 @@ namespace Emby.Server.Implementations
protected void FindParts()
{
InstallationManager = _serviceProvider.GetService<IInstallationManager>();
+ InstallationManager.PluginInstalled += PluginInstalled;
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
{
@@ -1125,7 +1124,7 @@ namespace Emby.Server.Implementations
MediaSourceManager.AddParts(GetExports<IMediaSourceProvider>());
NotificationManager.AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
- UserManager.AddParts(GetExports<IAuthenticationProvider>());
+ UserManager.AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
IsoManager.AddParts(GetExports<IIsoMounter>());
}
diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
new file mode 100644
index 000000000..e218749d9
--- /dev/null
+++ b/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Authentication;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Cryptography;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Model.Users;
+
+namespace Emby.Server.Implementations.Library
+{
+ public class DefaultPasswordResetProvider : IPasswordResetProvider
+ {
+ public string Name => "Default Password Reset Provider";
+
+ public bool IsEnabled => true;
+
+ private readonly string _passwordResetFileBase;
+ private readonly string _passwordResetFileBaseDir;
+ private readonly string _passwordResetFileBaseName = "passwordreset";
+
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly IUserManager _userManager;
+ private readonly ICryptoProvider _crypto;
+
+ public DefaultPasswordResetProvider(IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IUserManager userManager, ICryptoProvider cryptoProvider)
+ {
+ _passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
+ _passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, _passwordResetFileBaseName);
+ _jsonSerializer = jsonSerializer;
+ _userManager = userManager;
+ _crypto = cryptoProvider;
+ }
+
+ public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
+ {
+ SerializablePasswordReset spr;
+ HashSet<string> usersreset = new HashSet<string>();
+ foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{_passwordResetFileBaseName}*"))
+ {
+ using (var str = File.OpenRead(resetfile))
+ {
+ spr = await _jsonSerializer.DeserializeFromStreamAsync<SerializablePasswordReset>(str).ConfigureAwait(false);
+ }
+
+ if (spr.ExpirationDate < DateTime.Now)
+ {
+ File.Delete(resetfile);
+ }
+ else if (spr.Pin.Replace("-", "").Equals(pin.Replace("-", ""), StringComparison.InvariantCultureIgnoreCase))
+ {
+ var resetUser = _userManager.GetUserByName(spr.UserName);
+ if (resetUser == null)
+ {
+ throw new Exception($"User with a username of {spr.UserName} not found");
+ }
+
+ await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
+ usersreset.Add(resetUser.Name);
+ File.Delete(resetfile);
+ }
+ }
+
+ if (usersreset.Count < 1)
+ {
+ throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}");
+ }
+ else
+ {
+ return new PinRedeemResult
+ {
+ Success = true,
+ UsersReset = usersreset.ToArray()
+ };
+ }
+ }
+
+ public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ {
+ string pin = string.Empty;
+ using (var cryptoRandom = System.Security.Cryptography.RandomNumberGenerator.Create())
+ {
+ byte[] bytes = new byte[4];
+ cryptoRandom.GetBytes(bytes);
+ pin = BitConverter.ToString(bytes);
+ }
+
+ DateTime expireTime = DateTime.Now.AddMinutes(30);
+ string filePath = _passwordResetFileBase + user.InternalId + ".json";
+ SerializablePasswordReset spr = new SerializablePasswordReset
+ {
+ ExpirationDate = expireTime,
+ Pin = pin,
+ PinFile = filePath,
+ UserName = user.Name
+ };
+
+ try
+ {
+ using (FileStream fileStream = File.OpenWrite(filePath))
+ {
+ _jsonSerializer.SerializeToStream(spr, fileStream);
+ await fileStream.FlushAsync().ConfigureAwait(false);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new Exception($"Error serializing or writing password reset for {user.Name} to location: {filePath}", e);
+ }
+
+ return new ForgotPasswordResult
+ {
+ Action = ForgotPasswordAction.PinCode,
+ PinExpirationDate = expireTime,
+ PinFile = filePath
+ };
+ }
+
+ private class SerializablePasswordReset : PasswordPinCreationResult
+ {
+ public string Pin { get; set; }
+
+ public string UserName { get; set; }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 4cf703add..75c82ca71 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -79,6 +79,9 @@ namespace Emby.Server.Implementations.Library
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
+ private IPasswordResetProvider[] _passwordResetProviders;
+ private DefaultPasswordResetProvider _defaultPasswordResetProvider;
+
public UserManager(
ILoggerFactory loggerFactory,
IServerConfigurationManager configurationManager,
@@ -102,8 +105,6 @@ namespace Emby.Server.Implementations.Library
_fileSystem = fileSystem;
ConfigurationManager = configurationManager;
_users = Array.Empty<User>();
-
- DeletePinFile();
}
public NameIdPair[] GetAuthenticationProviders()
@@ -120,11 +121,29 @@ namespace Emby.Server.Implementations.Library
.ToArray();
}
- public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders)
+ public NameIdPair[] GetPasswordResetProviders()
+ {
+ return _passwordResetProviders
+ .Where(i => i.IsEnabled)
+ .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1)
+ .ThenBy(i => i.Name)
+ .Select(i => new NameIdPair
+ {
+ Name = i.Name,
+ Id = GetPasswordResetProviderId(i)
+ })
+ .ToArray();
+ }
+
+ public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders,IEnumerable<IPasswordResetProvider> passwordResetProviders)
{
_authenticationProviders = authenticationProviders.ToArray();
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
+
+ _passwordResetProviders = passwordResetProviders.ToArray();
+
+ _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
}
#region UserUpdated Event
@@ -342,11 +361,21 @@ namespace Emby.Server.Implementations.Library
return provider.GetType().FullName;
}
+ private static string GetPasswordResetProviderId(IPasswordResetProvider provider)
+ {
+ return provider.GetType().FullName;
+ }
+
private IAuthenticationProvider GetAuthenticationProvider(User user)
{
return GetAuthenticationProviders(user).First();
}
+ private IPasswordResetProvider GetPasswordResetProvider(User user)
+ {
+ return GetPasswordResetProviders(user)[0];
+ }
+
private IAuthenticationProvider[] GetAuthenticationProviders(User user)
{
var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId;
@@ -366,6 +395,25 @@ namespace Emby.Server.Implementations.Library
return providers;
}
+ private IPasswordResetProvider[] GetPasswordResetProviders(User user)
+ {
+ var passwordResetProviderId = user?.Policy.PasswordResetProviderId;
+
+ var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
+
+ if (!string.IsNullOrEmpty(passwordResetProviderId))
+ {
+ providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray();
+ }
+
+ if (providers.Length == 0)
+ {
+ providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider };
+ }
+
+ return providers;
+ }
+
private async Task<bool> AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser)
{
try
@@ -844,159 +892,51 @@ namespace Emby.Server.Implementations.Library
Id = Guid.NewGuid(),
DateCreated = DateTime.UtcNow,
DateModified = DateTime.UtcNow,
- UsesIdForConfigurationPath = true,
- //Salt = BCrypt.GenerateSalt()
- };
- }
-
- private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt");
-
- private string _lastPin;
- private PasswordPinCreationResult _lastPasswordPinCreationResult;
- private int _pinAttempts;
-
- private async Task<PasswordPinCreationResult> CreatePasswordResetPin()
- {
- var num = new Random().Next(1, 9999);
-
- var path = PasswordResetFile;
-
- var pin = num.ToString("0000", CultureInfo.InvariantCulture);
- _lastPin = pin;
-
- var time = TimeSpan.FromMinutes(5);
- var expiration = DateTime.UtcNow.Add(time);
-
- var text = new StringBuilder();
-
- var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty;
-
- text.AppendLine("Use your web browser to visit:");
- text.AppendLine(string.Empty);
- text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html");
- text.AppendLine(string.Empty);
- text.AppendLine("Enter the following pin code:");
- text.AppendLine(string.Empty);
- text.AppendLine(pin);
- text.AppendLine(string.Empty);
-
- var localExpirationTime = expiration.ToLocalTime();
- // Tuesday, 22 August 2006 06:30 AM
- text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture));
-
- File.WriteAllText(path, text.ToString(), Encoding.UTF8);
-
- var result = new PasswordPinCreationResult
- {
- PinFile = path,
- ExpirationDate = expiration
+ UsesIdForConfigurationPath = true
};
-
- _lastPasswordPinCreationResult = result;
- _pinAttempts = 0;
-
- return result;
}
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(string enteredUsername, bool isInNetwork)
{
- DeletePinFile();
-
var user = string.IsNullOrWhiteSpace(enteredUsername) ?
null :
GetUserByName(enteredUsername);
var action = ForgotPasswordAction.InNetworkRequired;
- string pinFile = null;
- DateTime? expirationDate = null;
- if (user != null && !user.Policy.IsAdministrator)
+ if (user != null && isInNetwork)
{
- action = ForgotPasswordAction.ContactAdmin;
+ var passwordResetProvider = GetPasswordResetProvider(user);
+ return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false);
}
else
{
- if (isInNetwork)
+ return new ForgotPasswordResult
{
- action = ForgotPasswordAction.PinCode;
- }
-
- var result = await CreatePasswordResetPin().ConfigureAwait(false);
- pinFile = result.PinFile;
- expirationDate = result.ExpirationDate;
+ Action = action,
+ PinFile = string.Empty
+ };
}
-
- return new ForgotPasswordResult
- {
- Action = action,
- PinFile = pinFile,
- PinExpirationDate = expirationDate
- };
}
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
- DeletePinFile();
-
- var usersReset = new List<string>();
-
- var valid = !string.IsNullOrWhiteSpace(_lastPin) &&
- string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) &&
- _lastPasswordPinCreationResult != null &&
- _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow;
-
- if (valid)
+ foreach (var provider in _passwordResetProviders)
{
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
-
- foreach (var user in Users)
+ var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false);
+ if (result.Success)
{
- await ResetPassword(user).ConfigureAwait(false);
-
- if (user.Policy.IsDisabled)
- {
- user.Policy.IsDisabled = false;
- UpdateUserPolicy(user, user.Policy, true);
- }
- usersReset.Add(user.Name);
- }
- }
- else
- {
- _pinAttempts++;
- if (_pinAttempts >= 3)
- {
- _lastPin = null;
- _lastPasswordPinCreationResult = null;
+ return result;
}
}
return new PinRedeemResult
{
- Success = valid,
- UsersReset = usersReset.ToArray()
+ Success = false,
+ UsersReset = Array.Empty<string>()
};
}
- private void DeletePinFile()
- {
- try
- {
- _fileSystem.DeleteFile(PasswordResetFile);
- }
- catch
- {
-
- }
- }
-
- class PasswordPinCreationResult
- {
- public string PinFile { get; set; }
- public DateTime ExpirationDate { get; set; }
- }
-
public UserPolicy GetUserPolicy(User user)
{
var path = GetPolicyFilePath(user);
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs
index f011e6e41..4109b12bf 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Session/SessionsService.cs
@@ -245,6 +245,12 @@ namespace MediaBrowser.Api.Session
{
}
+ [Route("/Auth/PasswordResetProviders", "GET")]
+ [Authenticated(Roles = "Admin")]
+ public class GetPasswordResetProviders : IReturn<NameIdPair[]>
+ {
+ }
+
[Route("/Auth/Keys/{Key}", "DELETE")]
[Authenticated(Roles = "Admin")]
public class RevokeKey
@@ -294,6 +300,11 @@ namespace MediaBrowser.Api.Session
return _userManager.GetAuthenticationProviders();
}
+ public object Get(GetPasswordResetProviders request)
+ {
+ return _userManager.GetPasswordResetProviders();
+ }
+
public void Delete(RevokeKey request)
{
_sessionManager.RevokeToken(request.Key);
diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
new file mode 100644
index 000000000..9e5cd8816
--- /dev/null
+++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Users;
+
+namespace MediaBrowser.Controller.Authentication
+{
+ public interface IPasswordResetProvider
+ {
+ string Name { get; }
+ bool IsEnabled { get; }
+ Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork);
+ Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
+ }
+ public class PasswordPinCreationResult
+ {
+ public string PinFile { get; set; }
+ public DateTime ExpirationDate { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 925d91a37..7f7370893 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -200,8 +200,9 @@ namespace MediaBrowser.Controller.Library
/// <returns>System.String.</returns>
string MakeValidUsername(string username);
- void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders);
+ void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
NameIdPair[] GetAuthenticationProviders();
+ NameIdPair[] GetPasswordResetProviders();
}
}
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 5415fd5e8..f63ab2bb4 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -75,6 +75,7 @@ namespace MediaBrowser.Model.Users
public int RemoteClientBitrateLimit { get; set; }
public string AuthenticationProviderId { get; set; }
+ public string PasswordResetProviderId { get; set; }
public UserPolicy()
{
diff --git a/build.yaml b/build.yaml
index b0d2502d5..289c1cadd 100644
--- a/build.yaml
+++ b/build.yaml
@@ -6,6 +6,7 @@ packages:
- debian-package-x64
- debian-package-armhf
- ubuntu-package-x64
+ - ubuntu-package-armhf
- fedora-package-x64
- centos-package-x64
- linux-x64
diff --git a/bump_version b/bump_version
index a63fbf735..b118af54b 100755
--- a/bump_version
+++ b/bump_version
@@ -9,7 +9,7 @@ usage() {
echo -e "bump_version - increase the shared version and generate changelogs"
echo -e ""
echo -e "Usage:"
- echo -e " $ bump_version [-b/--web-branch <web_branch>] <new_version>"
+ echo -e " $ bump_version <new_version>"
echo -e ""
echo -e "The web_branch defaults to the same branch name as the current main branch."
echo -e "This helps facilitate releases where both branches would be called release-X.Y.Z"
@@ -22,14 +22,9 @@ if [[ -z $1 ]]; then
fi
shared_version_file="./SharedVersion.cs"
+build_file="./build.yaml"
-# Parse branch option
-if [[ $1 == '-b' || $1 == '--web-branch' ]]; then
- web_branch="$2"
- shift 2
-else
- web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )"
-fi
+web_branch="$( git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/' )"
# Initialize submodules
git submodule update --init --recursive
@@ -47,22 +42,11 @@ if ! git diff-index --quiet HEAD --; then
fi
git fetch --all
-# If this is an official branch name, fetch it from origin
-official_branches_regex="^master$|^dev$|^release-.*$|^hotfix-.*$"
-if [[ ${web_branch} =~ ${official_branches_regex} ]]; then
- git checkout origin/${web_branch} || {
- echo "ERROR: 'jellyfin-web' branch 'origin/${web_branch}' is invalid."
- exit 1
- }
-# Otherwise, just check out the local branch (for testing, etc.)
-else
- git checkout ${web_branch} || {
- echo "ERROR: 'jellyfin-web' branch '${web_branch}' is invalid."
- exit 1
- }
-fi
+git checkout origin/${web_branch}
popd
+git add MediaBrowser.WebDashboard/jellyfin-web
+
new_version="$1"
# Parse the version from the AssemblyVersion
@@ -73,91 +57,31 @@ old_version="$(
# Set the shared version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
-sed -i "s/${old_version_sed}/${new_version}/g" ${shared_version_file}
-
-declare -a pr_merges_since_last_master
-declare changelog_string_github
-declare changelog_string_deb
-declare changelog_string_yum
-
-# Build up a changelog from merge commits
-for repo in ./ MediaBrowser.WebDashboard/jellyfin-web/; do
- last_master_merge_commit=""
- pr_merges_since_last_master=()
- git_show_details=""
- pull_request_id=""
- pull_request_description=""
- changelog_strings_repo_github=""
- changelog_strings_repo_deb=""
- changelog_strings_repo_yum=""
-
- case $repo in
- *jellyfin-web*)
- repo_name="jellyfin-web"
- ;;
- *)
- repo_name="jellyfin"
- ;;
- esac
-
- pushd ${repo}
-
- # Find the last release commit, so we know what's happened since
- last_master_branch="release-${old_version}"
- last_master_merge_commit="$(
- git log --merges --pretty=oneline \
- | grep -F "${last_master_branch}" \
- | awk '{ print $1 }' \
- || true # Don't die here with errexit
- )"
- if [[ -z ${last_master_merge_commit} ]]; then
- # This repo has no last proper commit, so just skip it
- popd
- continue
- fi
- # Get all the PR merge commits since the last master merge commit in `jellyfin`
- pr_merges_since_last_master+=( $(
- git log --merges --pretty=oneline ${last_master_merge_commit}..HEAD \
- | grep -F "Merge pull request" \
- | awk '{ print $1 }'
- ) )
-
- for commit_hash in ${pr_merges_since_last_master[@]}; do
- git_show_details="$( git show ${commit_hash} )"
- pull_request_id="$(
- awk '
- /Merge pull request/{ print $4 }
- { next }
- ' <<<"${git_show_details}"
- )"
- pull_request_description="$(
- awk '
- /^[a-zA-Z]/{ next }
- /^ Merge/{ next }
- /^$/{ next }
- { print $0 }
- ' <<<"${git_show_details}"
- )"
- pull_request_description="$( sed ':a;N;$!ba;s/\n//g; s/ \+//g' <<<"${pull_request_description}" )"
- changelog_strings_repo_github="${changelog_strings_repo_github}\n* ${pull_request_id}: ${pull_request_description}"
- changelog_strings_repo_deb="${changelog_strings_repo_deb}\n * $( sed 's/#/PR/' <<<"${pull_request_id}" ) ${pull_request_description}"
- changelog_strings_repo_yum="${changelog_strings_repo_yum}\n- $( sed 's/#/PR/' <<<"${pull_request_id}" ) ${pull_request_description}"
- done
-
- changelog_string_github="${changelog_string_github}\n#### ${repo_name}:\n$( echo -e "${changelog_strings_repo_github}" | sort -nk2 )\n"
- changelog_string_deb="${changelog_string_deb}\n * ${repo_name}:$( echo -e "${changelog_strings_repo_deb}" | sort -nk2 )"
- changelog_string_yum="${changelog_string_yum}\n- ${repo_name}:$( echo -e "${changelog_strings_repo_yum}" | sort -nk2 )"
+new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
+sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
- popd
-done
+old_version="$(
+ grep "version:" ${build_file} \
+ | sed -E 's/version: "([0-9\.]+)"/\1/'
+)"
+
+old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
+sed -i "s/${old_version_sed}/${new_version}/g" ${build_file}
+
+if [[ ${new_version} == *"-"* ]]; then
+ new_version_deb="$( sed 's/-/~/g' <<<"${new_version}" )"
+else
+ new_version_deb="${new_version}-1"
+fi
# Write out a temporary Debian changelog with our new stuff appended and some templated formatting
debian_changelog_file="deployment/debian-package-x64/pkg-src/changelog"
debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog
echo -e "### DEBIAN PACKAGE CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit.
-jellyfin (${new_version}-1) unstable; urgency=medium
-${changelog_string_deb}
+jellyfin (${new_version_deb}) unstable; urgency=medium
+
+ * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
" >> ${debian_changelog_temp}
@@ -180,13 +104,14 @@ pushd ${fedora_spec_temp_dir}
# Split out the stuff before and after changelog
csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01
# Update the version in xx00
-sed -i "s/${old_version_sed}/${new_version}/g" xx00
+sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# Remove the header from xx01
sed -i '/^%changelog/d' xx01
# Create new temp file with our changelog
echo -e "### YUM SPEC CHANGELOG: Verify this file looks correct or edit accordingly, then delete this line, write, and exit.
%changelog
-* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>${changelog_string_yum}" >> ${fedora_changelog_temp}
+* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
+- New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v${new_version}" >> ${fedora_changelog_temp}
cat xx01 >> ${fedora_changelog_temp}
# Edit the file to verify
$EDITOR ${fedora_changelog_temp}
@@ -199,10 +124,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file}
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
# Stage the changed files for commit
-git add ${shared_version_file} ${debian_changelog_file} ${fedora_spec_file}
+git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
git status
-
-# Write out the GitHub-formatted changelog for the merge request/release pages
-echo ""
-echo "=== The GitHub-formatted changelog follows ==="
-echo -e "${changelog_string_github}"
diff --git a/deployment/common.build.sh b/deployment/common.build.sh
index d028e3a66..000872ea9 100755
--- a/deployment/common.build.sh
+++ b/deployment/common.build.sh
@@ -17,12 +17,12 @@ DEFAULT_PKG_DIR="pkg-dist"
DEFAULT_DOCKERFILE="Dockerfile"
DEFAULT_ARCHIVE_CMD="tar -xvzf"
-# Parse the version from the AssemblyVersion
+# Parse the version from the build.yaml version
get_version()
(
local ROOT=${1-$DEFAULT_ROOT}
- grep "AssemblyVersion" ${ROOT}/SharedVersion.cs \
- | sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/'
+ grep "version:" ${ROOT}/build.yaml \
+ | sed -E 's/version: "([0-9\.]+.*)"/\1/'
)
# Run a build
diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin
index bc00c37e2..c6e595f15 100644
--- a/deployment/debian-package-x64/pkg-src/conf/jellyfin
+++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin
@@ -22,7 +22,7 @@ JELLYFIN_CACHE_DIR="/var/cache/jellyfin"
JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh"
# ffmpeg binary paths, overriding the system values
-JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/share/jellyfin-ffmpeg/ffmpeg"
+JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
# [OPTIONAL] run Jellyfin as a headless service
#JELLYFIN_SERVICE_OPT="--service"
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64
new file mode 100644
index 000000000..ef0735e42
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64
@@ -0,0 +1,53 @@
+FROM ubuntu:bionic
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf
+ARG ARTIFACT_DIR=/dist
+ARG SDK_VERSION=2.2
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV DEB_BUILD_OPTIONS=noddebs
+ENV ARCH=amd64
+
+# Prepare Debian build environment
+RUN apt-get update \
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
+
+# Install dotnet repository
+# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
+RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4ced-81e6-286620b390ab/8ab938cf6f5e83b2221630354160ef21/dotnet-sdk-2.2.104-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+ && mkdir -p dotnet-sdk \
+ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
+ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+
+# Prepare the cross-toolchain
+RUN rm /etc/apt/sources.list \
+ && export CODENAME="$( lsb_release -c -s )" \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/amd64.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME} main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-updates main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-backports main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && echo "deb [arch=armhf] http://ports.ubuntu.com/ ${CODENAME}-security main restricted universe multiverse" >>/etc/apt/sources.list.d/armhf.list \
+ && dpkg --add-architecture armhf \
+ && apt-get update \
+ && apt-get install -y cross-gcc-dev \
+ && TARGET_LIST="armhf" cross-gcc-gensource 6 \
+ && cd cross-gcc-packages-amd64/cross-gcc-6-armhf \
+ && ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
+ && apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf
+
+# Link to docker-build script
+RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+
+# Link to Debian source dir; mkdir needed or it fails, can't force dest
+RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+
+VOLUME ${ARTIFACT_DIR}/
+
+COPY . ${SOURCE_DIR}/
+
+ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf
new file mode 100644
index 000000000..72c464724
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/Dockerfile.armhf
@@ -0,0 +1,34 @@
+FROM ubuntu:bionic
+# Docker build arguments
+ARG SOURCE_DIR=/jellyfin
+ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-armhf
+ARG ARTIFACT_DIR=/dist
+ARG SDK_VERSION=2.2
+# Docker run environment
+ENV SOURCE_DIR=/jellyfin
+ENV ARTIFACT_DIR=/dist
+ENV DEB_BUILD_OPTIONS=noddebs
+ENV ARCH=armhf
+
+# Prepare Debian build environment
+RUN apt-get update \
+ && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev liblttng-ust0
+
+# Install dotnet repository
+# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
+RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4dfa-a905-b7648d3401d0/6312573ac13d7a8ddc16e4058f7d7dc5/dotnet-sdk-2.2.104-linux-arm.tar.gz -O dotnet-sdk.tar.gz \
+ && mkdir -p dotnet-sdk \
+ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
+ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+
+# Link to docker-build script
+RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
+
+# Link to Debian source dir; mkdir needed or it fails, can't force dest
+RUN mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
+
+VOLUME ${ARTIFACT_DIR}/
+
+COPY . ${SOURCE_DIR}/
+
+ENTRYPOINT ["/docker-build.sh"]
diff --git a/deployment/ubuntu-package-armhf/clean.sh b/deployment/ubuntu-package-armhf/clean.sh
new file mode 100755
index 000000000..c92c7fdec
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/clean.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+source ../common.build.sh
+
+keep_artifacts="${1}"
+
+WORKDIR="$( pwd )"
+
+package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
+output_dir="${WORKDIR}/pkg-dist"
+current_user="$( whoami )"
+image_name="jellyfin-ubuntu-build"
+
+rm -rf "${package_temporary_dir}" &>/dev/null \
+ || sudo rm -rf "${package_temporary_dir}" &>/dev/null
+
+rm -rf "${output_dir}" &>/dev/null \
+ || sudo rm -rf "${output_dir}" &>/dev/null
+
+if [[ ${keep_artifacts} == 'n' ]]; then
+ docker_sudo=""
+ if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
+ && [[ ! ${EUID:-1000} -eq 0 ]] \
+ && [[ ! ${USER} == "root" ]] \
+ && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
+ docker_sudo=sudo
+ fi
+ ${docker_sudo} docker image rm ${image_name} --force
+fi
diff --git a/deployment/ubuntu-package-armhf/dependencies.txt b/deployment/ubuntu-package-armhf/dependencies.txt
new file mode 100644
index 000000000..bdb967096
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/dependencies.txt
@@ -0,0 +1 @@
+docker
diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh
new file mode 100755
index 000000000..45e68f0c6
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/docker-build.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# Builds the DEB inside the Docker container
+
+set -o errexit
+set -o xtrace
+
+# Move to source directory
+pushd ${SOURCE_DIR}
+
+# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image
+sed -i '/dotnet-sdk-2.2,/d' debian/control
+
+# Build DEB
+export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
+dpkg-buildpackage -us -uc -aarmhf
+
+# Move the artifacts out
+mkdir -p ${ARTIFACT_DIR}/deb
+mv /jellyfin_* ${ARTIFACT_DIR}/deb/
diff --git a/deployment/ubuntu-package-armhf/package.sh b/deployment/ubuntu-package-armhf/package.sh
new file mode 100755
index 000000000..fb03652cd
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/package.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+source ../common.build.sh
+
+ARCH="$( arch )"
+WORKDIR="$( pwd )"
+
+package_temporary_dir="${WORKDIR}/pkg-dist-tmp"
+output_dir="${WORKDIR}/pkg-dist"
+current_user="$( whoami )"
+image_name="jellyfin-ubuntu_armhf-build"
+
+# Determine if sudo should be used for Docker
+if [[ ! -z $(id -Gn | grep -q 'docker') ]] \
+ && [[ ! ${EUID:-1000} -eq 0 ]] \
+ && [[ ! ${USER} == "root" ]] \
+ && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then
+ docker_sudo="sudo"
+else
+ docker_sudo=""
+fi
+
+# Determine which Dockerfile to use
+case $ARCH in
+ 'x86_64')
+ DOCKERFILE="Dockerfile.amd64"
+ ;;
+ 'armv7l')
+ DOCKERFILE="Dockerfile.armhf"
+ ;;
+esac
+
+# Set up the build environment Docker image
+${docker_sudo} docker build ../.. -t "${image_name}" -f ./${DOCKERFILE}
+# Build the DEBs and copy out to ${package_temporary_dir}
+${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}"
+# Correct ownership on the DEBs (as current user, then as root if that fails)
+chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \
+ || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null
+# Move the DEBs to the output directory
+mkdir -p "${output_dir}"
+mv "${package_temporary_dir}"/deb/* "${output_dir}"
diff --git a/deployment/ubuntu-package-armhf/pkg-src b/deployment/ubuntu-package-armhf/pkg-src
new file mode 120000
index 000000000..4c695fea1
--- /dev/null
+++ b/deployment/ubuntu-package-armhf/pkg-src
@@ -0,0 +1 @@
+../debian-package-x64/pkg-src \ No newline at end of file