Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 102 additions & 71 deletions overlays/php/usr/local/bin/tkl-upgrade-php
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,48 @@ import sys
PHP_VERSION_RE = re.compile(r"^Package: (php\d+\.\d+)-")


def get_output(args):
def get_output(args: list[str]) -> str:
return subprocess.check_output(args, text=True).strip()


ARCH = get_output(["dpkg", "--print-architecture"])
CODENAME = get_output(["lsb_release", "-c"]).split()[1]
CODENAME = get_output(["lsb_release", "-sc"])


SECURITY_NOTICE = """
NOTE:
Older version of PHP may not be actively maintained, this includes not just
bug fixes but also security fixes.

Versions which have reached EOL should not be expected to be secure.
Versions which have reached EOL should not be expected to be secure.

It is always encouraged that you only install the newest version that
applies for your use case.
"""


def get_supported_versions() -> dict[str, str]:
with request.urlopen('http://www.php.net/releases/active') as iob:
with request.urlopen("https://www.php.net/releases/active") as iob:
data = json.loads(iob.read().decode())
versions = {}

for major in data.keys():
for major_minor in data[major].keys():
tags = data[major][major_minor]['tags']
if 'security' in tags:
versions[major_minor] = 'security only'
tags = data[major][major_minor]["tags"]
if "security" in tags:
versions[major_minor] = "security only"
else:
versions[major_minor] = 'supported'
versions[major_minor] = "supported"
return versions


def get_php_versions() -> list[str]:
"""Get all available php versions and return them in the form phpx.y"""
print("\nChecking Available PHP Versions")

url = f"https://packages.sury.org/php/dists/{CODENAME}/main/binary-{ARCH}/Packages"
with request.urlopen(url) as iob:
url = "https://packages.sury.org"
pkg_file_url = f"{url}/php/dists/{CODENAME}/main/binary-{ARCH}/Packages"
with request.urlopen(pkg_file_url) as iob:
data = iob.read().decode()

php_versions = set()
Expand All @@ -68,75 +71,107 @@ def choose_version(choices, version_support, deb_version) -> str:

print("Select the PHP version to install:")
for i, opt in enumerate(choices):
support = version_support.get(opt[3:], 'end of life')
support = version_support.get(opt[3:], "end of life")
if opt[3:] == deb_version:
print(f"{i+1}). {opt} ({support}) (provided by debian)")
print(f"{i + 1}). {opt} ({support}) (provided by debian)")
else:
print(f"{i+1}). {opt} ({support})")
print(f"{i + 1}). {opt} ({support})")

inp = input(f"\nEnter choice [1-{len(choices)}]: ")

if not inp.isdigit():
print("Input must be a number! Exiting")
sys.exit(1)

inp = int(inp)
choice_inp = int(inp)

if inp < 1 or inp > len(choices):
if choice_inp < 1 or choice_inp > len(choices):
print(f"Input out of range [1-{len(choices)}]! Exiting")
sys.exit(1)

return choices[inp - 1]
return choices[choice_inp - 1]


def get_packages(php_version):
curr_package = None
packages = {}
def get_packages(php_version: str) -> list[str]:
curr_package = ""
packages: dict[str, str] = {}
for line in get_output(
["apt-cache", "policy", "--", "*" + php_version]
).splitlines():
if not line[0].isspace():
curr_package = line.rstrip(":")
packages[curr_package] = []
else:
if "Installed: (none)" in line:
del packages[curr_package]
curr_package = None
return packages.keys()
packages[curr_package] = ""
elif "Installed: (none)" in line:
del packages[curr_package]
curr_package = ""
return list(packages.keys())


def debian_version():
out = get_output(['apt-cache', 'policy', 'php'])
out = out.split('Version table:')[1].strip()
out = get_output(["apt-cache", "policy", "php"])
out = out.split("Version table:")[1].strip()

last_line = None
for line in out.splitlines():
if 'deb.debian.org/debian bookworm/main' in line:
if f"deb.debian.org/debian {CODENAME}/main" in line:
if last_line:
return last_line.strip().split(':')[1].split('+')[0]
return last_line.strip().split(":")[1].split("+")[0]
return None
last_line = line
return None


def check_new_packages(new_packages):
missing_packages = []
found_packages = []
for pkg in new_packages:
if subprocess.run(
['apt-cache', 'show', pkg],
stdout=DEVNULL, stderr=DEVNULL).returncode != 0:
if (
subprocess.run(
["apt-cache", "show", pkg], stdout=DEVNULL, stderr=DEVNULL
).returncode
!= 0
):
missing_packages.append(pkg)
else:
found_packages.append(pkg)

if missing_packages:
print('The following packages do not exist in the requested PHP version:')
print(' ' + ', '.join(missing_packages))
print(
"The following packages do not exist in the requested PHP version:"
)
print(" " + ", ".join(missing_packages))

return found_packages


def is_active(service):
return subprocess.run(['systemctl', 'is-active', '--quiet', service]).returncode == 0
return (
subprocess.run(
["systemctl", "is-active", "--quiet", service]
).returncode == 0
)


if __name__ == '__main__':
def php_sources(codename: str = CODENAME, enabled: bool = True) -> None:
enabled_str = "yes" if enabled else "no"
with open("/etc/apt/sources.list.d/php.sources", "w") as fob:
fob.write(
"# DEB.SURY.ORG repo for php\n"
"Types: deb\n"
"URIs: https://packages.sury.org/php/\n"
f"Suites: {CODENAME}\n"
"Components: main\n"
f"Enabled: {enabled_str}\n"
"Signed-By: /usr/share/keyrings/debsuryorg-archive-keyring.gpg\n"
)


def update_pins(new_php_version: str) -> None:
"""Update/write apt pins (preferences)."""
# TODO....


if __name__ == "__main__":
print("Starting PHP upgrade script ...")
print("Current PHP version")
subprocess.run(["/usr/bin/php", "-v"])
Expand All @@ -145,72 +180,68 @@ if __name__ == '__main__':
["php", "-r", 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;']
)

print('Determining supported version ...')
print("Determining supported version ...")
version_support = get_supported_versions()
deb_version = debian_version()
if not deb_version:
print('Running "apt-get update" ...')

subprocess.run(['apt-get', 'update'])
subprocess.run(["apt-get", "update"])
deb_version = debian_version()


new_php_version = choose_version(get_php_versions(), version_support,
deb_version)
new_php_version = choose_version(
get_php_versions(), version_support, deb_version
)
new_php_pkgs = [
pkg.replace("php" + current_php_version, new_php_version)
for pkg in get_packages(current_php_version)
]

if new_php_version[3:] == deb_version:
if os.path.isfile('/etc/apt/sources.list.d/php.list'):
os.remove('/etc/apt/sources.list.d/php.list')
subprocess.run(['dpkg', '-r', 'debsuryorg-archive-keyring'])
# remove legacy php.list file if it exists
if os.path.isfile("/etc/apt/sources.list.d/php.list"):
os.remove("/etc/apt/sources.list.d/php.list")

if new_php_version[3:] == deb_version:
if os.path.isfile("/etc/apt/sources.list.d/php.list"):
subprocess.run(["dpkg", "-r", "debsuryorg-archive-keyring"])
else:
print("Downloading Sury Archive Keyring ...")

print('Downloading Sury Archive Keyring ...')

url = 'https://packages.sury.org/debsuryorg-archive-keyring.deb'
url = "https://packages.sury.org/debsuryorg-archive-keyring.deb"
with request.urlopen(url) as iob:
with open('/tmp/debsuryorg-archive.keyring.deb', 'wb') as fob:
with open("/tmp/debsuryorg-archive.keyring.deb", "wb") as fob:
fob.write(iob.read())

subprocess.run(['dpkg', '-i', '/tmp/debsuryorg-archive.keyring.deb'])

with open('/etc/apt/sources.list.d/php.list', 'w') as fob:
fob.write(
'deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg]'
f' https://packages.sury.org/php/ {CODENAME} main'
)
subprocess.run(["dpkg", "-i", "/tmp/debsuryorg-archive.keyring.deb"])

print('Running "apt-get update" ...')

subprocess.run(['apt-get', 'update'])
subprocess.run(["apt-get", "update"])

found_packages = check_new_packages(new_php_pkgs)

print('Installing new packages ...')
print("Installing new packages ...")

subprocess.run(['apt-get', '-y', 'install', *found_packages])
subprocess.run(["apt-get", "-y", "install", *found_packages])

print('Configuring Webserver ...')
print("Configuring Webserver ...")

if is_active('apache2'):
subprocess.run(['a2dismod', 'php' + current_php_version])
subprocess.run(['a2enmod', new_php_version])
subprocess.run(['systemctl', 'restart', 'apache2'])
elif is_active('nginx'):
subprocess.run(['systemctl', 'restart', new_php_version + '-fpm'])
elif is_active('lighttpd'):
subprocess.run(['systemctl', 'restart', new_php_version + '-fpm'])
if is_active("apache2"):
subprocess.run(["a2dismod", "php" + current_php_version])
subprocess.run(["a2enmod", new_php_version])
subprocess.run(["systemctl", "restart", "apache2"])
elif is_active("nginx"):
subprocess.run(["systemctl", "restart", new_php_version + "-fpm"])
elif is_active("lighttpd"):
subprocess.run(["systemctl", "restart", new_php_version + "-fpm"])
else:
print(
"No supported web server detected. Please manually configure "
"your web server to use the new PHP version."
)

subprocess.run(['update-alternatives', '--set', 'php', '/usr/bin/' +
new_php_version])
subprocess.run(
["update-alternatives", "--set", "php", "/usr/bin/" + new_php_version]
)

print('PHP upgrade completed successfully.')
print("PHP upgrade completed successfully.")