#! /usr/bin/env perl # Copyright (c) 2025, R. W. Rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Revision History: # 2026-10-13 Copilot v2.0.0 stage-3 uninstall rewrite # 2026-10-13 Copilot v2.0.1 header/history alignment and list-form OS detection use strict; use warnings; # find our location and use it for searching for libraries BEGIN { use FindBin; use File::Spec; # use libraries from the directory this script is in use lib File::Spec->catdir($FindBin::Bin); # and its parent use lib File::Spec->catdir( $FindBin::Bin . '/../' ); } use File::Basename qw(basename); use Getopt::Long; use version; our $VERSION = version->declare('v2.0.1'); use installer_shared qw( loadInstallerConfig getRootPrefix rootPath removeTreeIfExists runCommandList slurpCommand ); my $keepConfig = 0; my $os = ''; my $quiet = 0; my $dryRun = 0; my $help = 0; my $showVersion = 0; my $root = ''; ####################################################### # # printHelp() # # Displays usage text. # # Parameters: # none # # Returns: # none # # Side Effects: # writes STDOUT # ####################################################### sub printHelp { print basename($0) . " $VERSION\n"; print <<'END_HELP'; uninstall [options] Options: --keep-config preserve configuration directory --os|-o override OS detection --root test root prefix --quiet|-q reduce output --dryrun|-n no filesystem changes --version|-v display version and exit --help|-h show this screen END_HELP } ####################################################### # # detectOs() # # Resolves OS token using determineOS helper. # # Parameters: # none # # Returns: # scalar OS token # # Side Effects: # runs determineOS # ####################################################### sub detectOs { return $os if $os ne ''; my $rootPrefix = getRootPrefix($root); local $ENV{'SYSINFO_ROOT'} = $rootPrefix if defined($rootPrefix) && $rootPrefix ne ''; my $det = slurpCommand(['perl', "$FindBin::Bin/determineOS"]); chomp $det if defined($det); return defined($det) ? $det : ''; } Getopt::Long::Configure('bundling'); GetOptions( 'keep-config' => \$keepConfig, 'os|o=s' => \$os, 'root=s' => \$root, 'quiet|q' => \$quiet, 'dryrun|n' => \$dryRun, 'version|v' => \$showVersion, 'help|h' => \$help, ) or die "Error parsing command line\n"; if ($help) { printHelp(); exit 0; } if ($showVersion) { print basename($0) . " $VERSION\n"; exit 0; } my $cfg = loadInstallerConfig("$FindBin::Bin/installer_config.pl"); my $osName = detectOs(); if (!exists($cfg->{'operatingSystems'}{$osName})) { print STDERR "unsupported operating system\n"; exit 2; } my $rootPrefix = getRootPrefix($root); my $row = $cfg->{'operatingSystems'}{$osName}; my $bindir = rootPath($rootPrefix, $row->{'bindir'}); my $previous = $bindir . '.previous'; my $confdir = rootPath($rootPrefix, $row->{'confdir'}); my $schedule = $row->{'schedule'} eq '' ? '' : rootPath($rootPrefix, $row->{'schedule'}); my $legacySchedule = $row->{'legacySchedule'} eq '' ? '' : rootPath($rootPrefix, $row->{'legacySchedule'}); my $actions = $row->{'opnsenseActions'} eq '' ? '' : rootPath($rootPrefix, $row->{'opnsenseActions'}); print "Removing bindir $bindir\n" unless $quiet; removeTreeIfExists($bindir, $dryRun) or exit 2; print "Removing previous tree $previous\n" unless $quiet; removeTreeIfExists($previous, $dryRun) or exit 2; if ($schedule ne '') { print "Removing schedule entry $schedule\n" unless $quiet; removeTreeIfExists($schedule, $dryRun) or exit 2; } if ($legacySchedule ne '') { print "Removing legacy schedule entry $legacySchedule\n" unless $quiet; removeTreeIfExists($legacySchedule, $dryRun) or exit 2; } if ($actions ne '') { print "Removing OPNsense actions file $actions\n" unless $quiet; removeTreeIfExists($actions, $dryRun) or exit 2; my $rc = runCommandList(['service', 'configd', 'restart'], $dryRun); if ($rc != 0 && !$dryRun) { print STDERR "warning: could not restart configd\n"; } } if (!$keepConfig) { print "Removing confdir $confdir\n" unless $quiet; removeTreeIfExists($confdir, $dryRun) or exit 2; } exit 0;