Merge pull request #35 from leigh-hackspace/netatalk-post

Add Netatalk installation post
This commit is contained in:
2024-03-03 22:03:06 +00:00
committed by GitHub
17 changed files with 482 additions and 5 deletions

View File

@@ -13,6 +13,22 @@ priority = -1
bgColor = '#ffffff'
resampleFilter = 'CatmullRom'
[markup]
[markup.highlight]
anchorLineNos = false
codeFences = true
guessSyntax = false
hl_Lines = ''
hl_inline = false
lineAnchors = ''
lineNoStart = 1
lineNos = false
lineNumbersInTable = true
noClasses = false
noHl = false
style = 'emacs'
tabWidth = 4
[params]
company = "Leigh Hackspace CIC"
company_number = "09404083"

View File

@@ -0,0 +1,202 @@
---
title: "Creating an AppleTalk NAS"
subtitle: "Enabling file sharing with pre-OSX Mac systems"
date: 2024-02-21T07:40:38Z
tags:
- macos
- appletalk
- apple
draft: false
author: Andrew Williams
author_email: andy@tensixtyone.com
listing_image: network-browser.jpg
---
{{< image src="netatalk.png" width="400x" class="is-pulled-right" title="The Netatalk logo.">}}
Working on [retro Apple Macs](../../2023/powerbook-g4-disk-replacement/) sometimes presents its own challenges in a modern context, most systems make use of either outdated or dying media which may be unreliable or difficult to locate. In my case, I've recently acquired a Power Mac G4 Sawtooth which while it has USB the interface is slow and wrangling files between computers using USB sticks isn't the quickest method. For even older Macs this isn't even an option, so you have to resort to burnt CDs, Zip disks, or floppy disks. Thankfully Apple has always supported some level of networking on even their earliest machines; AppleTalk.
[Netatalk](https://netatalk.io) is a modern implementation of the AppleTalk protocols and can be used to provide file shares and time services over Ethernet. To connect to LocalTalk devices you'll need a [quite expensive network converter](). We're going to setu p, install, and configure a basic Netatalk 2.3 server on Ubuntu 24.02 and share a folder and time services.
## System preparation
First of all, you'll need a Ubuntu 24.02 system, or any Debian-based distribution for that matter, which is out of the scope of this article. As we'll be building Netatalk from source (as no package is available for the later Ubuntu/Debian versions) we'll need to install some prereq packages:
```bash-session
# apt-get install automake libtool build-essential pkg-config checkinstall git-core avahi-daemon libavahi-client-dev libssl-dev libdb5.3-dev db-util db5.3-util libgcrypt20 libgcrypt20-dev libcrack2-dev libpam0g-dev libdbus-1-dev libdbus-glib-1-dev libglib2.0-dev libwrap0-dev systemtap-sdt-dev libacl1-dev libldap2-dev libevent-dev
```
## Downloading, Compiling, and installing Netatalk
```bash-session
# curl -O netatalk-2.3.1.tar.bz2 https://sourceforge.net/projects/netatalk/files/netatalk-2-3-1/netatalk-2.3.1.tar.bz2/download
# tar jxvf netatalk-2.3.1.tar.bz2
```
You should have a folder named `netatalk-2.3.1` as the result. Now we need to configure the build, for anyone who has ever compiled any standard C project in the last ten years should find this quite familiar:
```bash-session
# ./configure --enable-systemd --sysconfdir=/etc --with-uams-path=/usr/lib/netatalk --enable-ddp
```
A quick rundown of the options used here:
* `--enable-systemd` - Creates systemd compatible startup scripts, needed for Ubuntu/Debian
* `--sysconfdir=/etc` - Set the configuration folder to `/etc`, if you want you can have this set to `/usr/local/etc` to completely keep it separate from the distribution install.
* `--with-uams-path=/usr/local/lib/netatalk` - Destination for the 'UAMS' modules, which provide authentication methods.
* `--enable-ddp` - Enables AppleTalk, should be on by default but just to make sure...
Netatalk includes several other options, all of which can be viewed by using `./configure --help`, these include configuring CUPS printing via PAP, SLP support for OSX 10.1, and various backend database support for metadata (which the stock one is just fine).
Now we need to build and install:
```bash-session
# make
# make install
# systemctl daemon-reload
```
If everything worked as expected, you should have a `/etc/netatalk` folder with some basic configuration files, and the main executables in `/usr/sbin`.
## Configuring Netatalk
### `atalkd`
`atalkd` is the service that handles the AppleTalk over Ethernet side of the services. The service heavily leans on the kernel's support for AppleTalk which is still included in most modern Linux kernels, including Ubuntu and Debian. To be sure that it is available run `modprobe appletalk`.
Out of the box, `atalkd` should run without any additional configuration, but we're going to modify some parameters for the small network I have at home.
**/etc/netatalk/atalkd.conf**
```
ens192 -router -phase 2 -net 1 -addr 1.1 -zone "Doofnet"
```
So, to break down what that means:
* `ens192` - the network interface we want to speak AppleTalk on, in this instance, we're using a VM so the primary network interface is `ens192`, but on your system, it'll be different.
* `-router` - advertise this service as a router.
* `-phase 2` - use ['Phase II' AppleTalk over Ethernet](https://en.wikipedia.org/wiki/AppleTalk).
* `-net 1 -addr 1.1` - The network and node ID of this service, **this can be skipped** as `atalkd` will auto-generate this if it is missing.
* `-zone "Doofnet"` - The name of the 'zone' to advertise this service as part of, this can be anything or skipped, as this is just more for visual fluff for client devices.
Once the configuration file is done, you can start `atalkd`:
```bash-session
# systemctl start atalkd
```
After a short pause, you can check `journalctl` and see the logs of the service starting up.
```bash-session
# journalctl -u atalkd -f -n 100
Feb 20 22:35:39 nas-afp systemd[1]: Starting atalkd.service - Netatalk AppleTalk daemon...
Feb 20 22:35:39 nas-afp atalkd[44891]: Set syslog logging to level: LOG_DEBUG
Feb 20 22:35:39 nas-afp atalkd[44891]: restart (2.3.1)
Feb 20 22:35:40 nas-afp atalkd[44891]: zip_getnetinfo for ens192
Feb 20 22:35:50 nas-afp atalkd[44891]: zip_getnetinfo for ens192
Feb 20 22:36:00 nas-afp atalkd[44891]: zip_getnetinfo for ens192
Feb 20 22:36:10 nas-afp atalkd[44891]: as_timer configured ens192 phase 2 from seed
Feb 20 22:36:10 nas-afp atalkd[44891]: ready 0/0/0
Feb 20 22:36:10 nas-afp systemd[1]: Started atalkd.service - Netatalk AppleTalk daemon.
```
### `afpd`
`afpd` handles the AppleTalk File Protocol requests, much like `smbd`. This service only needs a few configuration items to get working, and again, leaving the defaults will be absolutely fine. But, as we're trying to make things look good we'll add some extra fluff:
**/etc/netatalk/afpd.conf**
```
- -transall -uamlist uams_guest.so,uams_randnum.so,uams_dhx.so,uams_dhx2.so -icon -mimicmodel AirPort
```
* `-transall` - Use TCP and Appletalk (all transports).
* `-uamlist` - The list of 'UAM' modules to use for authentication, we've enabled Guest, Randnum, DHX, and DHX2.
* `-icon` - Use the Netatalk icon for mounted volumes.
* `-mimicmodel` - Mimic a Mac model, in this instance an AirPort (options can be found in `man afpd.conf` and `/System/Library/CoreServices/CoreTypes.bundle/Contents/Info.plist` on a macOS system).
Again, we restart `aftpd` and watch the results
```bash-session
# systemctl restart afpd
# journalctl -u afpd -f
Feb 20 22:36:10 nas-afp systemd[1]: Starting afpd.service - Netatalk AFP fileserver for Macintosh clients...
Feb 20 22:36:10 nas-afp systemd[1]: Started afpd.service - Netatalk AFP fileserver for Macintosh clients.
Feb 20 22:36:16 nas-afp afpd[44900]: AFP/TCP started, advertising 10.101.2.123:548 (2.3.1)
```
Now, with a little luck, your new AFP server should be visible to your client devices:
{{< gallery >}}
{{< image src="network-browser.jpg" title="Mac OS 9.2.2 'Network Browser' showing the 'nas-afp' service available via AFP over AppleTalk.">}}
{{< image src="osx-finder.png" title="Mac OS X 10.4 Finder showing the 'nas-afp' service available via AFP over TCP/IP.">}}
{{< image src="sonoma-finder.png" title="macOS Sonoma Finder showing the 'nas-afp' service available via AFP over TCP/IP.">}}
{{< /gallery >}}
### Sharing Folders
The final step is to get some folders shared, but first we need to approach the issue of [resource forks](https://en.wikipedia.org/wiki/Resource_fork).
#### Resource Forks
Apple files are not just files, due to how the original Mac OS system was designed files consist of a data and resource fork and while some files work perfectly fine with just a 'data' fork, others require the resource fork. The resource forks are stored differently on the file system and were unique to Mac OS for a while, so much so that FAT/FAT32, NFS, SMBv1 have no concept of how to handle them. Over time support has improved and NTFS and modern Linux filesystems have the concept of 'Extended Attributes' which can be used to store resource forks.
This matters because, behind the scenes, Netatalk handles resource forks either by working with the filesystem to write 'extended attributes' or storing the resource forks and other metadata in 'AppleDouble' files. When creating shares via `afpd` you'll need to take this into account, but here is some bias suggestions:
* Avoid NFS mounts - it's easy to just mount a folder on a VM and share out with AFP, but that presents a whole set of new problems and no extended attribute support
* Use a filesystem like XFS - It supports extended attributes and is just straight up awesome. ext2/3/4 supports them also but with size limits.
Avoid interfering with `.AppleDouble` files! Loss of this data may make other files useless.
Now, back to the configuration:
#### Configuring Volumes
Configuration for the shares is held in `AppleVolumes.default`, some other files exist as well, but you can read about them from `man AppleVolumes`
**/etc/netatalk/AppleVolumes.default**
```
:DEFAULT: options:upriv,usedots
/srv/transfer "Transfer" ea:sys
/srv/archive "Software Archive" ea:sys
```
Here we're sharing two folders, Transfer and Software Archive, both from within the `/srv` folder (know your Linux FHS people!). We only have a few options here:
* `/srv/transfer` - The path to the folder to share
* `"Transfer"` - The name of the share to show to the client devices
* `ea:sys` - Use 'extended attributes' on the filesystem for metadata and [resource forks](https://en.wikipedia.org/wiki/Resource_fork). This can probably be set to `ea:auto` or missed out (as the default is `auto`).
Once done, we can restart `aftpd` again:
```bash-session
# systemctl restart afpd
```
{{< gallery >}}
{{< image src="network-browser-share.jpg" title="Mac OS 9.2.2 'Network Browser' showing the 'nas-afp' server and shares.">}}
{{< image src="osx-shares.png" title="Mac OS X 10.4 Finder showing the 'nas-afp' server and shares.">}}
{{< image src="sonoma-shares.png" title="macOS Sonoma Finder showing the 'nas-afp' server and shares.">}}
{{< /gallery >}}
## Common Issues
### Debugging Issue
Unfortunately, before Mac OSX we had little or no tools to diagnose issues on the client side (to my understanding, I may be wrong), but with Mac OS X we have the `appletalk` command which can be used to print out node information, routing table and other useful bits of information to gain insights on how the network is operating.
On the server side, you can use `tcpdump` to inspect AppleTalk traffic. I've found success with the following filter set:
```bash-session
# tcpdump -i ens192 atalk or aarp or stp
```
### My AppleTalk devices can't see the server
First thing to check is that your network switches, make sure that 'IGMP Snooping' isn't enabled. This setting tries to reduce the load of multicast traffic on the network, which AppleTalk is mostly multicast traffic! This setting will strip out the vast majority of AppleTalk related broadcast traffic and will cause systems to be isolated away from each other.
In UniFi, this is under the Site configuration and the network settings.
### My WiFi AppleTalk device can't see the server
Do you have Unifi? Check the 'Multicast Enhancements' setting under your AP configuration, much like IGMP Snooping it is trying to do 'smart' things to reduce broadcast traffic over WiFi. This usually presents with being able to run a `tcpdump` on the server and seeing the client device announcements, but it's not responding to any broadcasts from your server.

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

View File

@@ -0,0 +1 @@
{{ .Inner }}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,145 @@
.slider
position: relative
width: 100%
// overflow: hidden
.slider-container
display: flex
flex-wrap: nowrap
flex-direction: row
overflow: hidden
transform: translate3d(0, 0, 0)
min-height: 100%
&.is-vertical
flex-direction: column
.slider-item
flex: none
.image
&.is-covered
img
object-fit: cover
object-position: center center
height: 100%
width: 100%
// Responsive embedded objects
.video-container
height: 0
padding-bottom: 0
padding-top: 56.25% // ratio 16:9
margin: 0
position: relative
&.is-1by1,
&.is-square
padding-top: 100%
&.is-4by3
padding-top: 75%
&.is-21by9
padding-top: 42.857143%
& iframe,
& object,
& embed
position: absolute
top: 0
left: 0
width: 100% !important
height: 100% !important
.slider-navigation-previous,
.slider-navigation-next
display: flex
justify-content: center
align-items: center
position: absolute
width: 42px
height: 42px
background: white center center no-repeat
background-size: 20px 20px
border: 1px solid white
border-radius: 25091983px
box-shadow: 0 2px 5px #3232321a
top: 50%
margin-top: -20px
left: 0px
cursor: pointer
transition: transform .3s, opacity .3s
&:hover
transform: scale(1.2)
&.is-hidden
display: none
opacity: 0
svg
width: 25%
.slider-navigation-next
left: auto
right: 0px
background: white center center no-repeat
background-size: 20px 20px
.slider-pagination
display: none
justify-content: center
align-items: center
position: absolute
bottom: 0
left: 0
right: 0
padding: .5rem 1rem
text-align: center
.slider-page
background: white
width: 10px
height: 10px
border-radius: 25091983px
display: inline-block
margin: 0 3px
box-shadow: 0 2px 5px #3232321a
transition: transform .3s
cursor: pointer
&.is-active,
&:hover
transform: scale(1.4)
@media screen and (min-width: 800px)
display: flex
// Hero Carousel
=hero-carousel
position: absolute
top: 0
left: 0
bottom: 0
right: 0
height: auto
border: none
margin: auto
padding: 0
z-index: 0
.hero
&.has-carousel
position: relative
+ .hero-body,
+ .hero-head,
+ .hero-footer
z-index: 10
overflow: hidden
.hero-carousel
+hero-carousel
.slider
width: 100%
max-width: 100%
overflow: hidden
height: 100% !important
max-height: 100%
z-index: 0
.has-background
max-height: 100%
.is-background
object-fit: cover
object-position: center center
height: 100%
width: 100%
.hero-body
margin: 0 3rem
z-index: 10

View File

@@ -0,0 +1,86 @@
/* Background */ .bg { background-color: #f8f8f8; }
/* PreWrapper */ .chroma { background-color: #f8f8f8; }
/* Other */ .chroma .x { }
/* Error */ .chroma .err { }
/* CodeLine */ .chroma .cl { }
/* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .chroma .hl { background-color: #dfdfdf }
/* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Line */ .chroma .line { display: flex; }
/* Keyword */ .chroma .k { color: #aa22ff; font-weight: bold }
/* KeywordConstant */ .chroma .kc { color: #aa22ff; font-weight: bold }
/* KeywordDeclaration */ .chroma .kd { color: #aa22ff; font-weight: bold }
/* KeywordNamespace */ .chroma .kn { color: #aa22ff; font-weight: bold }
/* KeywordPseudo */ .chroma .kp { color: #aa22ff }
/* KeywordReserved */ .chroma .kr { color: #aa22ff; font-weight: bold }
/* KeywordType */ .chroma .kt { color: #00bb00; font-weight: bold }
/* Name */ .chroma .n { }
/* NameAttribute */ .chroma .na { color: #bb4444 }
/* NameBuiltin */ .chroma .nb { color: #aa22ff }
/* NameBuiltinPseudo */ .chroma .bp { }
/* NameClass */ .chroma .nc { color: #0000ff }
/* NameConstant */ .chroma .no { color: #880000 }
/* NameDecorator */ .chroma .nd { color: #aa22ff }
/* NameEntity */ .chroma .ni { color: #999999; font-weight: bold }
/* NameException */ .chroma .ne { color: #d2413a; font-weight: bold }
/* NameFunction */ .chroma .nf { color: #00a000 }
/* NameFunctionMagic */ .chroma .fm { }
/* NameLabel */ .chroma .nl { color: #a0a000 }
/* NameNamespace */ .chroma .nn { color: #0000ff; font-weight: bold }
/* NameOther */ .chroma .nx { }
/* NameProperty */ .chroma .py { }
/* NameTag */ .chroma .nt { color: #008000; font-weight: bold }
/* NameVariable */ .chroma .nv { color: #b8860b }
/* NameVariableClass */ .chroma .vc { }
/* NameVariableGlobal */ .chroma .vg { }
/* NameVariableInstance */ .chroma .vi { }
/* NameVariableMagic */ .chroma .vm { }
/* Literal */ .chroma .l { }
/* LiteralDate */ .chroma .ld { }
/* LiteralString */ .chroma .s { color: #bb4444 }
/* LiteralStringAffix */ .chroma .sa { color: #bb4444 }
/* LiteralStringBacktick */ .chroma .sb { color: #bb4444 }
/* LiteralStringChar */ .chroma .sc { color: #bb4444 }
/* LiteralStringDelimiter */ .chroma .dl { color: #bb4444 }
/* LiteralStringDoc */ .chroma .sd { color: #bb4444; font-style: italic }
/* LiteralStringDouble */ .chroma .s2 { color: #bb4444 }
/* LiteralStringEscape */ .chroma .se { color: #bb6622; font-weight: bold }
/* LiteralStringHeredoc */ .chroma .sh { color: #bb4444 }
/* LiteralStringInterpol */ .chroma .si { color: #bb6688; font-weight: bold }
/* LiteralStringOther */ .chroma .sx { color: #008000 }
/* LiteralStringRegex */ .chroma .sr { color: #bb6688 }
/* LiteralStringSingle */ .chroma .s1 { color: #bb4444 }
/* LiteralStringSymbol */ .chroma .ss { color: #b8860b }
/* LiteralNumber */ .chroma .m { color: #666666 }
/* LiteralNumberBin */ .chroma .mb { color: #666666 }
/* LiteralNumberFloat */ .chroma .mf { color: #666666 }
/* LiteralNumberHex */ .chroma .mh { color: #666666 }
/* LiteralNumberInteger */ .chroma .mi { color: #666666 }
/* LiteralNumberIntegerLong */ .chroma .il { color: #666666 }
/* LiteralNumberOct */ .chroma .mo { color: #666666 }
/* Operator */ .chroma .o { color: #666666 }
/* OperatorWord */ .chroma .ow { color: #aa22ff; font-weight: bold }
/* Punctuation */ .chroma .p { }
/* Comment */ .chroma .c { color: #008800; font-style: italic }
/* CommentHashbang */ .chroma .ch { color: #008800; font-style: italic }
/* CommentMultiline */ .chroma .cm { color: #008800; font-style: italic }
/* CommentSingle */ .chroma .c1 { color: #008800; font-style: italic }
/* CommentSpecial */ .chroma .cs { color: #008800; font-weight: bold }
/* CommentPreproc */ .chroma .cp { color: #008800 }
/* CommentPreprocFile */ .chroma .cpf { color: #008800 }
/* Generic */ .chroma .g { }
/* GenericDeleted */ .chroma .gd { color: #a00000 }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericError */ .chroma .gr { color: #ff0000 }
/* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
/* GenericInserted */ .chroma .gi { color: #00a000 }
/* GenericOutput */ .chroma .go { color: #888888 }
/* GenericPrompt */ .chroma .gp { color: #000080; font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
/* GenericTraceback */ .chroma .gt { color: #0044dd }
/* GenericUnderline */ .chroma .gl { text-decoration: underline }
/* TextWhitespace */ .chroma .w { color: #bbbbbb }

View File

@@ -26,6 +26,10 @@ $body-font-size: 1.25em;
// Import Bulmas styles
@import "bulma.sass";
@import "chroma.scss";
// Add Carousel add-on
@import "bulma-carousel.sass";
// -------------------------------------------------------

View File

@@ -2,10 +2,19 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
{{ if .Page.Store.Get "hasMermaid" }}
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
</script>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
</script>
{{ end }}
{{ if .Page.Store.Get "hasCarousel" }}
{{ with resources.Get "js/bulma-carousel.min.js" }}
<script src="{{ .RelPermalink }}"></script>
{{ end }}
<script>
bulmaCarousel.attach('.carousel', { pagination: false });
</script>
{{ end }}
{{ $theme := resources.Get "js/theme.js" }}

View File

@@ -0,0 +1,7 @@
<!-- Start Carousel -->
<div class="carousel">
{{ .Inner }}
</div>
<!-- End Carousel -->
{{ .Page.Store.Set "hasCarousel" true }}

View File

@@ -3,6 +3,9 @@
{{ $img = $img.Resize (print (default "1264x" $width) " webp") }}
{{ $title := .Get "title" }}
{{ $class := .Get "class" }}
{{- with .Parent -}}
<div>
{{ end }}
<figure class="image{{ if $class }} {{ $class }}{{ end }}">
<img src="{{ $img.RelPermalink }}" width="{{ $img.Width }}" height="{{ $img.Height }}" {{ if $title }}
alt="{{ $title }}" {{ end }}>
@@ -14,3 +17,6 @@
</figcaption>
{{ end }}
</figure>
{{- with .Parent -}}
</div>
{{ end }}