diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py
index 41755e79..78916177 100644
--- a/src/ahriman/core/repository/executor.py
+++ b/src/ahriman/core/repository/executor.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
-import shutil
+import shutil # shutil.move is used here to ensure cross fs file movement
from collections.abc import Iterable
from pathlib import Path
@@ -27,7 +27,7 @@ from ahriman.core.build_tools.package_archive import PackageArchive
from ahriman.core.build_tools.task import Task
from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.package_info import PackageInfo
-from ahriman.core.utils import safe_filename
+from ahriman.core.utils import safe_filename, utcnow
from ahriman.models.changes import Changes
from ahriman.models.event import EventType
from ahriman.models.package import Package
@@ -187,9 +187,12 @@ class Executor(PackageInfo, Cleaner):
# in theory, it might be NOT packages directory, but we suppose it is
full_path = self.paths.packages / name
files = self.sign.process_sign_package(full_path, packager_key)
+
for src in files:
- dst = self.paths.repository / safe_filename(src.name)
- shutil.move(src, dst)
+ archive = self.paths.archive_for(package_base) / safe_filename(src.name)
+ shutil.move(src, archive)
+ for symlink in (self.paths.repository / archive.name, self.paths.archive_for(utcnow()) / archive.name):
+ symlink.symlink_to(archive.relative_to(symlink.parent, walk_up=True))
package_path = self.paths.repository / safe_filename(name)
self.repo.add(package_path)
diff --git a/src/ahriman/models/repository_paths.py b/src/ahriman/models/repository_paths.py
index 7e8ae558..a2fdec8d 100644
--- a/src/ahriman/models/repository_paths.py
+++ b/src/ahriman/models/repository_paths.py
@@ -18,6 +18,7 @@
# along with this program. If not, see .
#
import contextlib
+import datetime
import os
import shutil
@@ -85,6 +86,16 @@ class RepositoryPaths(LazyLogging):
return Path(self.repository_id.architecture) # legacy tree suffix
return Path(self.repository_id.name) / self.repository_id.architecture
+ @property
+ def archive(self) -> Path:
+ """
+ archive directory root
+
+ Returns:
+ Path: archive directory root
+ """
+ return self.root / "archive" / self._suffix
+
@property
def build_root(self) -> Path:
"""
@@ -249,6 +260,34 @@ class RepositoryPaths(LazyLogging):
set_owner(path)
path = path.parent
+ def archive_for(self, category: datetime.date | str) -> Path:
+ """
+ get path to archive specified search criteria
+
+ Args:
+ category(datetime.date | str): either time to search or package base name
+
+ Returns:
+ Path: path to archive directory for specified identifier
+
+ Raises:
+ NotImplementedError: if category type is not supported
+ """
+ match category:
+ case datetime.date():
+ suffix = Path("repos") / f"{category.year}" / f"{category.month:02d}" / f"{category.day:02d}"
+ case str() if len(category) > 0:
+ suffix = Path("packages") / category[0] / category
+ case _:
+ raise NotImplementedError
+
+ directory = self.archive / suffix
+ if not directory.is_dir(): # create if not exists
+ with self.preserve_owner(self.archive):
+ directory.mkdir(mode=0o755, parents=True)
+
+ return directory
+
def cache_for(self, package_base: str) -> Path:
"""
get path to cached PKGBUILD and package sources for the package base
@@ -320,6 +359,7 @@ class RepositoryPaths(LazyLogging):
with self.preserve_owner():
for directory in (
+ self.archive,
self.cache,
self.chroot,
self.packages,