Skip to content

Commit

Permalink
static build for runner deb package
Browse files Browse the repository at this point in the history
- since LAPS runner is a critical system component, I decided to build static binaries as with the OCO agent to make sure the program still runs even if the python runtime changed on the system
- this is done in contrast to the LAPS GUI/CLI client, where I want to keep the during installation dynamically created venv
  • Loading branch information
schorschii committed Jun 14, 2024
1 parent 907b61d commit f55063c
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 17 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/build_packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ jobs:
name: Get version name for Github release title
run: cd laps-client && echo "version=$(python3 -c 'import laps_client; print(laps_client.__version__)')" >> $GITHUB_OUTPUT

- name: Create venv, install Python packages, compile binaries - Runner
run: |
cd laps-runner
python -m venv venv
venv/bin/pip3 install pyinstaller .
venv/bin/pyinstaller laps-runner.linux.spec
cd ..
- name: Execute deb build
run: cd installer/deb/ && ./build.sh

Expand Down
10 changes: 4 additions & 6 deletions installer/deb/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,11 @@ BUILDDIR=laps4linux-runner
if [ -d "$BUILDDIR/usr" ]; then
sudo rm -r $BUILDDIR/usr
fi
mkdir -p $BUILDDIR/usr/share

# copy files in place
sudo install -D -m 644 ../../README.md -t $BUILDDIR/$INSTALLDIR
sudo install -D -m 644 ../../assets/laps-runner.cron $BUILDDIR/etc/cron.hourly/laps-runner
sudo install -D -m 644 ../../laps-runner/laps_runner/*.py -t $BUILDDIR/$INSTALLDIR/laps_runner
sudo install -D -m 644 ../../laps-runner/requirements.txt -t $BUILDDIR/$INSTALLDIR
sudo install -D -m 644 ../../laps-runner/setup.py -t $BUILDDIR/$INSTALLDIR
cp -r ../../laps-runner/dist/laps-runner $BUILDDIR/$INSTALLDIR

# test if we have our own laps-runner config
if [ -f ../../laps-runner/laps-runner.json ]; then
sudo install -D -m 644 ../../laps-runner/laps-runner.json $BUILDDIR/etc/laps-runner.json
Expand All @@ -65,7 +63,7 @@ sudo chown -R root:root $BUILDDIR

# make binaries available in PATH
sudo mkdir -p $BUILDDIR/usr/sbin
sudo ln -sf $INSTALLDIR/venv/bin/laps-runner $BUILDDIR/usr/sbin/laps-runner
sudo ln -sf $INSTALLDIR/laps-runner $BUILDDIR/usr/sbin/laps-runner


# build debs
Expand Down
1 change: 0 additions & 1 deletion installer/deb/laps4linux-client/DEBIAN/control
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ Conflicts: laps4linux, laps4linux-gui, laps4linux-cli
Maintainer: Georg Sieber <[email protected]>
Description: Linux implementation of the Local Administrator Password Solution (LAPS) GUI from Microsoft.
This package contains the management client GUI und CLI for viewing the admin passwords and setting new expiration dates.

3 changes: 1 addition & 2 deletions installer/deb/laps4linux-runner/DEBIAN/control
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ Version: 1.8.0
Section: base
Priority: optional
Architecture: all
Depends: python3, python3-venv, python3-pip, python3-setuptools, python3-gssapi, python3-dnspython, krb5-user, libkrb5-dev
Depends: python3, krb5-user, libkrb5-dev
Conflicts: laps4linux, laps4linux-gui, laps4linux-cli
Maintainer: Georg Sieber <[email protected]>
Description: Linux implementation of the Local Administrator Password Solution (LAPS) from Microsoft.
This package contains the LAPS runner for automatically changing the local admin password. In order to use the runner, the machine needs to be joined into your domain using Samba 'net ads join', PBIS 'domainjoin-cli join' or 'adcli join'. Please adjust the config file /etc/laps-runner.ini and check if LDAP access is working by executing 'laps-runner -f'.

7 changes: 0 additions & 7 deletions installer/deb/laps4linux-runner/DEBIAN/postinst
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,3 @@ set -e

# source debconf library.
#. /usr/share/debconf/confmodule

DIR=/usr/share/laps4linux-runner

# create system-wide venv and install python libraries via pip
python3 -m venv $DIR/venv
$DIR/venv/bin/pip3 install --upgrade $DIR
$DIR/venv/bin/pip3 uninstall -y pip
107 changes: 107 additions & 0 deletions laps-client/laps-client.linux.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

def Entrypoint(dist, group, name, **kwargs):
import pkg_resources

# get toplevel packages of distribution from metadata
def get_toplevel(dist):
distribution = pkg_resources.get_distribution(dist)
if distribution.has_metadata('top_level.txt'):
return list(distribution.get_metadata('top_level.txt').split())
else:
return []

kwargs.setdefault('hiddenimports', [])
packages = []
for distribution in kwargs['hiddenimports']:
packages += get_toplevel(distribution)

kwargs.setdefault('pathex', [])
# get the entry point
ep = pkg_resources.get_entry_info(dist, group, name)
# insert path of the egg at the verify front of the search path
kwargs['pathex'] = [ep.dist.location] + kwargs['pathex']
# script name must not be a valid module name to avoid name clashes on import
script_path = os.path.join(workpath, name + '-script.py')
print("creating script for entry point", dist, group, name)
with open(script_path, 'w') as fh:
print("import", ep.module_name, file=fh)
print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh)
for package in packages:
print("import", package, file=fh)

return Analysis(
[script_path] + kwargs.get('scripts', []),
**kwargs
)

gui_a = Entrypoint('laps4linux_client', 'gui_scripts', 'laps-gui',
pathex=['.'],
binaries=[],
datas=[ ('../assets/laps.png', '.') ],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
cipher=block_cipher,
noarchive=False,
optimize=0,
)
cli_a = Entrypoint('laps4linux_client', 'console_scripts', 'laps-cli',
pathex=['.'],
binaries=[],
datas=[ ('../assets/laps.png', '.') ],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
cipher=block_cipher,
noarchive=False,
optimize=0,
)
MERGE( (gui_a, 'laps-gui', 'laps-gui'), (cli_a, 'laps-cli', 'laps-cli') )

gui_pyz = PYZ(gui_a.pure, gui_a.zipped_data, cipher=block_cipher)
gui_exe = EXE(gui_pyz, gui_a.scripts, [],
exclude_binaries=True,
name='laps-gui',
contents_directory='.',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

cli_pyz = PYZ(cli_a.pure, cli_a.zipped_data, cipher=block_cipher)
cli_exe = EXE(cli_pyz, cli_a.scripts, [],
exclude_binaries=True,
name='laps-cli',
contents_directory='.',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

coll = COLLECT(
gui_exe, gui_a.binaries, gui_a.zipfiles, gui_a.datas,
cli_exe, cli_a.binaries, cli_a.zipfiles, cli_a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='laps-client'
)
74 changes: 74 additions & 0 deletions laps-runner/laps-runner.linux.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

def Entrypoint(dist, group, name, **kwargs):
import pkg_resources

# get toplevel packages of distribution from metadata
def get_toplevel(dist):
distribution = pkg_resources.get_distribution(dist)
if distribution.has_metadata('top_level.txt'):
return list(distribution.get_metadata('top_level.txt').split())
else:
return []

kwargs.setdefault('hiddenimports', [])
packages = []
for distribution in kwargs['hiddenimports']:
packages += get_toplevel(distribution)

kwargs.setdefault('pathex', [])
# get the entry point
ep = pkg_resources.get_entry_info(dist, group, name)
# insert path of the egg at the verify front of the search path
kwargs['pathex'] = [ep.dist.location] + kwargs['pathex']
# script name must not be a valid module name to avoid name clashes on import
script_path = os.path.join(workpath, name + '-script.py')
print("creating script for entry point", dist, group, name)
with open(script_path, 'w') as fh:
print("import", ep.module_name, file=fh)
print("%s.%s()" % (ep.module_name, '.'.join(ep.attrs)), file=fh)
for package in packages:
print("import", package, file=fh)

return Analysis(
[script_path] + kwargs.get('scripts', []),
**kwargs
)

a = Entrypoint('laps4linux_runner', 'console_scripts', 'laps-runner',
pathex=['.'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
cipher=block_cipher,
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(pyz, a.scripts, [],
exclude_binaries=True,
name='laps-runner',
contents_directory='.',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(exe, a.binaries, a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='laps-runner',
)
2 changes: 1 addition & 1 deletion laps-runner/laps_runner/laps_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ def main():
except Exception as e:
print(traceback.format_exc())
runner.logger.critical(__title__+': Error while executing workflow: '+str(e))
exit(1)
sys.exit(1)

return

Expand Down

0 comments on commit f55063c

Please sign in to comment.