Search Results
Installing FreeBSD via Cobbler
Summary
Protocols utilized in the original posts remain necessary…TFTP, DHCP, and HTTP. Cobbler remains our main provisioning platform. There are several references to Cobbler here. Some hacking may be required to permit these concepts to work on other provisioning platforms.
The result is the emergence of a platform utilizing tftp to load the iPXE boot code which, in turn, loads a custom FreeBSD bootonly ISO that performs a sysinstall(8) based installation. Only the first step of this process is executed over tftp, the remaining steps are performed over HTTP.
Abbreviated Process
This is a brief description of the overall process of building a FreeBSD system.
- PXE boot client sends a broadcast DHCP request and the DHCP server responds with an IP based on the client’s MAC address.
- DHCP client executes a lookup against the DHCP Server based on the MAC Address of the PXE Client and the following data is sent to the client upon discovery of a valid host entry:
- IP Address
- Netmask
- Default Gateway
- Hostname
- Name Servers
- next-server
- filename
- The PXE boot client configures networking based on the data returned above and initiates a tftp request for “filename” from “next-server”. In this scenario, the filename is the iPXE bootstrap program.
- The iPXE bootloader runs, executing a DHCP request on the interface it was downloaded on (net0). iPXE sets “user-class” to “iPXE” when executing this request.
- The DHCP server responds to the incoming iPXE request with the following and sets “filename” based on the value of “user-class”:
- IP Address
- Netmask
- Default Gateway
- Hostname
- Name Servers
- next-server
- filename
- iPXE executes an HTTP GET request for a configuration file derived from the DHCP options written to a leases file (described later). The response from the Cobbler server is based on the netboot-enabled attribute (In this scenario, netboot-enabled is true).
- iPXE bootloader executes an HTTP GET for memdisk (necessary to boot the ISO) and bootonly.iso.zip (described later).
- bootonly.iso.zip (described later) runs sysinstall and performs an OS installation on the host.
Generate A Release
Patch sysinstall
Until FreeBSD 8.4-RELEASE and 9.2-RELEASE (with the exception of -STABLE), sysinstall(8) lacked support for direct HTTP installs. Devin Teske and I worked closely to get this patch committed to stable/8 and stable/9 to add the functionality to releng/8.4 and releng/9.2.
Index: usr.sbin/sysinstall/dispatch.c
===================================================================
--- usr.sbin/sysinstall/dispatch.c (revision 248310)
+++ usr.sbin/sysinstall/dispatch.c (revision 248313)
@@ -103,6 +103,7 @@
{ "mediaSetFTPActive", mediaSetFTPActive },
{ "mediaSetFTPPassive", mediaSetFTPPassive },
{ "mediaSetHTTP", mediaSetHTTP },
+ { "mediaSetHTTPDirect", mediaSetHTTPDirect },
{ "mediaSetUFS", mediaSetUFS },
{ "mediaSetNFS", mediaSetNFS },
{ "mediaSetFTPUserPass", mediaSetFTPUserPass },
Index: usr.sbin/sysinstall/media.c
===================================================================
--- usr.sbin/sysinstall/media.c (revision 248310)
+++ usr.sbin/sysinstall/media.c (revision 248313)
@@ -52,6 +52,7 @@
static Boolean got_intr = FALSE;
static Boolean ftp_skip_resolve = FALSE;
+static Boolean http_skip_resolve = FALSE;
/* timeout handler */
static void
@@ -508,6 +509,139 @@
mediaDevice->shutdown = dummyShutdown;
return DITEM_SUCCESS | DITEM_LEAVE_MENU | what;
}
+
+/*
+ * Return 0 if we successfully found and set the installation type to
+ * be an http server
+ */
+int
+mediaSetHTTPDirect(dialogMenuItem *self)
+{
+ static Device httpDevice;
+ char *cp, hbuf[MAXPATHLEN], *hostname, *dir;
+ struct addrinfo hints, *res;
+ int af;
+ size_t urllen;
+ int HttpPort;
+ static Device *networkDev = NULL;
+
+ mediaClose();
+ cp = variable_get(VAR_HTTP_PATH);
+ /* If we've been through here before ... */
+ if (networkDev && cp && msgYesNo("Re-use old HTTP site selection values?"))
+ cp = NULL;
+ if (!cp) {
+ if (!dmenuOpenSimple(&MenuMediaHTTPDirect, FALSE))
+ return DITEM_FAILURE;
+ else
+ cp = variable_get(VAR_HTTP_PATH);
+ }
+ if (!cp)
+ return DITEM_FAILURE;
+ else if (!strcmp(cp, "other")) {
+ variable_set2(VAR_HTTP_PATH, "http://", 0);
+ cp = variable_get_value(VAR_HTTP_PATH, "Please specify the URL of a FreeBSD distribution on a\n"
+ "remote http site.\n"
+ "A URL looks like this: http://<hostname>/<path>", 0);
+ if (!cp || !*cp || !strcmp(cp, "http://")) {
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ urllen = strlen(cp);
+ if (urllen >= sizeof(httpDevice.name)) {
+ msgConfirm("Length of specified URL is %zu characters. Allowable maximum is %zu.",
+ urllen,sizeof(httpDevice.name)-1);
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ }
+ if (strncmp("http://", cp, 7)) {
+ msgConfirm("Sorry, %s is an invalid URL!", cp);
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ SAFE_STRCPY(httpDevice.name, cp);
+ SAFE_STRCPY(hbuf, cp + 7);
+ hostname = hbuf;
+
+ if (!networkDev || msgYesNo("You've already done the network configuration once,\n"
+ "would you like to skip over it now?") != 0) {
+ if (networkDev)
+ DEVICE_SHUTDOWN(networkDev);
+ if (!(networkDev = tcpDeviceSelect())) {
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ }
+ if (!DEVICE_INIT(networkDev)) {
+ if (isDebug())
+ msgDebug("mediaSetHTTPDirect: Net device init failed.\n");
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ if (*hostname == '[' && (cp = index(hostname + 1, ']')) != NULL &&
+ (*++cp == '\0' || *cp == '/' || *cp == ':')) {
+ ++hostname;
+ *(cp - 1) = '\0';
+ }
+ else
+ cp = index(hostname, ':');
+ if (cp != NULL && *cp == ':') {
+ *(cp++) = '\0';
+ HttpPort = strtol(cp, 0, 0);
+ }
+ else
+ HttpPort = 80;
+ if ((dir = index(cp ? cp : hostname, '/')) != NULL)
+ *(dir++) = '\0';
+ if (isDebug()) {
+ msgDebug("hostname = `%s'\n", hostname);
+ msgDebug("dir = `%s'\n", dir ? dir : "/");
+ msgDebug("port # = `%d'\n", HttpPort);
+ }
+ if (!http_skip_resolve && variable_get(VAR_NAMESERVER)) {
+ msgNotify("Looking up host %s.", hostname);
+ if (isDebug())
+ msgDebug("Starting DNS.\n");
+ kickstart_dns();
+ if (isDebug())
+ msgDebug("Looking up hostname, %s, using getaddrinfo(AI_NUMERICHOST).\n", hostname);
+ af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+ if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
+ if (isDebug())
+ msgDebug("Looking up hostname, %s, using getaddrinfo().\n",
+ hostname);
+ hints.ai_flags = AI_PASSIVE;
+ if (getaddrinfo(hostname, NULL, &hints, &res) != 0) {
+ msgConfirm("Cannot resolve hostname `%s'! Are you sure that"
+ " your\nname server, gateway and network interface are"
+ " correctly configured?", hostname);
+ if (networkDev)
+ DEVICE_SHUTDOWN(networkDev);
+ networkDev = NULL;
+ variable_unset(VAR_HTTP_PATH);
+ return DITEM_FAILURE;
+ }
+ }
+ freeaddrinfo(res);
+ if (isDebug())
+ msgDebug("Found DNS entry for %s successfully..\n", hostname);
+ }
+ variable_set2(VAR_HTTP_HOST, hostname, 0);
+ variable_set2(VAR_HTTP_DIR, dir ? dir : "/", 0);
+ variable_set2(VAR_HTTP_PORT, itoa(HttpPort), 0);
+ httpDevice.type = DEVICE_TYPE_HTTP_DIRECT;
+ httpDevice.init = mediaInitHTTPDirect;
+ httpDevice.get = mediaGetHTTPDirect;
+ httpDevice.shutdown = dummyShutdown;
+ httpDevice.private = networkDev;
+ mediaDevice = &httpDevice;
+ return DITEM_SUCCESS | DITEM_LEAVE_MENU | DITEM_RESTORE;
+}
int
Index: usr.sbin/sysinstall/http.c
===================================================================
--- usr.sbin/sysinstall/http.c (revision 248310)
+++ usr.sbin/sysinstall/http.c (revision 248313)
@@ -36,18 +36,9 @@
extern const char *ftp_dirs[]; /* defined in ftp.c */
-static Boolean
-checkAccess(Boolean proxyCheckOnly)
+Boolean
+checkAccess(Boolean connectCheckOnly, Boolean isProxy)
{
-/*
- * Some proxies fetch files with certain extensions in "ascii mode" instead
- * of "binary mode" for FTP. The FTP server then translates all LF to CRLF.
- *
- * You can force Squid to use binary mode by appending ";type=i" to the URL,
- * which is what I do here. For other proxies, the LF->CRLF substitution
- * is reverted in distExtract().
- */
-
int rv, s, af;
bool el, found=FALSE; /* end of header line */
char *cp, buf[PATH_MAX], req[BUFSIZ];
@@ -76,18 +67,26 @@
}
freeaddrinfo(res0);
if (s == -1) {
- msgConfirm("Couldn't connect to proxy %s:%s",
- variable_get(VAR_HTTP_HOST),variable_get(VAR_HTTP_PORT));
+ if (isProxy) {
+ msgConfirm("Couldn't connect to proxy %s:%s",
+ variable_get(VAR_HTTP_HOST),variable_get(VAR_HTTP_PORT));
+ } else {
+ msgConfirm("Couldn't connect to server http://%s:%s/",
+ variable_get(VAR_HTTP_HOST),variable_get(VAR_HTTP_PORT));
+ }
variable_unset(VAR_HTTP_HOST);
return FALSE;
}
- if (proxyCheckOnly) {
+ if (connectCheckOnly) {
close(s);
return TRUE;
}
msgNotify("Checking access to\n %s", variable_get(VAR_HTTP_PATH));
- sprintf(req,"GET %s/ HTTP/1.0\r\n\r\n", variable_get(VAR_HTTP_PATH));
+ if (isProxy)
+ sprintf(req,"GET %s/ HTTP/1.0\r\n\r\n", variable_get(VAR_HTTP_PATH));
+ else
+ sprintf(req,"GET /%s/ HTTP/1.0\r\n\r\n", variable_get(VAR_HTTP_PATH));
write(s,req,strlen(req));
/*
* scan the headers of the response
@@ -108,7 +107,16 @@
}
}
- if (!strncmp(buf,"Server: ",8)) {
+ /*
+ * Some proxies fetch files with certain extensions in "ascii mode"
+ * instead of "binary mode" for FTP. The FTP server then translates
+ * all LF to CRLF.
+ *
+ * You can force Squid to use binary mode by appending ";type=i" to
+ * the URL, which is what I do here. For other proxies, the
+ * LF->CRLF substitution is reverted in distExtract().
+ */
+ if (isProxy && !strncmp(buf,"Server: ",8)) {
if (!strncmp(buf,"Server: Squid",13)) {
variable_set2(VAR_HTTP_FTP_MODE,";type=i",0);
} else {
@@ -143,11 +151,11 @@
/*
* First verify the proxy access
*/
- checkAccess(TRUE);
+ checkAccess(TRUE, TRUE);
while (variable_get(VAR_HTTP_HOST) == NULL) {
if (DITEM_STATUS(mediaSetHTTP(NULL)) == DITEM_FAILURE)
return FALSE;
- checkAccess(TRUE);
+ checkAccess(TRUE, TRUE);
}
again:
/* If the release is specified as "__RELEASE" or "any", then just
@@ -163,14 +171,14 @@
sprintf(req, "%s/%s/%s", variable_get(VAR_FTP_PATH),
ftp_dirs[fdir], rel);
variable_set2(VAR_HTTP_PATH, req, 0);
- if (checkAccess(FALSE)) {
+ if (checkAccess(FALSE, TRUE)) {
found = TRUE;
break;
}
}
} else {
variable_set2(VAR_HTTP_PATH, variable_get(VAR_FTP_PATH), 0);
- found = checkAccess(FALSE);
+ found = checkAccess(FALSE, TRUE);
}
if (!found) {
msgConfirm("No such directory: %s\n"
Index: usr.sbin/sysinstall/Makefile
===================================================================
--- usr.sbin/sysinstall/Makefile (revision 248310)
+++ usr.sbin/sysinstall/Makefile (revision 248313)
@@ -8,9 +8,9 @@
MAN= sysinstall.8
SRCS= anonFTP.c cdrom.c command.c config.c devices.c dhcp.c \
disks.c dispatch.c dist.c dmenu.c doc.c dos.c floppy.c \
- ftp.c globals.c http.c index.c install.c installUpgrade.c keymap.c \
- label.c main.c makedevs.c media.c menus.c misc.c modules.c \
- mouse.c msg.c network.c nfs.c options.c package.c \
+ ftp.c globals.c http.c httpdirect.c index.c install.c \
+ installUpgrade.c keymap.c label.c main.c makedevs.c media.c menus.c \
+ misc.c modules.c mouse.c msg.c network.c nfs.c options.c package.c \
system.c tcpip.c termcap.c ttys.c ufs.c usb.c user.c \
variable.c ${_wizard} keymap.h countries.h
Index: usr.sbin/sysinstall/help/media.hlp
===================================================================
--- usr.sbin/sysinstall/help/media.hlp (revision 248310)
+++ usr.sbin/sysinstall/help/media.hlp (revision 248313)
@@ -41,6 +41,14 @@
Options screen.
+ HTTP Direct
+ Get the distribution files directly from an HTTP server.
+
+ If you chose to enter your own URL in the HTTP Direct menu,
+ please note that all paths are *relative* to the root
+ directory of the web server.
+
+
NFS Get the distribution files from an NFS server somewhere
(make sure that permissions on the server allow this!).
If this install method hangs on you or refuses to work
Index: usr.sbin/sysinstall/sysinstall.h
===================================================================
--- usr.sbin/sysinstall/sysinstall.h (revision 248310)
+++ usr.sbin/sysinstall/sysinstall.h (revision 248313)
@@ -118,6 +118,7 @@
#define VAR_FTP_STATE "ftpState"
#define VAR_FTP_USER "ftpUser"
#define VAR_FTP_HOST "ftpHost"
+#define VAR_HTTP_DIR "httpDirectory"
#define VAR_HTTP_PATH "_httpPath"
#define VAR_HTTP_PROXY "httpProxy"
#define VAR_HTTP_PORT "httpPort"
@@ -273,6 +274,7 @@
DEVICE_TYPE_NFS,
DEVICE_TYPE_ANY,
DEVICE_TYPE_HTTP,
+ DEVICE_TYPE_HTTP_DIRECT,
} DeviceType;
/* CDROM mount codes */
@@ -443,6 +445,7 @@
extern DMenu MenuMediaDOS; /* DOS media menu */
extern DMenu MenuMediaFloppy; /* Floppy media menu */
extern DMenu MenuMediaFTP; /* FTP media menu */
+extern DMenu MenuMediaHTTPDirect; /* HTTP Direct media menu */
extern DMenu MenuNetworkDevice; /* Network device menu */
extern DMenu MenuNTP; /* NTP time server menu */
extern DMenu MenuSecurity; /* System security options menu*/
@@ -650,9 +653,14 @@
extern void mediaShutdownFTP(Device *dev);
/* http.c */
+extern Boolean checkAccess(Boolean connectCheckOnly, Boolean isProxy);
extern Boolean mediaInitHTTP(Device *dev);
extern FILE *mediaGetHTTP(Device *dev, char *file, Boolean probe);
+/* httpdirect.c */
+extern Boolean mediaInitHTTPDirect(Device *dev);
+extern FILE *mediaGetHTTPDirect(Device *dev, char *file, Boolean probe);
+
/* globals.c */
extern void globalsInit(void);
@@ -726,6 +734,7 @@
extern int mediaSetFTPActive(dialogMenuItem *self);
extern int mediaSetFTPPassive(dialogMenuItem *self);
extern int mediaSetHTTP(dialogMenuItem *self);
+extern int mediaSetHTTPDirect(dialogMenuItem *self);
extern int mediaSetUFS(dialogMenuItem *self);
extern int mediaSetNFS(dialogMenuItem *self);
extern int mediaSetFTPUserPass(dialogMenuItem *self);
Index: usr.sbin/sysinstall/httpdirect.c
===================================================================
--- usr.sbin/sysinstall/httpdirect.c (revision 0)
+++ usr.sbin/sysinstall/httpdirect.c (revision 248313)
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 1999
+ * Philipp Mergenthaler <philipp.mergenthaler@stud.uni-karlsruhe.de>
+ * 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 AUTHOR 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 AUTHOR 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, LIFE 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.
+ *
+ * $FreeBSD$
+ */
+
+#include "sysinstall.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/param.h>
+#include <netdb.h>
+
+extern const char *ftp_dirs[]; /* defined in ftp.c */
+
+Boolean
+mediaInitHTTPDirect(Device *dev)
+{
+ bool found=FALSE; /* end of header line */
+ char *rel, req[BUFSIZ];
+ int fdir;
+
+ /*
+ * First verify basic access
+ */
+ checkAccess(TRUE, FALSE);
+ while (variable_get(VAR_HTTP_HOST) == NULL) {
+ if (DITEM_STATUS(mediaSetHTTPDirect(NULL)) == DITEM_FAILURE)
+ return FALSE;
+ checkAccess(TRUE, FALSE);
+ }
+again:
+ /* If the release is specified as "__RELEASE" or "any", then just
+ * assume that the path the user gave is ok.
+ */
+ rel = variable_get(VAR_RELNAME);
+ /*
+ msgConfirm("rel: -%s-", rel);
+ */
+
+ if (strcmp(rel, "__RELEASE") && strcmp(rel, "any")) {
+ for (fdir = 0; ftp_dirs[fdir]; fdir++) {
+ sprintf(req, "%s/%s/%s", variable_get(VAR_HTTP_DIR),
+ ftp_dirs[fdir], rel);
+ variable_set2(VAR_HTTP_PATH, req, 0);
+ if (checkAccess(FALSE, FALSE)) {
+ found = TRUE;
+ break;
+ }
+ }
+ } else {
+ variable_set2(VAR_HTTP_PATH, variable_get(VAR_HTTP_DIR), 0);
+ found = checkAccess(FALSE, FALSE);
+ }
+ if (!found) {
+ msgConfirm("No such directory: %s\n"
+ "please check the URL and try again.", variable_get(VAR_HTTP_PATH));
+ variable_unset(VAR_HTTP_PATH);
+ dialog_clear_norefresh();
+ clear();
+ if (DITEM_STATUS(mediaSetHTTPDirect(NULL)) != DITEM_FAILURE) goto again;
+ }
+ return found;
+}
+
+FILE *
+mediaGetHTTPDirect(Device *dev, char *file, Boolean probe)
+{
+ FILE *fp;
+ int rv, s, af;
+ bool el; /* end of header line */
+ char *cp, buf[PATH_MAX], req[BUFSIZ];
+ struct addrinfo hints, *res, *res0;
+
+ af = variable_cmp(VAR_IPV6_ENABLE, "YES") ? AF_INET : AF_UNSPEC;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = af;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = 0;
+ if ((rv = getaddrinfo(variable_get(VAR_HTTP_HOST),
+ variable_get(VAR_HTTP_PORT), &hints, &res0)) != 0) {
+ msgConfirm("%s", gai_strerror(rv));
+ return NULL;
+ }
+ s = -1;
+ for (res = res0; res; res = res->ai_next) {
+ if ((s = socket(res->ai_family, res->ai_socktype,
+ res->ai_protocol)) < 0)
+ continue;
+ if (connect(s, res->ai_addr, res->ai_addrlen) >= 0)
+ break;
+ close(s);
+ s = -1;
+ }
+ freeaddrinfo(res0);
+ if (s == -1) {
+ msgConfirm("Couldn't connect to http://%s:%s/",
+ variable_get(VAR_HTTP_HOST),variable_get(VAR_HTTP_PORT));
+ return NULL;
+ }
+
+ sprintf(req,"GET /%s/%s HTTP/1.0\r\n\r\n",
+ variable_get(VAR_HTTP_PATH), file);
+
+ if (isDebug()) {
+ msgDebug("sending http request: %s\n",req);
+ }
+ write(s,req,strlen(req));
+
+/*
+ * scan the headers of the response
+ * this is extremely quick'n dirty
+ *
+ */
+ cp=buf;
+ el=FALSE;
+ rv=read(s,cp,1);
+ while (rv>0) {
+ if ((*cp == '\012') && el) {
+ /* reached end of a header line */
+ if (!strncmp(buf,"HTTP",4)) {
+ rv=strtol((char *)(buf+9),0,0);
+ *(cp-1)='\0'; /* chop the CRLF off */
+ if (probe && (rv != 200)) {
+ return NULL;
+ } else if (rv >= 500) {
+ msgConfirm("Server error %s when sending %s, you could try an other server",buf, req);
+ return NULL;
+ } else if (rv == 404) {
+ msgConfirm("%s was not found, maybe directory or release-version are wrong?",req);
+ return NULL;
+ } else if (rv >= 400) {
+ msgConfirm("Client error %s, you could try an other server",buf);
+ return NULL;
+ } else if (rv >= 300) {
+ msgConfirm("Error %s",buf);
+ return NULL;
+ } else if (rv != 200) {
+ msgConfirm("Error %s when sending %s, you could try an other server",buf, req);
+ return NULL;
+ }
+ }
+ /* ignore other headers */
+ /* check for "\015\012" at beginning of line, i.e. end of headers */
+ if ((cp-buf) == 1)
+ break;
+ cp=buf;
+ rv=read(s,cp,1);
+ } else {
+ el=FALSE;
+ if (*cp == '\015')
+ el=TRUE;
+ cp++;
+ rv=read(s,cp,1);
+ }
+ }
+ fp=fdopen(s,"r");
+ return fp;
+}
Property changes on: usr.sbin/sysinstall/httpdirect.c
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: svn:keywords
## -0,0 +1 ##
+FreeBSD=%H
\ No newline at end of property
Index: usr.sbin/sysinstall/menus.c
===================================================================
--- usr.sbin/sysinstall/menus.c (revision 248310)
+++ usr.sbin/sysinstall/menus.c (revision 248313)
@@ -213,7 +213,8 @@
{ " Media, UFS", "Select UFS installation media.", NULL, mediaSetUFS },
{ " Media, FTP", "Select FTP installation media.", NULL, mediaSetFTP },
{ " Media, FTP Passive", "Select passive FTP installation media.", NULL, mediaSetFTPPassive },
- { " Media, HTTP", "Select FTP via HTTP proxy install media.", NULL, mediaSetHTTP },
+ { " Media, HTTP Proxy", "Select FTP via HTTP proxy install media.", NULL, mediaSetHTTP },
+ { " Media, HTTP Direct", "Select HTTP direct installation media.", NULL, mediaSetHTTPDirect },
{ " Network Interfaces", "Configure network interfaces", NULL, tcpMenuSelect },
{ " Networking Services", "The network services menu.", NULL, dmenuSubmenu, NULL, &MenuNetworking },
{ " NFS, client", "Set NFS client flag.", dmenuVarCheck, dmenuToggleVariable, NULL, "nfs_client_enable=YES" },
@@ -881,6 +882,21 @@
{ NULL } }
};
+DMenu MenuMediaHTTPDirect = {
+ DMENU_NORMAL_TYPE | DMENU_SELECTION_RETURNS,
+ "Please select a FreeBSD HTTP distribution site",
+ "Please select the site closest to you or \"other\" if you'd like to\n"
+ "specify a different choice. Also note that not every site listed here\n"
+ "carries more than the base distribution kits. Only Primary sites are\n"
+ "guaranteed to carry the full range of possible distributions.",
+ "Select a site that's close!",
+ NULL,
+ { { "URL", "Specify some other ftp site by URL", NULL, dmenuSetVariable, NULL,
+ VAR_HTTP_PATH "=other" },
+
+ { NULL } }
+};
+
DMenu MenuNetworkDevice = {
DMENU_NORMAL_TYPE | DMENU_SELECTION_RETURNS,
"Network interface information required",
@@ -926,12 +942,13 @@
{ { "1 CD/DVD", "Install from a FreeBSD CD/DVD", NULL, mediaSetCDROM },
{ "2 FTP", "Install from an FTP server", NULL, mediaSetFTPActive },
{ "3 FTP Passive", "Install from an FTP server through a firewall", NULL, mediaSetFTPPassive },
- { "4 HTTP", "Install from an FTP server through a http proxy", NULL, mediaSetHTTP },
- { "5 DOS", "Install from a DOS partition", NULL, mediaSetDOS },
- { "6 NFS", "Install over NFS", NULL, mediaSetNFS },
- { "7 File System", "Install from an existing filesystem", NULL, mediaSetUFS },
- { "8 Floppy", "Install from a floppy disk set", NULL, mediaSetFloppy },
- { "9 USB", "Install from a USB drive", NULL, mediaSetUSB },
+ { "4 HTTP Proxy", "Install from an FTP server through a http proxy", NULL, mediaSetHTTP },
+ { "5 HTTP Direct", "Install from an HTTP server", NULL, mediaSetHTTPDirect },
+ { "6 DOS", "Install from a DOS partition", NULL, mediaSetDOS },
+ { "7 NFS", "Install over NFS", NULL, mediaSetNFS },
+ { "8 File System", "Install from an existing filesystem", NULL, mediaSetUFS },
+ { "9 Floppy", "Install from a floppy disk set", NULL, mediaSetFloppy },
+ { "A USB", "Install from a USB drive", NULL, mediaSetUSB },
{ "X Options", "Go to the Options screen", NULL, optionsEditor },
{ NULL } },
};
Index: usr.sbin/sysinstall/sysinstall.8
===================================================================
--- usr.sbin/sysinstall/sysinstall.8 (revision 248310)
+++ usr.sbin/sysinstall/sysinstall.8 (revision 248313)
@@ -684,6 +684,8 @@
.Bl -tag -width indent
.It _httpPath
The proxy to use (host:port) (non-optional).
+.It httpDirectory
+The path from http root.
.El
.It mediaSetUFS
Select an existing UFS partition (mounted with the label editor) as
Index: usr.sbin/sysinstall/options.c
===================================================================
--- usr.sbin/sysinstall/options.c (revision 248310)
+++ usr.sbin/sysinstall/options.c (revision 248313)
@@ -78,6 +78,9 @@
case DEVICE_TYPE_HTTP:
return "HTTP Proxy";
+ case DEVICE_TYPE_HTTP_DIRECT:
+ return "HTTP Direct";
+
case DEVICE_TYPE_CDROM:
return "CDROM";
NOTE: This patch was tested with stable/8@r247902. Revisions untested are stable/9, releng/8.2 and older, releng/9.1 and older.
Update boot_crunch.conf
boot_crunch is a “crunched” binary containing compiled binaries and statically linked libraries into a single binary. boot_crunch.conf controls the contents of boot_crunch when compiling.
Replacing /usr/src/release/amd64/boot_crunch.conf with the following boot_crunch.conf causes release(7) to build a boot_crunch suitable for FreeBSD installations via ISO.
# $FreeBSD: src/release/amd64/boot_crunch.conf,v 1.67.2.1.2.1 2009/10/25 01:10:29 kensmith Exp $ buildopts -DRELEASE_CRUNCH -Dlint srcdirs /usr/src/bin progs hostname progs pwd progs rm progs sh progs ls progs test progs cat progs df progs sleep ln sh -sh ln test [ srcdirs /usr/src/sbin progs camcontrol special camcontrol objs camcontrol.o modeedit.o util.o progs dhclient progs fsck_ffs progs ifconfig progs mount_nfs progs newfs progs route progs rtsol progs tunefs progs kenv progs sysctl progs mdmfs progs mdconfig progs mount progs dmesg progs fdisk progs bsdlabel ln fsck_ffs fsck_4.2bsd ln fsck_ffs fsck_ufs srcdirs /usr/src/usr.bin progs uname progs cpio progs find progs minigzip progs sed progs awk progs fetch progs ifconfig ln minigzip gzip ln minigzip gunzip ln minigzip zcat srcdirs /usr/src/usr.sbin progs arp progs sysinstall progs usbconfig srcdirs /usr/src/gnu/usr.bin progs grep libs -ll -ledit -lfetch -lmd -lcrypt -lftpio -lz -lnetgraph libs -ldialog -lncurses -lcam -lsbuf -lutil -ldisk -lufs -ldevinfo -lkvm -lgeom libs -lbsdxml -larchive -lbz2 -lusb -lgnuregex -lz -llzma libs -lfetch -lmd -lssl -lcrypto -ljail -lm
NOTE: I found with r247902 and newer, the above “special” line was unnecessary and caused errors during `make release`. I am unsure when this was fixed.
make buildworld; make release
The release(7) man page describes a process for building world and making release. FreeBSD sources are a pre-requisite and can be checked out/exported from svn.freebsd.org.
There are several environment variables necessary to build a release. These are set in /usr/src/release/Makefile or on the command line. For simplicity’s sake, I’ll just set them on the command line here, but in reality, I have them set in the Makefile. The process to build world and make a release is:
# exec bash # cd /usr/src && make buildworld # CHROOTDIR=/usr/release CVSROOT=/home/ncvs EXTPORTSDIR=/usr/ports EXTSRCDIR=/usr/src MAKE_DVD=True NO_FLOPPIES=True NODOC=True cd release && make release
This creates compiled code in /usr/release/. Content for the bootonly ISO exists in ${CHROOTDIR}/R/cdrom/bootonly/. The mfsroot.gz necessary for the following step lives at ${CHROOTDIR}/R/cdrom/bootonly/boot/mfsroot.gz.
mfsroot.gz
mfsroot.gz is a gzip compressed BSD UFS filesystem environment containing config files, scripts, and binaries necessary for a FreeBSD 8.x install. This mfsroot.gz varies from the default as described below. Much of the below can be addressed during the release(7) process, but the main advantage of the process below is the release process does not have to be re-run for each change made to mfsroot.gz.
Devin Teske was kind enough to point me to FreeBSD Druid which is a sysinstall(8) based installation method where I was able to pick out a piece on customizing the mfsroot to use locally.
The email from from Devin:
If you want, you could follow my approach which is to take the completed mfsroot.gz and use a Makefile to manage the creation of custom mfsroots (keeping the original unmodified, making it simpler to test different iterations).
The advantage is that you don’t have to re-perform the release(7) process each time you want to make a change to your mfsroot.
Check it out:
http://druidbsd.cvs.sourceforge.net/viewvc/druidbsd/druidbsd/druid/dep/freebsd/mfsroot/standard/
Basically, you’d grab the Makefile (link below):
Then create a “dep” directory and “src” directory:
Next, take the virgin mfsroot.gz produced by the release(7) process and dump it into the “dep” directory.
Next, put your install.cfg into the “src” directory (just like you see that I did).
Optionally populate more files into the “src” directory (see the first link above for an example — example includes “boot/modules/nullfs.ko”, “etc/fstab”, and “etc/group”, etc.).
When the “src” directory represents what you’d like to add to the mfsroot, you’re ready to produce a new copy of the stored original (at “dep/mfsroot.gz”), complete with your additions.
Execute:
make from_dep
NOTE: sudo is required
What will happen is that “dep/mfsroot.gz” will be copied to the current working directory, the mfsroot is ripped open (requires sudo privileges), the “src” directory is layered onto the mfsroot, and finally the mfsroot is packaged back up (leaving you with a custom “./mfsroot.gz” for deployment).
Copy /usr/release/R/cdrom/bootonly/boot/mfsroot.gz to an alternate location setup identically to the way Devin described above. The install.cfg, doconfig.sh, and dhclient-script are copied to src/stand/ so when `make from_dep` is executed it runs the four (4) “stage” targets that modify the mfsroot and place these files in the resulting mfsroot.gz
NOTES:
* Comment line #41 of the Makefile if the environment does not utilize FreeBSD Druid.
* A larger mfsroot.gz file is necessary if additions are greater in size than the amount of space available in the mfsroot.gz. Alternatively, modify the size of the mfsroot.gz with the value of MFSSIZE in /usr/src/release/Makefile.
Finally, copy the mfsroot.gz to /usr/release/R/cdrom/bootonly/boot/mfsroot.gz and make the ISO.
Creating New mfsroot from Existing mfsroot
This process is useful when the mfsroot.gz does not have enough free space available for any additions.
This process assumes the following:
- we add a boot_crunch file
- Resulting mfsroot is 12MB.
- Existing mfsroot mount point is /tmp/mfsroot_old
- New mfsroot mount point is /tmp/mfsroot_new
- Existing mfsroot is /tmp/mfsroot_old
- Memdisk device for the existing mfsroot is md0
- Memdisk device for the new mfsroot is md1
- New boot_crunch file is /tmp/bootcrunch/boot_crunch
# mkdir /tmp/mfsroot_old /tmp/mfsroot_new
# dd if=/dev/zero of=/tmp/mfsroot bs=1024 count=12288
# mdconfig -f /tmp/mfsroot_old; mdconfig -f /tmp/mfsroot
# newfs /dev/md1
# mount /dev/md0 /tmp/mfsroot_old; mount /dev/md1 /tmp/mfsroot_new
# cd /tmp/mfsroot_old; tar -cf - . | (cd/tmp/mfsroot_new; tar -xf -)
# cp /tmp/bootcrunch/boot_crunch /tmp/mfsroot_new/stand/
# cd /tmp/mfsroot_new/stand
# for i in $(./boot_crunch 2>&1|grep -v usage); do
if [ "$i" != "boot_crunch" ]; then
rm -f ./"$i"; ln ./boot_crunch "$i";
fi
done
# cd /; umount /tmp/mfsroot_old; umount /tmp/mfsroot_new
# mdconfig -d -u 0; mdconfig -d -u 1
/tmp/mfsroot is the new mfsroot when the process is complete. Replace the old mfsroot with this one.
install.cfg
install.cfg is sysinstall’s config file. When it is executed it checks for this file. sysinstall(8) run interactively if the file is missing, but non-interactively if it is not. This file exists in stand/ inside the mfsroot.gz.
The syntax of install.cfg is archaic and strict, but it is quite powerful if you spend the time to learn it. The file below sets up the environment where sysinstall(8) runs then executes doconfig.sh (described below).
# Turn on extra debugging. debug=YES # Turn off all prompting. nonInteractive=YES noWarn=YES command=/bin/sh /stand/doconfig.sh system # Chain to the config we just downloaded configFile=/stand/cobbler.cfg loadConfig
doconfig.sh
This script, also in stand/ inside the mfsroot file, initializes the environment, sets variables, and communicates with Cobbler to grab the remainder of install.cfg which we see is referred to as stand/cobbler.cfg. This is described later.
#!/bin/sh
# Talk to cobbler, grab templates
DHCPLEASE=/var/empty/dhclient.leases
DHCPPID=/var/empty/dhclient.pid
# Create memdisk filesystem so we can start dhcp
mdmfs -s 32m md /var/empty
for ea in `ifconfig -l`; do
case $ea in
lo*) continue ;;
plip*) continue ;;
esac
/stand/ifconfig $ea >> /dev/null 1&>2
/stand/sleep 15
/stand/dhclient -p ${DHCPPID} -l ${DHCPLEASE} $ea
done
server=`grep dhcp-server ${DHCPLEASE} | awk '{ print $3 }' | awk -F\; '{ print $1}'`;
ip=`grep fixed-address ${DHCPLEASE} | awk '{ print $2 }' | awk -F\; '{ print $1}'`;
nm=`grep subnet-mask ${DHCPLEASE} | awk '{ print $3 }' | awk -F\; '{ print $1 }'`;
gw=`grep routers ${DHCPLEASE} | awk '{ print $3 }' | awk -F\; '{ print $1 }'`;
name=`grep host-name ${DHCPLEASE} | awk '{ print $3 }' | awk -F\" '{ print $2 }'`;
route=`grep routers ${DHCPLEASE} | awk '{ print $3 }' | awk -F\; '{ print $1 }'`;
iface=`grep interface ${DHCPLEASE} | awk '{ print $2 }' | awk -F\" '{ print $2 }'`;
kspath="http://${server}/cblr/svc/op/ks/system/${name}";
# use Fetch to get my answer file from my cobbler server
# use awk to pull out different sections using "% /path/to/file" syntax.
fetch -qo - "$kspath" | awk '/^% /{f=$2} /^[^%]/ && f{print > f}'
cat > /stand/media.cfg <<EOF
netDev=${iface}
defaultrouter=${route}
ipaddr=${ip}
netmask=${nm}
EOF
cobbler.cfg
cobbler.cfg is hosted remotely (by Cobbler) and fetched via HTTP. It can be separated into sections executed as shell scripts to enable dynamic control over the configuration. The doconfig.sh script above separates the sections of the files based on “% $filename” syntax.
The $disk variables below are substituted with the actual disk identifier. This can be found using the kern.disks kernel variable.
% /stand/cobbler.cfg
# The installation media is setup in the doconfig.sh script
hostname=$system_name
configFile=/stand/media.cfg
loadConfig
_httpPath=$getVar('httppath', '')
releaseName=any
mediaSetHTTPDirect
# Select which distributions we want.
dists=base kernels GENERIC SMP doc catpages
distSetCustom
# Figure out the disk configuration
disk=${disk}
partition=all
bootManager=standard
diskPartitionEditor
${disk}s1-1=ufs 12582912 / # 6 GB root
${disk}s1-2=swap ${swap} none # swap
${disk}s1-3=ufs 2097152 /tmp 1 # tmp
${disk}s1-4=ufs 4194304 /var 1 # 2 GB var
${disk}s1-5=ufs 4194304 /home 1 # 2 GB home
# OK, everything is set. Do it!
installCommit
package=perl
packageAdd
shutdown
It becomes necessary to use releaseName because checkAccess() appends various default directories to _httpPath in performing it’s search for the distributions when mediaSetHTTPDirect is specified.
Generate the bootonly ISO
After the release has been built and mfsroot.gz customized, it is time to create the ISO by executing the following command line syntax:
mkisofs -R -no-emul-boot -b boot/cdboot -o /FreeBSD-8.3-RELEASE-bootonly.iso $CHROOTDIR/R/cdrom/bootonly
Upload the resulting ISO to the respective Cobbler distribution where iPXE (described below) will download it from. In our example, /www/cobbler/ks_mirror/freebsd-8.3.4/boot/.
iPXE Configuration
iPXE is a bootstrap program with support for loading ISOs over http. We take the bootonly ISO generated above as well as the memdisk module and place them into the distributions boot/ directory.
iPXE loads, then downloads and executes the memdisk module (which provides iPXE the ability to load ISOs). iPXE instructs systems to either PXE boot or boot to disk based on the Cobbler system record.
gpxe_system_freebsd.template
This Cobbler template is used when “gPXE” has been enabled for a system. The system loads iPXE which in turn loads the memdisk module here.
#!gpxe kernel http://$server/cobbler/ks_mirror/$distro/boot/memdisk imgargs memdisk raw iso initrd http://$server/cobbler/ks_mirror/$distro/boot/bootonly.iso.zip boot
$server and $distro are dynamically templated out by Cobbler when the netboot enabled system PXE boots.
Cobbler Configurations
The following configuration are necessary in Cobbler:
- Set httppath in the kickstart metadata for the distro
- Set gPXE to enabled/true for each Cobbler system record necessary
- Set netboot enabled to the appropriate configuration
- Cobbler patches
httppath Kickstart Metadata
The distro must be imported into Cobbler before configuring this. The variable is set to the URI of the FreeBSD media. In our example, the distro is imported to /www/cobbler/ks_mirror/freebsd-8.3.4/. Thus necessitating the need to set the variable as follows (observe the image below):
httppath=http://@@http_server@@/cobbler/ks_mirror/freebsd-8.3.4/8.3-RELEASE-p4

gPXE Configuration
Systems or machines that may require re-installation should have the “Enable gPXE” option set to “True” or “Enabled”. This is accomplished via the Cobbler web or command line interfaces. Observe this image:

Netboot Configuration
A system record whose netboot option is set to enabled/true is expected to PXE boot and install the OS. Manipulating this option is done via the Cobbler web or command line interfaces. Observe:

Cobbler Patches
A colleague wrote these patches for Cobbler 2.4.0 which allow us to boot into iPXE, load memdisk and the bootonly.iso. They have been submitted to the Cobbler project.
These patches add support for:
- Local boot patch
- gpxe/ipxe support for FreeBSD
DHCP Configuration
Cobbler can be used to manage DHCP, which is how we have done it. Cobbler manages our DHCP configuration based on the DHCP template below, typically stored at /etc/cobbler/dhcp.template. Cobbler uses the cheetah templating language to produce a valid dhcpd.conf file. Review the template below.
NOTE: A colleague has developed a few patches for Cobbler which he intends to submit back to the Cobbler community.
Cobbler DHCP Template
# ******************************************************************
# Cobbler managed dhcpd.conf file
#
# generated from cobbler dhcp.conf template ($date)
# Do NOT make changes to /etc/dhcpd.conf. Instead, make your changes
# in /etc/cobbler/dhcp.template, as /etc/dhcpd.conf will be
# overwritten.
#
# ******************************************************************
ddns-update-style interim;
allow booting;
allow bootp;
ignore client-updates;
set vendorclass = option vendor-class-identifier;
subnet 192.168.2.0 netmask 255.255.255.0 {
option routers 192.168.2.1;
option ntp-servers 192.168.2.1;
option domain-name-servers 192.168.2.20, 192.168.3.20;
option subnet-mask 255.255.255.0;
pool {
range 192.168.2.50 192.168.2.100;
}
filename "/pxelinux.0";
default-lease-time 21600;
max-lease-time 43200;
next-server $next_server;
}
#for dhcp_tag in $dhcp_tags.keys():
## group could be subnet if your dhcp tags line up with your subnets
## or really any valid dhcpd.conf construct ... if you only use the
## default dhcp tag in cobbler, the group block can be deleted for a
## flat configuration
# group for Cobbler DHCP tag: $dhcp_tag
group {
#for mac in $dhcp_tags[$dhcp_tag].keys():
#set iface = $dhcp_tags[$dhcp_tag][$mac]
host $iface.name_iface {
hardware ethernet $mac;
#if $iface.ip_address:
fixed-address $iface.ip_address;
#end if
#if $iface.hostname:
#option host-name "$iface.hostname";
option host-name "$iface.name";
#end if
#if $iface.netmask:
option subnet-mask $iface.netmask;
#end if
#if $iface.gateway:
#option routers $iface.gateway;
#end if
#if $iface.enable_gpxe:
if exists user-class and option user-class = "iPXE" {
filename "http://$cobbler_server/cblr/svc/op/gpxe/system/$iface.owner";
} else {
filename "/undionly.kpxe";
}
#else
filename "$iface.filename";
#end if
## Cobbler defaults to $next_server, but some users
## may like to use $iface.system.server for proxied setups
next-server $next_server;
## next-server $iface.next_server;
}
#end for
}
#end for
DHCP Configuration File
Below is an example of a dhcp entry in dhcpd.conf
host testbsd {
hardware ethernet xx:xx:xx:xx:xx:xx;
fixed-address 192.168.0.5;
option host-name "testbsd";
option subnet-mask 255.255.255.0;
if exists user-class and option user-class = "iPXE" {
filename "http://192.168.0.2/cblr/svc/op/gpxe/system/testbsd";
} else {
filename "/undionly.kpxe";
}
next-server 192.168.0.2;
Disclaimer
This blog is posted for informational purposes only. Extensive testing is recommended prior to implementing changes discussed here.
Integrating FreeBSD w/ FreeIPA/SSSD
It is February 2018 and almost 2 years have passed since this post was originally published. It has become this blog’s most popular post. Hopefully it has been insightful and useful to those who have read it. Since the initial phases of this project, there have been some new experiences and lessons learned. Therefore, I thought it was a good idea to update this post integrating those experiences. The updated post also integrates solutions to the problem described in the first comment. Please enjoy!
This post is best viewed within a desktop environment due to the expression of config files containing lines longer than smaller displays. Therefore, it is not particularly well suited for mobile browsing though reader view may alleviate some formatting issues.
Integrating FreeBSD w/ FreeIPA/SSSD
One of my more recent projects was to integrate FreeBSD into a Kerberos-secured authentication and authorization system based on the FreeIPA architecture. This post is an aggregate HOWTO with information sourced from a couple public (and one private) websites and a mailing list in addition to my own personal experience. The architecture and infrastructure of the FreeIPA system are beyond the scope of this post and will not be covered here. The focus is installing and configuring a client to use the FreeIPA-based authentication and authorization system.
Prerequisites and Requirements
- Client must have A & PTR records in DNS
- Client hostname must be set to it’s FQDN
- Client host table must include a matching FQDN/IP address record
- Configured and running NTP
- Client should have a so-called break-glass UNIX account
- Binary package repository w/ packages supporting FreeIPA
- A Kerberos keytab for the client
Failure to meet requirements 1-3 generally introduces latency from the perspective of the client particularly when logging in remotely with ssh for example. NTP should also be configured and running.
The break-glass account is a local, UNIX-based account on the client filtered via SSSD policy/config. It will not use SSSD for authentication and it can be arbitrary in nature, so long as it is filtered. This is useful when remote access is lost due to service outage or similar. It is also useful to open a session to this user while this process is executed.
Client services won’t start and the client will fail authenticate and authorization against FreeIPA infrastructure without the proper software installed. Custom options are required to support FreeIPA. This can be done via Ports or packages; Described below is the latter.
Finally, the host keytab file must be generated in the FreeIPA infrastructure and securely copied to the client. The keytab facilitates system authentication via Kerberos.
Overview
The goal is to integrate FreeBSD into the FreeIPA architecture to provide services to ssh and sudo. No other authentication or authorization mechanisms were tested. Additionally, this post is constructed from notes and memory. Hopefully nothing has been missed and everything is accurate, but it is possible this is not the case. Help me keep this accurate, if necessary.
There are two high level tasks to be completed; Generate the binary package repository with FreeIPA-enabled packages and client installation/configuration. The binary package repository only needs to be done once. Ongoing maintenance of binary repositories is necessary to make newer versions available when necessary.
Building The Binary Package Repository
Execute this process to build a binary package repository containing the necessary packages w/ custom options enabled. It is assumed a pre-existing Poudriere build and distribution environment is available.
$jail_id is the Poudriere jail ID within which the repo is to be built
$portstree_id is the Poudrere Ports tree ID on which to execute the build
$ cat >> $jail_id-make.conf <<-EOF WITH_GSSAPI="YES" WANT_OPENLDAP_SASL="YES" KRB5_IMPL="mit" EOF $ cat > pkg_list <<EOF security/cyrus-sasl2-gssapi security/sssd security/sudo security/openssh-portable security/pam_mkhomedir EOF $ poudriere options -c -n -p $portstree_id security/sssd security/sudo security/openssh-portable security/cyrus-sasl-gssapi $ poudriere bulk -f pkg_list -j $jail_id -p $portstree_id
The kerberos implementation deployed in IPA dictates the kerberos implementation software is built to support. MIT kerberos is the predominant implementation in use within the US. The above configuration supports MIT kerberos.
Step 3 presents dialog boxes populated with various radio options. A null radio is a deselected option while options denoted by an “X” are selected. Configure options for each of the packages as described here:
security/sssd: Enable SMB (Builds and installs the IPA provider)
security/sudo: Enable SSSD backend
security/openssh-portable: Enable MIT Kerberos and KERB_GSSAPI
KERB_GSSAPI can cause the openssh-portable port to be marked as BROKEN because it’s dependent on an unmaintained patch, which has been updated occasionally by the Debian project. This generally appears to be the case when the port is updated to a newer version.
Installing/Configuring The Client
FreeIPA includes a client installer for Linux that is unavailable for FreeBSD. Rumors suggest porting it to FreeBSD is non-trivial. It was not done for this project. It would simplify this process significantly however.
The procedure below includes commands executed on the command line as well as descriptions of operations needed to be completed such as editing or creating a file. Several commands and/or files include hostnames inherently describing a dependency on DNS. Open a standby shell in order to maintain access to the system during this operation as access to the system can be halted in executing it.
$cblrserver is the hostname or IP of the server from which to grab content. This is simply an HTTP accessible resource.
$ipaserver is the hostname or IP of the IPA server with which to communicate
$domain is the domain of the target host
$hostname is the fully qualified hostname
1) Configure the binary package repository and install the required packages:
$ cat > /etc/pkg/ipa.conf <<-EOF
ipa: {
url: "http://$cblrserver/cobbler/repo_mirror/freebsd-10_3-amd64-pkgs-ipa",
mirror_type: "none",
enabled: yes
}
EOF
$ pkg update -f
$ mkdir -p /etc/ipa /usr/compat/linux/proc /var/log/sssd /var/run/sss/private /var/db/sss
$ pkg install -y -r ipa cyrus-sasl-gssapi sssd sudo openssh-portable pam_mkhomedir
2) Configure Kerberos and SSSD
$ cat /etc/krb5.conf
[libdefaults]
default_realm = EXAMPLE.COM
default_keytab_name = FILE:/etc/krb5.keytab
default_tkt_enctypes = aes256-cts des-cbc-crc aes128-cts arcfour-hmac
default_tgs_enctypes = aes256-cts des-cbc-crc aes128-cts arcfour-hmac
dns_lookup_realm = false
dns_lookup_kdc = false
rdns = false
ticket_lifetime = 24h
forwardable = yes
[realms]
EXAMPLE.COM = {
kdc = $ipaserver:88
master_kdc = $ipaserver:88
admin_server = $ipaserver:749
default_domain = $domain
pkinit_anchors = FILE:/etc/ipa/ca.crt
}
[domain_realm]
.example.com = EXAMPLE.COM
[logging]
kdc = FILE:/var/log/krb5/krb5kdc.log
admin_server = FILE:/var/log/krb5/kadmin.log
kadmin_local = FILE:/var/log/krb5/kadmin_local.log
default = FILE:/var/log/krb5/krb5lib.log
[edit]
$
$
$ cat /usr/local/etc/sssd/sssd.conf
[domain/$domain]
#debug_level = 9
cache_credentials = True
krb5_store_password_if_offline = True
krb5_realm = EXAMPLE.COM
ipa_domain = $domain
id_provider = ipa
auth_provider = ipa
access_provider = ipa
ipa_hostname = $hostname
chpass_provider = ipa
ipa_server = _srv_, $ipaserver
ldap_tls_cacert = /etc/ipa/ca.crt
krb5_keytab = /etc/krb5.keytab
[sssd]
services = nss, pam, ssh, sudo
config_file_version = 2
domains = $domain
[nss]
filter_users = root,smash
homedir_substring = /home
[pam]
[sudo]
[ssh]
$
$
chpass_provider is irrelevant to FreeBSD because the IPA tools are required and currently unavailable to FreeBSD. This service can be provided to users on separate infrastructure using Linux instead. Also note filter_users includes root in addition to the break-glass account.
3) Configure NSS similarly to the below:
$ egrep "sss|netgroup" /etc/nsswitch.conf group: files sss passwd: files sss sudoers: files sss netgroup: files
NSS interrogates the data source in the order in which it’s listed. The above policy first validates authentication/authorization for local UNIX accounts before SSSD. NSS validates via SSSD when the invoking user is not a local UNIX user.
4) Configure and mount the linproc filesystem
# echo "linproc /usr/compat/linux/proc linprocfs rw 0 0" >> /etc/fstab # mount -a
5) Configure rc.conf and domainname
$ cat /etc/rc.conf rpcbind_enable=“YES” sshd_enable=“NO” openssh_enable="YES" ntpd_enable="YES" nisdomainname=“example.com” sssd_enable="YES" $ sudo domainname example.com
6) Configure openssh-portable similarly to:
$ grep GSSAPI /usr/local/etc/ssh/sshd_config GSSAPIAuthentication yes GSSAPICleanupCredentials yes
7) Download the CA certificate required by kerberos
$ wget -O /etc/ipa/ca.crt http://$ipaserver/$uri/ca.crt
8) Configure PAM according to these configs:
$ cat /etc/pam.d/sshd # auth auth sufficient pam_opie.so no_warn no_fake_prompts auth requisite pam_opieaccess.so no_warn allow_local auth sufficient /usr/local/lib/pam_sss.so use_first_pass ignore_unknown_user auth required pam_unix.so no_warn try_first_pass # account account required pam_nologin.so account sufficient /usr/local/lib/pam_sss.so ignore_unknown_user account required pam_login_access.so account required pam_unix.so # session session required pam_lastlog.so no_fail session required /usr/local/lib/pam_mkhomedir.so # password password sufficient /usr/local/lib/pam_sss.so password required pam_unix.so no_warn try_first_pass $ $ $ cat /etc/pam.d/system # auth auth sufficient pam_opie.so no_warn no_fake_prompts auth requisite pam_opieaccess.so no_warn allow_local auth sufficient /usr/local/lib/pam_sss.so use_first_pass auth required pam_unix.so no_warn try_first_pass nullok # account account sufficient /usr/local/lib/pam_sss.so ignore_unknown_user account required pam_login_access.so account required pam_unix.so # session session required pam_lastlog.so no_fail session required /usr/local/lib/pam_mkhomedir.so # password password sufficient /usr/local/lib/pam_sss.so use_first_pass password required pam_unix.so no_warn try_first_pass
It may be desirable to symlink /etc/pam.d/sudo to /etc/pam.d/system or /etc/pam.d/su depending on the desired behavior.
9) Create /etc/netgroup with the following script. Set this script to run via cron to rebuild the file periodically.
This script is ported from one of the references to update /etc/netgroup locally on FreeBSD hosts. LDAPSRV is the hostname or IP of the LDAP server to query. This is generally the same as the IPA servers. Executing this script via cron will keep the file updated.
Note the patch below may be useful if /etc/netgroup tends to be large file. If LDAP returns netgroups in response to a query, sudo calls innetgr(), which subsequently loads and iterates the file to determine if the host belongs to a netgroup in which the invoking user is a member. NSS’ netgroup database provides the data source for innetgr() which supports neither sss nor ldap. The time required to iterate the file is proportional to it’s size and can introduce severe performance degradation. The patch below — when applied to the script — ensures only relevant entries exist thus reducing the file’s size. The patch assumes the script is called mknetgroup.sh.
FreeBSD PR 226119 is open as a feature request to implement a LDAP data source for NSS’ netgroup database.
diff --git a/mknetgroup.sh b/mknetgroup.sh
index 48eda74..5ba4e6d 100755
--- a/mknetgroup.sh
+++ b/mknetgroup.sh
@@ -7,6 +7,7 @@
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
LDAPSRV=__IPASERVER
+HOSTNAME=$(hostname -s)
export PATH
progrname=$(basename $0)
@@ -46,7 +47,9 @@ ldapsearch -LLLx -H ldap://${LDAPSRV} \
elif [ "$key" = "nisNetgroupTriple" ]; then
host=${value%%,cn*}
host=${host##fqdn=}
- members="$members $host"
+ case $host in
+ *${HOSTNAME}*) members="$members $host" ;;
+ esac
fi
done
#!/bin/sh
#
# Construct a netgroup file from LDAP hostgroup definitions.
# This is a hack for FreeBSD IPA clients because they can't get netgroup
# data through LDAP or sssd backends (lacking nsswitch/nsdb support).
#
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
LDAPSRV=ipa.example.com
export PATH
progname=$(basename $0)
tmpf=$(mktemp)
trap "rm -f $tmpf" EXIT
ldapsearch -LLLx -H ldap://${LDAPSRV} \
-b 'dc=example,dc=com' \
'(objectClass=nisNetgroup)' cn nisNetgroupTriple \
| while read line; do
# new line between records; this means a record ended.
if [ "$line" = "" ]; then
# output netgroup line if we have members.
if [ "$members" != "" ]; then
echo "$groupname \\" >>$tmpf
for host in $members; do
echo "$host \\" >>$tmpf
done
echo "" >>$tmpf
fi
# reset data
groupname=""
members=""
continue
fi
# parse "key: value" from LDAP
key=${line%%: *}
value=${line##*: }
if [ "$key" = "dn" ]; then
continue
elif [ "$key" = "cn" ]; then
groupname=$value
elif [ "$key" = "$netgroupobj" ]; then
host=${value%%,cn*}
host=${host##fqdn=}
members="$members $host"
fi
done
if [ ! -s "$tmpf" ]; then
echo "$progname: refusing to install an empty file, bailing" >&2
exit 1
fi
install -m 0644 -o root -g wheel $tmpf /etc/netgroup
rc=$?
if [ $rc -ne 0 ]; then
echo "$progname: error installing /etc/netgroup (rc = $rc)" >&2
exit 2
fi
exit 0
10) Restart services or reboot
$ sudo service sssd start $ sudo service sshd stop && sudo service openssh start
Upon completion, the target host has been configured to communicate with the authentication and authorization frameworks. Reboot is not required, but certainly recommended.
References
Disclaimer
Data and information described on this blog are for informational purposes only. The author of this blog provides no warranty/guarantee, expressed or implied, that this data and information will function as described here. Readers are expected to exercise due diligence when researching, developing, and deploying techniques and methods for use within their environments.
Comments posted are the explicit opinions of the comment poster themselves and does not necessarily reflect the views and opinions of the author of this blog.
Enabling mrepo Support for FreeBSD Packages
Enabling mrepo Support for FreeBSD Packages
Enabling preliminary support for FreeBSD packages involves the following procedure. This procedure assumes repos are available via HTTP at the location where content is downloaded from. We also assume a web server is installed and configured to serve mrepo content. Any discussion involving the configuration of these components is beyond the scope of this post.
- Apply the patch below to the mrepo.conf and add some FreeBSD repos
- Update the local mrepo repositories
- Execute `find` symlinking to the updated content
- Setup cronjobs to update repos periodically
Applying The Patch
This patch was generated with GNU patch on RHEL6 and should be applied using GNU patch in the following manner to minimize potential issues. It patches the mrepo script, mrepo.conf, and adds three mrepo repositories. The patch:
diff -u usr/bin/mrepo usr/bin/mrepo.new
--- usr/bin/mrepo 2011-07-01 21:16:09.000000000 -0400
+++ usr/bin/mrepo.new 2013-09-26 12:10:22.072081702 -0400
@@ -214,6 +214,7 @@
self.lftpcleanup = self.getoption('main', 'lftp-cleanup', 'yes') not in disable
self.lftpexcldebug = self.getoption('main', 'lftp-exclude-debug', 'yes') not in disable
self.lftpexclsrpm = self.getoption('main', 'lftp-exclude-srpm', 'yes') not in disable
+ self.lftpincltbz = self.getoption('main', 'lftp-include-tbz', 'yes') not in disable
self.lftpoptions = self.getoption('main', 'lftp-options', '')
self.lftpcommands = self.getoption('main', 'lftp-commands', '')
self.lftpmirroroptions = self.getoption('main', 'lftp-mirror-options', '-c -P')
@@ -1282,6 +1283,8 @@
mirroropts = mirroropts + ' -X \"*.src.rpm\" -X \"/SRPMS/\"'
if cf.lftpexcldebug:
mirroropts = mirroropts + ' -X \"*-debuginfo-*.rpm\" -X \"/debug/\"'
+ if cf.lftpincltbz:
+ mirroropts = mirroropts + ' -I \"*.tbz*\"'
ret = run('%s %s -c \'%s mirror %s %s %s\'' % (cf.cmd['lftp'], opts, cmds, mirroropts, url, path),dryrun=True)
if ret:
diff -ruN etc/mrepo.conf newetc/mrepo.conf
--- etc/mrepo.conf 2013-09-30 10:57:32.036840555 -0400
+++ newetc/mrepo.conf 2013-09-30 09:08:34.960901787 -0400
@@ -1,4 +1,4 @@
-
+### Configuration file for mrepo
### The [main] section allows to override mrepo's default settings
### The mrepo-example.conf gives an overview of all the possible settings
@@ -23,3 +23,5 @@
hardlink = no
shareiso = no
rhnget-download-all = yes
+
+lftp-include-tbz = yes
diff -ruN etc/mrepo.conf.d/FreeBSD-8-x86_64-dev.conf newetc/mrepo.conf.d/FreeBSD-8-x86_64-dev.conf
--- etc/mrepo.conf.d/FreeBSD-8-x86_64-dev.conf 1969-12-31 19:00:00.000000000 -0500
+++ newetc/mrepo.conf.d/FreeBSD-8-x86_64-dev.conf 2013-09-30 10:59:07.834704096 -0400
@@ -0,0 +1,5 @@
+[FreeBSD-8-x86_64-dev]
+name = FreeBSD 8.x Packages - Dev Repo
+release = 8
+metadata = yum repomd
+dev-repo = $url_to_the_development_repo
diff -ruN etc/mrepo.conf.d/FreeBSD-8-x86_64-prod.conf newetc/mrepo.conf.d/FreeBSD-8-x86_64-prod.conf
--- etc/mrepo.conf.d/FreeBSD-8-x86_64-prod.conf 1969-12-31 19:00:00.000000000 -0500
+++ newetc/mrepo.conf.d/FreeBSD-8-x86_64-prod.conf 2013-09-30 10:59:18.794803601 -0400
@@ -0,0 +1,5 @@
+[FreeBSD-8-x86_64-prod]
+name = FreeBSD 8.x Packages - Production Repo
+release = 8
+metadata = yum repomd
+prod-repo = $url_to_the_production_repo
diff -ruN etc/mrepo.conf.d/FreeBSD-8-x86_64-qa.conf newetc/mrepo.conf.d/FreeBSD-8-x86_64-qa.conf
--- etc/mrepo.conf.d/FreeBSD-8-x86_64-qa.conf 1969-12-31 19:00:00.000000000 -0500
+++ newetc/mrepo.conf.d/FreeBSD-8-x86_64-qa.conf 2013-09-30 10:59:28.171888437 -0400
@@ -0,0 +1,5 @@
+[FreeBSD-8-x86_64-qa]
+name = FreeBSD 8.x Packages - QA Repo
+release = 8
+metadata = yum repomd
+qa-repo = $url_to_the_qa_repo
are applied by executing the following respectively for each patch after the patches have been downloaded:
# cd / && patch < mrepo.patch
Note this patch includes variables that should be replaced by URLs where the repos exist. This URL could be the URL to FreeBSD’s binary package repos, but the size of the repo could cause some issues with this process, especially the step below where the symlinks are generated. My use case involves HTTP repos of home grown FreeBSD packages.
This patch can also be modified to enable support for other file types. For example, tgz and txz files which are supported in FreeBSD’s new pkg framework. which will be default in FreeBSD 10.0-RELEASE. Doing so would enable support for FreeBSD 10 ‘pkg’ files to mrepo as well.
Updating The Repositories
mrepo repositories are updated by executing the patched mrepo script as described here:
# mrepo -ug dev-repo # mrepo -ug qa-repo # mrepo -ug prod-repo
These commands update each of the above repos [ included in the patch ] according to srcdir and wwwdir specified in mrepo.conf. Therefore, content downloads to srcdir and wwwdir will be updated in the next step.
Symlink Content
mrepo downloads content from the URLs specified in the mrepo config files located in /etc/mrepo.conf.d/. The FreeBSD-8-x86_64-*.conf repositories in this case. The files are symlinked to from the repo identified by the combination of the value in wwwdir specified in /etc/mrepo.conf and the repo name. This is done by executing the following:
# cd $wwwdir/$repodir && find $srcdir/$repo –type f –name '*tbz' -exec ln –s {} . \;
Where $wwwdir and $srcdir are taken from /etc/mrepo.conf and $repodir is actual directory name of the repo on disk. This step should be executed for each of the repos. Completion of this step means the repos are now available via HTTP.
Configure Cronjobs
It is generally considered a good idea to enable periodic updates of mrepo repos. This can be done in any number of ways, including cronjobs. I choose cronjobs here for simplicity’s sake. Your cronjobs may look similar to the following:
0 0 1 * * mrepo -ug $repo && cd $wwwdir/$repo && find $srcdir/$repo –type f –name ‘*tbz’ -exec ln –s {} . \;
Where $repo is the repo tag specified in it’s config file in /etc/mrepo.conf.d/, $srcdir and $wwwdir are as specified in /etc/mrepo.conf.
Make Content Available Via Cobbler
Some technologists may employ provisioning platforms to distribute OS and package repo contents throughout an environment. I utilize Cobbler in my environments. Therefore, an additional one-time step to create the Cobbler repo is necessary for each repo.
Once the symlinks are created in $wwwdir/$repo, one can add the repo to Cobbler making it available there. This is done by executing:
# cobbler repo add --name="$repo_name" --breed="rsync" --mirror="$wwwdir/$repo"
The new repos will be updated any time a `cobbler reposync` is executed.
Disclaimer
This blog is posted for informational purposes only. Extensive testing is recommended prior to implementing changes discussed here.
Building FreeBSD Media With Custom Packages
Building FreeBSD Media With Custom Packages
Building a FreeBSD release can be as simple as executing:
# cd /usr/src # make buildworld # cd release # make release # PKG_TREE=$pkg_dir PKG_DEST=$CHROOTDIR/R/cdrom/dvd1 PKG_DVD=Yes make package-split
The Use Case

I generate company specific FreeBSD distributions based on our internal requirements including loading specific packages. The FreeBSD DVD release from freebsd.org includes a packages/ directory created by the package-split.py script. I use the same script to accomplish the same task with custom packages.
We generate an ISO from the release and import it into Cobbler. We’ve setup Cobbler to allow us to provision operating systems non-interactively using PXE, DHCP, TFTP, and HTTP.
Prerequisites
- A directory containing at least the packages to include. One can download packages for 8.3-RELEASE via ftp at ftp://ftp.freebsd.org/pub/FreeBSD/ports/amd64/packages-8.3-release/.
- An INDEX file. The INDEX file will already exist if one downloads the packages from the above ftp server.
Configuring package-split.py
package-split.py is a Python script that “generates a master INDEX file for the CD images built by the FreeBSD release engineers. Each disc is given a list of desired packages. Dependencies of these packages are placed on either the same disc or an earlier disc. The resulting master INDEX file is then written out”.
In our use case, the files are not split into separate discs because we are packaging for a DVD as identified by the PKG_DVD environment variable.
View the package-split.py here. Because I am currently working with 8.3-RELEASE, I link directly to the that version of the script.
Our use case involves commenting lines 43 – 81 and copying lines 63 – 81. The copied lines are modified with names of packages installed in our base distribution. An example might look similar to:
if doing_dvd:
pkgs.extend(['ftp/wget',
'devel/subversion',
'lang/perl5.12',
'lang/python',
'lang/ruby18',
'net/rsync'])
Caveats
- There are inconsistencies in the INDEX file where some package names and origins do not match. An example such as ruby18-iconv shows the package name (column 1) is ruby18-iconv-$version while the origin (column 2) is /usr/ports/converters/ruby-iconv. `make package-split` errored on this package citing “Unable to find package for converters/ruby18-iconv” (see image below). Changing origin from “ruby-iconv” to “ruby18-iconv” resolved this.
- use the origin field to identify the package in configuring the package-split.py script. For example, ruby18-iconv, would be specified as ‘converters/ruby19-iconv’ as opposed to ‘ruby/ruby18-iconv’ despite having multiple categories and existing multiple directories.

Executing `make package-split`
Once the release is built, create the packages directory and move it to the new release by executing the following. This would also be an appropriate time to make additional modifications to the image such as replacing the mfsroot.gz with a new custom mfsroot.
# PKG_TREE=/tmp/packages PKG_DEST=$CHROOTDIR/R/cdrom PKG_DVD=Yes make package-split # mv $CHROOTDIR/R/cdrom/disc1/packages $CHROOTDIR/R/cdrom/dvd1
Generate the ISO
The release is now ready to be made into an ISO that we use to import the distribution into our provisioning platform, Cobbler.
# mkisofs -R -no-emul-boot -b boot/cdboot -o /tmp/iso/FreeBSD-8.3-RELEASE-p4-amd64-CUSTOM.iso $CHROOTDIR/R/cdrom/dvd1
Automating Generation of the mfsroot.gz
NOTE: This post is old. I no longer generate mfsroots in this fashion and my methods continue to evolve, but my latest approach to modifying mfsroots is documented in the Installing FreeBSD via Cobbler post.
Automating Generation of the mfsroot.gz
Like the crunchgen(1) script, this is a starting point only that makes many assumptions. In fact, while writing this I have come up with plans to combine them.
I store an uncompressed mfsroot.gz file in subversion. It is stored in a manner that includes the files in stand/. You may or may not be aware that this is where the boot_crunch file exists. It also happens to be the location of all the hardlinks that point back to the boot_crunch inode. Subversion stores these as if they were all binaries. Below you will see that I programmatically restore the hardlinks.
A side effect of this management structure is that generating the same mfsroot.gz multiple times results in differing file sizes each time despite using the same content. I’m not 100% sure, but believe that this is due to the file metadata being different each time the content is exported from the subversion repo.
NOTE: The URL in the script below is a dummy URL. The real URL is internally hosted with no connectivity to the Internet.
#! /bin/sh
#
# mkmfsroot.sh: Generate a FreeBSD mfsroot file. This script does not
# function on hosts that are not running FreeBSD.
#
# Author: Rick
# Date: 21 August 2012
# Modif:
#####
## Variables
#####
SELF="${0##*/}";
NULL="/dev/null";
TMP="/tmp";
PATH="${PATH}";
ZERO="/dev/zero";
MFSROOTDIR="${1}";
SVNREPO="https://dummy.subversion.com/ats/ad/freebsd-prov/mfsroot/${MFSROOTDIR}";
REPODIR="/data/freebsd-prov/mfsroot/${MFSROOTDIR}";
MFSROOTSTAND="${REPODIR}/stand";
SVN="/usr/local/bin/svn";
STANDFILES="doconfig.sh install.cfg boot_crunch";
MFSROOT="/tmp/mfsroot";
MFSROOTMNT="/tmp/mfsrootmnt";
#####
## Functions
#####
# Clean what this script has done
CLEANUP() {
[ "${MDCFG}" = "True" ] && mdconfig -d -u ${DEVNUM};
if [ -d ${MFSROOTMNT} ]; then
cd && umount ${MFSROOTMNT} > ${NULL} 2>&1;
rm -rf ${MFSROOTMNT};
fi
[ -e ${MFSROOT} ] && rm ${MFSROOT};
[ -e ${REPODIR} ] && rm -rf ${REPODIR};
}
# We are erroring and exiting the script
ERR_EXIT() {
echo "${1}" 1>&2;
CLEANUP;
exit 1;
}
#####
## Main
#####
# Force two command line arguments
[ -z ${1} ] && ERR_EXIT "Specify an mfsroot!";
# Verify we're on a FreeBSD host
HOSTOS=`uname -s`;
[ ${HOSTOS} != "FreeBSD" ] && ERR_EXIT "Build host is not FreeBSD! Exiting!";
# Ensure we're using the latest repo version
[ -d ${REPODIR} ] && rm -rf ${REPODIR};
${SVN} export ${SVNREPO} ${REPODIR};
# Does the mfsroot dir we asked for exist?
[ ! -d ${REPODIR} ] && ERR_EXIT "${1} does not exist! Exiting!";
# Remove .svn if it exists
[ -d ${REPODIR}/.svn ] && rm -rf ${REPODIR}/.svn
# Fix stand/
#
# What do we do here and why?
#
# Since the contents of the mfsroot are stored in subversion, when the content
# is checked out or exported, each of the files in stand/ are stored as
# individual files when they are actually supposed to be hardlinks back to
# boot_crunch. Therefore, the code below checks out the content, removes the
# files in stand/, and exports boot_crunch and a couple other scripts. It
# finishes by regenerating all the hardlinks back to boot_crunch.
#
# We could resolve this in any number of ways, but as a stop gap measure for
# now we will leave it this way and fix it in the future.
if [ -d ${MFSROOTSTAND} ]; then
rm ${MFSROOTSTAND}/*;
for ea in ${STANDFILES}; do
${SVN} export ${SVNREPO}/stand/${ea} ${MFSROOTSTAND};
done
cd ${MFSROOTSTAND};
for i in $(./boot_crunch 2>&1 | grep -v usage); do
if [ "$i" != "boot_crunch" ]; then
rm -f ./"$i";
ln ./boot_crunch "$i";
fi
done
fi
# Calculate the size of the mfsroot, prepare the file and populate it
MFSROOTSIZE=$(du -sk ${REPODIR} | awk '{ print $1 }');
[ -z ${MFSROOTSIZE} ] && ERR_EXIT "Unable to determine mfsroot size! Exiting!";
dd if=${ZERO} of=${MFSROOT} bs=1024 count=$((${MFSROOTSIZE} + 1024));
[ $? -ge 1 ] && ERR_EXIT "Unable to create ${MFSROOT}! Exiting!";
MFSROOTDEV=$(mdconfig -f ${MFSROOT});
[ -z ${MFSROOTDEV} ] && ERR_EXIT "Unable to mount ${MFSROOT}! Exiting!";
DEVNUM=$(echo ${MFSROOTDEV} | awk '{ print substr($0,length($0)-0,length($0)) }'
);
MDCFG="True";
newfs /dev/${MFSROOTDEV};
[ ! -d ${MFSROOTMNT} ] && mkdir ${MFSROOTMNT};
mount /dev/${MFSROOTDEV} ${MFSROOTMNT} && \
(cd ${REPODIR}; tar -cf - .) | (cd ${MFSROOTMNT}; tar -xf -);
# Now the new mfsroot has been populated, it's time to save it and clean up
umount ${MFSROOTMNT} && mdconfig -d -u ${DEVNUM} && gzip ${MFSROOT};
[ $? -ne 0 ] && ERR_EXIT "Error unmounting ${MFSROOT}! Exiting!";
MDCFG="False";
CLEANUP;
#####
## EOF
#####
Using sysinstall for automated FreeBSD 8.x installs
First, allow me to point out that as of FreeBSD 9 sysinstall is no longer the default install mechanism. Therefore, this post addresses versions previous to FreeBSD 9.
Summary
It’s important to understand that our provisioning platform is Cobbler. Therefore, you may find references to Cobbler in this post. There may need to be code modifications to permit these concepts to work within other platforms.
This set of config files and scripts allows us to more dynamically control host builds by scripting much of the process and hosting an answer file remotely. Doing so helps us avoid the need of creating multiple mfsroot files for varying profiles, etc.
Out of the box, sysinstall does not support pure HTTP installs. To enable full HTTP support, one of my former colleagues wrote a patch which was applied to the sysinstall source and compiled. This new binary was put into the mfsroot file.
mfsroot.gz
The mfsroot.gz is a BSD UFS filesystem environment that contains config files, scripts, and binaries necessary to complete a FreeBSD 8.x install. This file is retrieved by the PXE boot file and loads it into memory. The procedure described below takes the contents of an mfsroot.gz from the FreeBSD DVD install media and adds the files described later in this post to that file.
The mfsroot contained within the vendor install media is setup to run sysinstall. It can be
modified in various ways to manipulate the FreeBSD install.
Prerequisites
- mfsroot file: This file can be found on the vendor media. Just copy it
locally. - A FreeBSD host: This host is where you will perform these procedures.
- The FreeBSD host should have bash installed and when executing this
procedure, it is the current shell
Procedure
Modifying the mfsroot involves changes to the existing file to change the behavior of the environment. Some common files modified are the loader.rc and loader.conf files. When adding new files to the mfsroot, such as a newly compiled boot_crunch file, it will require creating a new mfsroot file. This section will describe both methods of modifying the mfsroot.
Modifying Existing mfsroot
To modify an existing mfsroot all that needs to be done is to create a memdisk device and mount it. Once mounted, you can modify the mfsroot simply by cd’ing into the new mount point and making your desired changes. We assume the memdisk device is md0 and the mount point is /mnt.
To create this memdisk and mount it, one only need to run the following:
# mdconfig -f $path_to_mfsroot # mount /dev/md0 /mnt
Once changes have been made to the mfsroot, you can dismount and remove the memdisk device by executing the following:
# umount /mnt mdconfig -d -u 0 # mdconfig -d -u 0
Creating New mfsroot from Existing mfsroot
When adding files to an mfsroot, such as the boot_crunch, will require a new, larger mfsroot. This procedure discussed how to accomplish this.
This procedure assumes the following:
- we are adding a boot_crunch file
- Resulting mfsroot will need to be 12MB.
- Existing mfsroot mount point will be /tmp/mfsroot_old
- New mfsroot mount point will be /tmp/mfsroot_new
- Existing mfsroot is /tmp/mfsroot_old
- Memdisk device for the existing mfsroot is md0
- Memdisk device for the new mfsroot is md1
- New boot_crunch file is /tmp/bootcrunch/boot_crunch
# mkdir /tmp/mfsroot_old /tmp/mfsroot_new
# dd if=/dev/zero of=/tmp/mfsroot bs=1024 count=12288
# mdconfig -f /tmp/mfsroot_old; mdconfig -f /tmp/mfsroot
# newfs /dev/md1
# mount /dev/md0 /tmp/mfsroot_old; mount /dev/md1 /tmp/mfsroot_new
# cd /tmp/mfsroot_old; tar -cf - . | (cd/tmp/mfsroot_new; tar -xf -)
# cp /tmp/bootcrunch/boot_crunch /tmp/mfsroot_new/stand/
# cd /tmp/mfsroot_new/stand
# for i in $(./boot_crunch 2>&1|grep -v usage); do
if [ "$i" != "boot_crunch" ]; then
rm -f ./"$i"; ln ./boot_crunch "$i";
fi
done
# cd /; umount /tmp/mfsroot_old; umount /tmp/mfsroot_new
# mdconfig -d -u 0; mdconfig -d -u 1
At the completion of this procedure, /tmp/mfsroot will be the new mfsroot. Replace the old mfsroot with this one.
install.cfg
The install.cfg file is sysinstall’s config file. When sysinstall is executed it checks for the existence of this file. If the file does not exist, it runs interactively else it executes the file and performs the operations contained within it. This file exists in stand/ inside of the mfsroot file.
The syntax of the file is archaic and strict, but can be quite powerful if you spend the time to learn it. That is a moot point now though since sysinstall is deprecated in more recent versions of FreeBSD.
The file below simply sets up the environment where sysinstall will run, then executes a script called doconfig.sh which is described below.
# Turn on extra debugging. debug=YES # Turn off all prompting. nonInteractive=YES noWarn=YES command=/bin/sh /stand/doconfig.sh system # Chain to the config we just downloaded configFile=/stand/cobbler.cfg loadConfig
doconfig.sh
This script, also in stand/ inside the mfsroot file, is the workhorse. It completes the setup of the environment by setting variables and communicating with the remote server to grab the remainder of the install.cfg file which we see is referred to as stand/cobbler.cfg. We describe this later in the post.
#!/bin/sh
server=`kenv -q boot.nfsroot.server`
mac=`kenv -q boot.netif.hwaddr`
ip=`kenv -q boot.netif.ip`
nm=`kenv -q boot.netif.netmask`
gw=`kenv -q boot.netif.gateway`
name=`kenv -q dhcp.host-name`
route=`kenv -q dhcp.routers`
# Returns true if a given network interface has the specified MAC address.
macmatch()
{
local addr
addr=`ifconfig $1 | grep ether | awk -F" " '{ print $2 }'`
[ "$addr" = "$2" ]
return $?
}
for ifn in `ifconfig -l`; do
case $ifn in
*)
if macmatch $ifn $mac; then
iface=$ifn
fi
;;
esac
done
# Bring up the interface so we can fetch the remote install.cfg file. Confuses sysinstall, so
# bring it back down after fetching install.cfg. Kinda assuming we're on the same subnet
# as the server, otherwise won't work.
ifconfig "$iface" "$ip" netmask "$nm"
sleep 5
route add default $gw
# Use Fetch to get my answer file from my cobbler/remote server. Use awk to pull out different
# sections using "% /path/to/file" syntax.
fetch -qo - "http://$server/cblr/svc/op/ks/system/$name" |
awk '/^% /{f=$2} /^[^%]/ && f{print > f}'
# Bringing down the interface
ifconfig $iface down
route delete default $gw
# Setup our media. This file is loaded by sysinstall after it retrieves the install.cfg from the
# cobbler/remote server.
cat > /stand/media.cfg <<EOF
netDev=${iface}
defaultrouter=${route}
ipaddr=${ip}
netmask=${nm}
_httpPath=http://${server}/cobbler/ks_mirror/freebsd82-x86_64
mediaSetHTTP
EOF
cobbler.cfg
The cobbler.cfg is hosted on a remote server and is fetched via HTTP. It can be separated into sections that are executed as shell scripts to enable more dynamic control over the configuration. The doconfig.sh script above separates the sections of the files based on “% $filename” syntax.
The $disk variables below are substituted with the actual disk identifier. You can find that using the install.disk kernel variable.
% /stand/cobbler.cfg
# The installation media is setup in the doconfig.sh script
hostname=$system_name
configFile=/stand/media.cfg
loadConfig
# Select which distributions we want.
dists=base kernels GENERIC SMP doc catpages
distSetCustom
# Figure out the disk configuration
disk=${disk}
partition=all
bootManager=standard
diskPartitionEditor
${disk}s1-1=ufs 12582912 / # 6 GB root
${disk}s1-2=swap ${swap} none # swap
${disk}s1-3=ufs 2097152 /tmp 1 # tmp
${disk}s1-4=ufs 4194304 /var 1 # 2 GB var
${disk}s1-5=ufs 4194304 /home 1 # 2 GB home
# OK, everything is set. Do it!
installCommit
package=perl
packageAdd
shutdown
PXE Booting Into A FreeBSD Installation
NOTE: This post is old. For our new installation method, see the article on Installing FreeBSD via Cobbler.
In the modern day, technologies for provisioning OS installations has progressed to a point where network installs are the standard thus eliminating the need for physical media and even physical access to the asset (apart from it’s physical installation in a cabinet). Networked consoles enable system builds in place.
Installing via PXE requires three services…DHCP, TFTP, and FTP or HTTP. For the purposes of this post, we use ISC DHCP and the system TFTP daemon. It is assumed that your FTP and/or HTTP servers are already configured appropriately.
The environment I installed in included Cobbler, a linux build system that has been modified by some of my colleagues to support FreeBSD. Cobbler utilizes pxelinux as the PXE loader, therefore, my scenario included chaining Grub2pxe to pxelinux. I have not tested without pxelinux, but I assume that Grub2pxe will work find without pxelinux. Since we do employ Cobbler, my post will include the pxelinux configuration, but I will keep the information generic.
DHCP Configuration
An example of the dhcpd.conf file:
subnet 192.168.1.0 netmask 255.255.255.0 {
option routers 192.168.1.1;
option ntp-servers 192.168.1.1;
option domain-name-servers 192.168.1.2, 192.168.1.3;
option subnet-mask 255.255.255.0;
pool {
range 192.168.1.100 192.168.1.200;
}
filename "/pxelinux.0";
default-lease-time 21600;
max-lease-time 43200;
next-server 192.168.1.5;
}
TFTP Configuration
An example of /tftpboot/pxelinux.cfg/default file:
LABEL bsdpxegrub kernel /tftpboot/pxegrub.0 MENU LABEL bsdpxegrub append initrd=/tftpboot/pxegrub.0 locale= text ipappend 2
Most of the above configuration is superfluous, but does not cause any issues with the FreeBSD install.
Grub2pxe Configuration
The files and directories of interest here are:
- /tftpboot/pxegrub.0 (The PXE executable)
- /tftpboot/boot/grub/ (The default Grub directory)
- /tftpboot/boot/grub/grub.cfg (The default Grub config file)
- /tftpboot/boot/grub/i386-pc/* (All of Grub’s modules)
Once Grub is installed, grub-mknetdir will put everything into place including running grub-mkimage which generates the pxegrub.0 file. An example of the command is as follows:
grub-mknetdir –net-directory=/tftpboot –grub-mkimage=pxegrub.0
What you see below for the /tftpboot/boot/grub/grub.cfg, is a complete config for our environment. Other implementations may not need the config that we have generated. Many of the kernel environment variables below are used in our mfsroot.gz file. For a minimal working environment, all the lines up to and including the vfs.root.mountfrom kernel variable are necessary.
menuentry $arbitrary_profile_name {
echo -e "Fetching the kernel and UFS root...\c"
kfreebsd $kernel_path
kfreebsd_loadenv /boot/device.hints
kfreebsd_module $initrd_path type=mfs_root
set kFreeBSD.vfs.root.mountfrom=ufs:/dev/md0c
set kFreeBSD.boot.nfsroot.server=$pxe_default_server
set kFreeBSD.boot.netif.hwaddr=$net_pxe_mac
set kFreeBSD.boot.netif.ip=$net_pxe_ip
set kFreeBSD.boot.netif.netmask=255.255.255.0
set kFreeBSD.boot.netif.gateway=$system_gateway
set kFreeBSD.dhcp.host-name=$net_pxe_hostname
set kFreeBSD.dhcp.routers=$system_gateway
set kFreeBSD.boot.kspath=$ks_path
echo "Done!"
sleep 2
}
HTTP/FTP Configuration
Pure HTTP installs are not supported by sysinstall. A former colleague wrote an HTTP module for sysinstall that we compiled into the binary that is placed inside the mfsroot.gz. This patch was contributed back to the community, but was not committed to the FreeBSD sources due to the migration away from sysinstall in FreeBSD 9.0.