Website FINALLY Adapted to Apple Silicon

Back in March, I took advantage of a sale at Costco, advanced the inevitable update, and bought a blue-colored iMac. With its Apple M1 chip, after reinstalling essential packages using Homebrew, I had to make multiple changes to the Apache and Tomcat server configurations to allow my website, and the Tomcat server serving the biking weather suitability forecast application, to work properly again. Since these explorations – including a dead end trying to switch from using Apache to Nginx for the web server, given what seems to be the trend and the latter’s apparent strength being reverse proxy work – were done very sporadically, it took me until this month finally to get it right.

One thing I am glad I did relatively early in the process was to make the new configuration folders git repositories, so I could review the history of document changes and, more importantly, reverse them if needed. This was done using the following steps for the Apache (httpd) folder:

cd /opt/homebrew/etc/httpd/
git init
git checkout -b starting_m1_config
git add --all
git commit -m 'Initial commit of starting M1 config'

The first part of the update was dealing with the change of all /user/local/etc/httpd paths to /opt/homebrew/etc/httpd. After copying my backed-up configuration files from /usr/local/etc/httpd to /opt/homebrew/etc/httpd, I did a global search and replace for this path change throughout the files in the updated folder.

I uncommented a few modules and updated the php_module path reference in httpd.conf:

LoadModule xml2enc_module /opt/homebrew/Cellar/httpd/2.4.53/lib/httpd/modules/mod_xml2enc.so
LoadModule proxy_html_module /opt/homebrew/Cellar/httpd/2.4.53/lib/httpd/modules/mod_proxy_html.so
.
.
.
LoadModule proxy_connect_module /opt/homebrew/Cellar/httpd/2.4.53/lib/httpd/modules/mod_proxy_connect.so
.
.
.
LoadModule php_module /opt/homebrew/Cellar/php/8.1.6/lib/httpd/modules/libphp.so

Finally, I moved the ProxyPass and ProxyPassReverse parameters from httpd.conf to the two <VirtualHost *:443> elements – the only difference between the two being one with a ServerName of localhost, the other johnwatne.no-ip.biz – within the extra/httpd-vhosts.conf configuration file. I also added additional proxy-related parameters based on suggestions in various items found in searching for answers. Whether or not they were needed, I don’t know, but they are part of my final, working solution:

   ProxyPreserveHost Off
   ProxyRequests Off
   ProxyVia Off
   <Proxy *>
      Require all granted
   </Proxy>
   ProxyPass "/bikingweather" "http://localhost:[tomcat-port]/bikingweather"
   ProxyPassReverse "/bikingweather" "http://localhost:[tomcat-port]/bikingweather"

In the code above, [tomcat-port] is the port on which the Tomcat server is listening, which is 8080 by default. The final thing I needed to do with the Apache configuration was to make sure I was consistent in having both paths listed for the ProxyPass and ProxyPassReverse parameters not end with a “/” character. Having only one of them end with the additional character was breaking the proxy URL reference, and I kept getting whitelabel error pages from Tomcat.

One item I needed to fix for the update to Apple Silicon on the web application side was to update one of the dependencies used by the biking suitability forecast application, a Spring Boot application. Looking through the logs for the application when loading the application, I saw the following [edited to remove more info than is likely helpful]:

2022-05-29 23:04:44,238 ERROR nsServerAddressStreamProviders[line  73] Unable to load io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS.
java.lang.reflect.InvocationTargetException: null
	at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:79) ~[?:?]
	at java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) ~[?:?]
	at java.lang.reflect.Constructor.newInstance(Constructor.java:483) ~[?:?]
	at io.netty.resolver.dns.DnsServerAddressStreamProviders.<clinit>(DnsServerAddressStreamProviders.java:64) ~[netty-resolver-dns-4.1.76.Final.jar:4.1.76.Final]
.
.
.
Caused by: java.lang.UnsatisfiedLinkError: failed to load the required native library
	at io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider.ensureAvailability(MacOSDnsServerAddressStreamProvider.java:110) ~[netty-resolver-dns-classes-macos-4.1.76.Final.jar:4.1.76.Final]
	at io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider.<init>(MacOSDnsServerAddressStreamProvider.java:120) ~[netty-resolver-dns-classes-macos-4.1.76.Final.jar:4.1.76.Final]
	at jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:67) ~[?:?]
	... 94 more
Caused by: java.lang.UnsatisfiedLinkError: could not load a native library: netty_resolver_dns_native_macos_aarch_64
	at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:224) ~[netty-common-4.1.76.Final.jar:4.1.76.Final]
.
.
.
	... 91 more
	Suppressed: java.lang.UnsatisfiedLinkError: could not load a native library: netty_resolver_dns_native_macos
		at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:224) ~[netty-common-4.1.76.Final.jar:4.1.76.Final]
.
.
.
	Caused by: java.io.FileNotFoundException: META-INF/native/libnetty_resolver_dns_native_macos.jnilib
		at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:166) ~[netty-common-4.1.76.Final.jar:4.1.76.Final]
		... 99 more
		Suppressed: java.lang.UnsatisfiedLinkError: no netty_resolver_dns_native_macos in java.library.path: /Users/John/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.
			at java.lang.ClassLoader.loadLibrary(ClassLoader.java:2434) ~[?:?]
.
.
.
Caused by: java.io.FileNotFoundException: META-INF/native/libnetty_resolver_dns_native_macos_aarch_64.jnilib
	at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:166) ~[netty-common-4.1.76.Final.jar:4.1.76.Final]
.
.
.

A little bit of digging revealed that these were related to using the Intel version of the netty-resolver-dns-native-macos Maven artifact. To use the correct version, I needed to add it to the dependencies, along with the dependency-specific “classifier” value to ensure I was using the Apple Silicon – aarch_64 – version of the resolver. This was done by adding the following to the list of dependencies in the application’s pom.xml file.

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-resolver-dns-native-macos</artifactId>
            <classifier>osx-aarch_64</classifier>
        </dependency>

I also updated the netty.version value in the properties section of pom.xml to the current version.

After all these fixes, the main WordPress site’s and the web application’s links to each other – including shared font files – are once again working correctly.

Further New Year Updates for Forced Logouts in Programmatic MacOS Parental Controls

Early this January, 2022, Sara and I noticed that it seemed like the boys could stay on the computer much longer than what the forced time quotas my refined program enforced. Investigating the code revealed something I should have anticipated, having been part of a “Y2K readiness team” earlier in my career.

The problem was that my “UserLoginTime” objects were obtained given the output from the bash shell’s “who” command, which only shows the login time for users using a two-digit month and two-digit day, but no year. When I constructed UserLoginTimes that were holdovers from old logins at the end of December 2021, the code was assuming that it was December of the current year – nearly a year in  the future. So, when checking whether the total time online for the boys was greater than the specified value, the code was subtracting a future date and time from the current date and time, resulting in a negative “elapsed time”, thus never timing out.

The original code for the constructor, with the error of always assuming the login date was in the current year, was as follows.

 

    /**
     * Constructs a UserLoginTime for the information in the passed line from
     * &quot;who&quot; output.
     *
     * @param line
     *            a line of output from the MacOS / BSD &quot;who&quot; command.
     */
    public UserLoginTime(final String line) {
        final LocalDateTime now = LocalDateTime.now();
        final int year = now.getYear();
        final String[] split = line.split("[\\s]+");
        this.setUser(split[0]);
        this.setTty(split[1]);
        final StringBuilder builder = new StringBuilder();
        builder.append(year);

        for (int i = 2; (i < Math.min(split.length, 5)); i++) {
            builder.append(" ");
            builder.append(split[i]);
        }

        this.setLoginTime(
                LocalDateTime.parse(builder.toString(), DATE_TIME_FORMATTER));
    }

To fix the problem, I simply subtract a year from the originally calculated login date, by adding the following to the end of the constructor, after the initial call to setLoginTime(…) that previously ended the constructor code, as follows:

        // Check for change of year.
        final LocalDateTime originalLoginTime = this.getLoginTime();

        if (originalLoginTime.isAfter(now)) {
            LOGGER.warn("originalLoginTime: "
                    + originalLoginTime.format(DATE_TIME_FORMATTER));
            this.setLoginTime(originalLoginTime.minusYears(1L));
            LOGGER.warn("adjusted login time: "
                    + this.getLoginTime().format(DATE_TIME_FORMATTER));
        }

I suppose that, if I wanted to cover all possibilities, I would use a while loop and keep subtracting a year from the login time until the result of calling it’s “isAfter(now)” method was false. However, the kids’ login accounts would never be logged in for a span covering more than two calendar years. With power outages, OS updates, and reboots either to clear problems or to boot up an older version of MacOS to allow the boys to play an old 32-bit game, the computer wouldn’t stay online that long anyway.

One final refinement I needed to make was to handle the case where my program was unable to connect to the database, in which case it was throwing Exceptions upon startup, and never doing any kind of check. I added some code to the main application to call an alternate method if an Exception was thrown when starting up the application, by adding the Exception handling block shown below, added to the LogtimerApp’s main method.

    public static void main(final String[] args) {
        try {
            SpringApplication.run(LogtimerApp.class, args);
        } catch (final Exception e) {
            LOGGER.error(
                    "Unable to run LogtimerApp application; attempt to check logins without using database.",
                    e);
            LogtimerRunner.checkCurrentLoginsOnly();
        }
    }

I then added the following code to LogtimerRunner, recycling code I had used on the early version of the program that did not store history in the database, requiring the first refinement.

    /**
     * Fallback method to check only the currently logged in sessions, to be
     * called when unable to obtain a database connection on startup.
     */
    public static void checkCurrentLoginsOnly() {
        LOGGER.info("*** checkCurrentLoginsOnly ***");
        LogtimerRunner runner = new LogtimerRunner();

        try {
            Map<String, List<UserLoginTime>> userLogins;
            userLogins = runner.getUserLogins();

            if (userLogins != null) {
                LogtimerRunner.checkCurrentLogins(userLogins);
            }
        } catch (final Exception e) {
            LOGGER.error("Error checking current logins", e);
        }
    }

    /**
     * Check the given Map of users to Lists of user login times and log out
     * those who have exceeded their quota.
     *
     * @param userLogins
     *            a Map of Lists of login times for each user.
     */
    public static void checkCurrentLogins(
            final Map<String, List<UserLoginTime>> userLogins) {
        LOGGER.debug("*** User login lists ****");
        final LocalDateTime now = LocalDateTime.now();

        for (final Entry<String, List<UserLoginTime>> entry : userLogins
                .entrySet()) {
            final String user = entry.getKey();
            final List<UserLoginTime> loginsForUser = entry.getValue();
            final UserLoginTime lastLoginForUser =
                    Collections.max(loginsForUser);
            LOGGER.info("*** Maximum login: " + lastLoginForUser);
            final LocalDateTime fromTemp =
                    LocalDateTime.from(lastLoginForUser.getLoginTime());
            final long minutes = fromTemp.until(now, ChronoUnit.MINUTES);
            LOGGER.debug("Elapsed time: " + minutes + " minutes");

            try {
                (new LogtimerRunner()).logoutIfKidLoggedInTooLong(user,
                        minutes);
            } catch (final IOException e) {
                LOGGER.error("I/O error", e);
            } catch (final InterruptedException e) {
                LOGGER.warn("Thread interrupted", e);
            }
        }
    }

I hope readers may find this information helpful, either if using this program, or to offer some ideas if they find themselves running into similar problems with other programs they maintain.