diff --git a/assets/coverage-badge.svg b/assets/coverage-badge.svg
index a1c07ea..ac4f67b 100644
--- a/assets/coverage-badge.svg
+++ b/assets/coverage-badge.svg
@@ -11,7 +11,7 @@
coverage
coverage
- 17.5%
- 17.5%
+ 19.0%
+ 19.0%
diff --git a/e2e-tests/firejailed_package_test.go b/e2e-tests/firejailed_package_test.go
new file mode 100644
index 0000000..58ad2d0
--- /dev/null
+++ b/e2e-tests/firejailed_package_test.go
@@ -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 .
+
+//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'")
+ },
+ )
+}
diff --git a/internal/build/firejail.go b/internal/build/firejail.go
new file mode 100644
index 0000000..5c5c766
--- /dev/null
+++ b/internal/build/firejail.go
@@ -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 .
+
+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
+}
diff --git a/internal/build/firejail_test.go b/internal/build/firejail_test.go
new file mode 100644
index 0000000..dcc6bcd
--- /dev/null
+++ b/internal/build/firejail_test.go
@@ -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 .
+
+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=")
+ }
+ })
+ }
+}
diff --git a/internal/build/script_executor.go b/internal/build/script_executor.go
index b0bb8f7..18417d5 100644
--- a/internal/build/script_executor.go
+++ b/internal/build/script_executor.go
@@ -248,6 +248,16 @@ func buildPkgMetadata(
if err != nil {
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
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
diff --git a/internal/build/utils.go b/internal/build/utils.go
index 1ef2f9d..871b724 100644
--- a/internal/build/utils.go
+++ b/internal/build/utils.go
@@ -154,6 +154,12 @@ func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents
return contents, nil
}
+func normalizeContents(contents []*files.Content) {
+ for _, content := range contents {
+ content.Destination = filepath.Join("/", content.Destination)
+ }
+}
+
var RegexpALRPackageName = regexp.MustCompile(`^(?P[^+]+)\+alr-(?P.+)$`)
func getBasePkgInfo(vars *alrsh.Package, input interface {
diff --git a/internal/translations/default.pot b/internal/translations/default.pot
index 42457b4..101951e 100644
--- a/internal/translations/default.pot
+++ b/internal/translations/default.pot
@@ -220,19 +220,23 @@ msgstr ""
msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
+#: internal/build/firejail.go:59
+msgid "Applying FireJail integration"
+msgstr ""
+
#: internal/build/script_executor.go:145
msgid "Building package metadata"
msgstr ""
-#: internal/build/script_executor.go:275
+#: internal/build/script_executor.go:285
msgid "Executing prepare()"
msgstr ""
-#: internal/build/script_executor.go:284
+#: internal/build/script_executor.go:294
msgid "Executing build()"
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()"
msgstr ""
diff --git a/internal/translations/po/ru/default.po b/internal/translations/po/ru/default.po
index 4b47a80..4be205f 100644
--- a/internal/translations/po/ru/default.po
+++ b/internal/translations/po/ru/default.po
@@ -5,15 +5,15 @@
msgid ""
msgstr ""
"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 \n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
-"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Gtranslator 48.0\n"
#: build.go:42
@@ -231,19 +231,23 @@ msgid "AutoReq is not implemented for this package format, so it's skipped"
msgstr ""
"AutoReq не реализовано для этого формата пакета, поэтому будет пропущено"
+#: internal/build/firejail.go:59
+msgid "Applying FireJail integration"
+msgstr "Применение интеграции FireJail"
+
#: internal/build/script_executor.go:145
msgid "Building package metadata"
msgstr "Сборка метаданных пакета"
-#: internal/build/script_executor.go:275
+#: internal/build/script_executor.go:285
msgid "Executing prepare()"
msgstr "Выполнение prepare()"
-#: internal/build/script_executor.go:284
+#: internal/build/script_executor.go:294
msgid "Executing 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()"
msgstr "Выполнение %s()"
@@ -352,8 +356,8 @@ msgid ""
"This command is deprecated and would be removed in the future, use \"%s\" "
"instead!"
msgstr ""
-"Эта команда устарела и будет удалена в будущем, используйте вместо нее \"%s"
-"\"!"
+"Эта команда устарела и будет удалена в будущем, используйте вместо нее "
+"\"%s\"!"
#: internal/db/db.go:76
msgid "Database version mismatch; resetting"
diff --git a/pkg/alrsh/package.go b/pkg/alrsh/package.go
index c24d0b2..5674cd3 100644
--- a/pkg/alrsh/package.go
+++ b/pkg/alrsh/package.go
@@ -68,6 +68,9 @@ type Package struct {
AutoProv OverridableField[[]string] `sh:"auto_prov" xorm:"-"`
AutoReqSkipList OverridableField[[]string] `sh:"auto_req_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 {