1#!/usr/bin/env python3

2# Copyright 2019 Canonical Ltd.

3# All rights reserved.

4#

5# Written by:

6# Jonathan Cave <jonathan.cave@canonical.com>

7#

8# Save/Restore NetworkManager wifi connections

9

10 import os

11 import shutil

12 import subprocess as sp

13 import sys

14 import glob

15 from pathlib import Path

16

17 from packaging import version as version_parser

18

19

20 NM_CON_DIR = "/etc/NetworkManager/system-connections"

21 SAVE_DIR = os.path.join(

22 os.path.expandvars("$PLAINBOX_SESSION_SHARE"), "stored-system-connections"

23)

24

25

26# versions of NM in cosmic or later allow you to request the keyfile name

27# in `nmcli -t -f <FIELDS> c` output used below

28 def legacy_nmcli():

29 cmd = "nmcli -v"

30 output = sp.check_output(cmd, shell=True)

31 version = version_parser.parse(output.strip().split()[-1].decode())

32 # check if using an earlier nmcli version with different api

33 # nmcli in cosmic is 1.12.4, bionic is 1.10

34 return version < version_parser.parse("1.12.0")

35

36

37# Creation of keyfile names can be found in:

38# https://gitlab.freedesktop.org/NetworkManager/NetworkManager/blob/master/libnm-core/nm-keyfile.c#L4046

39# Old format is to replace path separators with '*', in versions that use

40# the new format we can just use the filename supplied by nmcli

41 def get_nm_keyfiles():

42 filenames = []

43 if legacy_nmcli():

44 cmd = "nmcli -t -f TYPE,NAME c"

45 output = sp.check_output(cmd, shell=True)

46 for line in output.decode(sys.stdout.encoding).splitlines():

47 con_type, name = line.strip().split(":")

48 if con_type == "802-11-wireless":

49 filename = name.replace("/", "*")

50 filenames.append(os.path.join(NM_CON_DIR, filename))

51 else:

52 cmd = "nmcli -t -f TYPE,FILENAME c"

53 output = sp.check_output(cmd, shell=True)

54 for line in output.decode(sys.stdout.encoding).splitlines():

55 con_type, filename = line.strip().split(":")

56 if con_type == "802-11-wireless":

57 filenames.append(filename)

58 return filenames

59

60

61 def reload_nm_connections():

62 cmd = "nmcli c reload"

63 sp.check_call(cmd, shell=True)

64

65

66 def save_connections(keyfile_list):

67 os.makedirs(SAVE_DIR, exist_ok=True)

68

69 if not keyfile_list:

70 print("No stored 802.11 connections to save")

71 return

72

73 for f in keyfile_list:

74 print("Save connection {}".format(f))

75

76 if not os.path.exists(f):

77 print(" No stored connection found at {}".format(f))

78 continue

79

80 print(" Found file {}".format(f))

81 basedir = Path(f).parent.relative_to("/")

82 backup_loc = SAVE_DIR / basedir

83

84 os.makedirs(backup_loc, exist_ok=True)

85 save_f = shutil.copy(f, backup_loc)

86 print(" Saved copy at {}".format(save_f))

87

88

89 def restore_connections():

90 saved_list = glob.glob(

91 "{}/**/*.nmconnection".format(SAVE_DIR), recursive=True

92 )

93 if len(saved_list) == 0:

94 print("No stored 802.11 connections found")

95 return

96 for f in saved_list:

97 f_path = Path(f)

98 save_f = f_path.relative_to(SAVE_DIR)

99 dest_path = Path("/") / save_f

100 print("Restore connection {}".format(save_f))

101 shutil.move(f, dest_path)

102

103

104 if __name__ == "__main__":

105 if len(sys.argv) != 2:

106 raise SystemExit("ERROR: please specify save or restore")

107 action = sys.argv[1]

108

109 if action == "save":

110 save_connections(get_nm_keyfiles())

111 elif action == "restore":

112 restore_connections()

113 reload_nm_connections()

114 else:

115 raise SystemExit("ERROR: unrecognised action")