Document bundle installation and normalize release directory permissions
This commit is contained in:
1 parent
bfec60848d
commit
ac184576f6
2 files changed
+34
-9
No files matched your search
@@ -50,8 +50,23 @@ def sync_dir(path):
|
|||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directory(path, mode=0o755):
|
||||||
|
missing = []
|
||||||
|
cursor = path
|
||||||
|
while not cursor.exists():
|
||||||
|
if cursor.is_symlink():
|
||||||
|
fail(f'Refusing dangling directory symlink: {cursor}')
|
||||||
|
missing.append(cursor)
|
||||||
|
cursor = cursor.parent
|
||||||
|
|
||||||
|
for directory in reversed(missing):
|
||||||
|
directory.mkdir(mode=mode)
|
||||||
|
directory.chmod(mode)
|
||||||
|
sync_dir(directory.parent)
|
||||||
|
|
||||||
|
|
||||||
def atomic_bytes(path, data, mode=0o644):
|
def atomic_bytes(path, data, mode=0o644):
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
ensure_directory(path.parent)
|
||||||
fd, temporary = tempfile.mkstemp(prefix='.ferese-', dir=path.parent)
|
fd, temporary = tempfile.mkstemp(prefix='.ferese-', dir=path.parent)
|
||||||
try:
|
try:
|
||||||
with os.fdopen(fd, 'wb') as stream:
|
with os.fdopen(fd, 'wb') as stream:
|
||||||
@@ -68,7 +83,7 @@ def atomic_bytes(path, data, mode=0o644):
|
|||||||
|
|
||||||
|
|
||||||
def atomic_copy(path, source, mode):
|
def atomic_copy(path, source, mode):
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
ensure_directory(path.parent)
|
||||||
fd, temporary = tempfile.mkstemp(prefix='.ferese-', dir=path.parent)
|
fd, temporary = tempfile.mkstemp(prefix='.ferese-', dir=path.parent)
|
||||||
try:
|
try:
|
||||||
with os.fdopen(fd, 'wb') as output:
|
with os.fdopen(fd, 'wb') as output:
|
||||||
@@ -150,10 +165,10 @@ def bundle(args):
|
|||||||
if output.exists() or output.is_symlink():
|
if output.exists() or output.is_symlink():
|
||||||
fail(f'Bundle already exists: {output}')
|
fail(f'Bundle already exists: {output}')
|
||||||
|
|
||||||
output.parent.mkdir(parents=True, exist_ok=True)
|
ensure_directory(output.parent)
|
||||||
with tempfile.TemporaryDirectory(prefix='.bundle-', dir=output.parent) as temporary:
|
with tempfile.TemporaryDirectory(prefix='.bundle-', dir=output.parent) as temporary:
|
||||||
staging = Path(temporary) / 'release'
|
staging = Path(temporary) / 'release'
|
||||||
staging.mkdir()
|
ensure_directory(staging)
|
||||||
files = []
|
files = []
|
||||||
for rule in INVENTORY['files']:
|
for rule in INVENTORY['files']:
|
||||||
sources = sorted(source.glob(rule['source']))
|
sources = sorted(source.glob(rule['source']))
|
||||||
@@ -210,7 +225,7 @@ class Installation:
|
|||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def lock(self):
|
def lock(self):
|
||||||
self.base.mkdir(parents=True, exist_ok=True)
|
ensure_directory(self.base)
|
||||||
lock_path = self.path(PREFIX + '/.install.lock')
|
lock_path = self.path(PREFIX + '/.install.lock')
|
||||||
fd = os.open(lock_path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
|
fd = os.open(lock_path, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600)
|
||||||
try:
|
try:
|
||||||
@@ -339,7 +354,7 @@ class Installation:
|
|||||||
|
|
||||||
def write_state(self, absolute, state, source=None):
|
def write_state(self, absolute, state, source=None):
|
||||||
path = self.path(absolute)
|
path = self.path(absolute)
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
ensure_directory(path.parent)
|
||||||
if state['kind'] == 'absent':
|
if state['kind'] == 'absent':
|
||||||
path.unlink(missing_ok=True)
|
path.unlink(missing_ok=True)
|
||||||
sync_dir(path.parent)
|
sync_dir(path.parent)
|
||||||
@@ -359,7 +374,7 @@ class Installation:
|
|||||||
|
|
||||||
def archive(self, journal):
|
def archive(self, journal):
|
||||||
history = self.path(PREFIX + '/transactions')
|
history = self.path(PREFIX + '/transactions')
|
||||||
history.mkdir(mode=0o700, exist_ok=True)
|
ensure_directory(history, 0o700)
|
||||||
os.rename(self.transaction, history / journal['id'])
|
os.rename(self.transaction, history / journal['id'])
|
||||||
sync_dir(history)
|
sync_dir(history)
|
||||||
sync_dir(self.base)
|
sync_dir(self.base)
|
||||||
@@ -433,7 +448,7 @@ class Installation:
|
|||||||
if releases.is_symlink():
|
if releases.is_symlink():
|
||||||
fail(f'Refusing symlinked release directory: {releases}')
|
fail(f'Refusing symlinked release directory: {releases}')
|
||||||
|
|
||||||
releases.mkdir(parents=True, exist_ok=True)
|
ensure_directory(releases)
|
||||||
destination = releases / manifest['release']
|
destination = releases / manifest['release']
|
||||||
if os.path.lexists(destination):
|
if os.path.lexists(destination):
|
||||||
if self.manifest(manifest['release']) != manifest:
|
if self.manifest(manifest['release']) != manifest:
|
||||||
@@ -443,7 +458,7 @@ class Installation:
|
|||||||
|
|
||||||
with tempfile.TemporaryDirectory(prefix='.stage-', dir=releases) as temporary:
|
with tempfile.TemporaryDirectory(prefix='.stage-', dir=releases) as temporary:
|
||||||
staging = Path(temporary) / 'release'
|
staging = Path(temporary) / 'release'
|
||||||
staging.mkdir()
|
ensure_directory(staging)
|
||||||
for item in manifest['files']:
|
for item in manifest['files']:
|
||||||
atomic_copy(staging / item['target'], origin / item['target'], int(item['mode'], 8))
|
atomic_copy(staging / item['target'], origin / item['target'], int(item['mode'], 8))
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,16 @@ m.main()
|
|||||||
self.assertIn('in progress', result.stderr)
|
self.assertIn('in progress', result.stderr)
|
||||||
self.assertFalse((self.base / 'current').exists())
|
self.assertFalse((self.base / 'current').exists())
|
||||||
|
|
||||||
|
def test_restrictive_umask_does_not_make_installed_release_private(self):
|
||||||
|
result = subprocess.run([sys.executable, '-c',
|
||||||
|
f"import os, runpy; os.umask(0o077); runpy.run_path({str(HELPER)!r}, run_name='__main__')",
|
||||||
|
'install', str(self.bundles[0]), '--root', str(self.root)],
|
||||||
|
capture_output=True, text=True)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
for relative in ('usr', 'usr/local', PREFIX, PREFIX + '/releases', PREFIX + '/releases/first',
|
||||||
|
PREFIX + '/releases/first/wallpapers', 'usr/share/wayland-sessions'):
|
||||||
|
self.assertEqual((self.root / relative).stat().st_mode & 0o777, 0o755, relative)
|
||||||
|
|
||||||
def test_dry_run_does_not_write(self):
|
def test_dry_run_does_not_write(self):
|
||||||
before = self.snapshot()
|
before = self.snapshot()
|
||||||
self.run_command('install', self.bundles[0], '--dry-run')
|
self.run_command('install', self.bundles[0], '--dry-run')
|
||||||
|
|||||||
Reference in new issue
Block a user