feat: add firejailed support (#106)
Some checks failed
Update alr-git / changelog (push) Successful in 27s
Create Release / changelog (push) Failing after 3m15s

Reviewed-on: #106
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
This commit is contained in:
2025-06-15 13:16:02 +00:00
committed by Maxim Slipenko
parent d1fe02fa57
commit bd79dcf401
9 changed files with 634 additions and 13 deletions

View File

@ -11,7 +11,7 @@
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text> <text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text> <text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">17.5%</text> <text x="86" y="15" fill="#010101" fill-opacity=".3">19.0%</text>
<text x="86" y="14">17.5%</text> <text x="86" y="14">19.0%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 926 B

After

Width:  |  Height:  |  Size: 926 B

View File

@ -0,0 +1,41 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EFirejailedPackage(t *testing.T) {
dockerMultipleRun(
t,
"firejailed-package",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
},
)
}

237
internal/build/firejail.go Normal file
View File

@ -0,0 +1,237 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/goreleaser/nfpm/v2/files"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
const (
firejailedDir = "/usr/lib/alr/firejailed"
defaultDirMode = 0o755
defaultScriptMode = 0o755
)
var (
ErrInvalidDestination = errors.New("invalid destination path")
ErrMissingProfile = errors.New("default profile is missing")
ErrEmptyPackageName = errors.New("package name cannot be empty")
)
var binaryDirectories = []string{
"/usr/bin/",
"/bin/",
"/usr/local/bin/",
}
func applyFirejailIntegration(
vars *alrsh.Package,
dirs types.Directories,
contents []*files.Content,
) ([]*files.Content, error) {
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
}
newContents, err := processBinaryFiles(vars, contents, dirs)
if err != nil {
return nil, fmt.Errorf("failed to process binary files: %w", err)
}
return append(contents, newContents...), nil
}
func createFirejailedDirectory(pkgDir string) error {
firejailedPath := filepath.Join(pkgDir, firejailedDir)
return os.MkdirAll(firejailedPath, defaultDirMode)
}
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
var newContents []*files.Content
for _, content := range contents {
if content.Type == "dir" {
continue
}
if !isBinaryFile(content.Destination) {
slog.Debug("content not binary file", "content", content)
continue
}
slog.Debug("process content", "content", content)
newContent, err := createFirejailedBinary(pkg, content, dirs)
if err != nil {
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
}
if newContent != nil {
newContents = append(newContents, newContent...)
}
}
return newContents, nil
}
func isBinaryFile(destination string) bool {
for _, binDir := range binaryDirectories {
if strings.HasPrefix(destination, binDir) {
return true
}
}
return false
}
func createFirejailedBinary(
pkg *alrsh.Package,
content *files.Content,
dirs types.Directories,
) ([]*files.Content, error) {
origFilePath, err := generateFirejailedPath(content.Destination)
if err != nil {
return nil, err
}
profiles := pkg.FireJailProfiles.Resolved()
sourceProfilePath, ok := profiles[content.Destination]
if !ok {
sourceProfilePath, ok = profiles["default"]
if !ok {
return nil, errors.New("default profile is missing")
}
}
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
dest, err := createFirejailProfilePath(content.Destination)
if err != nil {
return nil, err
}
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
if err != nil {
return nil, err
}
if err := osutils.Move(content.Source, filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
return nil, fmt.Errorf("failed to move original binary: %w", err)
}
// Create wrapper script
if err := createWrapperScript(content.Source, origFilePath, dest); err != nil {
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
}
profile, err := getContentFromPath(dest, dirs.PkgDir)
if err != nil {
return nil, err
}
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
if err != nil {
return nil, err
}
return []*files.Content{
bin,
profile,
}, nil
}
func getContentFromPath(path, base string) (*files.Content, error) {
absPath := filepath.Join(base, path)
fi, err := os.Lstat(absPath)
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
return &files.Content{
Source: absPath,
Destination: path,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}, nil
}
func generateSafeName(destination string) (string, error) {
cleanPath := strings.TrimPrefix(destination, ".")
if cleanPath == "" {
return "", fmt.Errorf("invalid destination path: %s", destination)
}
return strings.ReplaceAll(cleanPath, "/", "_"), nil
}
func generateFirejailedPath(destination string) (string, error) {
safeName, err := generateSafeName(destination)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, safeName), nil
}
func createProfile(destProfilePath, profilePath string) error {
srcFile, err := os.Open(profilePath)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(destProfilePath)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
return destFile.Sync()
}
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
}
func createFirejailProfilePath(binaryPath string) (string, error) {
name, err := generateSafeName(binaryPath)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
}

View File

@ -0,0 +1,316 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"os"
"path/filepath"
"testing"
"github.com/goreleaser/nfpm/v2/files"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func TestIsBinaryFile(t *testing.T) {
tests := []struct {
name string
destination string
expected bool
}{
{"usr/bin binary", "/usr/bin/test", true},
{"bin binary", "/bin/test", true},
{"usr/local/bin binary", "/usr/local/bin/test", true},
{"lib file", "/usr/lib/test.so", false},
{"etc file", "/etc/config", false},
{"empty destination", "", false},
{"root level file", "./test", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isBinaryFile(tt.destination)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGenerateSafeName(t *testing.T) {
tests := []struct {
name string
destination string
expected string
expectError bool
}{
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
{"bin path", "./bin/test", "_bin_test", false},
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
{"empty after trim", ".", "", true},
{"empty string", "", "", true},
{"only dots", "..", ".", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := generateSafeName(tt.destination)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestCreateWrapperScript(t *testing.T) {
tests := []struct {
name string
origFilePath string
profilePath string
expectedContent string
}{
{
"basic wrapper",
"/usr/lib/alr/firejailed/_usr_bin_test",
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
},
{
"path with spaces",
"/usr/lib/alr/firejailed/_usr_bin_my_app",
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
assert.NoError(t, err)
assert.FileExists(t, scriptPath)
content, err := os.ReadFile(scriptPath)
assert.NoError(t, err)
assert.Equal(t, tt.expectedContent, string(content))
// Check file permissions
info, err := os.Stat(scriptPath)
assert.NoError(t, err)
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
})
}
}
func TestCreateFirejailedBinary(t *testing.T) {
tests := []struct {
name string
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
expectError bool
errorMsg string
}{
{
"successful creation with default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "./usr/bin/test-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"successful creation with specific profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "special-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
specialProfile := filepath.Join(scriptDir, "special.profile")
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "./usr/bin/special-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"missing default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"default profile is missing",
},
{
"profile file not found",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
{
"invalid destination path",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
pkg, content, dirs := tt.setupFunc(tmpDir)
err := createFirejailedDirectory(dirs.PkgDir)
assert.NoError(t, err)
result, err := createFirejailedBinary(pkg, content, dirs)
if tt.expectError {
assert.Error(t, err)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg)
}
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Len(t, result, 2)
binContent := result[0]
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
assert.FileExists(t, binContent.Source)
profileContent := result[1]
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
assert.Contains(t, profileContent.Destination, ".profile")
assert.FileExists(t, profileContent.Source)
assert.FileExists(t, content.Source)
wrapperBytes, err := os.ReadFile(content.Source)
assert.NoError(t, err)
wrapper := string(wrapperBytes)
assert.Contains(t, wrapper, "#!/bin/bash")
assert.Contains(t, wrapper, "firejail --profile=")
}
})
}
}

View File

@ -248,6 +248,16 @@ func buildPkgMetadata(
if err != nil { if err != nil {
return nil, err return nil, err
} }
normalizeContents(contents)
if vars.FireJailed.Resolved() {
contents, err = applyFirejailIntegration(vars, dirs, contents)
if err != nil {
return nil, err
}
}
pkgInfo.Overridables.Contents = contents pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) { if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {

View File

@ -154,6 +154,12 @@ func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents
return contents, nil return contents, nil
} }
func normalizeContents(contents []*files.Content) {
for _, content := range contents {
content.Destination = filepath.Join("/", content.Destination)
}
}
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`) var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
func getBasePkgInfo(vars *alrsh.Package, input interface { func getBasePkgInfo(vars *alrsh.Package, input interface {

View File

@ -220,19 +220,23 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped" msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
#: internal/build/firejail.go:59
msgid "Applying FireJail integration"
msgstr ""
#: internal/build/script_executor.go:145 #: internal/build/script_executor.go:145
msgid "Building package metadata" msgid "Building package metadata"
msgstr "" msgstr ""
#: internal/build/script_executor.go:275 #: internal/build/script_executor.go:285
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:284 #: internal/build/script_executor.go:294
msgid "Executing build()" msgid "Executing build()"
msgstr "" msgstr ""
#: internal/build/script_executor.go:313 internal/build/script_executor.go:333 #: internal/build/script_executor.go:323 internal/build/script_executor.go:343
msgid "Executing %s()" msgid "Executing %s()"
msgstr "" msgstr ""

View File

@ -5,15 +5,15 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: unnamed project\n" "Project-Id-Version: unnamed project\n"
"PO-Revision-Date: 2025-05-16 20:47+0300\n" "PO-Revision-Date: 2025-06-15 16:05+0300\n"
"Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n" "Last-Translator: Maxim Slipenko <maks1ms@alt-gnome.ru>\n"
"Language-Team: Russian\n" "Language-Team: Russian\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 48.0\n" "X-Generator: Gtranslator 48.0\n"
#: build.go:42 #: build.go:42
@ -231,19 +231,23 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr "" msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено" "AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
#: internal/build/firejail.go:59
msgid "Applying FireJail integration"
msgstr "Применение интеграции FireJail"
#: internal/build/script_executor.go:145 #: internal/build/script_executor.go:145
msgid "Building package metadata" msgid "Building package metadata"
msgstr "Сборка метаданных пакета" msgstr "Сборка метаданных пакета"
#: internal/build/script_executor.go:275 #: internal/build/script_executor.go:285
msgid "Executing prepare()" msgid "Executing prepare()"
msgstr "Выполнение prepare()" msgstr "Выполнение prepare()"
#: internal/build/script_executor.go:284 #: internal/build/script_executor.go:294
msgid "Executing build()" msgid "Executing build()"
msgstr "Выполнение build()" msgstr "Выполнение build()"
#: internal/build/script_executor.go:313 internal/build/script_executor.go:333 #: internal/build/script_executor.go:323 internal/build/script_executor.go:343
msgid "Executing %s()" msgid "Executing %s()"
msgstr "Выполнение %s()" msgstr "Выполнение %s()"
@ -352,8 +356,8 @@ msgid ""
"This command is deprecated and would be removed in the future, use \"%s\" " "This command is deprecated and would be removed in the future, use \"%s\" "
"instead!" "instead!"
msgstr "" msgstr ""
"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s" "Эта команда устарела и будет удалена в будущем, используйте вместо нее "
"\"!" "\"%s\"!"
#: internal/db/db.go:76 #: internal/db/db.go:76
msgid "Database version mismatch; resetting" msgid "Database version mismatch; resetting"

View File

@ -68,6 +68,9 @@ type Package struct {
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"` AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"` AutoReqSkipList OverridableField[[]string] `sh:"auto_req_skiplist" xorm:"-"`
AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"` AutoProvSkipList OverridableField[[]string] `sh:"auto_prov_skiplist" xorm:"-"`
FireJailed OverridableField[bool] `sh:"firejailed" xorm:"-"`
FireJailProfiles OverridableField[map[string]string] `sh:"firejail_profiles" xorm:"-"`
} }
type Scripts struct { type Scripts struct {